From: Stefan Reinauer Date: Thu, 4 Jun 2026 14:58:14 +0000 (-0700) Subject: od-unix: add Qt configuration frontend X-Git-Url: https://git.unchartedbackwaters.co.uk/w/?a=commitdiff_plain;h=62a9cfe846edc40c6640b9acbdc72263e9bcd6d7;p=francis%2Fwinuae.git od-unix: add Qt configuration frontend Add the Qt launcher, preferences adapter, mount configuration helpers, and frontend tests for the Unix target. The frontend reads and writes typed preferences while keeping UI state out of the shared configuration parser. --- diff --git a/od-unix/gui.cpp b/od-unix/gui.cpp index d4365b2e..ecffb8d1 100644 --- a/od-unix/gui.cpp +++ b/od-unix/gui.cpp @@ -32,13 +32,6 @@ void target_main_set_args(int argc, TCHAR **argv) int target_main_handle_early(int argc, TCHAR **argv) { -#ifdef WINUAE_UNIX_WITH_INTEGRATED_QT_UI - for (int i = 1; i < argc; i++) { - if (!_tcscmp(argv[i], _T("--qt-board-catalog"))) { - return runWinUaeQtBoardCatalogDump(); - } - } -#endif return -1; } diff --git a/od-unix/qt/config.cpp b/od-unix/qt/config.cpp new file mode 100644 index 00000000..8d709a3d --- /dev/null +++ b/od-unix/qt/config.cpp @@ -0,0 +1,374 @@ +#include "config.h" + +#include +#include + +#include + +static bool isUiOnlySetting(const QString &key) +{ + return key.startsWith(QStringLiteral("unix.ui.")); +} + +WinUaeQtConfig::WinUaeQtConfig(Settings settings) +{ + setSettings(std::move(settings)); +} + +WinUaeQtConfig::DocumentLine WinUaeQtConfig::makeSettingLine(const QString &key, const QString &value) +{ + DocumentLine line; + line.text = QStringLiteral("%1=%2").arg(key, value); + line.key = key; + line.value = value; + line.setting = true; + return line; +} + +const WinUaeQtConfig::Settings &WinUaeQtConfig::settings() const +{ + return configSettings; +} + +const WinUaeQtConfig::OrderedSettings &WinUaeQtConfig::orderedSettings() const +{ + return orderedConfigSettings; +} + +QStringList WinUaeQtConfig::values(const QString &key) const +{ + QStringList result; + for (const Setting &setting : orderedConfigSettings) { + if (setting.key == key && !setting.value.isEmpty()) { + result.append(setting.value); + } + } + return result; +} + +void WinUaeQtConfig::setSettings(Settings settings) +{ + configSettings = std::move(settings); + orderedConfigSettings = orderedFromSettings(configSettings); + documentLines.clear(); + documentLoaded = false; +} + +WinUaeQtConfig::OrderedSettings WinUaeQtConfig::orderedFromSettings(const Settings &settings) +{ + OrderedSettings ordered; + for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) { + if (!it.value().isEmpty()) { + ordered.append({ it.key(), it.value() }); + } + } + return ordered; +} + +void WinUaeQtConfig::rebuildSettingsFromOrderedSettings() +{ + configSettings.clear(); + for (const Setting &setting : std::as_const(orderedConfigSettings)) { + if (!setting.value.isEmpty()) { + configSettings.insert(setting.key, setting.value); + } + } +} + +void WinUaeQtConfig::rebuildSettingsFromDocumentLines() +{ + configSettings.clear(); + orderedConfigSettings.clear(); + for (const DocumentLine &line : std::as_const(documentLines)) { + if (line.setting && !line.value.isEmpty()) { + orderedConfigSettings.append({ line.key, line.value }); + configSettings.insert(line.key, line.value); + } + } +} + +void WinUaeQtConfig::applySettings(const Settings &settings, const QStringList &ownedKeys) +{ + for (const QString &key : ownedKeys) { + const QString value = settings.value(key); + if (settings.contains(key) && !value.isEmpty()) { + configSettings.insert(key, value); + } else { + configSettings.remove(key); + } + } + + OrderedSettings updatedSettings; + for (const Setting &setting : std::as_const(orderedConfigSettings)) { + if (!ownedKeys.contains(setting.key)) { + updatedSettings.append(setting); + } + } + for (const QString &key : ownedKeys) { + const QString value = settings.value(key); + if (settings.contains(key) && !value.isEmpty()) { + updatedSettings.append({ key, value }); + } + } + orderedConfigSettings = updatedSettings; + + if (!documentLoaded) { + return; + } + + QList updated; + QStringList emitted; + for (const DocumentLine &line : std::as_const(documentLines)) { + if (!line.setting || !ownedKeys.contains(line.key)) { + updated.append(line); + continue; + } + + const QString value = settings.value(line.key); + if (settings.contains(line.key) && !value.isEmpty() && !emitted.contains(line.key)) { + updated.append(makeSettingLine(line.key, value)); + emitted.append(line.key); + } + } + + for (const QString &key : ownedKeys) { + const QString value = settings.value(key); + if (settings.contains(key) && !value.isEmpty() && !emitted.contains(key)) { + updated.append(makeSettingLine(key, value)); + emitted.append(key); + } + } + + documentLines = updated; + rebuildSettingsFromDocumentLines(); +} + +void WinUaeQtConfig::applyRepeatedSettings(const OrderedSettings &settings, const QStringList &ownedKeys) +{ + OrderedSettings updatedSettings; + for (const Setting &setting : std::as_const(orderedConfigSettings)) { + if (!ownedKeys.contains(setting.key)) { + updatedSettings.append(setting); + } + } + for (const Setting &setting : settings) { + if (ownedKeys.contains(setting.key) && !setting.value.isEmpty()) { + updatedSettings.append(setting); + } + } + orderedConfigSettings = updatedSettings; + rebuildSettingsFromOrderedSettings(); + + if (!documentLoaded) { + return; + } + + QList updated; + bool inserted = false; + for (const DocumentLine &line : std::as_const(documentLines)) { + if (!line.setting || !ownedKeys.contains(line.key)) { + updated.append(line); + continue; + } + if (!inserted) { + for (const Setting &setting : settings) { + if (ownedKeys.contains(setting.key) && !setting.value.isEmpty()) { + updated.append(makeSettingLine(setting.key, setting.value)); + } + } + inserted = true; + } + } + + if (!inserted) { + for (const Setting &setting : settings) { + if (ownedKeys.contains(setting.key) && !setting.value.isEmpty()) { + updated.append(makeSettingLine(setting.key, setting.value)); + } + } + } + + documentLines = updated; + rebuildSettingsFromDocumentLines(); +} + +void WinUaeQtConfig::moveSettingBefore(const QString &key, const QStringList &beforeKeys) +{ + Setting moved; + bool found = false; + OrderedSettings updatedSettings; + for (const Setting &setting : std::as_const(orderedConfigSettings)) { + if (setting.key == key) { + if (!found) { + moved = setting; + found = true; + } + continue; + } + updatedSettings.append(setting); + } + if (!found) { + return; + } + + int insertIndex = updatedSettings.size(); + for (int i = 0; i < updatedSettings.size(); i++) { + if (beforeKeys.contains(updatedSettings[i].key)) { + insertIndex = i; + break; + } + } + updatedSettings.insert(insertIndex, moved); + orderedConfigSettings = updatedSettings; + rebuildSettingsFromOrderedSettings(); + + if (!documentLoaded) { + return; + } + + DocumentLine movedLine = makeSettingLine(moved.key, moved.value); + QList updatedLines; + bool lineFound = false; + for (const DocumentLine &line : std::as_const(documentLines)) { + if (line.setting && line.key == key) { + if (!lineFound) { + movedLine = line; + lineFound = true; + } + continue; + } + updatedLines.append(line); + } + + int lineInsertIndex = updatedLines.size(); + for (int i = 0; i < updatedLines.size(); i++) { + if (updatedLines[i].setting && beforeKeys.contains(updatedLines[i].key)) { + lineInsertIndex = i; + break; + } + } + updatedLines.insert(lineInsertIndex, movedLine); + documentLines = updatedLines; + rebuildSettingsFromDocumentLines(); +} + +QString WinUaeQtConfig::value(const QString &key, const QString &defaultValue) const +{ + return configSettings.value(key, defaultValue); +} + +void WinUaeQtConfig::setValue(const QString &key, const QString &value) +{ + Settings singleSetting; + if (!value.isEmpty()) { + singleSetting.insert(key, value); + } + applySettings(singleSetting, QStringList { key }); +} + +void WinUaeQtConfig::removeValue(const QString &key) +{ + applySettings(Settings(), QStringList { key }); +} + +bool WinUaeQtConfig::load(const QString &path, QString *error) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (error) { + *error = file.errorString(); + } + return false; + } + + Settings loaded; + OrderedSettings ordered; + QList lines; + while (!file.atEnd()) { + QString text = QString::fromUtf8(file.readLine()); + while (text.endsWith(QLatin1Char('\n')) || text.endsWith(QLatin1Char('\r'))) { + text.chop(1); + } + + DocumentLine line; + line.text = text; + + const QString trimmed = text.trimmed(); + if (trimmed.isEmpty() || trimmed.startsWith(QLatin1Char(';')) || trimmed.startsWith(QLatin1Char('#'))) { + lines.append(line); + continue; + } + + const int separator = text.indexOf(QLatin1Char('=')); + if (separator <= 0) { + lines.append(line); + continue; + } + + const QString key = text.left(separator).trimmed(); + const QString value = text.mid(separator + 1).trimmed(); + if (!key.isEmpty()) { + line.key = key; + line.value = value; + line.setting = true; + loaded.insert(key, value); + if (!value.isEmpty()) { + ordered.append({ key, value }); + } + } + lines.append(line); + } + + configSettings = loaded; + orderedConfigSettings = ordered; + documentLines = lines; + documentLoaded = true; + return true; +} + +bool WinUaeQtConfig::save(const QString &path, QString *error) const +{ + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + if (error) { + *error = file.errorString(); + } + return false; + } + + QTextStream out(&file); + if (documentLoaded) { + for (const DocumentLine &line : documentLines) { + out << line.text << "\n"; + } + return true; + } + + out << "; WinUAE Unix Qt configuration\n"; + for (const Setting &setting : orderedConfigSettings) { + if (!setting.value.isEmpty()) { + out << setting.key << "=" << setting.value << "\n"; + } + } + return true; +} + +QStringList WinUaeQtConfig::commandArguments() const +{ + QStringList args; + for (const Setting &setting : orderedConfigSettings) { + if (!setting.value.isEmpty() && !isUiOnlySetting(setting.key)) { + args << QStringLiteral("-s") << QStringLiteral("%1=%2").arg(setting.key, setting.value); + } + } + return args; +} + +QStringList WinUaeQtConfig::validateForLaunch() const +{ + QStringList errors; + if (value(QStringLiteral("kickstart_rom_file")).trimmed().isEmpty()) { + errors << QStringLiteral("Main ROM file is required."); + } + return errors; +} diff --git a/od-unix/qt/config.h b/od-unix/qt/config.h new file mode 100644 index 00000000..2e55c18d --- /dev/null +++ b/od-unix/qt/config.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include + +class WinUaeQtConfig { +public: + using Settings = QMap; + struct Setting { + QString key; + QString value; + }; + using OrderedSettings = QList; + + WinUaeQtConfig() = default; + explicit WinUaeQtConfig(Settings settings); + + const Settings &settings() const; + const OrderedSettings &orderedSettings() const; + QStringList values(const QString &key) const; + void setSettings(Settings settings); + void applySettings(const Settings &settings, const QStringList &ownedKeys); + void applyRepeatedSettings(const OrderedSettings &settings, const QStringList &ownedKeys); + void moveSettingBefore(const QString &key, const QStringList &beforeKeys); + + QString value(const QString &key, const QString &defaultValue = QString()) const; + void setValue(const QString &key, const QString &value); + void removeValue(const QString &key); + + bool load(const QString &path, QString *error = nullptr); + bool save(const QString &path, QString *error = nullptr) const; + + QStringList commandArguments() const; + QStringList validateForLaunch() const; + +private: + struct DocumentLine { + QString text; + QString key; + QString value; + bool setting = false; + }; + + static DocumentLine makeSettingLine(const QString &key, const QString &value); + static OrderedSettings orderedFromSettings(const Settings &settings); + + void rebuildSettingsFromDocumentLines(); + void rebuildSettingsFromOrderedSettings(); + + Settings configSettings; + OrderedSettings orderedConfigSettings; + QList documentLines; + bool documentLoaded = false; +}; diff --git a/od-unix/qt/config_roundtrip_test.cpp b/od-unix/qt/config_roundtrip_test.cpp new file mode 100644 index 00000000..7d0c9bf7 --- /dev/null +++ b/od-unix/qt/config_roundtrip_test.cpp @@ -0,0 +1,322 @@ +#include "config.h" + +#include +#include +#include +#include +#include + +static bool writeText(const QString &path, const QString &text) +{ + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + qWarning().noquote() << file.errorString(); + return false; + } + QTextStream out(&file); + out << text; + return true; +} + +static QString readText(const QString &path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning().noquote() << file.errorString(); + return QString(); + } + return QString::fromUtf8(file.readAll()); +} + +static bool requireContains(const QString &text, const QString &needle) +{ + if (text.contains(needle)) { + return true; + } + qWarning().noquote() << "missing expected text:" << needle; + return false; +} + +static bool requireNotContains(const QString &text, const QString &needle) +{ + if (!text.contains(needle)) { + return true; + } + qWarning().noquote() << "unexpected text:" << needle; + return false; +} + +static bool requireCount(const QString &text, const QString &needle, int expected) +{ + int count = 0; + int offset = 0; + for (;;) { + offset = text.indexOf(needle, offset); + if (offset < 0) { + break; + } + count++; + offset += needle.size(); + } + if (count == expected) { + return true; + } + qWarning().noquote() << needle << "expected count" << expected << "got" << count; + return false; +} + +static bool requireBefore(const QString &text, const QString &first, const QString &second) +{ + const int firstIndex = text.indexOf(first); + const int secondIndex = text.indexOf(second); + if (firstIndex >= 0 && secondIndex >= 0 && firstIndex < secondIndex) { + return true; + } + qWarning().noquote() << first << "was not before" << second; + return false; +} + +static bool requireArgBefore(const QStringList &args, const QString &first, const QString &second) +{ + const int firstIndex = args.indexOf(first); + const int secondIndex = args.indexOf(second); + if (firstIndex >= 0 && secondIndex >= 0 && firstIndex < secondIndex) { + return true; + } + qWarning().noquote() << first << "argument was not before" << second; + return false; +} + +int main() +{ + QTemporaryDir tempDir; + if (!tempDir.isValid()) { + qWarning().noquote() << "failed to create temporary directory"; + return 1; + } + + const QString inputPath = QDir(tempDir.path()).filePath(QStringLiteral("input.uae")); + const QString outputPath = QDir(tempDir.path()).filePath(QStringLiteral("output.uae")); + const QString input = + QStringLiteral("; keep this comment\n") + + QStringLiteral("unknown_setting=keep-me\n") + + QStringLiteral("kickstart_rom_file=/old.rom\n") + + QStringLiteral("kickstart_ext_rom_file=/old-ext.rom\n") + + QStringLiteral("malformed line without separator\n") + + QStringLiteral("# keep this too\n") + + QStringLiteral("chipset=ecs\n") + + QStringLiteral("serial_port=TCP:127.0.0.1:1234\n") + + QStringLiteral("midiout_device=-2\n") + + QStringLiteral("midiout_device_name=none\n") + + QStringLiteral("midiin_device=-1\n") + + QStringLiteral("midiin_device_name=none\n") + + QStringLiteral("midirouter=false\n") + + QStringLiteral("filesystem2=rw,DH0:Old:/old/System,0\n") + + QStringLiteral("; keep mount comment\n") + + QStringLiteral("filesystem2=rw,DH1:Old2:/old/Work,0\n") + + QStringLiteral("hardfile2=rw,DH2:/old/disk.hdf,32,1,2,512,0,,uae0\n"); + + if (!writeText(inputPath, input)) { + return 1; + } + + WinUaeQtConfig config; + QString error; + if (!config.load(inputPath, &error)) { + qWarning().noquote() << error; + return 1; + } + + WinUaeQtConfig::Settings edited; + edited.insert(QStringLiteral("kickstart_rom_file"), QStringLiteral("/new.rom")); + edited.insert(QStringLiteral("chipset"), QStringLiteral("aga")); + edited.insert(QStringLiteral("cpu_model"), QStringLiteral("68020")); + edited.insert(QStringLiteral("unix.serial_port"), QStringLiteral("TCP://0.0.0.0:1234")); + edited.insert(QStringLiteral("midiout_device"), QStringLiteral("-1")); + edited.insert(QStringLiteral("midiout_device_name"), QStringLiteral("default")); + edited.insert(QStringLiteral("midiin_device"), QStringLiteral("0")); + edited.insert(QStringLiteral("midiin_device_name"), QStringLiteral("Loopback MIDI")); + edited.insert(QStringLiteral("midirouter"), QStringLiteral("true")); + edited.insert(QStringLiteral("unix.ui.config_path"), QStringLiteral("/configs")); + edited.insert(QStringLiteral("unix.screenshot_path"), QStringLiteral("/screenshots")); + config.applySettings(edited, { + QStringLiteral("kickstart_rom_file"), + QStringLiteral("kickstart_ext_rom_file"), + QStringLiteral("chipset"), + QStringLiteral("cpu_model"), + QStringLiteral("serial_port"), + QStringLiteral("unix.serial_port"), + QStringLiteral("midiout_device"), + QStringLiteral("midiout_device_name"), + QStringLiteral("midiin_device"), + QStringLiteral("midiin_device_name"), + QStringLiteral("midirouter"), + QStringLiteral("unix.ui.config_path"), + QStringLiteral("unix.screenshot_path") + }); + config.applyRepeatedSettings({ + { QStringLiteral("filesystem2"), QStringLiteral("rw,DH0:System:/new/System,0") }, + { QStringLiteral("filesystem2"), QStringLiteral("ro,DH1:Work:\"/new/Work,Disk\",5") } + }, { + QStringLiteral("filesystem2"), + QStringLiteral("hardfile2"), + QStringLiteral("uaehf0") + }); + + if (!config.save(outputPath, &error)) { + qWarning().noquote() << error; + return 1; + } + + const QString output = readText(outputPath); + bool ok = true; + ok = requireContains(output, QStringLiteral("; keep this comment\n")) && ok; + ok = requireContains(output, QStringLiteral("unknown_setting=keep-me\n")) && ok; + ok = requireContains(output, QStringLiteral("malformed line without separator\n")) && ok; + ok = requireContains(output, QStringLiteral("# keep this too\n")) && ok; + ok = requireContains(output, QStringLiteral("; keep mount comment\n")) && ok; + ok = requireContains(output, QStringLiteral("kickstart_rom_file=/new.rom\n")) && ok; + ok = requireContains(output, QStringLiteral("chipset=aga\n")) && ok; + ok = requireContains(output, QStringLiteral("cpu_model=68020\n")) && ok; + ok = requireContains(output, QStringLiteral("unix.serial_port=TCP://0.0.0.0:1234\n")) && ok; + ok = requireContains(output, QStringLiteral("midiout_device=-1\n")) && ok; + ok = requireContains(output, QStringLiteral("midiout_device_name=default\n")) && ok; + ok = requireContains(output, QStringLiteral("midiin_device=0\n")) && ok; + ok = requireContains(output, QStringLiteral("midiin_device_name=Loopback MIDI\n")) && ok; + ok = requireContains(output, QStringLiteral("midirouter=true\n")) && ok; + ok = requireContains(output, QStringLiteral("unix.ui.config_path=/configs\n")) && ok; + ok = requireContains(output, QStringLiteral("unix.screenshot_path=/screenshots\n")) && ok; + ok = requireContains(output, QStringLiteral("filesystem2=rw,DH0:System:/new/System,0\n")) && ok; + ok = requireContains(output, QStringLiteral("filesystem2=ro,DH1:Work:\"/new/Work,Disk\",5\n")) && ok; + ok = requireCount(output, QStringLiteral("filesystem2="), 2) && ok; + ok = requireBefore(output, QStringLiteral("filesystem2=rw,DH0:System:/new/System,0\n"), QStringLiteral("filesystem2=ro,DH1:Work:\"/new/Work,Disk\",5\n")) && ok; + ok = requireNotContains(output, QStringLiteral("kickstart_ext_rom_file=")) && ok; + ok = requireNotContains(output, QStringLiteral("/old.rom")) && ok; + ok = requireNotContains(output, QStringLiteral("serial_port=TCP:127.0.0.1:1234\n")) && ok; + ok = requireNotContains(output, QStringLiteral("midiout_device=-2\n")) && ok; + ok = requireNotContains(output, QStringLiteral("midiout_device_name=none\n")) && ok; + ok = requireNotContains(output, QStringLiteral("/old/System")) && ok; + ok = requireNotContains(output, QStringLiteral("hardfile2=")) && ok; + + const QStringList args = config.commandArguments(); + ok = args.contains(QStringLiteral("unknown_setting=keep-me")) && ok; + ok = args.contains(QStringLiteral("kickstart_rom_file=/new.rom")) && ok; + ok = args.contains(QStringLiteral("unix.serial_port=TCP://0.0.0.0:1234")) && ok; + ok = args.contains(QStringLiteral("midiout_device=-1")) && ok; + ok = args.contains(QStringLiteral("midiin_device=0")) && ok; + ok = args.contains(QStringLiteral("midirouter=true")) && ok; + ok = !args.contains(QStringLiteral("unix.ui.config_path=/configs")) && ok; + ok = args.contains(QStringLiteral("unix.screenshot_path=/screenshots")) && ok; + ok = args.contains(QStringLiteral("filesystem2=rw,DH0:System:/new/System,0")) && ok; + ok = args.contains(QStringLiteral("filesystem2=ro,DH1:Work:\"/new/Work,Disk\",5")) && ok; + ok = requireArgBefore(args, QStringLiteral("filesystem2=rw,DH0:System:/new/System,0"), QStringLiteral("filesystem2=ro,DH1:Work:\"/new/Work,Disk\",5")) && ok; + ok = !args.contains(QStringLiteral("kickstart_ext_rom_file=/old-ext.rom")) && ok; + ok = requireCount(args.join(QLatin1Char('\n')), QStringLiteral("filesystem2="), 2) && ok; + + const QString expansionInputPath = QDir(tempDir.path()).filePath(QStringLiteral("expansion-input.uae")); + const QString expansionOutputPath = QDir(tempDir.path()).filePath(QStringLiteral("expansion-output.uae")); + const QString expansionInput = + QStringLiteral("config_description=A4000\n") + + QStringLiteral("a4091_rom_file=/old-a4091.rom\n") + + QStringLiteral("hardfile2=rw,SCSI_0:/old/disk.hdf,0,0,0,512,0,,scsi0\n") + + QStringLiteral("quickstart=A4000,1\n") + + QStringLiteral("chipset_compatible=A4000\n"); + + if (!writeText(expansionInputPath, expansionInput)) { + return 1; + } + + WinUaeQtConfig expansionConfig; + if (!expansionConfig.load(expansionInputPath, &error)) { + qWarning().noquote() << error; + return 1; + } + + const QStringList expansionKeys { + QStringLiteral("a4091_rom_file"), + QStringLiteral("a4091_rom_options") + }; + const QStringList mountKeys { + QStringLiteral("hardfile2") + }; + + expansionConfig.applySettings(WinUaeQtConfig::Settings(), expansionKeys); + expansionConfig.applyRepeatedSettings(WinUaeQtConfig::OrderedSettings(), mountKeys); + + WinUaeQtConfig::Settings expansionEdited; + expansionEdited.insert(QStringLiteral("chipset_compatible"), QStringLiteral("A4000")); + expansionConfig.applySettings(expansionEdited, { + QStringLiteral("quickstart"), + QStringLiteral("chipset_compatible") + }); + + WinUaeQtConfig::Settings expansionBoardEdited; + expansionBoardEdited.insert(QStringLiteral("a4091_rom_file"), QStringLiteral("/new-a4091.rom")); + expansionConfig.applySettings(expansionBoardEdited, expansionKeys); + expansionConfig.applyRepeatedSettings({ + { QStringLiteral("hardfile2"), QStringLiteral("rw,SCSI_0:/new/disk.hdf,0,0,0,512,0,,scsi0_a4091") } + }, mountKeys); + + if (!expansionConfig.save(expansionOutputPath, &error)) { + qWarning().noquote() << error; + return 1; + } + + const QString expansionOutput = readText(expansionOutputPath); + ok = requireContains(expansionOutput, QStringLiteral("a4091_rom_file=/new-a4091.rom\n")) && ok; + ok = requireContains(expansionOutput, QStringLiteral("hardfile2=rw,SCSI_0:/new/disk.hdf,0,0,0,512,0,,scsi0_a4091\n")) && ok; + ok = requireBefore(expansionOutput, QStringLiteral("a4091_rom_file=/new-a4091.rom\n"), QStringLiteral("hardfile2=rw,SCSI_0:/new/disk.hdf,0,0,0,512,0,,scsi0_a4091\n")) && ok; + ok = requireNotContains(expansionOutput, QStringLiteral("quickstart=")) && ok; + ok = requireNotContains(expansionOutput, QStringLiteral("/old-a4091.rom")) && ok; + ok = requireNotContains(expansionOutput, QStringLiteral("/old/disk.hdf")) && ok; + + const QString quickstartInputPath = QDir(tempDir.path()).filePath(QStringLiteral("quickstart-input.uae")); + const QString quickstartOutputPath = QDir(tempDir.path()).filePath(QStringLiteral("quickstart-output.uae")); + const QString quickstartInput = + QStringLiteral("cpu_model=68020\n") + + QStringLiteral("cpu_24bit_addressing=false\n") + + QStringLiteral("quickstart=A1200,1\n") + + QStringLiteral("gfxcard_size=4\n") + + QStringLiteral("gfxcard_type=ZorroIII\n"); + if (!writeText(quickstartInputPath, quickstartInput)) { + return 1; + } + + WinUaeQtConfig quickstartConfig; + if (!quickstartConfig.load(quickstartInputPath, &error)) { + qWarning().noquote() << error; + return 1; + } + quickstartConfig.moveSettingBefore(QStringLiteral("quickstart"), { + QStringLiteral("cpu_model"), + QStringLiteral("cpu_24bit_addressing"), + QStringLiteral("gfxcard_size"), + QStringLiteral("gfxcard_type") + }); + WinUaeQtConfig::Settings quickstartEdited; + quickstartEdited.insert(QStringLiteral("cpu_model"), QStringLiteral("68020")); + quickstartEdited.insert(QStringLiteral("cpu_24bit_addressing"), QStringLiteral("false")); + quickstartEdited.insert(QStringLiteral("gfxcard_size"), QStringLiteral("4")); + quickstartEdited.insert(QStringLiteral("gfxcard_type"), QStringLiteral("ZorroIII")); + quickstartConfig.applySettings(quickstartEdited, { + QStringLiteral("quickstart"), + QStringLiteral("cpu_model"), + QStringLiteral("cpu_24bit_addressing"), + QStringLiteral("gfxcard_size"), + QStringLiteral("gfxcard_type") + }); + if (!quickstartConfig.save(quickstartOutputPath, &error)) { + qWarning().noquote() << error; + return 1; + } + const QString quickstartOutput = readText(quickstartOutputPath); + ok = requireNotContains(quickstartOutput, QStringLiteral("quickstart=")) && ok; + ok = requireContains(quickstartOutput, QStringLiteral("cpu_model=68020\n")) && ok; + ok = requireBefore(quickstartOutput, QStringLiteral("cpu_24bit_addressing=false\n"), QStringLiteral("gfxcard_size=4\n")) && ok; + const QStringList quickstartArgs = quickstartConfig.commandArguments(); + ok = !quickstartArgs.contains(QStringLiteral("quickstart=A1200,1")) && ok; + ok = quickstartArgs.contains(QStringLiteral("cpu_model=68020")) && ok; + ok = quickstartArgs.contains(QStringLiteral("gfxcard_size=4")) && ok; + + return ok ? 0 : 1; +} diff --git a/od-unix/qt/launcher.cpp b/od-unix/qt/launcher.cpp new file mode 100644 index 00000000..8402ace5 --- /dev/null +++ b/od-unix/qt/launcher.cpp @@ -0,0 +1,17003 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#if defined(__APPLE__) || defined(__linux__) +#include +#include +#include +#endif +#if defined(__APPLE__) +#include +#if defined(UAE_UNIX_WITH_COREMIDI) +#include +#include +#endif +#elif defined(__linux__) +#include +#if defined(UAE_UNIX_WITH_ALSA_MIDI) +#include +#endif +#endif + +#ifdef UAE_UNIX_WITH_SDL3 +#define SDL_MAIN_HANDLED +#include +#include +#endif + +#include "config.h" +#include "launcher.h" +#include "mount_config.h" +#include "path_utils.h" + +#ifndef WINUAE_UNIX_SOURCE_DIR +#define WINUAE_UNIX_SOURCE_DIR "." +#endif + +#ifndef WINUAE_UNIX_INSTALL_DATA_DIR +#define WINUAE_UNIX_INSTALL_DATA_DIR WINUAE_UNIX_SOURCE_DIR +#endif + +#ifndef WINUAE_UNIX_INSTALL_DATADIR_RELATIVE +#define WINUAE_UNIX_INSTALL_DATADIR_RELATIVE "share/winuae" +#endif + +#ifndef WINUAE_UNIX_VERSION_MAJOR +#define WINUAE_UNIX_VERSION_MAJOR 0 +#endif + +#ifndef WINUAE_UNIX_VERSION_MINOR +#define WINUAE_UNIX_VERSION_MINOR 0 +#endif + +#ifndef WINUAE_UNIX_VERSION_REVISION +#define WINUAE_UNIX_VERSION_REVISION 0 +#endif + +#ifndef UAE_UNIX_WITH_BSDSOCKET +#define UAE_UNIX_WITH_BSDSOCKET 0 +#endif + +#ifndef UAE_UNIX_WITH_UAESCSI +#define UAE_UNIX_WITH_UAESCSI 0 +#endif + +#ifndef UAE_UNIX_WITH_SANA2 +#define UAE_UNIX_WITH_SANA2 0 +#endif + +#ifndef UAE_UNIX_WITH_JIT +#define UAE_UNIX_WITH_JIT 0 +#endif + +#ifndef UAE_UNIX_WITH_SAMPLER +#define UAE_UNIX_WITH_SAMPLER 0 +#endif + +#ifndef UAE_UNIX_WITH_MIDI +#define UAE_UNIX_WITH_MIDI 0 +#endif + +#ifndef UAE_UNIX_WITH_SNDBOARD +#define UAE_UNIX_WITH_SNDBOARD 0 +#endif + +#ifndef UAE_UNIX_WITH_SHADER_PIPELINE +#define UAE_UNIX_WITH_SHADER_PIPELINE 0 +#endif + +#ifndef UAE_UNIX_WITH_NATIVE_HARDDRIVES +#define UAE_UNIX_WITH_NATIVE_HARDDRIVES 0 +#endif + +#ifndef UAE_UNIX_WITH_TABLET +#define UAE_UNIX_WITH_TABLET 0 +#endif + +static bool systemPrefersDarkMode(); +static void applyApplicationColors(QApplication &app, bool dark); + +static constexpr int FpuInternal = -1; +static constexpr int MaxMountEntries = 8; +static constexpr int MaxControllerUnits = 8; +static constexpr int MaxCdSlots = 8; +static constexpr int MaxRomBoards = 4; +static constexpr int MaxDiskSwapperSlots = 20; +static constexpr int MaxQuickstartConfigs = 6; +static constexpr int MaxJoyportCustomSlots = 6; +static constexpr int CustomInputConfigSlots = 3; +static constexpr int GamePortsInputConfigLine = 4; +static constexpr int MaxInputSubEventSlots = 8; +static constexpr int HardwareBoardIndexRole = Qt::UserRole; +static constexpr int InputMappingKeyRole = Qt::UserRole; +static constexpr int InputMappingEditableRole = Qt::UserRole + 1; +static constexpr int InputFlagAutofire = 1; +static constexpr int InputFlagToggle = 2; +static constexpr int InputFlagInvertToggle = 16; +static constexpr int InputFlagInvert = 32; +static constexpr int InputFlagSetOnOff = 128; +static constexpr int InputFlagSetOnOffVal1 = 256; +static constexpr int InputFlagSetOnOffVal2 = 512; + +static bool unixJitBackendAvailable() +{ +#if UAE_UNIX_WITH_JIT + return true; +#else + return false; +#endif +} + +static bool unixShaderPipelineAvailable() +{ +#if UAE_UNIX_WITH_SHADER_PIPELINE + return true; +#else + return false; +#endif +} + +static bool unixTabletBackendAvailable() +{ +#if UAE_UNIX_WITH_TABLET + return true; +#else + return false; +#endif +} + +struct WinUaeQtInputEventChoice { + const char *display; + const char *config; +}; + +static const WinUaeQtInputEventChoice inputEventChoices[] = { + { "Joy1 Horizontal", "JOY1_HORIZ" }, + { "Joy1 Vertical", "JOY1_VERT" }, + { "Joy1 Horizontal (Analog)", "JOY1_HORIZ_POT" }, + { "Joy1 Vertical (Analog)", "JOY1_VERT_POT" }, + { "Joy1 Left", "JOY1_LEFT" }, + { "Joy1 Right", "JOY1_RIGHT" }, + { "Joy1 Up", "JOY1_UP" }, + { "Joy1 Down", "JOY1_DOWN" }, + { "Joy1 Left+Up", "JOY1_LEFT_UP" }, + { "Joy1 Left+Down", "JOY1_LEFT_DOWN" }, + { "Joy1 Right+Up", "JOY1_RIGHT_UP" }, + { "Joy1 Right+Down", "JOY1_RIGHT_DOWN" }, + { "Joy1 Fire/Mouse1 Left Button", "JOY1_FIRE_BUTTON" }, + { "Joy1 2nd Button/Mouse1 Right Button", "JOY1_2ND_BUTTON" }, + { "Joy1 3rd Button/Mouse1 Middle Button", "JOY1_3RD_BUTTON" }, + { "Joy1 CD32 Play", "JOY1_CD32_PLAY" }, + { "Joy1 CD32 RWD", "JOY1_CD32_RWD" }, + { "Joy1 CD32 FFW", "JOY1_CD32_FFW" }, + { "Joy1 CD32 Green", "JOY1_CD32_GREEN" }, + { "Joy1 CD32 Yellow", "JOY1_CD32_YELLOW" }, + { "Joy1 CD32 Red", "JOY1_CD32_RED" }, + { "Joy1 CD32 Blue", "JOY1_CD32_BLUE" }, + { "Mouse1 Horizontal", "MOUSE1_HORIZ" }, + { "Mouse1 Vertical", "MOUSE1_VERT" }, + { "Mouse1 Horizontal (inverted)", "MOUSE1_HORIZ_INV" }, + { "Mouse1 Vertical (inverted)", "MOUSE1_VERT_INV" }, + { "Mouse1 Up", "MOUSE1_UP" }, + { "Mouse1 Down", "MOUSE1_DOWN" }, + { "Mouse1 Left", "MOUSE1_LEFT" }, + { "Mouse1 Right", "MOUSE1_RIGHT" }, + { "Mouse1 Wheel", "MOUSE1_WHEEL" }, + { "Joy2 Horizontal", "JOY2_HORIZ" }, + { "Joy2 Vertical", "JOY2_VERT" }, + { "Joy2 Horizontal (Analog)", "JOY2_HORIZ_POT" }, + { "Joy2 Vertical (Analog)", "JOY2_VERT_POT" }, + { "Joy2 Left", "JOY2_LEFT" }, + { "Joy2 Right", "JOY2_RIGHT" }, + { "Joy2 Up", "JOY2_UP" }, + { "Joy2 Down", "JOY2_DOWN" }, + { "Joy2 Left+Up", "JOY2_LEFT_UP" }, + { "Joy2 Left+Down", "JOY2_LEFT_DOWN" }, + { "Joy2 Right+Up", "JOY2_RIGHT_UP" }, + { "Joy2 Right+Down", "JOY2_RIGHT_DOWN" }, + { "Joy2 Fire/Mouse2 Left Button", "JOY2_FIRE_BUTTON" }, + { "Joy2 2nd Button/Mouse2 Right Button", "JOY2_2ND_BUTTON" }, + { "Joy2 3rd Button/Mouse2 Middle Button", "JOY2_3RD_BUTTON" }, + { "Joy2 CD32 Play", "JOY2_CD32_PLAY" }, + { "Joy2 CD32 RWD", "JOY2_CD32_RWD" }, + { "Joy2 CD32 FFW", "JOY2_CD32_FFW" }, + { "Joy2 CD32 Green", "JOY2_CD32_GREEN" }, + { "Joy2 CD32 Yellow", "JOY2_CD32_YELLOW" }, + { "Joy2 CD32 Red", "JOY2_CD32_RED" }, + { "Joy2 CD32 Blue", "JOY2_CD32_BLUE" }, + { "Mouse2 Horizontal", "MOUSE2_HORIZ" }, + { "Mouse2 Vertical", "MOUSE2_VERT" }, + { "Mouse2 Horizontal (inverted)", "MOUSE2_HORIZ_INV" }, + { "Mouse2 Vertical (inverted)", "MOUSE2_VERT_INV" }, + { "Mouse2 Up", "MOUSE2_UP" }, + { "Mouse2 Down", "MOUSE2_DOWN" }, + { "Mouse2 Left", "MOUSE2_LEFT" }, + { "Mouse2 Right", "MOUSE2_RIGHT" }, + { "Parallel Joy1 Horizontal", "PAR_JOY1_HORIZ" }, + { "Parallel Joy1 Vertical", "PAR_JOY1_VERT" }, + { "Parallel Joy1 Left", "PAR_JOY1_LEFT" }, + { "Parallel Joy1 Right", "PAR_JOY1_RIGHT" }, + { "Parallel Joy1 Up", "PAR_JOY1_UP" }, + { "Parallel Joy1 Down", "PAR_JOY1_DOWN" }, + { "Parallel Joy1 Fire Button", "PAR_JOY1_FIRE_BUTTON" }, + { "Parallel Joy1 Spare/2nd Button", "PAR_JOY1_2ND_BUTTON" }, + { "Parallel Joy2 Horizontal", "PAR_JOY2_HORIZ" }, + { "Parallel Joy2 Vertical", "PAR_JOY2_VERT" }, + { "Parallel Joy2 Left", "PAR_JOY2_LEFT" }, + { "Parallel Joy2 Right", "PAR_JOY2_RIGHT" }, + { "Parallel Joy2 Up", "PAR_JOY2_UP" }, + { "Parallel Joy2 Down", "PAR_JOY2_DOWN" }, + { "Parallel Joy2 Fire Button", "PAR_JOY2_FIRE_BUTTON" }, + { "Parallel Joy2 Spare/2nd Button", "PAR_JOY2_2ND_BUTTON" }, + { "Enter GUI", "SPC_ENTERGUI" }, + { "Screenshot (file)", "SPC_SCREENSHOT" }, + { "Pause emulation", "SPC_PAUSE" }, + { "Warp mode", "SPC_WARP" }, + { "Quit emulator", "SPC_QUIT" }, + { "Reset emulation", "SPC_SOFTRESET" }, + { "Hard reset emulation", "SPC_HARDRESET" }, + { "Quick save state", "SPC_STATESAVE" }, + { "Quick restore state", "SPC_STATERESTORE" }, + { "Toggle windowed/fullscreen", "SPC_TOGGLEFULLSCREEN" }, + { "Toggle mouse grab", "SPC_TOGGLEMOUSEGRAB" }, + { "Swap joystick ports", "SPC_SWAPJOYPORTS" }, + { "Paste from host clipboard", "SPC_PASTE" } +}; + +struct WinUaeQtInputSlot { + QString event; + int flags = 0; + QString qualifiers; + QString suffix; + bool custom = false; +}; + +struct WinUaeQtInputRow { + QString label; + QString key; + bool editable = false; +}; + +struct WinUaeQtKeyboardChoice { + const char *display; + int scancode; + const char *defaultEvent; +}; + +static const WinUaeQtKeyboardChoice keyboardChoices[] = { + { "Escape", 41, "KEY_ESC" }, + { "F1", 58, "KEY_F1" }, + { "F2", 59, "KEY_F2" }, + { "F3", 60, "KEY_F3" }, + { "F4", 61, "KEY_F4" }, + { "F5", 62, "KEY_F5" }, + { "F6", 63, "KEY_F6" }, + { "F7", 64, "KEY_F7" }, + { "F8", 65, "KEY_F8" }, + { "F9", 66, "KEY_F9" }, + { "F10", 67, "KEY_F10" }, + { "F11", 68, "" }, + { "F12", 69, "" }, + { "1", 30, "KEY_1" }, + { "2", 31, "KEY_2" }, + { "3", 32, "KEY_3" }, + { "4", 33, "KEY_4" }, + { "5", 34, "KEY_5" }, + { "6", 35, "KEY_6" }, + { "7", 36, "KEY_7" }, + { "8", 37, "KEY_8" }, + { "9", 38, "KEY_9" }, + { "0", 39, "KEY_0" }, + { "Tab", 43, "KEY_TAB" }, + { "A", 4, "KEY_A" }, + { "B", 5, "KEY_B" }, + { "C", 6, "KEY_C" }, + { "D", 7, "KEY_D" }, + { "E", 8, "KEY_E" }, + { "F", 9, "KEY_F" }, + { "G", 10, "KEY_G" }, + { "H", 11, "KEY_H" }, + { "I", 12, "KEY_I" }, + { "J", 13, "KEY_J" }, + { "K", 14, "KEY_K" }, + { "L", 15, "KEY_L" }, + { "M", 16, "KEY_M" }, + { "N", 17, "KEY_N" }, + { "O", 18, "KEY_O" }, + { "P", 19, "KEY_P" }, + { "Q", 20, "KEY_Q" }, + { "R", 21, "KEY_R" }, + { "S", 22, "KEY_S" }, + { "T", 23, "KEY_T" }, + { "U", 24, "KEY_U" }, + { "V", 25, "KEY_V" }, + { "W", 26, "KEY_W" }, + { "X", 27, "KEY_X" }, + { "Y", 28, "KEY_Y" }, + { "Z", 29, "KEY_Z" }, + { "Caps Lock", 57, "KEY_CAPS_LOCK" }, + { "Numpad 1", 89, "KEY_NP_1" }, + { "Numpad 2", 90, "KEY_NP_2" }, + { "Numpad 3", 91, "KEY_NP_3" }, + { "Numpad 4", 92, "KEY_NP_4" }, + { "Numpad 5", 93, "KEY_NP_5" }, + { "Numpad 6", 94, "KEY_NP_6" }, + { "Numpad 7", 95, "KEY_NP_7" }, + { "Numpad 8", 96, "KEY_NP_8" }, + { "Numpad 9", 97, "KEY_NP_9" }, + { "Numpad 0", 98, "KEY_NP_0" }, + { "Numpad Period", 99, "KEY_NP_PERIOD" }, + { "Numpad Plus", 87, "KEY_NP_ADD" }, + { "Numpad Minus", 86, "KEY_NP_SUB" }, + { "Numpad Multiply", 85, "KEY_NP_MUL" }, + { "Numpad Divide", 84, "KEY_NP_DIV" }, + { "Numpad Enter", 88, "KEY_ENTER" }, + { "Minus", 45, "KEY_SUB" }, + { "Equals", 46, "KEY_EQUALS" }, + { "Backspace", 42, "KEY_BACKSPACE" }, + { "Return", 40, "KEY_RETURN" }, + { "Space", 44, "KEY_SPACE" }, + { "Left Shift", 225, "KEY_SHIFT_LEFT" }, + { "Left Ctrl", 224, "KEY_CTRL" }, + { "Left Amiga", 227, "KEY_AMIGA_LEFT" }, + { "Left Alt", 226, "KEY_ALT_LEFT" }, + { "Right Alt", 230, "KEY_ALT_RIGHT" }, + { "Right Amiga", 231, "KEY_AMIGA_RIGHT" }, + { "Right Ctrl", 228, "KEY_CTRL" }, + { "Right Shift", 229, "KEY_SHIFT_RIGHT" }, + { "Cursor Up", 82, "KEY_CURSOR_UP" }, + { "Cursor Down", 81, "KEY_CURSOR_DOWN" }, + { "Cursor Left", 80, "KEY_CURSOR_LEFT" }, + { "Cursor Right", 79, "KEY_CURSOR_RIGHT" }, + { "Insert", 73, "" }, + { "Delete", 76, "KEY_DEL" }, + { "Home", 74, "" }, + { "End", 77, "" }, + { "Page Up", 75, "" }, + { "Page Down", 78, "" }, + { "Back Quote", 53, "KEY_BACKQUOTE" }, + { "Left Bracket", 47, "KEY_LEFTBRACKET" }, + { "Right Bracket", 48, "KEY_RIGHTBRACKET" }, + { "Semicolon", 51, "KEY_SEMICOLON" }, + { "Single Quote", 52, "KEY_SINGLEQUOTE" }, + { "Backslash", 49, "KEY_BACKSLASH" }, + { "Numbersign", 50, "KEY_NUMBERSIGN" }, + { "Non-US Backslash", 100, "KEY_BACKSLASH" }, + { "Comma", 54, "KEY_COMMA" }, + { "Period", 55, "KEY_PERIOD" }, + { "Slash", 56, "KEY_DIV" }, + { "F13", 104, "" }, + { "F14", 105, "" }, + { "F15", 106, "" } +}; + +struct QuickstartModelChoice { + const char *display; + const char *configValue; + const char *compatible; + int compatibilityLevels; + int configCount; + const char *configs[MaxQuickstartConfigs]; +}; + +static const QuickstartModelChoice quickstartModelChoices[] = { + { "A500", "A500", "A500", 4, 6, { + "1.3 ROM, OCS, 512 KB Chip + 512 KB Slow RAM (most common)", + "1.3 ROM, ECS Agnus, 512 KB Chip RAM + 512 KB Slow RAM", + "1.3 ROM, ECS Agnus, 1 MB Chip RAM", + "1.3 ROM, OCS Agnus, 512 KB Chip RAM", + "1.2 ROM, OCS Agnus, 512 KB Chip RAM", + "1.2 ROM, OCS Agnus, 512 KB Chip RAM + 512 KB Slow RAM" + } }, + { "A500+", "A500+", "A500+", 4, 3, { + "Basic non-expanded configuration", + "2 MB Chip RAM expanded configuration", + "4 MB Fast RAM expanded configuration", + nullptr, + nullptr, + nullptr + } }, + { "A600", "A600", "A600", 4, 3, { + "Basic non-expanded configuration", + "2 MB Chip RAM expanded configuration", + "4 MB Fast RAM expanded configuration", + nullptr, + nullptr, + nullptr + } }, + { "A1000", "A1000", "A1000", 4, 4, { + "512 KB Chip RAM", + "ICS Denise without EHB support", + "256 KB Chip RAM", + "A1000 Velvet Prototype", + nullptr, + nullptr + } }, + { "A1200", "A1200", "A1200", 5, 6, { + "Basic non-expanded configuration", + "4 MB Fast RAM expanded configuration", + "Blizzard 1230 IV", + "Blizzard 1240", + "Blizzard 1260", + "Blizzard PPC" + } }, + { "A3000", "A3000", "A3000", 2, 3, { + "1.4 ROM, 2MB Chip + 8MB Fast", + "2.04 ROM, 2MB Chip + 8MB Fast", + "3.1 ROM, 2MB Chip + 8MB Fast", + nullptr, + nullptr, + nullptr + } }, + { "A4000", "A4000", "A4000", 1, 3, { + "68030, 3.1 ROM, 2MB Chip + 8MB Fast", + "68040, 3.1 ROM, 2MB Chip + 8MB Fast", + "CyberStorm PPC", + nullptr, + nullptr, + nullptr + } }, + { "CD32", "CD32", "CD32", 4, 3, { + "CD32", + "CD32 with Full Motion Video cartridge", + "Cubo CD32", + nullptr, + nullptr, + nullptr + } }, + { "CDTV", "CDTV", "CDTV", 4, 3, { + "CDTV", + "Floppy drive and 64KB SRAM card expanded CDTV", + "CDTV-CR", + nullptr, + nullptr, + nullptr + } } +}; + +struct WinUaeQtCdSlot { + QString path; + QString type; + bool inUse = false; +}; + +struct WinUaeQtRomBoard { + QString start; + QString end; + QString path; +}; + +struct WinUaeQtExpansionBoardState { + bool present = false; + QString romFile; + QString rawOptions; + QString subtype; + QMap optionBools; + QMap optionValues; + bool autobootDisabled = false; + bool dma24Bit = false; + bool inserted = false; + int id = 7; +}; + +struct WinUaeQtFilterState { + QString filter = QStringLiteral("none"); + QString modeH = QStringLiteral("1x"); + QString modeV = QStringLiteral("-"); + QString autoscale = QStringLiteral("auto"); + QString integerLimit = QStringLiteral("1/1"); + QString keepAspect = QStringLiteral("none"); + bool enable = true; + bool keepAutoscaleAspect = false; + bool bilinear = false; + double horizZoom = 0.0; + double vertZoom = 0.0; + double horizZoomMult = 1.0; + double vertZoomMult = 1.0; + double horizOffset = 0.0; + double vertOffset = 0.0; + int scanlines = 0; + int scanlineLevel = 0; + int scanlineOffset = 1; + int luminance = 0; + int contrast = 0; + int saturation = 0; + int gamma = 0; + int blur = 0; + int noise = 0; +}; + +enum MountDataRole { + MountKindRole = Qt::UserRole, + MountDeviceRole, + MountVolumeRole, + MountPathRole, + MountReadOnlyRole, + MountBootPriRole, + MountEmuUnitRole, + MountRawConfigRole, + MountHardfileGeometryRole, + MountHardfileTailRole +}; + +enum WinUaeQtMountControllerBus { + MountControllerBusUnknown, + MountControllerBusUae, + MountControllerBusIde, + MountControllerBusScsi +}; + +struct WinUaeQtMountControllerChoice { + QString display; + QString boardKey; + WinUaeQtMountControllerBus bus = MountControllerBusUnknown; + int maxUnit = 0; + bool valid = false; +}; + +static QStringList mountControllerParts(QString tail) +{ + if (!tail.startsWith(QLatin1Char(','))) { + tail.prepend(QLatin1Char(',')); + } + QStringList parts = winUaeQtConfigFieldList(tail); + while (parts.size() < 2) { + parts.append(QString()); + } + return parts; +} + +static QString mountControllerValue(const WinUaeQtMountEntry &entry, const QString &fallback) +{ + const QStringList parts = mountControllerParts(entry.hardfileTail); + return parts.value(1).isEmpty() ? fallback : parts.value(1); +} + +static QString mountTailWithController(const WinUaeQtMountEntry &entry, const QString &controller) +{ + QStringList parts = mountControllerParts(entry.hardfileTail); + parts[1] = controller; + return winUaeQtConfigJoinFields(parts); +} + +static QString mountControllerBaseDisplay(const QString &display) +{ + const int start = display.lastIndexOf(QStringLiteral(" [")); + if (start < 0 || !display.endsWith(QLatin1Char(']'))) { + return display; + } + bool ok = false; + display.mid(start + 2, display.size() - start - 3).toInt(&ok); + return ok ? display.left(start) : display; +} + +static int mountControllerDuplicateFromDisplay(const QString &display) +{ + const int start = display.lastIndexOf(QStringLiteral(" [")); + if (start < 0 || !display.endsWith(QLatin1Char(']'))) { + return 0; + } + bool ok = false; + const int oneBased = display.mid(start + 2, display.size() - start - 3).toInt(&ok); + return ok && oneBased >= 2 ? qBound(1, oneBased - 1, 8) : 0; +} + +static QString mountControllerDuplicateDisplaySuffix(int duplicate) +{ + return duplicate > 0 ? QStringLiteral(" [%1]").arg(duplicate + 1) : QString(); +} + +static QString mountControllerDuplicateConfigSuffix(int duplicate) +{ + return duplicate > 0 ? QStringLiteral("-%1").arg(duplicate + 1) : QString(); +} + +static WinUaeQtMountControllerBus mountControllerBusFromConfigValue(const QString &value) +{ + const QString lower = value.trimmed().toLower(); + if (lower.startsWith(QStringLiteral("uae"))) { + return MountControllerBusUae; + } + if (lower.startsWith(QStringLiteral("ide"))) { + return MountControllerBusIde; + } + if (lower.startsWith(QStringLiteral("scsi"))) { + return MountControllerBusScsi; + } + return MountControllerBusUnknown; +} + +static QString mountControllerExpansionKeyFromConfigValue(const QString &value) +{ + const int underscore = value.indexOf(QLatin1Char('_')); + if (underscore < 0) { + return QString(); + } + QString key = value.mid(underscore + 1).toLower(); + const int dash = key.lastIndexOf(QLatin1Char('-')); + if (dash > 0) { + bool ok = false; + const int duplicate = key.mid(dash + 1).toInt(&ok); + if (ok && duplicate >= 2) { + key = key.left(dash); + } + } + return key; +} + +static int mountControllerDuplicateFromConfigValue(const QString &value) +{ + const int underscore = value.indexOf(QLatin1Char('_')); + const int dash = value.lastIndexOf(QLatin1Char('-')); + if (underscore < 0 || dash <= underscore) { + return 0; + } + bool ok = false; + const int oneBased = value.mid(dash + 1).toInt(&ok); + return ok && oneBased >= 2 ? qBound(1, oneBased - 1, 8) : 0; +} + +static QString mountControllerBusName(WinUaeQtMountControllerBus bus) +{ + if (bus == MountControllerBusIde) { + return QStringLiteral("IDE"); + } + if (bus == MountControllerBusScsi) { + return QStringLiteral("SCSI"); + } + return QString(); +} + +static QString mountControllerBoardBaseName(const WinUaeQtExpansionBoardCatalogItem &board) +{ + const int manufacturer = board.display.lastIndexOf(QStringLiteral(" (")); + if (manufacturer > 0 && board.display.endsWith(QLatin1Char(')'))) { + return board.display.left(manufacturer); + } + return board.display; +} + +static bool mountControllerBoardSupportsBus( + const WinUaeQtExpansionBoardCatalogItem &board, + WinUaeQtMountControllerBus bus) +{ + if (bus == MountControllerBusIde) { + return board.categoryMask & WinUaeQtExpansionCategoryIde; + } + if (bus == MountControllerBusScsi) { + return board.categoryMask & WinUaeQtExpansionCategoryScsi; + } + return false; +} + +static int mountControllerBoardMaxUnit( + const WinUaeQtExpansionBoardCatalogItem &board, + WinUaeQtMountControllerBus bus) +{ + if (bus == MountControllerBusIde) { + return qMax(1, 1 + board.extraHdPorts); + } + if (bus == MountControllerBusScsi + && (board.categoryMask & (WinUaeQtExpansionCategorySasi | WinUaeQtExpansionCategoryCustom))) { + return 1; + } + return MaxControllerUnits - 1; +} + +static WinUaeQtMountControllerChoice mountControllerGenericChoice(WinUaeQtMountControllerBus bus) +{ + WinUaeQtMountControllerChoice choice; + choice.bus = bus; + choice.valid = true; + if (bus == MountControllerBusUae) { + choice.display = QStringLiteral("UAE (uaehf.device)"); + choice.maxUnit = MaxControllerUnits - 1; + } else if (bus == MountControllerBusIde) { + choice.display = QStringLiteral("IDE (Auto)"); + choice.maxUnit = 3; + } else if (bus == MountControllerBusScsi) { + choice.display = QStringLiteral("SCSI (Auto)"); + choice.maxUnit = MaxControllerUnits - 1; + } else { + choice.valid = false; + choice.maxUnit = 0; + } + return choice; +} + +static WinUaeQtMountControllerChoice mountControllerChoiceForBoard( + const WinUaeQtExpansionBoardCatalogItem &board, + WinUaeQtMountControllerBus bus) +{ + WinUaeQtMountControllerChoice choice; + if (!mountControllerBoardSupportsBus(board, bus)) { + return choice; + } + choice.display = QStringLiteral("%1 (%2)") + .arg(mountControllerBoardBaseName(board), mountControllerBusName(bus)); + choice.boardKey = board.key; + choice.bus = bus; + choice.maxUnit = mountControllerBoardMaxUnit(board, bus); + choice.valid = true; + return choice; +} + +static WinUaeQtMountControllerChoice mountControllerChoiceByDisplay( + const WinUaeQtBoardCatalog &catalog, + const QString &display) +{ + const QString baseDisplay = mountControllerBaseDisplay(display); + for (WinUaeQtMountControllerBus bus : { MountControllerBusUae, MountControllerBusIde, MountControllerBusScsi }) { + WinUaeQtMountControllerChoice choice = mountControllerGenericChoice(bus); + if (choice.valid && baseDisplay == choice.display) { + return choice; + } + } + for (const WinUaeQtExpansionBoardCatalogItem &board : catalog.expansionBoards) { + for (WinUaeQtMountControllerBus bus : { MountControllerBusIde, MountControllerBusScsi }) { + WinUaeQtMountControllerChoice choice = mountControllerChoiceForBoard(board, bus); + if (choice.valid && baseDisplay == choice.display) { + return choice; + } + } + } + return {}; +} + +static WinUaeQtMountControllerChoice mountControllerChoiceByKeyAndBus( + const WinUaeQtBoardCatalog &catalog, + const QString &key, + WinUaeQtMountControllerBus bus) +{ + for (const WinUaeQtExpansionBoardCatalogItem &board : catalog.expansionBoards) { + if (key.compare(board.key, Qt::CaseInsensitive) == 0) { + return mountControllerChoiceForBoard(board, bus); + } + } + return {}; +} + +static QString mountControllerGenericDisplay(WinUaeQtMountControllerBus bus) +{ + if (bus == MountControllerBusIde) { + return QStringLiteral("IDE (Auto)"); + } + if (bus == MountControllerBusScsi) { + return QStringLiteral("SCSI (Auto)"); + } + return QStringLiteral("UAE (uaehf.device)"); +} + +static QString mountControllerDisplayForConfigValue(const WinUaeQtBoardCatalog &catalog, const QString &value) +{ + const QString trimmed = value.trimmed(); + const WinUaeQtMountControllerBus bus = mountControllerBusFromConfigValue(trimmed); + const QString key = mountControllerExpansionKeyFromConfigValue(trimmed); + if (!key.isEmpty()) { + const WinUaeQtMountControllerChoice choice = mountControllerChoiceByKeyAndBus(catalog, key, bus); + if (choice.valid) { + return choice.display + mountControllerDuplicateDisplaySuffix(mountControllerDuplicateFromConfigValue(trimmed)); + } + return trimmed; + } + return mountControllerGenericDisplay(bus); +} + +static WinUaeQtMountControllerBus mountControllerBusFromDisplay(const WinUaeQtBoardCatalog &catalog, const QString &display) +{ + const WinUaeQtMountControllerChoice choice = mountControllerChoiceByDisplay(catalog, display); + if (choice.valid) { + return choice.bus; + } + return mountControllerBusFromConfigValue(display); +} + +static QString mountControllerFamily( + const WinUaeQtBoardCatalog &catalog, + const WinUaeQtMountEntry &entry, + const QString &fallback) +{ + return mountControllerDisplayForConfigValue(catalog, mountControllerValue(entry, fallback)); +} + +static int mountControllerUnit(const WinUaeQtMountEntry &entry, const QString &fallback) +{ + const QString value = mountControllerValue(entry, fallback).toLower(); + int index = 0; + while (index < value.size() && !value.at(index).isDigit()) { + index++; + } + if (index >= value.size()) { + return 0; + } + int end = index; + while (end < value.size() && value.at(end).isDigit()) { + end++; + } + return qBound(0, value.mid(index, end - index).toInt(), MaxControllerUnits - 1); +} + +static QString mountControllerConfigPrefix(WinUaeQtMountControllerBus bus) +{ + if (bus == MountControllerBusIde) { + return QStringLiteral("ide"); + } + if (bus == MountControllerBusScsi) { + return QStringLiteral("scsi"); + } + return QStringLiteral("uae"); +} + +static int mountControllerMaxUnit(const WinUaeQtBoardCatalog &catalog, const QString &display) +{ + const WinUaeQtMountControllerChoice choice = mountControllerChoiceByDisplay(catalog, display); + if (choice.valid) { + return choice.maxUnit; + } + return mountControllerBusFromDisplay(catalog, display) == MountControllerBusIde ? 3 : MaxControllerUnits - 1; +} + +static QString mountControllerConfigValue(const WinUaeQtBoardCatalog &catalog, const QString &display, int unit) +{ + const int duplicate = mountControllerDuplicateFromDisplay(display); + const WinUaeQtMountControllerChoice choice = mountControllerChoiceByDisplay(catalog, display); + if (choice.valid) { + QString value = mountControllerConfigPrefix(choice.bus) + QString::number(qBound(0, unit, choice.maxUnit)); + if (!choice.boardKey.isEmpty()) { + value += QStringLiteral("_") + choice.boardKey + mountControllerDuplicateConfigSuffix(duplicate); + } + return value; + } + + const WinUaeQtMountControllerBus bus = mountControllerBusFromConfigValue(display); + const QString key = mountControllerExpansionKeyFromConfigValue(display); + QString value = mountControllerConfigPrefix(bus) + QString::number(qBound(0, unit, mountControllerMaxUnit(catalog, display))); + if (!key.isEmpty()) { + value += QStringLiteral("_") + key + mountControllerDuplicateConfigSuffix(mountControllerDuplicateFromConfigValue(display)); + } + return value; +} + +static QString mountControllerDisplay(const WinUaeQtBoardCatalog &catalog, const WinUaeQtMountEntry &entry) +{ + const QString fallback = entry.kind == QStringLiteral("cd") ? QStringLiteral("ide0") : QStringLiteral("uae0"); + const QString value = mountControllerValue(entry, fallback); + const QString display = mountControllerDisplayForConfigValue(catalog, value); + const WinUaeQtMountControllerChoice choice = mountControllerChoiceByDisplay(catalog, display); + if (choice.valid && !choice.boardKey.isEmpty()) { + return QStringLiteral("%1:%2").arg(mountControllerBaseDisplay(display)).arg(mountControllerUnit(entry, fallback)); + } + const QString upper = value.toUpper(); + if (upper.startsWith(QStringLiteral("IDE"))) { + return QStringLiteral("IDE:%1").arg(mountControllerUnit(entry, fallback)); + } + if (upper.startsWith(QStringLiteral("SCSI"))) { + return QStringLiteral("SCSI:%1").arg(mountControllerUnit(entry, fallback)); + } + if (upper.startsWith(QStringLiteral("UAE"))) { + return QStringLiteral("UAE:%1").arg(mountControllerUnit(entry, fallback)); + } + return value; +} + +static bool isIntegerText(const QString &value) +{ + bool ok = false; + value.toInt(&ok); + return ok; +} + +static QStringList hardfileGeometryParts(const WinUaeQtMountEntry &entry) +{ + QStringList parts = entry.hardfileGeometry.split(QLatin1Char(',')); + while (parts.size() < 4) { + parts.append(QStringLiteral("0")); + } + return parts; +} + +static bool hardfileIsRdb(const WinUaeQtMountEntry &entry) +{ + const QStringList geometry = hardfileGeometryParts(entry); + return geometry.value(0).toInt() == 0 + && geometry.value(1).toInt() == 0 + && geometry.value(2).toInt() == 0; +} + +static bool hardfileHasPhysicalGeometry(const WinUaeQtMountEntry &entry) +{ + const QStringList parts = mountControllerParts(entry.hardfileTail); + return parts.size() > 3 + && isIntegerText(parts.value(2)) + && parts.value(3).contains(QLatin1Char('/')); +} + +static QStringList hardfilePhysicalGeometryParts(const WinUaeQtMountEntry &entry) +{ + QStringList geometry; + if (hardfileHasPhysicalGeometry(entry)) { + geometry = mountControllerParts(entry.hardfileTail).value(3).split(QLatin1Char('/')); + } + while (geometry.size() < 3) { + geometry.append(QStringLiteral("0")); + } + return geometry; +} + +static QString hardfileTailGeometryFile(const WinUaeQtMountEntry &entry) +{ + const QStringList parts = mountControllerParts(entry.hardfileTail); + return hardfileHasPhysicalGeometry(entry) ? parts.value(4) : QString(); +} + +static int hardfileTailExtraStart(const WinUaeQtMountEntry &entry) +{ + return hardfileHasPhysicalGeometry(entry) ? 5 : 2; +} + +static bool isManagedHardfileTailToken(const QString &token) +{ + static const QStringList managed = { + QStringLiteral("HD"), + QStringLiteral("CF"), + QStringLiteral("SCSI1"), + QStringLiteral("SCSI2"), + QStringLiteral("SASIE"), + QStringLiteral("SASI"), + QStringLiteral("SASI_CHS"), + QStringLiteral("ATA1"), + QStringLiteral("ATA2+"), + QStringLiteral("ATA2+S") + }; + for (const QString &item : managed) { + if (token.compare(item, Qt::CaseInsensitive) == 0) { + return true; + } + } + return false; +} + +static QStringList hardfilePreservedTailExtras(const WinUaeQtMountEntry &entry) +{ + const QStringList parts = mountControllerParts(entry.hardfileTail); + QStringList extras; + for (int i = hardfileTailExtraStart(entry); i < parts.size(); i++) { + if (!parts.value(i).isEmpty() && !isManagedHardfileTailToken(parts.value(i))) { + extras.append(parts.value(i)); + } + } + return extras; +} + +static bool hardfileTailHasToken(const WinUaeQtMountEntry &entry, const QString &token) +{ + const QStringList parts = mountControllerParts(entry.hardfileTail); + for (int i = hardfileTailExtraStart(entry); i < parts.size(); i++) { + if (parts.value(i).compare(token, Qt::CaseInsensitive) == 0) { + return true; + } + } + return false; +} + +static QString hardfileFeatureText( + const WinUaeQtBoardCatalog &catalog, + const WinUaeQtMountEntry &entry, + const QString &controllerFamily) +{ + const WinUaeQtMountControllerBus bus = mountControllerBusFromDisplay(catalog, controllerFamily); + if (bus == MountControllerBusIde) { + if (hardfileTailHasToken(entry, QStringLiteral("ATA2+S"))) { + return QStringLiteral("ATA-2+ Strict"); + } + if (hardfileTailHasToken(entry, QStringLiteral("ATA2+"))) { + return QStringLiteral("ATA-2+"); + } + if (hardfileTailHasToken(entry, QStringLiteral("ATA1"))) { + return QStringLiteral("ATA-1"); + } + } else if (bus == MountControllerBusScsi) { + if (hardfileTailHasToken(entry, QStringLiteral("SASI_CHS"))) { + return QStringLiteral("SASI CHS"); + } + if (hardfileTailHasToken(entry, QStringLiteral("SASI"))) { + return QStringLiteral("SASI"); + } + if (hardfileTailHasToken(entry, QStringLiteral("SCSI1"))) { + return QStringLiteral("SCSI-1"); + } + if (hardfileTailHasToken(entry, QStringLiteral("SCSI2"))) { + return QStringLiteral("SCSI-2"); + } + } + return QStringLiteral("Default"); +} + +static QString hardfileFeatureToken(const QString &featureText) +{ + if (featureText == QStringLiteral("ATA-1")) { + return QStringLiteral("ATA1"); + } + if (featureText == QStringLiteral("ATA-2+ Strict")) { + return QStringLiteral("ATA2+S"); + } + if (featureText == QStringLiteral("SCSI-1")) { + return QStringLiteral("SCSI1"); + } + if (featureText == QStringLiteral("SASI")) { + return QStringLiteral("SASI"); + } + if (featureText == QStringLiteral("SASI CHS")) { + return QStringLiteral("SASI_CHS"); + } + return QString(); +} + +static WinUaeQtCdSlot cdSlotFromConfigValue(const QString &value) +{ + WinUaeQtCdSlot slot; + if (value.compare(QStringLiteral("autodetect"), Qt::CaseInsensitive) == 0) { + slot.type = QStringLiteral("Autodetect"); + slot.inUse = true; + return slot; + } + if (value.isEmpty() + || value.compare(QStringLiteral("empty"), Qt::CaseInsensitive) == 0 + || value == QStringLiteral(".")) { + slot.type = QStringLiteral("Image file"); + return slot; + } + + const QStringList fields = winUaeQtConfigFieldList(value); + slot.path = fields.value(0); + slot.type = fields.value(1).compare(QStringLiteral("image"), Qt::CaseInsensitive) == 0 + ? QStringLiteral("Image file") + : QStringLiteral("Image file"); + slot.inUse = !slot.path.isEmpty(); + return slot; +} + +static QString cdSlotConfigValue(const WinUaeQtCdSlot &slot) +{ + if (!slot.inUse && slot.path.isEmpty()) { + return QString(); + } + if (slot.type == QStringLiteral("Autodetect") && slot.path.isEmpty()) { + return QStringLiteral("autodetect"); + } + if (slot.path.isEmpty()) { + return QString(); + } + return winUaeQtConfigEscapeMin(slot.path); +} + +static QString normalizedRomAddress(QString text, bool endAddress) +{ + text = text.trimmed(); + if (text.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive)) { + text = text.mid(2); + } + bool ok = false; + quint32 value = text.toUInt(&ok, 16); + if (!ok) { + return QString(); + } + if (endAddress && value == 0) { + return QString(); + } + if (endAddress) { + value = ((value - 1) & ~quint32(0xffff)) | quint32(0xffff); + } else { + value &= ~quint32(0xffff); + } + return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0')); +} + +static WinUaeQtRomBoard romBoardFromConfigValue(const QString &value) +{ + WinUaeQtRomBoard board; + for (const QString &field : winUaeQtConfigFieldList(value)) { + const int equals = field.indexOf(QLatin1Char('=')); + if (equals <= 0) { + continue; + } + const QString key = field.left(equals).trimmed().toLower(); + const QString fieldValue = field.mid(equals + 1).trimmed(); + if (key == QStringLiteral("start")) { + board.start = normalizedRomAddress(fieldValue, false); + } else if (key == QStringLiteral("end")) { + board.end = normalizedRomAddress(fieldValue, true); + } else if (key == QStringLiteral("file")) { + board.path = fieldValue; + } + } + return board; +} + +static QString romBoardConfigValue(const WinUaeQtRomBoard &board) +{ + const QString start = normalizedRomAddress(board.start, false); + const QString end = normalizedRomAddress(board.end, true); + if (start.isEmpty() || end.isEmpty()) { + return QString(); + } + QString value = QStringLiteral("start=%1,end=%2").arg(start, end); + if (!board.path.trimmed().isEmpty()) { + value += QStringLiteral(",file=%1").arg(winUaeQtConfigEscapeMin(board.path.trimmed())); + } + return value; +} + +static int romBoardIndexFromKey(const QString &key) +{ + if (key == QStringLiteral("romboard_options")) { + return 0; + } + if (!key.startsWith(QStringLiteral("romboard")) || !key.endsWith(QStringLiteral("_options"))) { + return -1; + } + bool ok = false; + const int board = key.mid(8, key.size() - 16).toInt(&ok); + if (!ok) { + return -1; + } + return board - 1; +} + +static QString romBoardKey(int index) +{ + return index == 0 + ? QStringLiteral("romboard_options") + : QStringLiteral("romboard%1_options").arg(index + 1); +} + +static int floppyKeyDrive(const QString &key, const QString &suffix = QString()) +{ + if (!key.startsWith(QStringLiteral("floppy"))) { + return -1; + } + if (key.size() != 7 + suffix.size() || !key.endsWith(suffix)) { + return -1; + } + const int drive = key.at(6).digitValue(); + return drive >= 0 && drive < 4 ? drive : -1; +} + +static QString uaeBoardConfigValue(const QString &text) +{ + if (text == QStringLiteral("New UAE (64k + F0 ROM)")) { + return QStringLiteral("min"); + } + if (text == QStringLiteral("New UAE (128k, ROM, Direct)")) { + return QStringLiteral("full"); + } + if (text == QStringLiteral("New UAE (128k, ROM, Indirect)")) { + return QStringLiteral("full+indirect"); + } + return QStringLiteral("disabled"); +} + +static QString uaeBoardText(const QString &value) +{ + const QString lower = value.toLower(); + if (lower == QStringLiteral("min") || lower == QStringLiteral("min_off")) { + return QStringLiteral("New UAE (64k + F0 ROM)"); + } + if (lower == QStringLiteral("full") || lower == QStringLiteral("full_off")) { + return QStringLiteral("New UAE (128k, ROM, Direct)"); + } + if (lower == QStringLiteral("full+indirect") || lower == QStringLiteral("full+indirect_off")) { + return QStringLiteral("New UAE (128k, ROM, Indirect)"); + } + return QStringLiteral("Original UAE (FS + F0 ROM)"); +} + +static QString fullscreenModeConfigValue(const QString &text) +{ + if (text == QStringLiteral("Fullscreen")) { + return QStringLiteral("true"); + } + if (text == QStringLiteral("Full-window")) { + return QStringLiteral("fullwindow"); + } + return QStringLiteral("false"); +} + +static QString fullscreenModeText(const QString &value) +{ + if (value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Fullscreen"); + } + if (value.compare(QStringLiteral("fullwindow"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Full-window"); + } + return QStringLiteral("Windowed"); +} + +static QString lineModeConfigValue(int normalId, int interlacedId) +{ + static const char *lineModes[] = { + "none", + "double", "scanlines", "scanlines2p", "scanlines3p", + "double2", "scanlines2", "scanlines2p2", "scanlines2p3", + "double3", "scanlines3", "scanlines3p2", "scanlines3p3" + }; + if (normalId <= 0) { + return QStringLiteral("none"); + } + const int progressiveScanlines = normalId == 2 ? 1 : (normalId == 3 ? 2 : (normalId == 4 ? 3 : 0)); + const int interlacedScanlines = interlacedId == 2 ? 1 : (interlacedId == 3 ? 2 : 0); + const int index = qBound(0, interlacedScanlines * 4 + progressiveScanlines + 1, int(sizeof(lineModes) / sizeof(lineModes[0])) - 1); + return QString::fromLatin1(lineModes[index]); +} + +static int progressiveLineModeId(const QString &value) +{ + const QString lower = value.toLower(); + if (lower == QStringLiteral("none")) { + return 0; + } + if (lower.contains(QStringLiteral("scanlines3p"))) { + return 4; + } + if (lower.contains(QStringLiteral("scanlines2p"))) { + return 3; + } + if (lower.contains(QStringLiteral("scanlines"))) { + return 2; + } + return 1; +} + +static int interlacedLineModeId(const QString &value) +{ + const QString lower = value.toLower(); + if (lower == QStringLiteral("none")) { + return 0; + } + if (lower.endsWith(QLatin1Char('3'))) { + return 3; + } + if (lower.endsWith(QLatin1Char('2'))) { + return 2; + } + return 1; +} + +static QString nativeVsyncConfigValue(const QString &text) +{ + if (text.contains(QStringLiteral("Autoswitch"), Qt::CaseInsensitive)) { + return QStringLiteral("autoswitch"); + } + if (text.startsWith(QStringLiteral("VSync"))) { + return QStringLiteral("true"); + } + return QStringLiteral("false"); +} + +static QString nativeVsyncModeConfigValue(const QString &text) +{ + return text.contains(QStringLiteral("Busy"), Qt::CaseInsensitive) ? QStringLiteral("busywait") : QStringLiteral("normal"); +} + +static QString nativeVsyncText(const QString &vsync, const QString &mode) +{ + const bool busy = mode.compare(QStringLiteral("busywait"), Qt::CaseInsensitive) == 0; + if (vsync.compare(QStringLiteral("autoswitch"), Qt::CaseInsensitive) == 0) { + return busy ? QStringLiteral("VSync autoswitch (Busy wait)") : QStringLiteral("VSync autoswitch"); + } + if (vsync.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0 || vsync == QStringLiteral("1")) { + return busy ? QStringLiteral("VSync (Busy wait)") : QStringLiteral("VSync"); + } + return QStringLiteral("No vsync"); +} + +static QString rtgVsyncText(const QString &value) +{ + return value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0 || value == QStringLiteral("1") + ? QStringLiteral("VSync (Busy wait)") + : QStringLiteral("-"); +} + +static QString overscanConfigValue(const QString &text) +{ + if (text == QStringLiteral("TV (narrow)")) { + return QStringLiteral("tv_narrow"); + } + if (text == QStringLiteral("TV (standard)")) { + return QStringLiteral("tv_standard"); + } + if (text == QStringLiteral("TV (wide)")) { + return QStringLiteral("tv_wide"); + } + if (text == QStringLiteral("Overscan+")) { + return QStringLiteral("broadcast"); + } + if (text == QStringLiteral("Extreme")) { + return QStringLiteral("extreme"); + } + if (text == QStringLiteral("Ultra extreme debug")) { + return QStringLiteral("ultra"); + } + if (text == QStringLiteral("Ultra extreme debug (HV)")) { + return QStringLiteral("ultra_hv"); + } + if (text == QStringLiteral("Ultra extreme debug (C)")) { + return QStringLiteral("ultra_csync"); + } + return QStringLiteral("overscan"); +} + +static QString overscanText(const QString &value) +{ + const QString lower = value.toLower(); + if (lower == QStringLiteral("tv_narrow")) { + return QStringLiteral("TV (narrow)"); + } + if (lower == QStringLiteral("tv_standard")) { + return QStringLiteral("TV (standard)"); + } + if (lower == QStringLiteral("tv_wide")) { + return QStringLiteral("TV (wide)"); + } + if (lower == QStringLiteral("broadcast")) { + return QStringLiteral("Overscan+"); + } + if (lower == QStringLiteral("extreme")) { + return QStringLiteral("Extreme"); + } + if (lower == QStringLiteral("ultra")) { + return QStringLiteral("Ultra extreme debug"); + } + if (lower == QStringLiteral("ultra_hv")) { + return QStringLiteral("Ultra extreme debug (HV)"); + } + if (lower == QStringLiteral("ultra_csync")) { + return QStringLiteral("Ultra extreme debug (C)"); + } + return QStringLiteral("Overscan"); +} + +static int autoResolutionValue(const QString &text) +{ + if (text == QStringLiteral("Always on")) { + return 1; + } + if (text == QStringLiteral("10%")) { + return 10; + } + if (text == QStringLiteral("33%")) { + return 33; + } + if (text == QStringLiteral("66%")) { + return 66; + } + if (text == QStringLiteral("100%")) { + return 100; + } + return 0; +} + +static QString autoResolutionText(int value) +{ + if (value == 1) { + return QStringLiteral("Always on"); + } + if (value <= 0) { + return QStringLiteral("Disabled"); + } + if (value <= 10) { + return QStringLiteral("10%"); + } + if (value <= 33) { + return QStringLiteral("33%"); + } + if (value <= 66) { + return QStringLiteral("66%"); + } + return QStringLiteral("100%"); +} + +static QStringList primaryPortDeviceItems() +{ + QStringList items = { + QStringLiteral("Mouse"), + QStringLiteral("Keyboard Layout A"), + QStringLiteral("Keyboard Layout B"), + QStringLiteral("Keyboard Layout C"), + QStringLiteral("Joystick 1"), + QStringLiteral("Joystick 2"), + QStringLiteral("Joystick 3"), + QStringLiteral("Joystick 4") + }; + for (int i = 0; i < MaxJoyportCustomSlots; i++) { + items.append(QStringLiteral("Custom %1").arg(i + 1)); + } + items.append(QStringLiteral("")); + return items; +} + +static QStringList parallelPortDeviceItems() +{ + QStringList items = { + QStringLiteral(""), + QStringLiteral("Keyboard Layout A"), + QStringLiteral("Keyboard Layout B"), + QStringLiteral("Keyboard Layout C"), + QStringLiteral("Joystick 1"), + QStringLiteral("Joystick 2"), + QStringLiteral("Joystick 3"), + QStringLiteral("Joystick 4") + }; + for (int i = 0; i < MaxJoyportCustomSlots; i++) { + items.append(QStringLiteral("Custom %1").arg(i + 1)); + } + return items; +} + +static QString joyportDeviceConfigValue(const QString &text) +{ + if (text == QStringLiteral("Mouse")) { + return QStringLiteral("mouse"); + } + if (text == QStringLiteral("Keyboard Layout A")) { + return QStringLiteral("kbd1"); + } + if (text == QStringLiteral("Keyboard Layout B")) { + return QStringLiteral("kbd2"); + } + if (text == QStringLiteral("Keyboard Layout C")) { + return QStringLiteral("kbd3"); + } + if (text.startsWith(QStringLiteral("Joystick "))) { + bool ok = false; + const int index = text.mid(9).toInt(&ok); + if (ok && index > 0) { + return QStringLiteral("joy%1").arg(index - 1); + } + } + if (text.startsWith(QStringLiteral("Custom "))) { + bool ok = false; + const int index = text.mid(7).toInt(&ok); + if (ok && index > 0 && index <= MaxJoyportCustomSlots) { + return QStringLiteral("custom%1").arg(index - 1); + } + } + return QStringLiteral("none"); +} + +static QString joyportDeviceText(const QString &value, bool allowMouse) +{ + const QString lower = value.toLower(); + if (allowMouse && (lower == QStringLiteral("mouse") || lower == QStringLiteral("mouse0"))) { + return QStringLiteral("Mouse"); + } + if (lower == QStringLiteral("kbd1")) { + return QStringLiteral("Keyboard Layout A"); + } + if (lower == QStringLiteral("kbd2")) { + return QStringLiteral("Keyboard Layout B"); + } + if (lower == QStringLiteral("kbd3")) { + return QStringLiteral("Keyboard Layout C"); + } + if (lower.startsWith(QStringLiteral("joy"))) { + bool ok = false; + const int index = lower.mid(3).toInt(&ok); + if (ok && index >= 0 && index < 4) { + return QStringLiteral("Joystick %1").arg(index + 1); + } + } + if (lower.startsWith(QStringLiteral("custom"))) { + bool ok = false; + const int index = lower.mid(6).toInt(&ok); + if (ok && index >= 0 && index < MaxJoyportCustomSlots) { + return QStringLiteral("Custom %1").arg(index + 1); + } + } + return QStringLiteral(""); +} + +static QString autofireConfigValue(const QString &text) +{ + if (text == QStringLiteral("Autofire")) { + return QStringLiteral("normal"); + } + if (text == QStringLiteral("Autofire (toggle)")) { + return QStringLiteral("toggle"); + } + if (text == QStringLiteral("Autofire (always)")) { + return QStringLiteral("always"); + } + if (text == QStringLiteral("No autofire (toggle)")) { + return QStringLiteral("togglebutton"); + } + return QStringLiteral("none"); +} + +static QString autofireText(const QString &value) +{ + if (value.compare(QStringLiteral("normal"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Autofire"); + } + if (value.compare(QStringLiteral("toggle"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Autofire (toggle)"); + } + if (value.compare(QStringLiteral("always"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Autofire (always)"); + } + if (value.compare(QStringLiteral("togglebutton"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("No autofire (toggle)"); + } + return QStringLiteral("No autofire (normal)"); +} + +static QString joyportModeConfigValue(const QString &text) +{ + if (text == QStringLiteral("Wheel Mouse")) { + return QStringLiteral("mouse"); + } + if (text == QStringLiteral("Mouse")) { + return QStringLiteral("mousenowheel"); + } + if (text == QStringLiteral("Joystick")) { + return QStringLiteral("djoy"); + } + if (text == QStringLiteral("Gamepad")) { + return QStringLiteral("gamepad"); + } + if (text == QStringLiteral("Analog joystick")) { + return QStringLiteral("ajoy"); + } + if (text == QStringLiteral("CDTV remote mouse")) { + return QStringLiteral("cdtvjoy"); + } + if (text == QStringLiteral("CD32 pad")) { + return QStringLiteral("cd32joy"); + } + if (text == QStringLiteral("Generic light pen/gun")) { + return QStringLiteral("lightpen"); + } + return QString(); +} + +static QString joyportModeText(const QString &value) +{ + if (value.compare(QStringLiteral("mouse"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Wheel Mouse"); + } + if (value.compare(QStringLiteral("mousenowheel"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Mouse"); + } + if (value.compare(QStringLiteral("djoy"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Joystick"); + } + if (value.compare(QStringLiteral("gamepad"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Gamepad"); + } + if (value.compare(QStringLiteral("ajoy"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Analog joystick"); + } + if (value.compare(QStringLiteral("cdtvjoy"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("CDTV remote mouse"); + } + if (value.compare(QStringLiteral("cd32joy"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("CD32 pad"); + } + if (value.compare(QStringLiteral("lightpen"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Generic light pen/gun"); + } + return QStringLiteral("Default"); +} + +static QString magicMouseCursorConfigValue(const QString &text) +{ + if (text == QStringLiteral("Show native cursor only")) { + return QStringLiteral("native"); + } + if (text == QStringLiteral("Show host cursor only")) { + return QStringLiteral("host"); + } + return QStringLiteral("both"); +} + +static QString magicMouseCursorText(const QString &value) +{ + if (value.compare(QStringLiteral("native"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Show native cursor only"); + } + if (value.compare(QStringLiteral("host"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Show host cursor only"); + } + return QStringLiteral("Show both cursors"); +} + +static constexpr int SoundVolumeCount = 5; +static constexpr int FloppySoundDriveCount = 4; + +static QVector> displayDeviceChoices() +{ + QVector> choices; + choices.append(qMakePair(QStringLiteral("Default display"), QString())); + +#ifdef UAE_UNIX_WITH_SDL3 + SDL_SetMainReady(); + const bool wasVideoInitialized = SDL_WasInit(SDL_INIT_VIDEO) != 0; + if (wasVideoInitialized || SDL_InitSubSystem(SDL_INIT_VIDEO)) { + int count = 0; + SDL_DisplayID *displays = SDL_GetDisplays(&count); + if (displays) { + for (int i = 0; i < count; i++) { + const char *name = SDL_GetDisplayName(displays[i]); + const QString friendly = QString::fromUtf8(name && name[0] ? name : "Unknown display"); + choices.append(qMakePair( + QStringLiteral("Display %1: %2").arg(i + 1).arg(friendly), + QStringLiteral("SDL:%1").arg(uint(displays[i])))); + } + SDL_free(displays); + } + if (!wasVideoInitialized) { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } + } +#endif + + return choices; +} + +static QString displayFriendlyName(const QString &displayText) +{ + if (displayText == QStringLiteral("Default display")) { + return QString(); + } + const QString prefix = QStringLiteral(": "); + const int separator = displayText.indexOf(prefix); + return separator >= 0 ? displayText.mid(separator + prefix.size()) : displayText; +} + +static QVector> soundDeviceChoices() +{ + QVector> choices; + choices.append(qMakePair(QStringLiteral("SDL: Default audio device"), QStringLiteral("SDL:Default Audio Device"))); + +#ifdef UAE_UNIX_WITH_SDL3 + SDL_SetMainReady(); + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { + int count = 0; + SDL_AudioDeviceID *devices = SDL_GetAudioPlaybackDevices(&count); + if (devices) { + for (int i = 0; i < count; i++) { + const char *name = SDL_GetAudioDeviceName(devices[i]); + const QString display = QString::fromUtf8(name && name[0] ? name : "Unknown audio device"); + choices.append(qMakePair(QStringLiteral("SDL: %1").arg(display), QStringLiteral("SDL:%1").arg(display))); + } + SDL_free(devices); + } + } +#endif + + return choices; +} + +struct WinUaeQtMidiChoice { + QString display; + QString configName; + int deviceId = -2; +}; + +#if defined(__APPLE__) && defined(UAE_UNIX_WITH_COREMIDI) +static QString coreMidiObjectString(MIDIObjectRef object, CFStringRef property) +{ + CFStringRef str = nullptr; + if (MIDIObjectGetStringProperty(object, property, &str) != noErr || !str) { + return QString(); + } + char buffer[512]; + const bool ok = CFStringGetCString(str, buffer, sizeof buffer, kCFStringEncodingUTF8); + CFRelease(str); + return ok ? QString::fromUtf8(buffer) : QString(); +} +#endif + +static QVector midiOutputChoices() +{ + QVector choices; +#if UAE_UNIX_WITH_MIDI +#if defined(__APPLE__) && defined(UAE_UNIX_WITH_COREMIDI) + const ItemCount count = MIDIGetNumberOfDestinations(); + if (count > 0) { + WinUaeQtMidiChoice def; + def.display = QStringLiteral("Default MIDI-Out Device"); + def.configName = QStringLiteral("default"); + def.deviceId = -1; + choices.append(def); + } + for (ItemCount i = 0; i < count; i++) { + MIDIEndpointRef endpoint = MIDIGetDestination(i); + if (!endpoint) { + continue; + } + QString name = coreMidiObjectString(endpoint, kMIDIPropertyDisplayName); + if (name.isEmpty()) { + name = coreMidiObjectString(endpoint, kMIDIPropertyName); + } + if (name.isEmpty()) { + name = QStringLiteral("CoreMIDI destination %1").arg(i + 1); + } + WinUaeQtMidiChoice choice; + choice.display = name; + choice.configName = name; + choice.deviceId = int(i); + choices.append(choice); + } +#elif defined(__linux__) && defined(UAE_UNIX_WITH_ALSA_MIDI) + snd_seq_t *seq = nullptr; + if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0) >= 0) { + QVector alsaChoices; + snd_seq_client_info_t *clientInfo; + snd_seq_port_info_t *portInfo; + snd_seq_client_info_alloca(&clientInfo); + snd_seq_port_info_alloca(&portInfo); + snd_seq_client_info_set_client(clientInfo, -1); + int deviceId = 0; + while (snd_seq_query_next_client(seq, clientInfo) >= 0) { + const int client = snd_seq_client_info_get_client(clientInfo); + snd_seq_port_info_set_client(portInfo, client); + snd_seq_port_info_set_port(portInfo, -1); + while (snd_seq_query_next_port(seq, portInfo) >= 0) { + const unsigned int caps = snd_seq_port_info_get_capability(portInfo); + if ((caps & SND_SEQ_PORT_CAP_WRITE) == 0 || (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) == 0) { + continue; + } + const int port = snd_seq_port_info_get_port(portInfo); + const char *clientName = snd_seq_client_info_get_name(clientInfo); + const char *portName = snd_seq_port_info_get_name(portInfo); + const QString name = QStringLiteral("%1:%2 %3%4%5") + .arg(client) + .arg(port) + .arg(QString::fromUtf8(clientName ? clientName : "ALSA")) + .arg(portName && portName[0] ? QStringLiteral(" ") : QString()) + .arg(QString::fromUtf8(portName ? portName : "")); + WinUaeQtMidiChoice choice; + choice.display = name; + choice.configName = name; + choice.deviceId = deviceId++; + alsaChoices.append(choice); + } + } + snd_seq_close(seq); + if (!alsaChoices.isEmpty()) { + WinUaeQtMidiChoice def; + def.display = QStringLiteral("Default MIDI-Out Device"); + def.configName = QStringLiteral("default"); + def.deviceId = -1; + choices.append(def); + choices += alsaChoices; + } + } +#endif +#endif + return choices; +} + +static QVector midiInputChoices() +{ + QVector choices; +#if UAE_UNIX_WITH_MIDI +#if defined(__APPLE__) && defined(UAE_UNIX_WITH_COREMIDI) + const ItemCount count = MIDIGetNumberOfSources(); + for (ItemCount i = 0; i < count; i++) { + MIDIEndpointRef endpoint = MIDIGetSource(i); + if (!endpoint) { + continue; + } + QString name = coreMidiObjectString(endpoint, kMIDIPropertyDisplayName); + if (name.isEmpty()) { + name = coreMidiObjectString(endpoint, kMIDIPropertyName); + } + if (name.isEmpty()) { + name = QStringLiteral("CoreMIDI source %1").arg(i + 1); + } + WinUaeQtMidiChoice choice; + choice.display = name; + choice.configName = name; + choice.deviceId = int(i); + choices.append(choice); + } +#elif defined(__linux__) && defined(UAE_UNIX_WITH_ALSA_MIDI) + snd_seq_t *seq = nullptr; + if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_INPUT, 0) >= 0) { + snd_seq_client_info_t *clientInfo; + snd_seq_port_info_t *portInfo; + snd_seq_client_info_alloca(&clientInfo); + snd_seq_port_info_alloca(&portInfo); + snd_seq_client_info_set_client(clientInfo, -1); + int deviceId = 0; + while (snd_seq_query_next_client(seq, clientInfo) >= 0) { + const int client = snd_seq_client_info_get_client(clientInfo); + snd_seq_port_info_set_client(portInfo, client); + snd_seq_port_info_set_port(portInfo, -1); + while (snd_seq_query_next_port(seq, portInfo) >= 0) { + const unsigned int caps = snd_seq_port_info_get_capability(portInfo); + if ((caps & SND_SEQ_PORT_CAP_READ) == 0 || (caps & SND_SEQ_PORT_CAP_SUBS_READ) == 0) { + continue; + } + const int port = snd_seq_port_info_get_port(portInfo); + const char *clientName = snd_seq_client_info_get_name(clientInfo); + const char *portName = snd_seq_port_info_get_name(portInfo); + const QString name = QStringLiteral("%1:%2 %3%4%5") + .arg(client) + .arg(port) + .arg(QString::fromUtf8(clientName ? clientName : "ALSA")) + .arg(portName && portName[0] ? QStringLiteral(" ") : QString()) + .arg(QString::fromUtf8(portName ? portName : "")); + WinUaeQtMidiChoice choice; + choice.display = name; + choice.configName = name; + choice.deviceId = deviceId++; + choices.append(choice); + } + } + snd_seq_close(seq); + } +#endif +#endif + return choices; +} + +static QVector> samplerDeviceChoices() +{ + QVector> choices; + choices.append(qMakePair(QStringLiteral(""), QString())); + +#if UAE_UNIX_WITH_SAMPLER && defined(UAE_UNIX_WITH_SDL3) + SDL_SetMainReady(); + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { + choices.append(qMakePair(QStringLiteral("SDL: Default recording device"), QStringLiteral("SDL:Default Recording Device"))); + int count = 0; + SDL_AudioDeviceID *devices = SDL_GetAudioRecordingDevices(&count); + if (devices) { + for (int i = 0; i < count; i++) { + const char *name = SDL_GetAudioDeviceName(devices[i]); + const QString display = QString::fromUtf8(name && name[0] ? name : "Unknown recording device"); + choices.append(qMakePair(QStringLiteral("SDL: %1").arg(display), QStringLiteral("SDL:%1").arg(display))); + } + SDL_free(devices); + } + } +#endif + + return choices; +} + +static QString soundOutputConfigValue(int id) +{ + switch (id) { + case 0: + return QStringLiteral("none"); + case 1: + return QStringLiteral("interrupts"); + case 2: + default: + return QStringLiteral("exact"); + } +} + +static int soundOutputId(const QString &value) +{ + if (value.compare(QStringLiteral("none"), Qt::CaseInsensitive) == 0) { + return 0; + } + if (value.compare(QStringLiteral("interrupts"), Qt::CaseInsensitive) == 0) { + return 1; + } + return 2; +} + +static QStringList soundChannelItems() +{ + return { + QStringLiteral("Mono"), + QStringLiteral("Stereo"), + QStringLiteral("Cloned Stereo (4 Channels)"), + QStringLiteral("4 Channels"), + QStringLiteral("Cloned Stereo (5.1)"), + QStringLiteral("5.1"), + QStringLiteral("Cloned stereo (7.1)"), + QStringLiteral("7.1") + }; +} + +static QString soundChannelConfigValue(const QString &text) +{ + static const QStringList values = { + QStringLiteral("mono"), + QStringLiteral("stereo"), + QStringLiteral("clonedstereo"), + QStringLiteral("4ch"), + QStringLiteral("clonedstereo6ch"), + QStringLiteral("6ch"), + QStringLiteral("clonedstereo8ch"), + QStringLiteral("8ch") + }; + const int index = soundChannelItems().indexOf(text); + return values.value(index, QStringLiteral("stereo")); +} + +static QString soundChannelText(const QString &value) +{ + static const QStringList values = { + QStringLiteral("mono"), + QStringLiteral("stereo"), + QStringLiteral("clonedstereo"), + QStringLiteral("4ch"), + QStringLiteral("clonedstereo6ch"), + QStringLiteral("6ch"), + QStringLiteral("clonedstereo8ch"), + QStringLiteral("8ch") + }; + const int index = values.indexOf(value.toLower()); + return soundChannelItems().value(index, QStringLiteral("Stereo")); +} + +static QString soundInterpolationConfigValue(const QString &text) +{ + if (text == QStringLiteral("Anti")) { + return QStringLiteral("anti"); + } + if (text == QStringLiteral("Sinc")) { + return QStringLiteral("sinc"); + } + if (text == QStringLiteral("RH")) { + return QStringLiteral("rh"); + } + if (text == QStringLiteral("Crux")) { + return QStringLiteral("crux"); + } + return QStringLiteral("none"); +} + +static QString soundInterpolationText(const QString &value) +{ + if (value.compare(QStringLiteral("anti"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Anti"); + } + if (value.compare(QStringLiteral("sinc"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Sinc"); + } + if (value.compare(QStringLiteral("rh"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("RH"); + } + if (value.compare(QStringLiteral("crux"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Crux"); + } + return QStringLiteral("Disabled"); +} + +static QString soundFilterConfigValue(const QString &text) +{ + if (text == QStringLiteral("Always off")) { + return QStringLiteral("off"); + } + return text.startsWith(QStringLiteral("Emulated")) ? QStringLiteral("emulated") : QStringLiteral("on"); +} + +static QString soundFilterTypeConfigValue(const QString &text) +{ + return text.contains(QStringLiteral("A1200")) ? QStringLiteral("enhanced") : QStringLiteral("standard"); +} + +static QString soundFilterText(const QString &filter, const QString &type) +{ + const bool enhanced = type.compare(QStringLiteral("enhanced"), Qt::CaseInsensitive) == 0; + if (filter.compare(QStringLiteral("off"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Always off"); + } + if (filter.compare(QStringLiteral("emulated"), Qt::CaseInsensitive) == 0) { + return enhanced ? QStringLiteral("Emulated (A1200)") : QStringLiteral("Emulated (A500)"); + } + return enhanced ? QStringLiteral("Always on (A1200)") : QStringLiteral("Always on (A500)"); +} + +static QString soundSwapText(bool paula, bool ahi) +{ + const int index = (paula ? 1 : 0) + (ahi ? 2 : 0); + return QStringList({ QStringLiteral("-"), QStringLiteral("Paula only"), QStringLiteral("AHI only"), QStringLiteral("Both") }).value(index); +} + +static int soundBufferSizeFromIndex(int index) +{ + static const int sizes[] = { 1024, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 32768, 65536 }; + if (index <= 0) { + return 0; + } + return sizes[qBound(1, index, 10) - 1]; +} + +static int soundBufferIndexFromSize(int size) +{ + static const int sizes[] = { 1024, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 32768, 65536 }; + if (size < sizes[0]) { + return 0; + } + int index = 0; + while (index < 9 && sizes[index] < size) { + index++; + } + return index + 1; +} + +static constexpr int RtgRgbClut = 1 << 1; +static constexpr int RtgRgbR8G8B8 = 1 << 2; +static constexpr int RtgRgbB8G8R8 = 1 << 3; +static constexpr int RtgRgbR5G6B5Pc = 1 << 4; +static constexpr int RtgRgbR5G5B5Pc = 1 << 5; +static constexpr int RtgRgbA8R8G8B8 = 1 << 6; +static constexpr int RtgRgbA8B8G8R8 = 1 << 7; +static constexpr int RtgRgbR8G8B8A8 = 1 << 8; +static constexpr int RtgRgbB8G8R8A8 = 1 << 9; +static constexpr int RtgRgbR5G6B5 = 1 << 10; +static constexpr int RtgRgbR5G5B5 = 1 << 11; +static constexpr int RtgRgbB5G6R5Pc = 1 << 12; +static constexpr int RtgRgbB5G5R5Pc = 1 << 13; +static constexpr int RtgDefaultModeMask = RtgRgbClut | RtgRgbR5G6B5Pc | RtgRgbB8G8R8A8; + +static QString rtgScaleConfigValue(bool scale, bool center, bool integer) +{ + if (integer) { + return QStringLiteral("integer"); + } + if (center) { + return QStringLiteral("center"); + } + if (scale) { + return QStringLiteral("scale"); + } + return QStringLiteral("resize"); +} + +static QString rtgBufferConfigValue(const QString &text) +{ + return text == QStringLiteral("Triple") ? QStringLiteral("2") : QStringLiteral("1"); +} + +static QString rtgBufferText(const QString &value) +{ + return value == QStringLiteral("2") ? QStringLiteral("Triple") : QStringLiteral("Double"); +} + +static QString rtgVBlankConfigValue(const QString &text) +{ + const QString value = text.trimmed(); + if (value == QStringLiteral("Chipset")) { + return QStringLiteral("chipset"); + } + if (value == QStringLiteral("Default")) { + return QStringLiteral("real"); + } + return value.isEmpty() ? QStringLiteral("chipset") : value; +} + +static QString rtgVBlankText(const QString &value) +{ + const QString normalized = value.trimmed().toLower(); + if (normalized == QStringLiteral("real") || normalized == QStringLiteral("-1") || normalized == QStringLiteral("default")) { + return QStringLiteral("Default"); + } + if (normalized == QStringLiteral("chipset") || normalized == QStringLiteral("disabled") || + normalized == QStringLiteral("0") || normalized == QStringLiteral("-2")) { + return QStringLiteral("Chipset"); + } + return value.trimmed().isEmpty() ? QStringLiteral("Chipset") : value.trimmed(); +} + +static int rtgColorDepthMask(const QString &text) +{ + if (text == QStringLiteral("8-bit (*)")) { + return RtgRgbClut; + } + if (text == QStringLiteral("All 15/16-bit")) { + return RtgRgbR5G6B5Pc | RtgRgbR5G5B5Pc | RtgRgbR5G6B5 | RtgRgbR5G5B5 | RtgRgbB5G6R5Pc | RtgRgbB5G5R5Pc; + } + if (text == QStringLiteral("R5G6B5PC (*)")) { + return RtgRgbR5G6B5Pc; + } + if (text == QStringLiteral("R5G5B5PC")) { + return RtgRgbR5G5B5Pc; + } + if (text == QStringLiteral("R5G6B5")) { + return RtgRgbR5G6B5; + } + if (text == QStringLiteral("R5G5B5")) { + return RtgRgbR5G5B5; + } + if (text == QStringLiteral("B5G6R5PC")) { + return RtgRgbB5G6R5Pc; + } + if (text == QStringLiteral("B5G5R5PC")) { + return RtgRgbB5G5R5Pc; + } + if (text == QStringLiteral("All 24-bit")) { + return RtgRgbR8G8B8 | RtgRgbB8G8R8; + } + if (text == QStringLiteral("R8G8B8")) { + return RtgRgbR8G8B8; + } + if (text == QStringLiteral("B8G8R8")) { + return RtgRgbB8G8R8; + } + if (text == QStringLiteral("All 32-bit")) { + return RtgRgbA8R8G8B8 | RtgRgbA8B8G8R8 | RtgRgbR8G8B8A8 | RtgRgbB8G8R8A8; + } + if (text == QStringLiteral("A8R8G8B8")) { + return RtgRgbA8R8G8B8; + } + if (text == QStringLiteral("A8B8G8R8")) { + return RtgRgbA8B8G8R8; + } + if (text == QStringLiteral("R8G8B8A8")) { + return RtgRgbR8G8B8A8; + } + if (text == QStringLiteral("B8G8R8A8 (*)")) { + return RtgRgbB8G8R8A8; + } + return 0; +} + +static QString rtg8BitText(int mask) +{ + return (mask & RtgRgbClut) ? QStringLiteral("8-bit (*)") : QStringLiteral("(8bit)"); +} + +static QString rtg16BitText(int mask) +{ + const int all = RtgRgbR5G6B5Pc | RtgRgbR5G5B5Pc | RtgRgbR5G6B5 | RtgRgbR5G5B5 | RtgRgbB5G6R5Pc | RtgRgbB5G5R5Pc; + if ((mask & all) == all) { + return QStringLiteral("All 15/16-bit"); + } + if (mask & RtgRgbR5G6B5Pc) { + return QStringLiteral("R5G6B5PC (*)"); + } + if (mask & RtgRgbR5G5B5Pc) { + return QStringLiteral("R5G5B5PC"); + } + if (mask & RtgRgbR5G6B5) { + return QStringLiteral("R5G6B5"); + } + if (mask & RtgRgbR5G5B5) { + return QStringLiteral("R5G5B5"); + } + if (mask & RtgRgbB5G6R5Pc) { + return QStringLiteral("B5G6R5PC"); + } + if (mask & RtgRgbB5G5R5Pc) { + return QStringLiteral("B5G5R5PC"); + } + return QStringLiteral("(15/16bit)"); +} + +static QString rtg24BitText(int mask) +{ + const int all = RtgRgbR8G8B8 | RtgRgbB8G8R8; + if ((mask & all) == all) { + return QStringLiteral("All 24-bit"); + } + if (mask & RtgRgbR8G8B8) { + return QStringLiteral("R8G8B8"); + } + if (mask & RtgRgbB8G8R8) { + return QStringLiteral("B8G8R8"); + } + return QStringLiteral("(24bit)"); +} + +static QString rtg32BitText(int mask) +{ + const int all = RtgRgbA8R8G8B8 | RtgRgbA8B8G8R8 | RtgRgbR8G8B8A8 | RtgRgbB8G8R8A8; + if ((mask & all) == all) { + return QStringLiteral("All 32-bit"); + } + if (mask & RtgRgbA8R8G8B8) { + return QStringLiteral("A8R8G8B8"); + } + if (mask & RtgRgbA8B8G8R8) { + return QStringLiteral("A8B8G8R8"); + } + if (mask & RtgRgbR8G8B8A8) { + return QStringLiteral("R8G8B8A8"); + } + if (mask & RtgRgbB8G8R8A8) { + return QStringLiteral("B8G8R8A8 (*)"); + } + return QStringLiteral("(32bit)"); +} + +static QString sourceFile(const QString &relative) +{ + return QDir(QString::fromUtf8(WINUAE_UNIX_SOURCE_DIR)).filePath(relative); +} + +static QString resourceFile(const QString &relative) +{ + const QDir appDir(QCoreApplication::applicationDirPath()); +#ifdef Q_OS_MACOS + const QString bundlePath = appDir.filePath(QStringLiteral("../Resources/") + relative); + if (QFileInfo::exists(bundlePath)) { + return bundlePath; + } +#endif + const QString localPath = appDir.filePath(relative); + if (QFileInfo::exists(localPath)) { + return localPath; + } + const QString relativeInstallPath = appDir.filePath(QStringLiteral("../") + QString::fromUtf8(WINUAE_UNIX_INSTALL_DATADIR_RELATIVE) + QStringLiteral("/") + relative); + if (QFileInfo::exists(relativeInstallPath)) { + return relativeInstallPath; + } + const QString installPath = QDir(QString::fromUtf8(WINUAE_UNIX_INSTALL_DATA_DIR)).filePath(relative); + if (QFileInfo::exists(installPath)) { + return installPath; + } + return sourceFile(relative); +} + +static QString versionString() +{ + return QStringLiteral("WinUAE %1.%2.%3") + .arg(WINUAE_UNIX_VERSION_MAJOR) + .arg(WINUAE_UNIX_VERSION_MINOR) + .arg(WINUAE_UNIX_VERSION_REVISION); +} + +static QStringList contributorLines() +{ + return { + QStringLiteral("Bernd Schmidt - The Grand-Master"), + QStringLiteral("Sam Jordan - Custom-chip, floppy-DMA, etc."), + QStringLiteral("Mathias Ortmann - Original WinUAE Main Guy, BSD Socket support"), + QStringLiteral("Brian King - Picasso96 Support, Integrated GUI for WinUAE, previous WinUAE Main Guy"), + QStringLiteral("Toni Wilen - Core updates, WinUAE Main Guy"), + QStringLiteral("Gustavo Goedert / Peter Remmers / Michael Sontheimer / Tomi Hakala / Tim Gunn / Nemo Pohle - DOS Port Stuff"), + QStringLiteral("Samuel Devulder / Olaf Barthel / Sam Jordan - Amiga Ports"), + QStringLiteral("Krister Bergman - XFree86 and OS/2 Port"), + QStringLiteral("A. Blanchard / Ernesto Corvi - MacOS Port"), + QStringLiteral("Christian Bauer - BeOS Port"), + QStringLiteral("Ian Stephenson - NextStep Port"), + QStringLiteral("Peter Teichmann - Acorn/RiscOS Port"), + QStringLiteral("Stefan Reinauer - ZorroII/III AutoConfig, Serial Support"), + QStringLiteral("Christian Schmitt / Chris Hames - Serial Support"), + QStringLiteral("Herman ten Brugge - 68020/68881 Emulation Code"), + QStringLiteral("Tauno Taipaleenmaki - Various UAE-Control/UAE-Library Support"), + QStringLiteral("Brett Eden / Tim Gunn / Paolo Besser / Nemo Pohle - Various Docs and Web-Sites"), + QStringLiteral("Georg Veichtlbauer - Help File coordinator, German GUI"), + QStringLiteral("Fulvio Leonardi - Italian translator for WinUAE"), + QStringLiteral("Arnljot Arntsen, Bill Panagouleas, Cloanto, Zak Jennings - Hardware support"), + QStringLiteral("Special thanks to Alexander Kneer and Tobias Abt (The Picasso96 Team)"), + QStringLiteral("Steven Weiser - Postscript printing emulation idea and testing"), + QStringLiteral("Peter Toth / Balazs Ratkai / Ivan Herczeg / Andras Arato - Hungarian translation"), + QStringLiteral("Karsten Bock, Gavin Fance, Dirk Trowe and Christian Schindler - Freezer cartridge hardware support"), + QStringLiteral("Mikko Nieminen - Demo compatibility testing"), + QStringLiteral("Arabuusimiehet - [This information is on a need-to-know basis]"), + QStringLiteral("Ross - Chipset torture test programs") + }; +} + +struct AboutLink { + const char *display; + const char *url; +}; + +static const AboutLink aboutLinks[] = { + { "Cloanto's Amiga Forever", "https://www.amigaforever.com/" }, + { "Amiga Corporation", "https://amiga.com/" }, + { "WinUAE Home Page", "http://www.winuae.net/" }, + { "abime.net", "http://www.abime.net/" }, + { "SPS", "http://www.softpres.org/" }, + { "AmiKit", "http://amikit.amiga.sk/" } +}; + +struct ConfigChoice { + const char *display; + const char *value; +}; + +static const ConfigChoice printerTypeChoices[] = { + { "Passthrough", "none" }, + { "ASCII-Only", "ascii" }, + { "Epson Matrix Printer Emulation, 9pin", "epson_matrix_9pin" }, + { "Epson Matrix Printer Emulation, 48pin", "epson_matrix_48pin" }, + { "PostScript (Passthrough)", "postscript_passthrough" }, + { "PostScript (Emulation)", "postscript_emulation" } +}; + +static const ConfigChoice dongleChoices[] = { + { "None", "none" }, + { "RoboCop 3", "robocop 3" }, + { "Leader Board", "leaderboard" }, + { "B.A.T. II", "b.a.t. ii" }, + { "Italy '90 Soccer", "italy'90 soccer" }, + { "Dames Grand-Maitre", "dames grand maitre" }, + { "Rugby Coach", "rugby coach" }, + { "Cricket Captain", "cricket captain" }, + { "Leviathan", "leviathan" }, + { "Music Master", "musicmaster" }, + { "Logistics/SuperBase", "logistics" }, + { "Scala MM (Red)", "scala red" }, + { "Scala MM (Green)", "scala green" }, + { "Striker Manager", "strikermanager" }, + { "Multi-Player Soccer Manager", "multi-player soccer manager" }, + { "Football Director 2", "football director 2" } +}; + +static const ConfigChoice keyboardLedChoices[] = { + { "None", "none" }, + { "Power", "POWER" }, + { "DF0", "DF0" }, + { "DF1", "DF1" }, + { "DF2", "DF2" }, + { "DF3", "DF3" }, + { "HD", "HD" }, + { "CD", "CD" }, + { "DF*", "DFx" } +}; + +struct MiscCheckChoice { + const char *display; + const char *key; + bool defaultChecked; +}; + +static const MiscCheckChoice miscCheckChoices[] = { + { "Show GUI on startup", "use_gui", true }, + { "Synchronize clock", "synchronize_clock", false }, + { "One second reboot pause", "cpu_reset_pause", false }, + { "Faster RTG", "rtg_nocustom", true }, + { "Clipboard sharing", "clipboard_sharing", false }, + { "Allow native code", "native_code", false }, + { "Native on-screen display", "show_leds", false }, + { "RTG on-screen display", "show_leds_rtg", false }, + { "Log illegal memory accesses", "log_illegal_mem", false }, + { "Master floppy write protection", "floppy_write_protect", false }, + { "Master harddrive write protection", "harddrive_write_protect", false }, + { "Hide all UAE autoconfig boards", "uae_hide_autoconfig", false }, + { "Power led dims when audio filter is disabled", "power_led_dim", false }, + { "Debug memory space", "debug_mem", false }, + { "Force hard reset if CPU halted", "cpu_halt_auto_reset", false }, + { "A600/A1200/A4000 IDE scsi.device disable", "scsidevice_disable", false }, + { "Warp mode reset", "warpboot", false } +}; + +static bool miscCheckChoiceEnabled(const QString &key) +{ + Q_UNUSED(key); + return true; +} + +static QString miscCheckChoiceDisabledReason(const QString &key) +{ + Q_UNUSED(key); + return QString(); +} + +struct ActivityPriorityChoice { + const char *display; + int value; +}; + +static const ActivityPriorityChoice activityPriorityChoices[] = { + { "Above normal", 1 }, + { "Normal", 0 }, + { "Below normal", -1 }, + { "Low", -2 } +}; + +struct AssociationChoice { + const char *extension; +}; + +static const AssociationChoice associationChoices[] = { + { ".uae" }, + { ".adf" }, + { ".adz" }, + { ".dms" }, + { ".fdi" }, + { ".ipf" }, + { ".uss" } +}; + +struct AdvancedCheckChoice { + const char *display; + const char *key; + bool defaultChecked; +}; + +static const ConfigChoice rtcChoices[] = { + { "None", "none" }, + { "MSM6242B", "MSM6242B" }, + { "RF5C01A", "RP5C01A" }, + { "A2000 MSM6242B", "MSM6242B_A2000" } +}; + +static const ConfigChoice ciaTodChoices[] = { + { "Vertical Sync", "vblank" }, + { "Power Supply 50Hz", "50hz" }, + { "Power Supply 60Hz", "60hz" } +}; + +static const ConfigChoice unmappedAddressChoices[] = { + { "Floating", "floating" }, + { "All zeros", "zero" }, + { "All ones", "one" } +}; + +static const ConfigChoice ciaSyncChoices[] = { + { "Autoselect", "default" }, + { "68000", "68000" }, + { "Gayle", "Gayle" }, + { "68000 Alternate", "68000_opt" } +}; + +static const ConfigChoice agnusModelChoices[] = { + { "Auto", "default" }, + { "Velvet", "velvet" }, + { "A1000", "a1000" } +}; + +static const ConfigChoice agnusSizeChoices[] = { + { "Auto", "default" }, + { "512k", "512k" }, + { "1M", "1m" }, + { "2M", "2m" } +}; + +static const ConfigChoice deniseModelChoices[] = { + { "Auto", "default" }, + { "Velvet", "velvet" }, + { "A1000 No-EHB", "a1000_noehb" }, + { "A1000", "a1000" } +}; + +static const ConfigChoice hvSyncChoices[] = { + { "Combined + Blanking", "hvcsync" }, + { "Composite Sync + Blanking", "csync" }, + { "H/V Sync + Blanking", "hvsync" }, + { "Combined + Sync", "hvcsync_s" }, + { "Composite Sync + Sync", "csync_s" }, + { "H/V Sync + Sync", "hvsync_s" } +}; + +static const ConfigChoice genlockModeChoices[] = { + { "-", "none" }, + { "Noise (built-in)", "noise" }, + { "Test card (built-in)", "testcard" }, + { "Image file (png)", "image" }, + { "Video file", "video" }, + { "Capture device", "stream" }, + { "American Laser Games/Picmatic LaserDisc Player", "ld" }, + { "Sony LaserDisc Player", "sony_ld" }, + { "Pioneer LaserDisc Player", "pioneer_ld" } +}; + +static const ConfigChoice keyboardModeChoices[] = { + { "Keyboard disconnected", "disconnected" }, + { "UAE High level emulation", "UAE" }, + { "A500 / A500+ (6570-036 MCU)", "a500_6570-036" }, + { "A600 (6570-036 MCU)", "a600_6570-036" }, + { "A1000 (6500-1 MCU, ROM not yet dumped)", "a1000_6500-1" }, + { "A1000 (6570-036 MCU)", "a1000_6570-036" }, + { "A1200 (68HC05C MCU)", "a1200_6805" }, + { "A2000 (Cherry, 8039 MCU)", "a2000_8039" }, + { "A2000/A3000/A4000 (6570-036 MCU)", "ax000_6570-036" } +}; + +static const ConfigChoice z3MappingChoices[] = { + { "Automatic", "auto" }, + { "UAE", "uae" }, + { "Real", "real" } +}; + +static const ConfigChoice scsiModeChoices[] = { + { "SCSI emulation", "SCSIEMU" }, + { "SPTI", "SPTI" }, + { "SPTI + SCSI SCAN", "SPTI+SCSISCAN" } +}; + +static const AdvancedCheckChoice advancedCheckChoices[] = { + { "CIA ROM Overlay", "cia_overlay", true }, + { "CD32 CD", "cd32cd", false }, + { "CDTV CD", "cdtvcd", false }, + { "ROM Mirror (E0)", "ksmirror_e0", true }, + { "DF0: ID Hardware", "df0idhw", true }, + { "KB Reset Warning", "resetwarning", true }, + { "CIA TOD bug", "cia_todbug", false }, + { "1M Chip / 0.5M+0.5M", "1mchipjumper", false }, + { "Toshiba Gary", "toshiba_gary", false }, + { "A1000 Boot RAM/ROM", "a1000ram", false }, + { "CD32 C2P", "cd32c2p", false }, + { "CDTV SRAM", "cdtvram", false }, + { "ROM Mirror (A8)", "ksmirror_a8", false }, + { "Z3 Autoconfig", "z3_autoconfig", false }, + { "KS ROM has Chip RAM speed", "rom_is_slow", false }, + { "CD32 NVRAM", "cd32nvram", false }, + { "CDTV-CR", "cdtv-cr", false }, + { "PCMCIA", "pcmcia", false }, + { "Composite color burst", "color_burst", false }, + { "Power up memory pattern", "memory_pattern", false } +}; + +static const ConfigChoice filterTargetChoices[] = { + { "Native", "" }, + { "RTG", "_rtg" }, + { "Interlaced", "_lace" } +}; + +static const ConfigChoice filterModeChoices[] = { + { "None", "none" } +}; + +static const ConfigChoice filterModeHChoices[] = { + { "1x", "1x" }, + { "2x", "2x" }, + { "3x", "3x" }, + { "4x", "4x" } +}; + +static const ConfigChoice filterModeVChoices[] = { + { "-", "-" }, + { "1x", "1x" }, + { "2x", "2x" }, + { "3x", "3x" }, + { "4x", "4x" } +}; + +static const ConfigChoice filterAutoscaleChoices[] = { + { "Disabled", "none" }, + { "Automatic", "auto" }, + { "TV", "standard" }, + { "Maximum", "max" }, + { "Scale", "scale" }, + { "Resize", "resize" }, + { "Center", "center" }, + { "Manual", "manual" }, + { "Integer", "integer" }, + { "Integer autoscale", "integer_auto" }, + { "Overscan blanking", "overscan_blanking" } +}; + +static const ConfigChoice filterAutoscaleRtgChoices[] = { + { "Resize", "resize" }, + { "Scale", "scale" }, + { "Center", "center" }, + { "Integer", "integer" } +}; + +static const ConfigChoice filterIntegerLimitChoices[] = { + { "1/1", "1/1" }, + { "1/2", "1/2" }, + { "1/4", "1/4" }, + { "1/8", "1/8" } +}; + +static const ConfigChoice filterAspectChoices[] = { + { "VGA", "vga" }, + { "TV", "tv" } +}; + +static const ConfigChoice rtgAspectRatioChoices[] = { + { "Disabled", "0:0" }, + { "Automatic", "-1:-1" }, + { "4:3", "4:3" }, + { "5:4", "5:4" }, + { "16:9", "16:9" }, + { "16:10", "16:10" } +}; + +static QIcon resourceIcon(const QString &name) +{ + const QString path = resourceFile(QStringLiteral("od-win32/resources/") + name); + return QFileInfo::exists(path) ? QIcon(path) : QIcon(); +} + +static QLabel *label(const QString &text) +{ + QLabel *w = new QLabel(text); + w->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + return w; +} + +static QComboBox *combo(const QStringList &items, const QString ¤t = QString()) +{ + QComboBox *w = new QComboBox; + w->setEditable(false); + w->addItems(items); + if (!current.isEmpty()) { + const int index = w->findText(current); + if (index >= 0) { + w->setCurrentIndex(index); + } + } + return w; +} + +static QStringList quickstartModelItems() +{ + QStringList items; + for (const QuickstartModelChoice &choice : quickstartModelChoices) { + items.append(QString::fromLatin1(choice.display)); + } + return items; +} + +static const QuickstartModelChoice *quickstartModelChoiceByDisplay(const QString &display) +{ + for (const QuickstartModelChoice &choice : quickstartModelChoices) { + if (display == QString::fromLatin1(choice.display)) { + return &choice; + } + } + return nullptr; +} + +static const QuickstartModelChoice *quickstartModelChoiceByConfigValue(const QString &value) +{ + for (const QuickstartModelChoice &choice : quickstartModelChoices) { + if (value.compare(QString::fromLatin1(choice.configValue), Qt::CaseInsensitive) == 0) { + return &choice; + } + } + return nullptr; +} + +static QStringList quickstartConfigItems(const QuickstartModelChoice &choice) +{ + QStringList items; + for (int i = 0; i < choice.configCount && i < MaxQuickstartConfigs; i++) { + if (choice.configs[i]) { + items.append(QString::fromLatin1(choice.configs[i])); + } + } + return items; +} + +static QStringList activityPriorityDisplays() +{ + QStringList items; + for (int i = 0; i < int(sizeof(activityPriorityChoices) / sizeof(activityPriorityChoices[0])); i++) { + items.append(QString::fromLatin1(activityPriorityChoices[i].display)); + } + return items; +} + +static QString activityPriorityText(int value) +{ + for (int i = 0; i < int(sizeof(activityPriorityChoices) / sizeof(activityPriorityChoices[0])); i++) { + if (activityPriorityChoices[i].value == value) { + return QString::fromLatin1(activityPriorityChoices[i].display); + } + } + return QStringLiteral("Normal"); +} + +static int activityPriorityValue(const QString &text) +{ + for (int i = 0; i < int(sizeof(activityPriorityChoices) / sizeof(activityPriorityChoices[0])); i++) { + if (text == QString::fromLatin1(activityPriorityChoices[i].display)) { + return activityPriorityChoices[i].value; + } + } + return 0; +} + +static QButtonGroup *radioGroup(const ConfigChoice *choices, int count, int current, QWidget *parent, QGridLayout *layout) +{ + QButtonGroup *group = new QButtonGroup(parent); + for (int i = 0; i < count; i++) { + QRadioButton *button = new QRadioButton(QString::fromLatin1(choices[i].display)); + group->addButton(button, i); + layout->addWidget(button, i / 4, i % 4); + if (i == current) { + button->setChecked(true); + } + } + return group; +} + +static QComboBox *pathCombo() +{ + QComboBox *w = new QComboBox; + w->setEditable(true); + w->setInsertPolicy(QComboBox::NoInsert); + w->lineEdit()->setClearButtonEnabled(true); + return w; +} + +static QStringList configChoiceDisplays(const ConfigChoice *choices, int count) +{ + QStringList items; + for (int i = 0; i < count; i++) { + items.append(QString::fromLatin1(choices[i].display)); + } + return items; +} + +static QString configChoiceValue(const ConfigChoice *choices, int count, const QString &display) +{ + for (int i = 0; i < count; i++) { + if (display == QString::fromLatin1(choices[i].display)) { + return QString::fromLatin1(choices[i].value); + } + } + return QString(); +} + +static QString configChoiceDisplay(const ConfigChoice *choices, int count, const QString &value) +{ + for (int i = 0; i < count; i++) { + if (value.compare(QString::fromLatin1(choices[i].value), Qt::CaseInsensitive) == 0) { + return QString::fromLatin1(choices[i].display); + } + } + return QString::fromLatin1(choices[0].display); +} + +static QString configChoiceValueAt(const ConfigChoice *choices, int count, int index) +{ + return QString::fromLatin1(choices[qBound(0, index, count - 1)].value); +} + +static int configChoiceIndex(const ConfigChoice *choices, int count, const QString &value) +{ + for (int i = 0; i < count; i++) { + if (value.compare(QString::fromLatin1(choices[i].value), Qt::CaseInsensitive) == 0) { + return i; + } + } + return 0; +} + +static int configIntegerValue(const QString &value, bool *ok) +{ + bool localOk = false; + int result = value.trimmed().toInt(&localOk, 0); + if (!localOk) { + result = value.trimmed().toInt(&localOk, 16); + } + if (ok) { + *ok = localOk; + } + return result; +} + +struct WinUaeQtZipEntry { + QByteArray name; + QByteArray data; +}; + +struct WinUaeQtZipCentralEntry { + QByteArray name; + quint32 crc = 0; + quint32 size = 0; + quint32 offset = 0; + quint16 time = 0; + quint16 date = 0; +}; + +static void appendLe16(QByteArray *out, quint16 value) +{ + out->append(char(value & 0xff)); + out->append(char((value >> 8) & 0xff)); +} + +static void appendLe32(QByteArray *out, quint32 value) +{ + appendLe16(out, quint16(value & 0xffff)); + appendLe16(out, quint16((value >> 16) & 0xffff)); +} + +static quint32 crc32ForBytes(const QByteArray &bytes) +{ + quint32 crc = 0xffffffffu; + for (char ch : bytes) { + crc ^= quint8(ch); + for (int bit = 0; bit < 8; bit++) { + crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u))); + } + } + return crc ^ 0xffffffffu; +} + +static bool crc32ForFile(const QString &path, quint32 *out, QString *error = nullptr) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + if (error) { + *error = file.errorString(); + } + return false; + } + + quint32 crc = 0xffffffffu; + for (;;) { + const QByteArray chunk = file.read(1024 * 1024); + if (chunk.isEmpty()) { + break; + } + for (char ch : chunk) { + crc ^= quint8(ch); + for (int bit = 0; bit < 8; bit++) { + crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u))); + } + } + } + if (file.error() != QFileDevice::NoError) { + if (error) { + *error = file.errorString(); + } + return false; + } + if (out) { + *out = crc ^ 0xffffffffu; + } + return true; +} + +static quint32 readBe32(const QByteArray &data, int offset) +{ + if (offset < 0 || offset + 4 > data.size()) { + return 0; + } + return (quint32(quint8(data[offset])) << 24) + | (quint32(quint8(data[offset + 1])) << 16) + | (quint32(quint8(data[offset + 2])) << 8) + | quint32(quint8(data[offset + 3])); +} + +static QString hex32(quint32 value) +{ + return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0')).toUpper(); +} + +static QString formatByteSize(qint64 bytes) +{ + const double mib = double(bytes) / (1024.0 * 1024.0); + if (mib >= 1.0) { + return QStringLiteral("%1 bytes (%2 MiB)").arg(bytes).arg(mib, 0, 'f', 2); + } + return QStringLiteral("%1 bytes").arg(bytes); +} + +static bool isPlainSectorFloppyImage(const QFileInfo &info) +{ + const QString suffix = info.suffix().toLower(); + return suffix == QStringLiteral("adf") + || info.size() == 901120 + || info.size() == 1802240; +} + +static bool amigaBootBlockChecksumValid(const QByteArray &bootBlock) +{ + if (bootBlock.size() < 1024) { + return false; + } + + quint32 sum = 0; + for (int i = 0; i < 1024; i += 4) { + quint32 value = readBe32(bootBlock, i); + if (i == 4) { + value = 0; + } + const quint32 old = sum; + sum += value; + if (sum < old) { + sum++; + } + } + return (sum ^ 0xffffffffu) == readBe32(bootBlock, 4); +} + +static QString amigaBootBlockType(const QByteArray &bootBlock) +{ + if (bootBlock.size() < 1024) { + return QStringLiteral("Unavailable"); + } + + QByteArray zeroed = bootBlock; + zeroed[4] = 0; + zeroed[5] = 0; + zeroed[6] = 0; + zeroed[7] = 0; + + const quint32 dos = readBe32(bootBlock, 0); + if (crc32ForBytes(zeroed.left(0x31)) == 0xae5e282cu) { + return QStringLiteral("Standard 1.x"); + } + if (dos >= 0x444f5300u && dos <= 0x444f5307u + && crc32ForBytes(zeroed.mid(8, 0x5c - 8)) == 0xe158ca4bu) { + return QStringLiteral("Standard 2.x+"); + } + if (dos == 0x4b49434bu) { + return QStringLiteral("Kickstart"); + } + return QStringLiteral("Custom"); +} + +static QString amigaRootBlockLabel(const QByteArray &rootBlock) +{ + if (rootBlock.size() < 512 || readBe32(rootBlock, 0) != 2 || readBe32(rootBlock, 508) != 1) { + return QString(); + } + + const int nameOffset = 512 - 20 * 4; + const int nameLength = quint8(rootBlock[nameOffset]); + if (nameLength <= 0 || nameOffset + 1 + nameLength > rootBlock.size()) { + return QString(); + } + return QString::fromLatin1(rootBlock.constData() + nameOffset + 1, nameLength); +} + +static void currentDosDateTime(quint16 *dosDate, quint16 *dosTime) +{ + const QDateTime now = QDateTime::currentDateTime(); + const QDate date = now.date(); + const QTime time = now.time(); + const int year = qBound(1980, date.year(), 2107); + *dosDate = quint16(((year - 1980) << 9) | (date.month() << 5) | date.day()); + *dosTime = quint16((time.hour() << 11) | (time.minute() << 5) | (time.second() / 2)); +} + +static bool writeStoredZip(const QString &path, const QVector &entries, QString *error) +{ + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + if (error) { + *error = file.errorString(); + } + return false; + } + + QByteArray out; + QVector centralEntries; + for (const WinUaeQtZipEntry &entry : entries) { + if (entry.name.isEmpty() || entry.data.size() > INT_MAX) { + continue; + } + + WinUaeQtZipCentralEntry central; + central.name = entry.name; + central.crc = crc32ForBytes(entry.data); + central.size = quint32(entry.data.size()); + central.offset = quint32(out.size()); + currentDosDateTime(¢ral.date, ¢ral.time); + + appendLe32(&out, 0x04034b50u); + appendLe16(&out, 20); + appendLe16(&out, 0); + appendLe16(&out, 0); + appendLe16(&out, central.time); + appendLe16(&out, central.date); + appendLe32(&out, central.crc); + appendLe32(&out, central.size); + appendLe32(&out, central.size); + appendLe16(&out, quint16(central.name.size())); + appendLe16(&out, 0); + out.append(central.name); + out.append(entry.data); + centralEntries.append(central); + } + + const quint32 centralOffset = quint32(out.size()); + for (const WinUaeQtZipCentralEntry ¢ral : std::as_const(centralEntries)) { + appendLe32(&out, 0x02014b50u); + appendLe16(&out, 20); + appendLe16(&out, 20); + appendLe16(&out, 0); + appendLe16(&out, 0); + appendLe16(&out, central.time); + appendLe16(&out, central.date); + appendLe32(&out, central.crc); + appendLe32(&out, central.size); + appendLe32(&out, central.size); + appendLe16(&out, quint16(central.name.size())); + appendLe16(&out, 0); + appendLe16(&out, 0); + appendLe16(&out, 0); + appendLe16(&out, 0); + appendLe32(&out, 0); + appendLe32(&out, central.offset); + out.append(central.name); + } + const quint32 centralSize = quint32(out.size()) - centralOffset; + + appendLe32(&out, 0x06054b50u); + appendLe16(&out, 0); + appendLe16(&out, 0); + appendLe16(&out, quint16(centralEntries.size())); + appendLe16(&out, quint16(centralEntries.size())); + appendLe32(&out, centralSize); + appendLe32(&out, centralOffset); + appendLe16(&out, 0); + + if (file.write(out) != out.size()) { + if (error) { + *error = file.errorString(); + } + return false; + } + return true; +} + +static QString chipsetRevisionText(int value) +{ + if (value < 0) { + return QString(); + } + return QStringLiteral("%1").arg(qBound(0, value, 255), 2, 16, QLatin1Char('0')).toUpper(); +} + +static QString chipsetRevisionConfigValue(QCheckBox *enabled, QLineEdit *field, int defaultValue) +{ + if (!enabled || !enabled->isChecked()) { + return QStringLiteral("-1"); + } + bool ok = false; + int value = field ? field->text().trimmed().toInt(&ok, 16) : defaultValue; + if (!ok) { + value = defaultValue; + } + return QString::number(qBound(0, value, 255)); +} + +static QString filterSuffix(int target) +{ + return QString::fromLatin1(filterTargetChoices[qBound(0, target, 2)].value); +} + +static QString filterKey(const QString &base, int target) +{ + return base + filterSuffix(target); +} + +static bool isRtgAutoscaleValue(const QString &value) +{ + for (const ConfigChoice &choice : filterAutoscaleRtgChoices) { + if (value.compare(QString::fromLatin1(choice.value), Qt::CaseInsensitive) == 0) { + return true; + } + } + return false; +} + +static QString filterAutoscaleConfigValue(const QString &display, int target) +{ + QString value = configChoiceValue( + target == 1 ? filterAutoscaleRtgChoices : filterAutoscaleChoices, + target == 1 ? int(sizeof(filterAutoscaleRtgChoices) / sizeof(filterAutoscaleRtgChoices[0])) : int(sizeof(filterAutoscaleChoices) / sizeof(filterAutoscaleChoices[0])), + display); + if (target == 1 && !isRtgAutoscaleValue(value)) { + value = QStringLiteral("resize"); + } + return value; +} + +static QString filterAutoscaleDisplay(const QString &value, int target) +{ + return configChoiceDisplay( + target == 1 ? filterAutoscaleRtgChoices : filterAutoscaleChoices, + target == 1 ? int(sizeof(filterAutoscaleRtgChoices) / sizeof(filterAutoscaleRtgChoices[0])) : int(sizeof(filterAutoscaleChoices) / sizeof(filterAutoscaleChoices[0])), + target == 1 && !isRtgAutoscaleValue(value) ? QStringLiteral("resize") : value); +} + +static QStringList unixSerialPortItems() +{ + QStringList items { QStringLiteral("") }; + items.append(QStringLiteral("TCP://0.0.0.0:1234")); + items.append(QStringLiteral("TCP://0.0.0.0:1234/wait")); + const QStringList filters { + QStringLiteral("cu.*"), + QStringLiteral("tty.*"), + QStringLiteral("ttyUSB*"), + QStringLiteral("ttyACM*"), + QStringLiteral("ttyS*") + }; + const QStringList entries = QDir(QStringLiteral("/dev")).entryList(filters, QDir::System | QDir::Files | QDir::Readable, QDir::Name); + for (const QString &entry : entries) { + const QString path = QDir(QStringLiteral("/dev")).filePath(entry); + if (!items.contains(path)) { + items.append(path); + } + } + return items; +} + +struct WinUaeQtExpansionCategoryChoice { + const char *display; + int mask; +}; + +static const WinUaeQtExpansionCategoryChoice expansionBoardCategoryChoices[] = { + { "Built-in expansions", WinUaeQtExpansionCategoryInternal }, + { "SCSI controllers", WinUaeQtExpansionCategoryScsi }, + { "IDE controllers", WinUaeQtExpansionCategoryIde }, + { "SASI controllers", WinUaeQtExpansionCategorySasi }, + { "Custom disk controllers", WinUaeQtExpansionCategoryCustom }, + { "PCI bridgeboards", WinUaeQtExpansionCategoryPciBridge }, + { "PC bridgeboards", WinUaeQtExpansionCategoryX86Bridge }, + { "RTG board ROMs", WinUaeQtExpansionCategoryRtg }, + { "Sound boards", WinUaeQtExpansionCategorySound }, + { "Network adapters", WinUaeQtExpansionCategoryNet }, + { "Floppy controllers", WinUaeQtExpansionCategoryFloppy }, + { "PC expansions", WinUaeQtExpansionCategoryX86Expansion }, + { nullptr, 0 } +}; + +static bool expansionBoardMatchesCategory(const WinUaeQtExpansionBoardCatalogItem &board, const WinUaeQtExpansionCategoryChoice &category) +{ + if (!(board.categoryMask & category.mask)) { + return false; + } + if (category.mask == WinUaeQtExpansionCategoryInternal + && (board.categoryMask & (WinUaeQtExpansionCategorySasi | WinUaeQtExpansionCategoryCustom))) { + return false; + } + if ((board.categoryMask & WinUaeQtExpansionCategoryX86Expansion) + && category.mask != WinUaeQtExpansionCategoryX86Expansion) { + return false; + } + return true; +} + +static QString expansionBoardSortText(const QString &display) +{ + const int manufacturer = display.lastIndexOf(QStringLiteral(" (")); + if (manufacturer > 0 && display.endsWith(QLatin1Char(')'))) { + return display.left(manufacturer); + } + return display; +} + +static const WinUaeQtExpansionCategoryChoice *expansionBoardCategoryByDisplay(const QString &display) +{ + for (const WinUaeQtExpansionCategoryChoice *category = expansionBoardCategoryChoices; category->display; category++) { + if (display == QString::fromLatin1(category->display)) { + return category; + } + } + return nullptr; +} + +static QStringList expansionBoardCategoryItems(const WinUaeQtBoardCatalog &catalog) +{ + QStringList items; + for (const WinUaeQtExpansionCategoryChoice *category = expansionBoardCategoryChoices; category->display; category++) { + const bool hasBoard = std::any_of(catalog.expansionBoards.constBegin(), catalog.expansionBoards.constEnd(), [category](const WinUaeQtExpansionBoardCatalogItem &board) { + return expansionBoardMatchesCategory(board, *category); + }); + if (hasBoard) { + items.append(QString::fromLatin1(category->display)); + } + } + return items; +} + +static const WinUaeQtExpansionBoardCatalogItem *expansionBoardChoiceByKey(const WinUaeQtBoardCatalog &catalog, const QString &key) +{ + for (const WinUaeQtExpansionBoardCatalogItem &choice : catalog.expansionBoards) { + if (key.compare(choice.key, Qt::CaseInsensitive) == 0) { + return &choice; + } + } + return nullptr; +} + +static const WinUaeQtExpansionBoardCatalogItem *expansionBoardChoiceByDisplay(const WinUaeQtBoardCatalog &catalog, const QString &display) +{ + for (const WinUaeQtExpansionBoardCatalogItem &choice : catalog.expansionBoards) { + if (display == choice.display) { + return &choice; + } + } + return nullptr; +} + +static bool expansionBoardNoRomFile(const WinUaeQtExpansionBoardCatalogItem *board, const QString &subtype) +{ + if (!board) { + return false; + } + for (const WinUaeQtBoardSubtype &choice : board->subtypes) { + if (subtype.compare(choice.configValue, Qt::CaseInsensitive) == 0 && choice.hasRomTypeOverride) { + return choice.noRomFile; + } + } + return board->noRomFile; +} + +static const WinUaeQtCpuBoardCatalogItem *cpuBoardSubtypeChoiceByConfig(const WinUaeQtBoardCatalog &catalog, const QString &configValue) +{ + for (const WinUaeQtCpuBoardCatalogItem &choice : catalog.cpuBoards) { + if (configValue.compare(choice.configValue, Qt::CaseInsensitive) == 0) { + return &choice; + } + } + return nullptr; +} + +static bool cpuBoardTypeHasSubtypes(const WinUaeQtBoardCatalog &catalog, const QString &type) +{ + return std::any_of(catalog.cpuBoards.constBegin(), catalog.cpuBoards.constEnd(), [&type](const WinUaeQtCpuBoardCatalogItem &choice) { + return type == choice.type; + }); +} + +static const WinUaeQtBoardSetting *boardSettingChoiceByConfig(const QVector &settings, const QString &configValue) +{ + for (const WinUaeQtBoardSetting &choice : settings) { + if (configValue.compare(choice.configValue, Qt::CaseInsensitive) == 0) { + return &choice; + } + } + return nullptr; +} + +static QString expansionBoardConfigName(const QString &key, int slot) +{ + if (key.isEmpty()) { + return QString(); + } + return slot > 0 ? QStringLiteral("%1-%2").arg(key).arg(slot + 1) : key; +} + +static QString expansionBoardBaseKey(const QString &configName, int *slot) +{ + if (slot) { + *slot = 0; + } + const int dash = configName.lastIndexOf(QLatin1Char('-')); + if (dash <= 0) { + return configName; + } + bool ok = false; + const int oneBasedSlot = configName.mid(dash + 1).toInt(&ok); + if (!ok || oneBasedSlot < 2) { + return configName; + } + if (slot) { + *slot = oneBasedSlot - 1; + } + return configName.left(dash); +} + +static QStringList expansionOptionTokens(const QString &options) +{ + QStringList tokens; + for (QString token : options.split(QLatin1Char(','), Qt::SkipEmptyParts)) { + token = token.trimmed(); + if (!token.isEmpty()) { + tokens.append(token); + } + } + return tokens; +} + +static bool expansionOptionContains(const QString &options, const QString &name) +{ + for (const QString &token : expansionOptionTokens(options)) { + if (token.compare(name, Qt::CaseInsensitive) == 0) { + return true; + } + } + return false; +} + +static bool expansionOptionBool(const QString &options, const QString &name) +{ + const QString prefix = name + QLatin1Char('='); + for (const QString &token : expansionOptionTokens(options)) { + if (token.compare(name, Qt::CaseInsensitive) == 0) { + return true; + } + if (token.startsWith(prefix, Qt::CaseInsensitive)) { + const QString value = token.mid(prefix.size()).trimmed().toLower(); + return value == QStringLiteral("true") + || value == QStringLiteral("yes") + || value == QStringLiteral("on") + || value == QStringLiteral("1"); + } + } + return false; +} + +static QString expansionOptionValue(const QString &options, const QString &name) +{ + const QString prefix = name + QLatin1Char('='); + for (const QString &token : expansionOptionTokens(options)) { + if (token.startsWith(prefix, Qt::CaseInsensitive)) { + return token.mid(prefix.size()); + } + } + return QString(); +} + +static QString expansionOptionsWithValue(const QString &options, const QString &name, const QString &value) +{ + QStringList tokens; + const QString prefix = name + QLatin1Char('='); + bool inserted = false; + for (const QString &token : expansionOptionTokens(options)) { + if (token.compare(name, Qt::CaseInsensitive) == 0 || token.startsWith(prefix, Qt::CaseInsensitive)) { + if (!value.isEmpty() && !inserted) { + tokens.append(prefix + value); + inserted = true; + } + } else { + tokens.append(token); + } + } + if (!value.isEmpty() && !inserted) { + tokens.append(prefix + value); + } + return tokens.join(QLatin1Char(',')); +} + +static QString expansionBoardOptionsValue( + const WinUaeQtExpansionBoardState &state, + const WinUaeQtExpansionBoardCatalogItem *board) +{ + QStringList tokens; + const QStringList replaced { + QStringLiteral("autoboot_disabled"), + QStringLiteral("dma24bit"), + QStringLiteral("inserted"), + QStringLiteral("id"), + QStringLiteral("subtype") + }; + for (const QString &token : expansionOptionTokens(state.rawOptions)) { + const QString name = token.section(QLatin1Char('='), 0, 0).trimmed(); + bool known = false; + for (const QString &replacement : replaced) { + if (name.compare(replacement, Qt::CaseInsensitive) == 0) { + known = true; + break; + } + } + if (board) { + for (const WinUaeQtBoardSetting &choice : board->settings) { + if (choice.type == WinUaeQtBoardSettingType::CheckBox) { + known = name.compare(choice.configValue, Qt::CaseInsensitive) == 0; + } else if (choice.type == WinUaeQtBoardSettingType::String) { + known = name.compare(choice.configValue, Qt::CaseInsensitive) == 0; + } else if (choice.type == WinUaeQtBoardSettingType::Multi) { + for (const QString &value : choice.multiValues) { + if (name.compare(value, Qt::CaseInsensitive) == 0) { + known = true; + break; + } + } + } + if (known) { + known = true; + break; + } + } + } + if (!known) { + tokens.append(token); + } + } + if (!state.subtype.isEmpty()) { + tokens.append(QStringLiteral("subtype=%1").arg(state.subtype)); + } + if (state.autobootDisabled) { + tokens.append(QStringLiteral("autoboot_disabled=true")); + } + if (state.dma24Bit) { + tokens.append(QStringLiteral("dma24bit=true")); + } + if (state.inserted) { + tokens.append(QStringLiteral("inserted=true")); + } + if (state.id != 7 || !expansionOptionValue(state.rawOptions, QStringLiteral("id")).isEmpty()) { + tokens.append(QStringLiteral("id=%1").arg(qBound(0, state.id, 7))); + } + if (board) { + for (const WinUaeQtBoardSetting &choice : board->settings) { + if (choice.type == WinUaeQtBoardSettingType::CheckBox) { + if (state.optionBools.value(choice.configValue, false)) { + tokens.append(choice.configValue); + } + } else if (choice.type == WinUaeQtBoardSettingType::String) { + const QString value = state.optionValues.value(choice.configValue).trimmed(); + if (!value.isEmpty()) { + tokens.append(QStringLiteral("%1=%2").arg(choice.configValue, value)); + } + } else if (choice.type == WinUaeQtBoardSettingType::Multi) { + const QString value = state.optionValues.value(choice.configValue); + if (!value.isEmpty()) { + tokens.append(value); + } + } + } + } + return tokens.join(QLatin1Char(',')); +} + +static QStringList floppyTypeItems(int drive, bool quickstart) +{ + QStringList items = { + QStringLiteral("3.5 DD"), + QStringLiteral("3.5 HD"), + QStringLiteral("Disabled") + }; + if (quickstart) { + return items; + } + items.insert(2, QStringLiteral("5.25 SD")); + items.insert(3, QStringLiteral("5.25 (80)")); + items.insert(4, QStringLiteral("3.5 DD (Escom)")); + if (drive >= 2) { + items.insert(5, QStringLiteral("Bridgeboard 5.25 40")); + items.insert(6, QStringLiteral("Bridgeboard 5.25 80")); + items.insert(7, QStringLiteral("Bridgeboard 3.5 80")); + } + return items; +} + +static int floppyTypeConfigValue(const QString &text) +{ + if (text == QStringLiteral("Disabled")) { + return -1; + } + if (text == QStringLiteral("3.5 HD")) { + return 1; + } + if (text == QStringLiteral("5.25 SD")) { + return 2; + } + if (text == QStringLiteral("3.5 DD (Escom)")) { + return 3; + } + if (text == QStringLiteral("Bridgeboard 5.25 40")) { + return 4; + } + if (text == QStringLiteral("Bridgeboard 3.5 80")) { + return 5; + } + if (text == QStringLiteral("Bridgeboard 5.25 80")) { + return 6; + } + if (text == QStringLiteral("5.25 (80)")) { + return 7; + } + return 0; +} + +static QString floppyTypeText(int value) +{ + if (value < 0) { + return QStringLiteral("Disabled"); + } + if (value == 1) { + return QStringLiteral("3.5 HD"); + } + if (value == 2) { + return QStringLiteral("5.25 SD"); + } + if (value == 3) { + return QStringLiteral("3.5 DD (Escom)"); + } + if (value == 4) { + return QStringLiteral("Bridgeboard 5.25 40"); + } + if (value == 5) { + return QStringLiteral("Bridgeboard 3.5 80"); + } + if (value == 6) { + return QStringLiteral("Bridgeboard 5.25 80"); + } + if (value == 7) { + return QStringLiteral("5.25 (80)"); + } + return QStringLiteral("3.5 DD"); +} + +static int floppySpeedConfigValue(int sliderPosition) +{ + if (sliderPosition <= 0) { + return 0; + } + return 100 << qBound(0, sliderPosition - 1, 3); +} + +static int floppySpeedSliderPosition(int value) +{ + if (value <= 0) { + return 0; + } + if (value <= 100) { + return 1; + } + if (value <= 200) { + return 2; + } + if (value <= 400) { + return 3; + } + return 4; +} + +static QString floppySpeedText(int value) +{ + if (value <= 0) { + return QStringLiteral("Turbo"); + } + if (value == 100) { + return QStringLiteral("100% (Compatible)"); + } + return QStringLiteral("%1%").arg(value); +} + +static int jitCacheSizeFromPosition(int position) +{ + if (position <= 0) { + return 0; + } + return 1024 << qBound(0, position - 1, 7); +} + +static int jitCachePositionFromSize(int size) +{ + if (size <= 0) { + return 0; + } + int position = 1; + int cacheSize = 1024; + while (position < 8 && size > cacheSize) { + cacheSize <<= 1; + position++; + } + return position; +} + +static QString jitCacheText(int size) +{ + return QStringLiteral("%1 MB").arg(size > 0 ? size / 1024 : 0); +} + +static int defaultJitCacheSize() +{ + return 8192; +} + +static int cpuMultiplierValue(const QString &text) +{ + if (text.startsWith(QStringLiteral("2x"))) { + return 2; + } + if (text.startsWith(QStringLiteral("4x"))) { + return 4; + } + if (text.startsWith(QStringLiteral("8x"))) { + return 8; + } + if (text.startsWith(QStringLiteral("16x"))) { + return 16; + } + return 1; +} + +static QString cpuMultiplierText(int value) +{ + switch (value) { + case 2: + return QStringLiteral("2x (A500)"); + case 4: + return QStringLiteral("4x (A1200)"); + case 8: + return QStringLiteral("8x"); + case 16: + return QStringLiteral("16x"); + default: + return QStringLiteral("1x"); + } +} + +static QString chipsetConfigValue(const QString &text) +{ + if (text == QStringLiteral("A1000 (No EHB)")) { + return QStringLiteral("a1000_noehb"); + } + if (text == QStringLiteral("A1000")) { + return QStringLiteral("a1000"); + } + if (text == QStringLiteral("ECS Agnus")) { + return QStringLiteral("ecs_agnus"); + } + if (text == QStringLiteral("ECS Denise")) { + return QStringLiteral("ecs_denise"); + } + if (text == QStringLiteral("ECS")) { + return QStringLiteral("ecs"); + } + if (text == QStringLiteral("AGA")) { + return QStringLiteral("aga"); + } + return QStringLiteral("ocs"); +} + +static QString chipsetText(const QString &value) +{ + const QString lower = value.toLower(); + if (lower == QStringLiteral("a1000_noehb")) { + return QStringLiteral("A1000 (No EHB)"); + } + if (lower == QStringLiteral("a1000")) { + return QStringLiteral("A1000"); + } + if (lower == QStringLiteral("ecs_agnus")) { + return QStringLiteral("ECS Agnus"); + } + if (lower == QStringLiteral("ecs_denise")) { + return QStringLiteral("ECS Denise"); + } + if (lower == QStringLiteral("ecs")) { + return QStringLiteral("ECS"); + } + if (lower == QStringLiteral("aga")) { + return QStringLiteral("AGA"); + } + return QStringLiteral("OCS"); +} + +static QString displayOptimizationConfigValue(const QString &text) +{ + if (text == QStringLiteral("Partial")) { + return QStringLiteral("partial"); + } + if (text == QStringLiteral("None")) { + return QStringLiteral("none"); + } + return QStringLiteral("full"); +} + +static QString displayOptimizationText(const QString &value) +{ + const QString lower = value.toLower(); + if (lower == QStringLiteral("partial")) { + return QStringLiteral("Partial"); + } + if (lower == QStringLiteral("none")) { + return QStringLiteral("None"); + } + return QStringLiteral("Full"); +} + +static bool configBoolValue(const QString &value) +{ + return value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0 + || value.compare(QStringLiteral("yes"), Qt::CaseInsensitive) == 0 + || value == QStringLiteral("1"); +} + +static QString configBoolText(bool value) +{ + return value ? QStringLiteral("true") : QStringLiteral("false"); +} + +static QString amigaDiskImageFilter() +{ + return QStringLiteral("Amiga disk images (*.adf *.adz *.dms *.wrp *.ipf *.fdi *.scp *.hdf *.chd *.zip *.7z *.rar *.lha *.lzh *.lzx);;All files (*)"); +} + +static QString floppyDiskImageFilter() +{ + return QStringLiteral("Amiga disk images (*.adf *.adz *.dms *.wrp *.ipf *.zip *.7z *.rar *.lha *.lzh *.lzx);;All files (*)"); +} + +static QString cdImageFilter() +{ + return QStringLiteral("CD images (*.cue *.ccd *.mds *.iso *.chd *.nrg *.zip *.7z *.rar *.lha *.lzh *.lzx);;All files (*)"); +} + +static QString hardfileImageFilter() +{ + return QStringLiteral("Hardfiles (*.hdf *.hda *.vhd *.rdf *.hdz *.rdz *.chd);;All files (*)"); +} + +static QString directoryArchiveFilter() +{ + return QStringLiteral("Directory archives (*.zip *.7z *.rar *.lha *.lzh *.lzx);;All files (*)"); +} + +struct WinUaeQtNativeDriveChoice { + QString display; + QString configPath; + quint64 size = 0; + int blockSize = 512; +}; + +static QString formatNativeDriveSize(quint64 bytes) +{ + if (bytes >= 1024ULL * 1024ULL * 1024ULL) { + return QStringLiteral("%1G").arg(double(bytes) / (1024.0 * 1024.0 * 1024.0), 0, 'f', 1); + } + if (bytes >= 1024ULL * 1024ULL) { + return QStringLiteral("%1M").arg(double(bytes) / (1024.0 * 1024.0), 0, 'f', 1); + } + return QStringLiteral("%1K").arg(bytes / 1024ULL); +} + +static bool queryNativeDriveGeometry(const QString &path, quint64 *size, int *blockSize) +{ + if (size) { + *size = 0; + } + if (blockSize) { + *blockSize = 512; + } +#if defined(__APPLE__) || defined(__linux__) + const QByteArray localPath = QFile::encodeName(path); + const int fd = open(localPath.constData(), O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return false; + } +#if defined(__APPLE__) + uint32_t nativeBlockSize = 512; + uint64_t nativeBlockCount = 0; + if (ioctl(fd, DKIOCGETBLOCKSIZE, &nativeBlockSize) == 0 && nativeBlockSize > 0 && blockSize) { + *blockSize = int(nativeBlockSize); + } + if (ioctl(fd, DKIOCGETBLOCKCOUNT, &nativeBlockCount) == 0 && size) { + *size = nativeBlockCount * quint64(blockSize ? *blockSize : int(nativeBlockSize)); + } +#elif defined(__linux__) + unsigned long long nativeSize = 0; + int nativeBlockSize = 512; + if (ioctl(fd, BLKSSZGET, &nativeBlockSize) == 0 && nativeBlockSize > 0 && blockSize) { + *blockSize = nativeBlockSize; + } + if (ioctl(fd, BLKGETSIZE64, &nativeSize) == 0 && size) { + *size = quint64(nativeSize); + } +#endif + close(fd); + return size && *size > 0; +#else + Q_UNUSED(path); + return false; +#endif +} + +static void appendNativeDriveChoice(QVector *choices, QSet *seen, const QString &path, const QString &name) +{ + if (!choices || !seen || seen->contains(path)) { + return; + } + quint64 size = 0; + int blockSize = 512; + if (!queryNativeDriveGeometry(path, &size, &blockSize)) { + return; + } + seen->insert(path); + WinUaeQtNativeDriveChoice choice; + choice.size = size; + choice.blockSize = blockSize; + choice.configPath = QStringLiteral(":%1").arg(path); + choice.display = QStringLiteral("[OS] [%1,RO] %2").arg(formatNativeDriveSize(size), path); + if (!name.isEmpty() && name != path) { + choice.display += QStringLiteral(" (%1)").arg(name); + } + choices->append(choice); +} + +static QVector nativeHardDriveChoices() +{ + QVector choices; + QSet seen; +#if UAE_UNIX_WITH_NATIVE_HARDDRIVES +#if defined(__APPLE__) + const QDir dev(QStringLiteral("/dev")); + for (const QString &name : dev.entryList({ QStringLiteral("rdisk*") }, QDir::System | QDir::Files, QDir::Name)) { + if (name.mid(5).contains(QLatin1Char('s'))) { + continue; + } + appendNativeDriveChoice(&choices, &seen, dev.absoluteFilePath(name), name); + } +#elif defined(__linux__) + const QDir sysBlock(QStringLiteral("/sys/block")); + for (const QString &name : sysBlock.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)) { + if (name.startsWith(QStringLiteral("loop")) + || name.startsWith(QStringLiteral("ram")) + || name.startsWith(QStringLiteral("zram")) + || name.startsWith(QStringLiteral("dm-"))) { + continue; + } + appendNativeDriveChoice(&choices, &seen, QStringLiteral("/dev/%1").arg(name), name); + } +#endif +#endif + return choices; +} + +static bool genlockModeUsesImageFile(const QString &mode) +{ + return mode == QStringLiteral("image"); +} + +static bool genlockModeUsesVideoFile(const QString &mode) +{ + return mode == QStringLiteral("video") + || mode == QStringLiteral("ld") + || mode == QStringLiteral("sony_ld") + || mode == QStringLiteral("pioneer_ld"); +} + +static void insertLineEditSetting(WinUaeQtConfig::Settings &settings, const QString &key, const QLineEdit *field) +{ + if (!field) { + return; + } + const QString value = field->text().trimmed(); + if (!value.isEmpty()) { + settings.insert(key, value); + } +} + +static void insertCheckBoxSetting(WinUaeQtConfig::Settings &settings, const QString &key, const QCheckBox *field) +{ + if (field) { + settings.insert(key, configBoolText(field->isChecked())); + } +} + +static void setComboTextIfChanged(QComboBox *combo, const QString &text) +{ + if (combo && combo->currentText() != text) { + combo->setCurrentText(text); + } +} + +static void setCheckBoxIfChanged(QCheckBox *box, bool checked) +{ + if (box && box->isChecked() != checked) { + box->setChecked(checked); + } +} + +static QPushButton *smallButton(const QString &text) +{ + QPushButton *w = new QPushButton(text); + w->setFixedWidth(text == QStringLiteral("...") ? 24 : 34); + return w; +} + +static void disableUnavailable(QWidget *widget, const QString &reason) +{ + widget->setEnabled(false); + widget->setToolTip(reason); +} + +static void setComboItemEnabled(QComboBox *combo, int index, bool enabled, const QString &reason = QString()) +{ + if (!combo || index < 0 || index >= combo->count()) { + return; + } + if (QStandardItemModel *model = qobject_cast(combo->model())) { + if (QStandardItem *item = model->item(index)) { + item->setEnabled(enabled); + item->setToolTip(enabled ? QString() : reason); + } + } +} + +static QString winUaeQtInputEventDisplayName(const QString &configName) +{ + if (configName.isEmpty()) { + return QStringLiteral(""); + } + for (const WinUaeQtInputEventChoice &choice : inputEventChoices) { + if (configName.compare(QString::fromLatin1(choice.config), Qt::CaseInsensitive) == 0) { + return QString::fromLatin1(choice.display); + } + } + for (const WinUaeQtKeyboardChoice &choice : keyboardChoices) { + if (choice.defaultEvent[0] && configName.compare(QString::fromLatin1(choice.defaultEvent), Qt::CaseInsensitive) == 0) { + return QString::fromLatin1(choice.display); + } + } + return configName; +} + +static QStringList winUaeQtInputSlotFields(const QString &value) +{ + QStringList fields; + QString field; + bool quoted = false; + for (QChar ch : value) { + if (ch == QLatin1Char('\'')) { + quoted = !quoted; + } + if (ch == QLatin1Char(',') && !quoted) { + fields.append(field.trimmed()); + field.clear(); + continue; + } + field.append(ch); + } + if (!field.isEmpty() || value.endsWith(QLatin1Char(','))) { + fields.append(field.trimmed()); + } + return fields; +} + +static WinUaeQtInputSlot winUaeQtParseInputSlot(QString raw) +{ + WinUaeQtInputSlot slot; + raw = raw.trimmed(); + if (raw.isEmpty() || raw.compare(QStringLiteral("NULL"), Qt::CaseInsensitive) == 0) { + return slot; + } + + QString rest; + if (raw.startsWith(QLatin1Char('\''))) { + const int endQuote = raw.indexOf(QLatin1Char('\''), 1); + if (endQuote > 0) { + slot.custom = true; + slot.event = raw.mid(1, endQuote - 1); + rest = raw.mid(endQuote + 1); + } else { + slot.event = raw; + } + } else { + const int dot = raw.indexOf(QLatin1Char('.')); + if (dot >= 0) { + slot.event = raw.left(dot); + rest = raw.mid(dot); + } else { + slot.event = raw; + } + } + + if (rest.startsWith(QLatin1Char('.'))) { + rest.remove(0, 1); + int pos = 0; + while (pos < rest.size() && rest[pos].isDigit()) { + pos++; + } + if (pos > 0) { + slot.flags = rest.left(pos).toInt(); + rest.remove(0, pos); + } + if (rest.startsWith(QLatin1Char('.'))) { + int qpos = 1; + while (qpos < rest.size() && rest[qpos].isLetter()) { + qpos++; + } + if (qpos > 1) { + slot.qualifiers = rest.mid(1, qpos - 1); + rest.remove(0, qpos); + } + } + slot.suffix = rest; + } + return slot; +} + +static QString winUaeQtFormatInputSlot(const WinUaeQtInputSlot &slot) +{ + if (slot.event.trimmed().isEmpty()) { + return QStringLiteral("NULL"); + } + + QString text = slot.custom + ? QStringLiteral("'%1'").arg(slot.event) + : slot.event; + text += QStringLiteral(".%1").arg(slot.flags); + if (!slot.qualifiers.isEmpty()) { + text += QLatin1Char('.'); + text += slot.qualifiers; + } + text += slot.suffix; + return text; +} + +static bool winUaeQtIsInputDeviceConfigKey(const QString &key) +{ + const QStringList parts = key.split(QLatin1Char('.')); + if (parts.size() < 5 || parts.value(0) != QStringLiteral("input")) { + return false; + } + bool ok = false; + const int config = parts.value(1).toInt(&ok); + if (!ok || config < 1 || config > GamePortsInputConfigLine) { + return false; + } + const QString type = parts.value(2); + if (type != QStringLiteral("joystick") && type != QStringLiteral("mouse") + && type != QStringLiteral("keyboard") && type != QStringLiteral("internal")) { + return false; + } + parts.value(3).toInt(&ok); + if (!ok) { + return false; + } + const QString field = parts.value(4); + return field == QStringLiteral("axis") + || field == QStringLiteral("button") + || field == QStringLiteral("friendlyname") + || field == QStringLiteral("name") + || field == QStringLiteral("empty") + || field == QStringLiteral("disabled") + || field == QStringLiteral("custom"); +} + +static bool winUaeQtIsInputWidgetMappingKey(const QString &key) +{ + const QStringList parts = key.split(QLatin1Char('.')); + return winUaeQtIsInputDeviceConfigKey(key) + && (parts.value(4) == QStringLiteral("axis") || parts.value(4) == QStringLiteral("button")); +} + +static QString winUaeQtSwapInputEventPorts(QString event) +{ + static const QPair pairs[] = { + { QStringLiteral("JOY1_"), QStringLiteral("JOY2_") }, + { QStringLiteral("MOUSE1_"), QStringLiteral("MOUSE2_") }, + { QStringLiteral("PAR_JOY1_"), QStringLiteral("PAR_JOY2_") } + }; + for (const auto &pair : pairs) { + if (event.startsWith(pair.first)) { + return pair.second + event.mid(pair.first.size()); + } + if (event.startsWith(pair.second)) { + return pair.first + event.mid(pair.second.size()); + } + } + return event; +} + +#ifdef UAE_UNIX_WITH_SDL3 +enum class UnixQtInputWidgetKind { + Axis, + Button, + GamepadDpadX, + GamepadDpadY, + JoystickHatX, + JoystickHatY +}; + +struct UnixQtInputTestWidget { + QString name; + UnixQtInputWidgetKind kind = UnixQtInputWidgetKind::Axis; + int code = 0; + int mappingIndex = 0; + QChar mappingType = QLatin1Char('a'); + int state = 0; + QTreeWidgetItem *item = nullptr; +}; + +struct UnixQtInputTestDevice { + QString name; + QString uniqueName; + SDL_JoystickID instanceId = 0; + SDL_Gamepad *gamepad = nullptr; + SDL_Joystick *joystick = nullptr; + QVector widgets; +}; + +static QString sdlGamepadAxisName(SDL_GamepadAxis axis) +{ + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + return QStringLiteral("Left X Axis"); + case SDL_GAMEPAD_AXIS_LEFTY: + return QStringLiteral("Left Y Axis"); + case SDL_GAMEPAD_AXIS_RIGHTX: + return QStringLiteral("Right X Axis"); + case SDL_GAMEPAD_AXIS_RIGHTY: + return QStringLiteral("Right Y Axis"); + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + return QStringLiteral("Left Trigger"); + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + return QStringLiteral("Right Trigger"); + default: + return QStringLiteral("Axis %1").arg(int(axis) + 1); + } +} + +static QString sdlGamepadButtonName(SDL_GamepadButton button) +{ + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return QStringLiteral("South Button"); + case SDL_GAMEPAD_BUTTON_EAST: + return QStringLiteral("East Button"); + case SDL_GAMEPAD_BUTTON_WEST: + return QStringLiteral("West Button"); + case SDL_GAMEPAD_BUTTON_NORTH: + return QStringLiteral("North Button"); + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return QStringLiteral("Left Shoulder"); + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return QStringLiteral("Right Shoulder"); + case SDL_GAMEPAD_BUTTON_START: + return QStringLiteral("Start Button"); + case SDL_GAMEPAD_BUTTON_BACK: + return QStringLiteral("Back Button"); + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return QStringLiteral("Left Stick Button"); + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return QStringLiteral("Right Stick Button"); + case SDL_GAMEPAD_BUTTON_GUIDE: + return QStringLiteral("Guide Button"); + case SDL_GAMEPAD_BUTTON_MISC1: + return QStringLiteral("Misc Button 1"); + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: + return QStringLiteral("Right Paddle 1"); + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: + return QStringLiteral("Left Paddle 1"); + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: + return QStringLiteral("Right Paddle 2"); + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: + return QStringLiteral("Left Paddle 2"); + case SDL_GAMEPAD_BUTTON_TOUCHPAD: + return QStringLiteral("Touchpad Button"); + case SDL_GAMEPAD_BUTTON_MISC2: + return QStringLiteral("Misc Button 2"); + case SDL_GAMEPAD_BUTTON_MISC3: + return QStringLiteral("Misc Button 3"); + case SDL_GAMEPAD_BUTTON_MISC4: + return QStringLiteral("Misc Button 4"); + case SDL_GAMEPAD_BUTTON_MISC5: + return QStringLiteral("Misc Button 5"); + case SDL_GAMEPAD_BUTTON_MISC6: + return QStringLiteral("Misc Button 6"); + default: + return QStringLiteral("Button %1").arg(int(button) + 1); + } +} + +static bool unixQtInputWidgetActive(UnixQtInputWidgetKind kind, int state) +{ + if (kind == UnixQtInputWidgetKind::Button) { + return state != 0; + } + if (kind == UnixQtInputWidgetKind::GamepadDpadX || kind == UnixQtInputWidgetKind::GamepadDpadY || + kind == UnixQtInputWidgetKind::JoystickHatX || kind == UnixQtInputWidgetKind::JoystickHatY) { + return state != 0; + } + return qAbs(state) > 8000; +} +#endif + +class UnixQtInputMapDialog final : public QDialog { +public: + explicit UnixQtInputMapDialog(int port, const QString &context, const QString &customConfig, QWidget *parent = nullptr, bool captureSingle = false) + : QDialog(parent), + port(port), + contextText(context), + singleCapture(captureSingle), + customMappings(customConfig.split(QLatin1Char(' '), Qt::SkipEmptyParts)) + { + setWindowTitle(QStringLiteral("Input Remap")); + resize(640, 440); + setMinimumSize(560, 360); + + list = new QTreeWidget; + list->setRootIsDecorated(false); + list->setAlternatingRowColors(true); + list->setSelectionMode(QAbstractItemView::SingleSelection); + list->setHeaderLabels({ + QStringLiteral("Host widget"), + QStringLiteral("Device"), + QStringLiteral("Type"), + QStringLiteral("State") + }); + list->header()->setStretchLastSection(false); + list->header()->setSectionResizeMode(0, QHeaderView::Stretch); + list->header()->setSectionResizeMode(1, QHeaderView::Stretch); + list->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + list->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + + inputLine = new QLineEdit; + inputLine->setReadOnly(true); + inputLine->setEnabled(false); + mappingLine = new QPlainTextEdit; + mappingLine->setReadOnly(true); + mappingLine->setEnabled(false); + mappingLine->setFixedHeight(48); + + addEvent = new QComboBox; + populateEventChoices(); + addEventButton = new QPushButton(QStringLiteral("Add Event")); + autofireButton = new QPushButton(QStringLiteral("Autofire")); + testButton = new QPushButton(QStringLiteral("Test")); + remapButton = new QPushButton(QStringLiteral("Remap")); + deleteButton = new QPushButton(QStringLiteral("Delete")); + deleteAllButton = new QPushButton(QStringLiteral("Delete all")); + QPushButton *exitButton = new QPushButton(QStringLiteral("Exit")); + + if (port < 0) { + disableUnavailable(addEvent, QStringLiteral("Custom game-port mappings are only available from the Game Ports page.")); + disableUnavailable(addEventButton, QStringLiteral("Custom game-port mappings are only available from the Game Ports page.")); + disableUnavailable(autofireButton, QStringLiteral("Custom game-port mappings are only available from the Game Ports page.")); + disableUnavailable(remapButton, QStringLiteral("Custom game-port mappings are only available from the Game Ports page.")); + disableUnavailable(deleteButton, QStringLiteral("Custom game-port mappings are only available from the Game Ports page.")); + disableUnavailable(deleteAllButton, QStringLiteral("Custom game-port mappings are only available from the Game Ports page.")); + } + + QHBoxLayout *addRow = new QHBoxLayout; + addRow->setContentsMargins(0, 0, 0, 0); + addRow->addWidget(addEvent, 1); + addRow->addWidget(addEventButton); + addRow->addWidget(autofireButton); + + QHBoxLayout *buttonRow = new QHBoxLayout; + buttonRow->setContentsMargins(0, 0, 0, 0); + buttonRow->addWidget(testButton); + buttonRow->addWidget(remapButton); + buttonRow->addWidget(deleteButton); + buttonRow->addWidget(deleteAllButton); + buttonRow->addWidget(exitButton); + + QVBoxLayout *root = new QVBoxLayout(this); + root->setContentsMargins(6, 6, 6, 6); + root->setSpacing(5); + root->addWidget(list, 1); + root->addWidget(inputLine); + root->addWidget(mappingLine); + root->addLayout(addRow); + root->addLayout(buttonRow); + + timer = new QTimer(this); + timer->setInterval(30); + connect(timer, &QTimer::timeout, this, [this]() { pollInput(); }); + connect(testButton, &QPushButton::clicked, this, [this]() { setTesting(!testing); }); + connect(addEventButton, &QPushButton::clicked, this, [this]() { addSelectedMapping(); }); + connect(autofireButton, &QPushButton::clicked, this, [this]() { toggleSelectedMappingAutofire(); }); + connect(remapButton, &QPushButton::clicked, this, [this]() { toggleSequentialRemap(); }); + connect(deleteButton, &QPushButton::clicked, this, [this]() { deleteSelectedMapping(); }); + connect(deleteAllButton, &QPushButton::clicked, this, [this]() { + remapping = false; + customMappings.clear(); + changed = true; + updateMappingLine(); + updateActionState(); + }); + connect(exitButton, &QPushButton::clicked, this, &QDialog::accept); + connect(list, &QTreeWidget::currentItemChanged, this, [this](QTreeWidgetItem *, QTreeWidgetItem *) { + updateActionState(); + }); + + loadDevices(); + populateList(); + updateMappingLine(); + updateActionState(); + if (list->topLevelItemCount() == 0) { + QTreeWidgetItem *item = new QTreeWidgetItem(list, { + QStringLiteral(""), + QStringLiteral("SDL3"), + QStringLiteral("Input"), + QString() + }); + item->setDisabled(true); + inputLine->setText(QStringLiteral("No SDL3 joystick/gamepad devices detected.")); + } else if (singleCapture) { + setTesting(true); + inputLine->setText(contextText); + } + } + + ~UnixQtInputMapDialog() override + { + closeDevices(); + } + + bool hasChanges() const + { + return changed; + } + + QString customConfig() const + { + return customMappings.join(QLatin1Char(' ')); + } + + bool hasCapturedInput() const + { + return capturedDeviceIndex >= 0 && capturedWidgetIndex >= 0; + } + + int capturedDevice() const + { + return capturedDeviceIndex; + } + + int capturedWidget() const + { + return capturedWidgetIndex; + } + + QChar capturedWidgetType() const + { + return capturedMappingType; + } + + int capturedWidgetMappingIndex() const + { + return capturedMappingIndex; + } + + QString capturedDeviceDisplayName() const + { + return capturedDeviceName; + } + + QString capturedDeviceConfigName() const + { + return capturedDeviceUniqueName; + } + + QString capturedWidgetDisplayName() const + { + return capturedWidgetName; + } + +private: + int port = -1; + QString contextText; + QTreeWidget *list = nullptr; + QLineEdit *inputLine = nullptr; + QPlainTextEdit *mappingLine = nullptr; + QComboBox *addEvent = nullptr; + QPushButton *addEventButton = nullptr; + QPushButton *autofireButton = nullptr; + QPushButton *remapButton = nullptr; + QPushButton *deleteButton = nullptr; + QPushButton *deleteAllButton = nullptr; + QPushButton *testButton = nullptr; + QTimer *timer = nullptr; + bool testing = false; + bool changed = false; + bool remapping = false; + bool remapWaitingForRelease = false; + bool singleCapture = false; + int capturedDeviceIndex = -1; + int capturedWidgetIndex = -1; + QChar capturedMappingType = QLatin1Char('a'); + int capturedMappingIndex = 0; + QString capturedDeviceName; + QString capturedDeviceUniqueName; + QString capturedWidgetName; + int remapEventIndex = -1; + QStringList customMappings; + +#ifdef UAE_UNIX_WITH_SDL3 + QVector devices; + + void addGamepad(SDL_JoystickID instanceId) + { + SDL_Gamepad *gamepad = SDL_OpenGamepad(instanceId); + if (!gamepad) { + return; + } + + UnixQtInputTestDevice device; + device.instanceId = instanceId; + device.gamepad = gamepad; + const char *name = SDL_GetGamepadName(gamepad); + device.name = QString::fromUtf8(name && name[0] ? name : "SDL Gamepad"); + char guid[64]; + SDL_GUIDToString(SDL_GetJoystickGUIDForID(instanceId), guid, sizeof guid); + device.uniqueName = QStringLiteral("unix.gamepad.%1.%2").arg(QString::fromLatin1(guid)).arg(devices.size()); + + const SDL_GamepadAxis axes[] = { + SDL_GAMEPAD_AXIS_LEFTX, + SDL_GAMEPAD_AXIS_LEFTY, + SDL_GAMEPAD_AXIS_RIGHTX, + SDL_GAMEPAD_AXIS_RIGHTY, + SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER + }; + int axisSlot = 0; + for (SDL_GamepadAxis axis : axes) { + if (SDL_GamepadHasAxis(gamepad, axis)) { + UnixQtInputTestWidget widget; + widget.name = sdlGamepadAxisName(axis); + widget.kind = UnixQtInputWidgetKind::Axis; + widget.code = int(axis); + widget.mappingIndex = axisSlot++; + widget.mappingType = QLatin1Char('a'); + device.widgets.append(widget); + } + } + + UnixQtInputTestWidget dpadX; + dpadX.name = QStringLiteral("DPad X Axis"); + dpadX.kind = UnixQtInputWidgetKind::GamepadDpadX; + dpadX.mappingIndex = axisSlot++; + dpadX.mappingType = QLatin1Char('a'); + device.widgets.append(dpadX); + UnixQtInputTestWidget dpadY; + dpadY.name = QStringLiteral("DPad Y Axis"); + dpadY.kind = UnixQtInputWidgetKind::GamepadDpadY; + dpadY.mappingIndex = axisSlot++; + dpadY.mappingType = QLatin1Char('a'); + device.widgets.append(dpadY); + + const SDL_GamepadButton buttons[] = { + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_START, + SDL_GAMEPAD_BUTTON_BACK, + SDL_GAMEPAD_BUTTON_LEFT_STICK, + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + SDL_GAMEPAD_BUTTON_GUIDE, + SDL_GAMEPAD_BUTTON_MISC1, + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, + SDL_GAMEPAD_BUTTON_TOUCHPAD, + SDL_GAMEPAD_BUTTON_MISC2, + SDL_GAMEPAD_BUTTON_MISC3, + SDL_GAMEPAD_BUTTON_MISC4, + SDL_GAMEPAD_BUTTON_MISC5, + SDL_GAMEPAD_BUTTON_MISC6 + }; + int buttonSlot = 0; + for (SDL_GamepadButton button : buttons) { + if (SDL_GamepadHasButton(gamepad, button)) { + UnixQtInputTestWidget widget; + widget.name = sdlGamepadButtonName(button); + widget.kind = UnixQtInputWidgetKind::Button; + widget.code = int(button); + widget.mappingIndex = buttonSlot++; + widget.mappingType = QLatin1Char('b'); + device.widgets.append(widget); + } + } + + devices.append(device); + } + + void addJoystick(SDL_JoystickID instanceId) + { + if (SDL_IsGamepad(instanceId)) { + return; + } + SDL_Joystick *joystick = SDL_OpenJoystick(instanceId); + if (!joystick) { + return; + } + + UnixQtInputTestDevice device; + device.instanceId = instanceId; + device.joystick = joystick; + const char *name = SDL_GetJoystickName(joystick); + device.name = QString::fromUtf8(name && name[0] ? name : "SDL Joystick"); + char guid[64]; + SDL_GUIDToString(SDL_GetJoystickGUIDForID(instanceId), guid, sizeof guid); + device.uniqueName = QStringLiteral("unix.joystick.%1.%2").arg(QString::fromLatin1(guid)).arg(devices.size()); + + const int axisCount = qMax(0, SDL_GetNumJoystickAxes(joystick)); + for (int i = 0; i < axisCount; i++) { + UnixQtInputTestWidget widget; + widget.name = QStringLiteral("Axis %1").arg(i + 1); + widget.kind = UnixQtInputWidgetKind::Axis; + widget.code = i; + widget.mappingIndex = i; + widget.mappingType = QLatin1Char('a'); + device.widgets.append(widget); + } + + const int hatCount = qMax(0, SDL_GetNumJoystickHats(joystick)); + int axisSlot = axisCount; + for (int i = 0; i < hatCount; i++) { + UnixQtInputTestWidget hatX; + hatX.name = QStringLiteral("Hat %1 X Axis").arg(i + 1); + hatX.kind = UnixQtInputWidgetKind::JoystickHatX; + hatX.code = i; + hatX.mappingIndex = axisSlot++; + hatX.mappingType = QLatin1Char('a'); + device.widgets.append(hatX); + UnixQtInputTestWidget hatY; + hatY.name = QStringLiteral("Hat %1 Y Axis").arg(i + 1); + hatY.kind = UnixQtInputWidgetKind::JoystickHatY; + hatY.code = i; + hatY.mappingIndex = axisSlot++; + hatY.mappingType = QLatin1Char('a'); + device.widgets.append(hatY); + } + + const int buttonCount = qMax(0, SDL_GetNumJoystickButtons(joystick)); + for (int i = 0; i < buttonCount; i++) { + UnixQtInputTestWidget widget; + widget.name = QStringLiteral("Button %1").arg(i + 1); + widget.kind = UnixQtInputWidgetKind::Button; + widget.code = i; + widget.mappingIndex = i; + widget.mappingType = QLatin1Char('b'); + device.widgets.append(widget); + } + + devices.append(device); + } + + void loadDevices() + { + SDL_SetMainReady(); + if (!SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { + inputLine->setText(QStringLiteral("SDL3 input backend is unavailable: %1").arg(QString::fromUtf8(SDL_GetError()))); + return; + } + + int count = 0; + SDL_JoystickID *ids = SDL_GetGamepads(&count); + if (ids) { + for (int i = 0; i < count; i++) { + addGamepad(ids[i]); + } + SDL_free(ids); + } + + count = 0; + ids = SDL_GetJoysticks(&count); + if (ids) { + for (int i = 0; i < count; i++) { + addJoystick(ids[i]); + } + SDL_free(ids); + } + } + + void closeDevices() + { + for (UnixQtInputTestDevice &device : devices) { + if (device.gamepad) { + SDL_CloseGamepad(device.gamepad); + device.gamepad = nullptr; + } + if (device.joystick) { + SDL_CloseJoystick(device.joystick); + device.joystick = nullptr; + } + } + } + + int readWidgetState(const UnixQtInputTestDevice &device, const UnixQtInputTestWidget &widget) const + { + if (device.gamepad) { + switch (widget.kind) { + case UnixQtInputWidgetKind::Axis: + return SDL_GetGamepadAxis(device.gamepad, SDL_GamepadAxis(widget.code)); + case UnixQtInputWidgetKind::Button: + return SDL_GetGamepadButton(device.gamepad, SDL_GamepadButton(widget.code)) ? 1 : 0; + case UnixQtInputWidgetKind::GamepadDpadX: + return (SDL_GetGamepadButton(device.gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT) ? 1 : 0) - + (SDL_GetGamepadButton(device.gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT) ? 1 : 0); + case UnixQtInputWidgetKind::GamepadDpadY: + return (SDL_GetGamepadButton(device.gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN) ? 1 : 0) - + (SDL_GetGamepadButton(device.gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP) ? 1 : 0); + case UnixQtInputWidgetKind::JoystickHatX: + case UnixQtInputWidgetKind::JoystickHatY: + return 0; + } + } + if (device.joystick) { + switch (widget.kind) { + case UnixQtInputWidgetKind::Axis: + return SDL_GetJoystickAxis(device.joystick, widget.code); + case UnixQtInputWidgetKind::Button: + return SDL_GetJoystickButton(device.joystick, widget.code) ? 1 : 0; + case UnixQtInputWidgetKind::JoystickHatX: + { + const Uint8 hat = SDL_GetJoystickHat(device.joystick, widget.code); + return ((hat & SDL_HAT_RIGHT) ? 1 : 0) - ((hat & SDL_HAT_LEFT) ? 1 : 0); + } + case UnixQtInputWidgetKind::JoystickHatY: + { + const Uint8 hat = SDL_GetJoystickHat(device.joystick, widget.code); + return ((hat & SDL_HAT_DOWN) ? 1 : 0) - ((hat & SDL_HAT_UP) ? 1 : 0); + } + case UnixQtInputWidgetKind::GamepadDpadX: + case UnixQtInputWidgetKind::GamepadDpadY: + return 0; + } + } + return 0; + } +#else + void loadDevices() + { + inputLine->setText(QStringLiteral("SDL3 input backend is not compiled into this build.")); + } + + void closeDevices() + { + } +#endif + + void populateList() + { +#ifdef UAE_UNIX_WITH_SDL3 + for (int deviceIndex = 0; deviceIndex < devices.size(); deviceIndex++) { + UnixQtInputTestDevice &device = devices[deviceIndex]; + for (int widgetIndex = 0; widgetIndex < device.widgets.size(); widgetIndex++) { + UnixQtInputTestWidget &widget = device.widgets[widgetIndex]; + const QString type = widget.kind == UnixQtInputWidgetKind::Button ? QStringLiteral("Button") : QStringLiteral("Axis"); + widget.item = new QTreeWidgetItem(list, { + widget.name, + device.name, + type, + QStringLiteral("0") + }); + widget.item->setData(0, Qt::UserRole, deviceIndex); + widget.item->setData(0, Qt::UserRole + 1, widgetIndex); + } + } +#endif + } + + QString portEventPrefix() const + { + switch (port) { + case 0: + return QStringLiteral("JOY1"); + case 1: + return QStringLiteral("JOY2"); + case 2: + return QStringLiteral("PAR_JOY1"); + case 3: + return QStringLiteral("PAR_JOY2"); + default: + return QString(); + } + } + + void populateEventChoices() + { + const QString prefix = portEventPrefix(); + if (prefix.isEmpty()) { + addEvent->addItem(QStringLiteral("")); + return; + } + addEvent->addItem(QStringLiteral("Horizontal"), prefix + QStringLiteral("_HORIZ")); + addEvent->addItem(QStringLiteral("Vertical"), prefix + QStringLiteral("_VERT")); + addEvent->addItem(QStringLiteral("Up"), prefix + QStringLiteral("_UP")); + addEvent->addItem(QStringLiteral("Down"), prefix + QStringLiteral("_DOWN")); + addEvent->addItem(QStringLiteral("Left"), prefix + QStringLiteral("_LEFT")); + addEvent->addItem(QStringLiteral("Right"), prefix + QStringLiteral("_RIGHT")); + addEvent->addItem(QStringLiteral("Fire"), prefix + QStringLiteral("_FIRE_BUTTON")); + if (port < 2) { + addEvent->addItem(QStringLiteral("2nd Button"), prefix + QStringLiteral("_2ND_BUTTON")); + addEvent->addItem(QStringLiteral("3rd Button"), prefix + QStringLiteral("_3RD_BUTTON")); + } + } + + void updateMappingLine() + { + QStringList lines; + if (!contextText.isEmpty()) { + lines.append(contextText.split(QLatin1Char('\n'))); + } + if (!customMappings.isEmpty()) { + lines.append(customMappings); + } + mappingLine->setPlainText(lines.join(QLatin1Char('\n'))); + } + + void updateActionState() + { + if (port < 0) { + return; + } + const bool hasSelection = list->currentItem() && !list->currentItem()->isDisabled(); + const bool canEdit = hasSelection && addEvent->count() > 0 && !remapping; + addEvent->setEnabled(canEdit); + addEventButton->setEnabled(canEdit); + if (autofireButton) { + autofireButton->setEnabled(canEdit); +#ifdef UAE_UNIX_WITH_SDL3 + const int flags = selectedMappingFlags(); + if ((flags & (InputFlagInvertToggle | InputFlagAutofire)) == (InputFlagInvertToggle | InputFlagAutofire)) { + autofireButton->setText(QStringLiteral("Autofire: Toggle")); + } else if (flags & InputFlagAutofire) { + autofireButton->setText(QStringLiteral("Autofire: On")); + } else { + autofireButton->setText(QStringLiteral("Autofire")); + } +#else + autofireButton->setText(QStringLiteral("Autofire")); +#endif + } + if (deleteButton) { + deleteButton->setEnabled(canEdit && !customMappings.isEmpty()); + } + if (remapButton) { + remapButton->setEnabled(hasSelection && addEvent->count() > 0); + remapButton->setText(remapping ? QStringLiteral("Stop") : QStringLiteral("Remap")); + } + deleteAllButton->setEnabled(!remapping && !customMappings.isEmpty()); + } + +#ifdef UAE_UNIX_WITH_SDL3 + int selectedDeviceIndex() const + { + const int deviceIndex = list->currentItem()->data(0, Qt::UserRole).toInt(); + return deviceIndex >= 0 && deviceIndex < devices.size() ? deviceIndex : -1; + } + + int selectedWidgetIndex(int deviceIndex) const + { + const int widgetIndex = list->currentItem()->data(0, Qt::UserRole + 1).toInt(); + return deviceIndex >= 0 && deviceIndex < devices.size() && widgetIndex >= 0 && widgetIndex < devices[deviceIndex].widgets.size() + ? widgetIndex + : -1; + } + + QString mappingPrefix(int deviceIndex, const UnixQtInputTestWidget &widget) const + { + return QStringLiteral("j.%1.%2.%3") + .arg(deviceIndex) + .arg(widget.mappingType) + .arg(widget.mappingIndex); + } + + QString mappingFor(int deviceIndex, const UnixQtInputTestWidget &widget, const QString &event, int flags = 0) const + { + return QStringLiteral("%1.%2=%3").arg(mappingPrefix(deviceIndex, widget)).arg(flags).arg(event); + } + + static QString mappingLeft(const QString &mapping) + { + const int equals = mapping.indexOf(QLatin1Char('=')); + return equals >= 0 ? mapping.left(equals) : mapping; + } + + static QString mappingRight(const QString &mapping) + { + const int equals = mapping.indexOf(QLatin1Char('=')); + return equals >= 0 ? mapping.mid(equals + 1) : QString(); + } + + static int mappingFlags(const QString &left, const QString &prefix) + { + if (!left.startsWith(prefix + QLatin1Char('.'))) { + return 0; + } + QString suffix = left.mid(prefix.size() + 1); + const int dot = suffix.indexOf(QLatin1Char('.')); + if (dot >= 0) { + suffix = suffix.left(dot); + } + bool ok = false; + const int flags = suffix.toInt(&ok); + return ok ? flags : 0; + } + + static int toggledAutofireFlags(int flags) + { + if ((flags & (InputFlagInvertToggle | InputFlagAutofire)) == (InputFlagInvertToggle | InputFlagAutofire)) { + flags &= ~(InputFlagInvertToggle | InputFlagAutofire); + } else if (flags & InputFlagAutofire) { + flags |= InputFlagInvertToggle; + flags &= ~InputFlagToggle; + } else if (!(flags & (InputFlagInvertToggle | InputFlagAutofire))) { + flags |= InputFlagAutofire; + } else { + flags &= ~(InputFlagInvertToggle | InputFlagAutofire); + } + return flags; + } + + int removeMappings(const QString &prefix, const QString &event) + { + int removed = 0; + QStringList kept; + for (const QString &mapping : customMappings) { + const QString left = mappingLeft(mapping); + const QString right = mappingRight(mapping); + if (left.startsWith(prefix + QLatin1Char('.')) && (event.isEmpty() || right == event)) { + removed++; + } else { + kept.append(mapping); + } + } + customMappings = kept; + return removed; + } + + int selectedMappingFlags() const + { + if (port < 0 || !list->currentItem()) { + return 0; + } + const QString event = addEvent->currentData().toString(); + if (event.isEmpty()) { + return 0; + } + const int deviceIndex = selectedDeviceIndex(); + const int widgetIndex = selectedWidgetIndex(deviceIndex); + if (deviceIndex < 0 || widgetIndex < 0) { + return 0; + } + const QString prefix = mappingPrefix(deviceIndex, devices[deviceIndex].widgets[widgetIndex]); + for (const QString &mapping : customMappings) { + if (mappingRight(mapping) == event) { + const QString left = mappingLeft(mapping); + if (left.startsWith(prefix + QLatin1Char('.'))) { + return mappingFlags(left, prefix); + } + } + } + return 0; + } + + void toggleSelectedMappingAutofireSdl() + { + if (port < 0 || !list->currentItem() || remapping) { + return; + } + const QString event = addEvent->currentData().toString(); + if (event.isEmpty()) { + return; + } + const int deviceIndex = selectedDeviceIndex(); + const int widgetIndex = selectedWidgetIndex(deviceIndex); + if (deviceIndex < 0 || widgetIndex < 0) { + return; + } + const UnixQtInputTestWidget &widget = devices[deviceIndex].widgets[widgetIndex]; + const QString prefix = mappingPrefix(deviceIndex, widget); + const int flags = toggledAutofireFlags(selectedMappingFlags()); + removeMappings(prefix, event); + customMappings.append(mappingFor(deviceIndex, widget, event, flags)); + changed = true; + updateMappingLine(); + updateActionState(); + } + + int nextRemapEventIndex(int start) const + { + for (int i = qMax(0, start); i < addEvent->count(); i++) { + if (!addEvent->itemData(i).toString().isEmpty()) { + return i; + } + } + return -1; + } + + void setRemapPrompt() + { + if (remapping && remapEventIndex >= 0) { + inputLine->setText(QStringLiteral("Press input for %1.").arg(addEvent->itemText(remapEventIndex))); + } + } + + void finishSequentialRemap(const QString &message) + { + remapping = false; + remapWaitingForRelease = false; + remapEventIndex = -1; + if (testing) { + setTesting(false); + } + inputLine->setText(message); + updateActionState(); + } + + bool appendMappingFor(int deviceIndex, int widgetIndex, const QString &event) + { + if (deviceIndex < 0 || deviceIndex >= devices.size()) { + return false; + } + UnixQtInputTestDevice &device = devices[deviceIndex]; + if (widgetIndex < 0 || widgetIndex >= device.widgets.size()) { + return false; + } + const UnixQtInputTestWidget &widget = device.widgets[widgetIndex]; + removeMappings(mappingPrefix(deviceIndex, widget), event); + customMappings.append(mappingFor(deviceIndex, widget, event)); + changed = true; + updateMappingLine(); + updateActionState(); + return true; + } +#endif + + void toggleSelectedMappingAutofire() + { +#ifdef UAE_UNIX_WITH_SDL3 + toggleSelectedMappingAutofireSdl(); +#endif + } + + void addSelectedMapping() + { +#ifdef UAE_UNIX_WITH_SDL3 + if (port < 0 || !list->currentItem() || remapping) { + return; + } + const QString event = addEvent->currentData().toString(); + if (event.isEmpty()) { + return; + } + const int deviceIndex = selectedDeviceIndex(); + appendMappingFor(deviceIndex, selectedWidgetIndex(deviceIndex), event); +#endif + } + + void deleteSelectedMapping() + { +#ifdef UAE_UNIX_WITH_SDL3 + if (port < 0 || !list->currentItem() || remapping) { + return; + } + const int deviceIndex = selectedDeviceIndex(); + const int widgetIndex = selectedWidgetIndex(deviceIndex); + if (deviceIndex < 0 || widgetIndex < 0) { + return; + } + const QString event = addEvent->currentData().toString(); + const QString prefix = mappingPrefix(deviceIndex, devices[deviceIndex].widgets[widgetIndex]); + int removed = event.isEmpty() ? 0 : removeMappings(prefix, event); + if (!removed) { + removed = removeMappings(prefix, QString()); + } + if (removed) { + changed = true; + updateMappingLine(); + updateActionState(); + } +#endif + } + + void toggleSequentialRemap() + { +#ifdef UAE_UNIX_WITH_SDL3 + if (port < 0 || !list->currentItem()) { + return; + } + if (remapping) { + finishSequentialRemap(QStringLiteral("Remap canceled.")); + return; + } + remapEventIndex = nextRemapEventIndex(0); + if (remapEventIndex < 0) { + return; + } + customMappings.clear(); + changed = true; + remapping = true; + remapWaitingForRelease = false; + addEvent->setCurrentIndex(remapEventIndex); + updateMappingLine(); + updateActionState(); + if (!testing) { + setTesting(true); + } + setRemapPrompt(); +#endif + } + + void advanceSequentialRemap() + { +#ifdef UAE_UNIX_WITH_SDL3 + remapEventIndex = nextRemapEventIndex(remapEventIndex + 1); + if (remapEventIndex < 0) { + finishSequentialRemap(QStringLiteral("Remap complete.")); + return; + } + addEvent->setCurrentIndex(remapEventIndex); + setRemapPrompt(); + updateActionState(); +#endif + } + + void setTesting(bool enabled) + { + testing = enabled; + if (!testing && remapping) { + remapping = false; + remapWaitingForRelease = false; + remapEventIndex = -1; + } + testButton->setText(testing ? QStringLiteral("Stop") : QStringLiteral("Test")); + if (testing) { + timer->start(); + } else { + timer->stop(); + clearHighlights(); + } + updateActionState(); + } + + void clearHighlights() + { + for (int i = 0; i < list->topLevelItemCount(); i++) { + QTreeWidgetItem *item = list->topLevelItem(i); + for (int column = 0; column < list->columnCount(); column++) { + item->setBackground(column, QBrush()); + } + } + } + + void pollInput() + { +#ifdef UAE_UNIX_WITH_SDL3 + SDL_UpdateGamepads(); + SDL_UpdateJoysticks(); + bool foundActive = false; + QTreeWidgetItem *activeItem = nullptr; + const QColor activeColor = palette().color(QPalette::Highlight).lighter(170); + + for (UnixQtInputTestDevice &device : devices) { + for (UnixQtInputTestWidget &widget : device.widgets) { + const int state = readWidgetState(device, widget); + if (widget.item) { + widget.item->setText(3, QString::number(state)); + const bool active = unixQtInputWidgetActive(widget.kind, state); + for (int column = 0; column < list->columnCount(); column++) { + widget.item->setBackground(column, active ? QBrush(activeColor) : QBrush()); + } + if (active && !foundActive) { + foundActive = true; + activeItem = widget.item; + inputLine->setText(QStringLiteral("%1, %2").arg(widget.name, device.name)); + } + } + widget.state = state; + } + } + if (activeItem) { + list->setCurrentItem(activeItem); + list->scrollToItem(activeItem, QAbstractItemView::PositionAtCenter); + const int deviceIndex = activeItem->data(0, Qt::UserRole).toInt(); + const int widgetIndex = activeItem->data(0, Qt::UserRole + 1).toInt(); + if (singleCapture && deviceIndex >= 0 && deviceIndex < devices.size() + && widgetIndex >= 0 && widgetIndex < devices[deviceIndex].widgets.size()) { + const UnixQtInputTestWidget &widget = devices[deviceIndex].widgets[widgetIndex]; + capturedDeviceIndex = deviceIndex; + capturedWidgetIndex = widgetIndex; + capturedMappingType = widget.mappingType; + capturedMappingIndex = widget.mappingIndex; + capturedDeviceName = devices[deviceIndex].name; + capturedDeviceUniqueName = devices[deviceIndex].uniqueName; + capturedWidgetName = widget.name; + changed = true; + accept(); + return; + } + if (remapping && !remapWaitingForRelease && remapEventIndex >= 0) { + const QString event = addEvent->itemData(remapEventIndex).toString(); + if (!event.isEmpty() && appendMappingFor(deviceIndex, widgetIndex, event)) { + remapWaitingForRelease = true; + advanceSequentialRemap(); + } + } + } else if (remapping) { + remapWaitingForRelease = false; + setRemapPrompt(); + } +#endif + } +}; + +static QGroupBox *groupBox(const QString &title, QLayout *layout) +{ + QGroupBox *box = new QGroupBox(title); + QFont titleFont = box->font(); + titleFont.setPixelSize(13); + box->setFont(titleFont); + layout->setContentsMargins(6, 8, 6, 6); + if (QGridLayout *grid = dynamic_cast(layout)) { + grid->setHorizontalSpacing(qMax(grid->horizontalSpacing(), 8)); + grid->setVerticalSpacing(qMax(grid->verticalSpacing(), 5)); + } else { + layout->setSpacing(qMax(layout->spacing(), 5)); + } + box->setLayout(layout); + return box; +} + +static void setPathComboText(QComboBox *field, const QString &path) +{ + if (!field) { + return; + } + if (!path.isEmpty() && field->findText(path) < 0) { + field->insertItem(0, path); + } + field->setCurrentText(path); +} + +static QString envString(const char *name) +{ + return winUaeQtEnvString(name); +} + +static QString unixDefaultDataPath() +{ + return winUaeQtDefaultDataPath(); +} + +static QString unixDefaultDataSubPath(const QString &name) +{ + return winUaeQtDefaultDataSubPath(name); +} + +static QString expandUnixPath(QString path) +{ + return winUaeQtExpandUnixPath(path); +} + +static QString expandedPathText(const QString &path) +{ + return winUaeQtExpandedPathText(path); +} + +static QString fileDialogInitialPath(const QString &path) +{ + return winUaeQtFileDialogInitialPath(path); +} + +static QString fileDialogInitialDirectory(const QString &path) +{ + return winUaeQtFileDialogInitialDirectory(path); +} + +static QString fileDialogInitialSavePath(const QString &path, const QString &fallbackName) +{ + return winUaeQtFileDialogInitialSavePath(path, fallbackName); +} + +static void addBrowse(QComboBox *field, QWidget *parent, const QString &caption, const QString &filter) +{ + const QString path = QFileDialog::getOpenFileName(parent, caption, fileDialogInitialPath(field->currentText()), filter); + if (!path.isEmpty()) { + setPathComboText(field, path); + } +} + +static bool isConfigPath(const QString &path) +{ + return path.endsWith(QStringLiteral(".uae"), Qt::CaseInsensitive); +} + +static QString initialConfigPathFromArguments(const QStringList &arguments) +{ + for (int i = 1; i < arguments.size(); i++) { + const QString arg = arguments[i]; + if ((arg == QStringLiteral("-f") + || arg == QStringLiteral("-config") + || arg == QStringLiteral("--config")) && i + 1 < arguments.size()) { + const QString path = expandedPathText(arguments[i + 1]); + if (isConfigPath(path)) { + return path; + } + i++; + continue; + } + const QString configPrefix = QStringLiteral("-config="); + const QString longConfigPrefix = QStringLiteral("--config="); + if (arg.startsWith(configPrefix) || arg.startsWith(longConfigPrefix)) { + const int offset = arg.startsWith(configPrefix) ? configPrefix.size() : longConfigPrefix.size(); + const QString path = expandedPathText(arg.mid(offset)); + if (isConfigPath(path)) { + return path; + } + continue; + } + const QString path = expandedPathText(arg); + if (!arg.startsWith(QLatin1Char('-')) && isConfigPath(path)) { + return path; + } + } + return QString(); +} + +static int &prepareQtApplicationArguments(int &argc) +{ +#if defined(__linux__) + QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs); +#endif + return argc; +} + +class WinUaeQtApplication final : public QApplication { +public: + using ConfigOpenHandler = std::function; + + WinUaeQtApplication(int &argc, char **argv) + : QApplication(prepareQtApplicationArguments(argc), argv) + { + } + + void setConfigOpenHandler(ConfigOpenHandler handler) + { + configOpenHandler = std::move(handler); + if (!pendingConfigPath.isEmpty() && configOpenHandler) { + const QString path = pendingConfigPath; + pendingConfigPath.clear(); + configOpenHandler(path); + } + } + +protected: + bool event(QEvent *event) override + { + if (event->type() == QEvent::FileOpen) { + const QFileOpenEvent *openEvent = static_cast(event); + QString path = openEvent->file(); + if (path.isEmpty() && openEvent->url().isLocalFile()) { + path = openEvent->url().toLocalFile(); + } + if (isConfigPath(path)) { + if (configOpenHandler) { + configOpenHandler(path); + } else { + pendingConfigPath = path; + } + return true; + } + } + return QApplication::event(event); + } + +private: + QString pendingConfigPath; + ConfigOpenHandler configOpenHandler; +}; + +class WinUaeQtDialog final : public QDialog { +public: + explicit WinUaeQtDialog( + QWidget *parent = nullptr, + const QString &initialConfigPath = QString(), + const WinUaeQtHardwareInfoProvider &hardwareInfoProvider = WinUaeQtHardwareInfoProvider()) + : QDialog(parent), + hardwareProvider(hardwareInfoProvider), + boardCatalog(hardwareInfoProvider.boardCatalog + ? hardwareInfoProvider.boardCatalog(hardwareInfoProvider.context) + : WinUaeQtBoardCatalog()) + { + setWindowTitle(QStringLiteral("WinUAE Properties")); + setWindowIcon(resourceIcon(QStringLiteral("winuae.ico"))); + resize(880, 640); + setMinimumSize(820, 600); + + navigation = new QTreeWidget; + navigation->setHeaderHidden(true); + navigation->setRootIsDecorated(true); + navigation->setIndentation(12); + navigation->setIconSize(QSize(16, 16)); + navigation->setFixedWidth(166); + + pageStack = new QStackedWidget; + pageStack->setObjectName(QStringLiteral("pageStack")); + + QTreeWidgetItem *settingsFolder = addFolder(QStringLiteral("Settings")); + addPage(QStringLiteral("About"), QStringLiteral("amigainfo.ico"), makeAboutPage(), settingsFolder); + addPage(QStringLiteral("Paths"), QStringLiteral("paths.ico"), makePathsPage(), settingsFolder); + QTreeWidgetItem *quickstartPage = addPage(QStringLiteral("Quickstart"), QStringLiteral("quickstart.ico"), makeQuickstartPage(), settingsFolder); + addPage(QStringLiteral("Configurations"), QStringLiteral("configfile.ico"), makeConfigurationsPage(), settingsFolder); + addPage(QStringLiteral("Frontend"), QStringLiteral("quickstart.ico"), makeFrontendPage(), settingsFolder); + + QTreeWidgetItem *hardwareFolder = addFolder(QStringLiteral("Hardware"), settingsFolder); + addPage(QStringLiteral("CPU and FPU"), QStringLiteral("cpu.ico"), makeCpuPage(), hardwareFolder); + addPage(QStringLiteral("Chipset"), QStringLiteral("chip.ico"), makeChipsetPage(), hardwareFolder); + addPage(QStringLiteral("Adv. Chipset"), QStringLiteral("chip.ico"), makeAdvancedChipsetPage(), hardwareFolder); + addPage(QStringLiteral("ROM"), QStringLiteral("chip.ico"), makeRomPage(), hardwareFolder); + addPage(QStringLiteral("RAM"), QStringLiteral("chip.ico"), makeMemoryPage(), hardwareFolder); + addPage(QStringLiteral("Floppy drives"), QStringLiteral("35floppy.ico"), makeFloppyPage(), hardwareFolder); + addPage(QStringLiteral("CD & Hard drives"), QStringLiteral("drive.ico"), makeHardDrivesPage(), hardwareFolder); + addPage(QStringLiteral("Expansions"), QStringLiteral("expansion.ico"), makeExpansionsPage(), hardwareFolder); + addPage(QStringLiteral("RTG board"), QStringLiteral("expansion.ico"), makeExpansionPage(), hardwareFolder); + addPage(QStringLiteral("Hardware info"), QStringLiteral("expansion.ico"), makeHardwareInfoPage(), hardwareFolder); + + QTreeWidgetItem *hostFolder = addFolder(QStringLiteral("Host"), settingsFolder); + addPage(QStringLiteral("Display"), QStringLiteral("screen.ico"), makeDisplayPage(), hostFolder); + addPage(QStringLiteral("Sound"), QStringLiteral("sound.ico"), makeSoundPage(), hostFolder); + addPage(QStringLiteral("Game ports"), QStringLiteral("joystick.ico"), makeGamePortsPage(), hostFolder); + addPage(QStringLiteral("IO ports"), QStringLiteral("port.ico"), makeIoPortsPage(), hostFolder); + addPage(QStringLiteral("Input"), QStringLiteral("port.ico"), makeInputPage(), hostFolder); + addPage(QStringLiteral("Output"), QStringLiteral("avioutput.ico"), makeOutputPage(), hostFolder); + addPage(QStringLiteral("Filter"), QStringLiteral("screen.ico"), makeFilterPage(), hostFolder); + addPage(QStringLiteral("Disk Swapper"), QStringLiteral("diskimage.ico"), makeDiskSwapperPage(), hostFolder); + addPage(QStringLiteral("Miscellaneous"), QStringLiteral("misc.ico"), makeMiscPage(), hostFolder); + addPage(QStringLiteral("Pri. & Extensions"), QStringLiteral("misc.ico"), makeExtensionsPage(), hostFolder); + navigation->expandAll(); + + connect(navigation, &QTreeWidget::currentItemChanged, this, [this](QTreeWidgetItem *item) { + if (!item) { + return; + } + const QVariant pageIndex = item->data(0, Qt::UserRole); + if (!pageIndex.isValid()) { + return; + } + pageStack->setCurrentIndex(pageIndex.toInt()); + if (item->text(0) == QStringLiteral("Hardware info")) { + refreshHardwareInfoPage(); + } else if (item->text(0) == QStringLiteral("Frontend")) { + refreshFrontendPage(); + } + }); + + QFrame *outerFrame = new QFrame; + outerFrame->setFrameShape(QFrame::Box); + outerFrame->setObjectName(QStringLiteral("outerFrame")); + QVBoxLayout *frameLayout = new QVBoxLayout(outerFrame); + frameLayout->setContentsMargins(4, 4, 4, 4); + frameLayout->addWidget(pageStack); + + QHBoxLayout *content = new QHBoxLayout; + content->setContentsMargins(0, 0, 0, 0); + content->setSpacing(5); + content->addWidget(navigation); + content->addWidget(outerFrame, 1); + + QPushButton *reset = new QPushButton(QStringLiteral("Reset")); + QPushButton *quit = new QPushButton(QStringLiteral("Quit")); + QPushButton *restart = new QPushButton(QStringLiteral("Restart")); + QPushButton *errorLog = new QPushButton(QStringLiteral("Error log")); + QPushButton *start = new QPushButton(QStringLiteral("Start")); + QPushButton *cancel = new QPushButton(QStringLiteral("Cancel")); + QPushButton *help = new QPushButton(QStringLiteral("Help")); + restart->setVisible(false); + errorLog->setVisible(false); + start->setDefault(true); + + connect(reset, &QPushButton::clicked, this, [this]() { resetDefaults(); }); + connect(quit, &QPushButton::clicked, this, &QDialog::reject); + connect(cancel, &QPushButton::clicked, this, &QDialog::reject); + connect(start, &QPushButton::clicked, this, [this]() { startEmulator(); }); + connect(help, &QPushButton::clicked, this, [this]() { openHelp(); }); + + QHBoxLayout *buttons = new QHBoxLayout; + buttons->setContentsMargins(0, 0, 0, 0); + buttons->addWidget(reset); + buttons->addWidget(quit); + buttons->addWidget(restart); + buttons->addStretch(); + buttons->addWidget(errorLog); + buttons->addWidget(start); + buttons->addWidget(cancel); + buttons->addWidget(help); + + status = new QLabel; + status->setObjectName(QStringLiteral("statusLine")); + status->setFrameShape(QFrame::StyledPanel); + status->setMinimumHeight(22); + + QVBoxLayout *root = new QVBoxLayout(this); + root->setContentsMargins(6, 6, 6, 6); + root->setSpacing(5); + root->addLayout(content, 1); + root->addWidget(status); + root->addLayout(buttons); + + resetDefaults(); + navigation->setCurrentItem(quickstartPage); + if (!initialConfigPath.isEmpty()) { + loadConfig(initialConfigPath); + } + if (hardwareProvider.pollHostWindowEvents) { + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this]() { + hardwareProvider.pollHostWindowEvents(hardwareProvider.context); + }); + timer->start(100); + } + } + + const WinUaeQtLauncherResult &launcherResult() const + { + return result; + } + + void openConfigFile(const QString &path) + { + if (path.isEmpty()) { + return; + } + loadConfig(path); + raise(); + activateWindow(); + } + +private: + WinUaeQtHardwareInfoProvider hardwareProvider; + WinUaeQtBoardCatalog boardCatalog; + mutable QStringList hardwareOrderOwnedKeys; + QTreeWidget *navigation = nullptr; + QStackedWidget *pageStack = nullptr; + QLabel *status = nullptr; + + QComboBox *configName = nullptr; + QLineEdit *configPath = nullptr; + QLineEdit *configDescription = nullptr; + QTreeWidget *configTree = nullptr; + QLineEdit *configSearch = nullptr; + QComboBox *configFilter = nullptr; + + QComboBox *quickModel = nullptr; + QComboBox *quickConfiguration = nullptr; + QComboBox *quickHostConfiguration = nullptr; + QCheckBox *quickstartMode = nullptr; + QPushButton *quickstartSetConfig = nullptr; + QCheckBox *ntsc = nullptr; + QSlider *compatibility = nullptr; + QCheckBox *quickDfEnable[2] = {}; + QComboBox *quickDfType[2] = {}; + QComboBox *quickDfPath[2] = {}; + QCheckBox *quickDfWriteProtect[2] = {}; + bool quickstartUpdating = false; + + QComboBox *romFile = nullptr; + QComboBox *extendedRomFile = nullptr; + QComboBox *cartFile = nullptr; + QLineEdit *flashFile = nullptr; + QLineEdit *rtcFile = nullptr; + QCheckBox *mapRom = nullptr; + QCheckBox *kickShifter = nullptr; + QComboBox *customRomSelect = nullptr; + QLineEdit *customRomStart = nullptr; + QLineEdit *customRomEnd = nullptr; + QLineEdit *customRomFile = nullptr; + QComboBox *uaeBoardType = nullptr; + QVector customRomBoards; + int currentCustomRomBoard = 0; + bool customRomUpdating = false; + + QComboBox *cpuModel = nullptr; + QButtonGroup *cpuButtons = nullptr; + QButtonGroup *fpuButtons = nullptr; + QCheckBox *cpu24Bit = nullptr; + QCheckBox *moreCompatible = nullptr; + QCheckBox *cpuDataCache = nullptr; + QCheckBox *jit = nullptr; + QCheckBox *cpuUnimplemented = nullptr; + QButtonGroup *mmuButtons = nullptr; + QComboBox *chipset = nullptr; + QComboBox *chipsetCompatible = nullptr; + QCheckBox *fpuStrict = nullptr; + QCheckBox *fpuUnimplemented = nullptr; + QComboBox *fpuMode = nullptr; + QButtonGroup *cpuSpeedButtons = nullptr; + QSlider *cpuSpeed = nullptr; + QLineEdit *cpuSpeedLabel = nullptr; + QComboBox *cpuFrequency = nullptr; + QLineEdit *cpuFrequencyCustom = nullptr; + QSlider *jitCache = nullptr; + QLineEdit *jitCacheLabel = nullptr; + QCheckBox *jitFpu = nullptr; + QCheckBox *jitConstJump = nullptr; + QCheckBox *jitHardFlush = nullptr; + QButtonGroup *jitTrust = nullptr; + QCheckBox *jitNoFlags = nullptr; + QCheckBox *jitCatchFault = nullptr; + QCheckBox *chipsetNtsc = nullptr; + QCheckBox *chipsetCycleExact = nullptr; + QCheckBox *chipsetCycleExactMemory = nullptr; + QCheckBox *immediateBlits = nullptr; + QCheckBox *waitingBlits = nullptr; + QComboBox *displayOptimization = nullptr; + QComboBox *chipsetSyncMode = nullptr; + QButtonGroup *collisionButtons = nullptr; + QCheckBox *genlockConnected = nullptr; + QComboBox *genlockMode = nullptr; + QComboBox *genlockMix = nullptr; + QCheckBox *genlockAlpha = nullptr; + QCheckBox *genlockKeepAspect = nullptr; + QComboBox *genlockFile = nullptr; + QPushButton *genlockFileBrowse = nullptr; + QComboBox *keyboardMode = nullptr; + QCheckBox *keyboardNkro = nullptr; + QString genlockImagePath; + QString genlockVideoPath; + bool genlockUpdating = false; + QCheckBox *advancedCompatible = nullptr; + QButtonGroup *advancedRtcButtons = nullptr; + QLineEdit *advancedRtcAdjust = nullptr; + QButtonGroup *advancedCiaTodButtons = nullptr; + QMap advancedCheckBoxes; + QCheckBox *advancedIdeA600A1200 = nullptr; + QCheckBox *advancedIdeA4000 = nullptr; + QCheckBox *advancedScsiA3000 = nullptr; + QCheckBox *advancedScsiA4000T = nullptr; + QCheckBox *advancedCia391078 = nullptr; + QComboBox *advancedUnmappedAddress = nullptr; + QComboBox *advancedCiaSync = nullptr; + QCheckBox *advancedRamsey = nullptr; + QLineEdit *advancedRamseyRevision = nullptr; + QCheckBox *advancedFatGary = nullptr; + QLineEdit *advancedFatGaryRevision = nullptr; + QComboBox *advancedAgnusModel = nullptr; + QComboBox *advancedAgnusSize = nullptr; + QComboBox *advancedDeniseModel = nullptr; + + QComboBox *chipMem = nullptr; + QComboBox *z2Fast = nullptr; + QComboBox *slowMem = nullptr; + QComboBox *z3Fast = nullptr; + QComboBox *z3ChipMem = nullptr; + QComboBox *processorSlotMem = nullptr; + QComboBox *z3Mapping = nullptr; + QComboBox *rtgMem = nullptr; + QComboBox *rtgType = nullptr; + QComboBox *rtgMonitor = nullptr; + QCheckBox *rtgScale = nullptr; + QCheckBox *rtgScaleAllow = nullptr; + QCheckBox *rtgCenter = nullptr; + QCheckBox *rtgIntegerScale = nullptr; + QCheckBox *rtgMultithread = nullptr; + QCheckBox *rtgHardwareSprite = nullptr; + QCheckBox *rtgHardwareVBlank = nullptr; + QCheckBox *rtgAutoswitch = nullptr; + QCheckBox *rtgInitialMonitor = nullptr; + QComboBox *rtg8Bit = nullptr; + QComboBox *rtg16Bit = nullptr; + QComboBox *rtg24Bit = nullptr; + QComboBox *rtg32Bit = nullptr; + QComboBox *rtgDisplay = nullptr; + QComboBox *rtgRefreshRate = nullptr; + QComboBox *rtgBuffers = nullptr; + QComboBox *rtgAspectRatio = nullptr; + QComboBox *expansionRomCategory = nullptr; + QComboBox *expansionRomBoard = nullptr; + QComboBox *expansionRomSubtype = nullptr; + QComboBox *expansionRomSlot = nullptr; + QComboBox *expansionRomId = nullptr; + QComboBox *expansionRomFile = nullptr; + QComboBox *expansionBoardOption = nullptr; + QComboBox *expansionBoardSelector = nullptr; + QLineEdit *expansionBoardString = nullptr; + QCheckBox *expansionRom24BitDma = nullptr; + QCheckBox *expansionRomEnabled = nullptr; + QCheckBox *expansionRomAutobootDisabled = nullptr; + QCheckBox *expansionRomPcmciaInserted = nullptr; + QCheckBox *expansionBoardOptionCheck = nullptr; + QPushButton *expansionRomBrowse = nullptr; + QMap expansionBoardStates; + QString currentExpansionBoardConfigName; + bool expansionBoardUpdating = false; + QCheckBox *expansionBsdsocket = nullptr; + QCheckBox *expansionScsiDevice = nullptr; + QCheckBox *expansionSana2 = nullptr; + QComboBox *cpuBoardType = nullptr; + QComboBox *cpuBoardSubtype = nullptr; + QComboBox *cpuBoardRom = nullptr; + QComboBox *cpuBoardMem = nullptr; + QComboBox *acceleratorOption = nullptr; + QComboBox *acceleratorSelector = nullptr; + QCheckBox *acceleratorOptionCheck = nullptr; + QPushButton *cpuBoardBrowse = nullptr; + QString cpuBoardSettingsRaw; + bool cpuBoardUpdating = false; + QTreeWidget *hardwareBoardList = nullptr; + QCheckBox *hardwareCustomBoardOrder = nullptr; + QPushButton *hardwareMoveUp = nullptr; + QPushButton *hardwareMoveDown = nullptr; + + QCheckBox *dfEnable[4] = {}; + QComboBox *dfType[4] = {}; + QComboBox *dfPath[4] = {}; + QCheckBox *dfWriteProtect[4] = {}; + QSlider *floppySpeed = nullptr; + QLineEdit *floppySpeedLabel = nullptr; + QTableWidget *diskSwapperList = nullptr; + QComboBox *diskSwapperPath = nullptr; + QPushButton *diskSwapperInsertButton = nullptr; + QPushButton *diskSwapperRemoveButton = nullptr; + QPushButton *diskSwapperRemoveAllButton = nullptr; + QTreeWidget *mountedDrives = nullptr; + QPushButton *addDirectoryMountButton = nullptr; + QPushButton *addHardfileMountButton = nullptr; + QPushButton *addHardDriveMountButton = nullptr; + QPushButton *addCdMountButton = nullptr; + QPushButton *addTapeMountButton = nullptr; + QPushButton *propertiesMountButton = nullptr; + QPushButton *removeMountButton = nullptr; + QComboBox *cdSlotNumber = nullptr; + QComboBox *cdSlotType = nullptr; + QComboBox *cdSlotPath = nullptr; + QCheckBox *cdSpeedTurbo = nullptr; + QCheckBox *hostDriveAutomount = nullptr; + QCheckBox *hostRemovableDrives = nullptr; + QCheckBox *hostNetworkDrives = nullptr; + QCheckBox *hostCdDrives = nullptr; + QCheckBox *filesysNoFsdb = nullptr; + QCheckBox *hostNoRecycleBin = nullptr; + QCheckBox *hostAutomountRemovable = nullptr; + QCheckBox *filesysLimitSize = nullptr; + QVector cdSlots; + int currentCdSlot = 0; + bool cdSlotUpdating = false; + + QLineEdit *windowWidth = nullptr; + QLineEdit *windowHeight = nullptr; + QCheckBox *windowResize = nullptr; + QComboBox *hostDisplay = nullptr; + QComboBox *fullscreenResolution = nullptr; + QComboBox *displayRefreshRate = nullptr; + QComboBox *displayBufferCount = nullptr; + QComboBox *nativeMode = nullptr; + QComboBox *nativeVsync = nullptr; + QComboBox *nativeFrameSlices = nullptr; + QComboBox *rtgMode = nullptr; + QComboBox *rtgVsync = nullptr; + QComboBox *displayResolution = nullptr; + QComboBox *displayOverscan = nullptr; + QComboBox *displayAutoResolution = nullptr; + QSpinBox *displayFrameRate = nullptr; + QCheckBox *displayCenterHorizontal = nullptr; + QCheckBox *displayCenterVertical = nullptr; + QCheckBox *displayFlickerFixer = nullptr; + QCheckBox *displayLoresSmoothed = nullptr; + QCheckBox *displayBlackerThanBlack = nullptr; + QCheckBox *displayMonochrome = nullptr; + QCheckBox *displayAutoResolutionVga = nullptr; + QCheckBox *displayResyncBlank = nullptr; + QCheckBox *displayKeepAspect = nullptr; + QButtonGroup *displayLineModeButtons = nullptr; + QButtonGroup *displayInterlacedLineModeButtons = nullptr; + QComboBox *filterTarget = nullptr; + QCheckBox *filterEnable = nullptr; + QComboBox *filterMode = nullptr; + QComboBox *filterModeH = nullptr; + QComboBox *filterModeV = nullptr; + QComboBox *filterAutoscale = nullptr; + QComboBox *filterIntegerLimit = nullptr; + QDoubleSpinBox *filterHorizZoomMult = nullptr; + QDoubleSpinBox *filterVertZoomMult = nullptr; + QDoubleSpinBox *filterHorizZoom = nullptr; + QDoubleSpinBox *filterVertZoom = nullptr; + QDoubleSpinBox *filterHorizOffset = nullptr; + QDoubleSpinBox *filterVertOffset = nullptr; + QCheckBox *filterKeepAutoscaleAspect = nullptr; + QCheckBox *filterKeepAspect = nullptr; + QComboBox *filterAspect = nullptr; + QComboBox *filterPresetName = nullptr; + QCheckBox *filterNtscPixels = nullptr; + QCheckBox *filterBilinear = nullptr; + QSpinBox *filterScanlines = nullptr; + QSpinBox *filterScanlineLevel = nullptr; + QSpinBox *filterScanlineOffset = nullptr; + QSpinBox *filterLuminance = nullptr; + QSpinBox *filterContrast = nullptr; + QSpinBox *filterSaturation = nullptr; + QSpinBox *filterGamma = nullptr; + QSpinBox *filterBlur = nullptr; + QSpinBox *filterNoise = nullptr; + WinUaeQtFilterState filterStates[3]; + int currentFilterTarget = 0; + bool filterUpdating = false; + QLineEdit *outputFile = nullptr; + QCheckBox *outputAudio = nullptr; + QCheckBox *outputVideo = nullptr; + QCheckBox *outputEnabled = nullptr; + QComboBox *outputAudioCodec = nullptr; + QComboBox *outputVideoCodec = nullptr; + QCheckBox *outputFrameLimiter = nullptr; + QCheckBox *outputOriginalSize = nullptr; + QCheckBox *outputNoSound = nullptr; + QCheckBox *outputNoSoundSync = nullptr; + QCheckBox *screenshotOriginalSize = nullptr; + QCheckBox *screenshotPaletted = nullptr; + QCheckBox *screenshotClip = nullptr; + QCheckBox *screenshotAuto = nullptr; + QCheckBox *stateReplayAutoplay = nullptr; + QComboBox *stateReplayRate = nullptr; + QComboBox *stateReplayBuffers = nullptr; + QButtonGroup *soundOutputButtons = nullptr; + QComboBox *soundDevice = nullptr; + QCheckBox *soundAutomatic = nullptr; + QSlider *soundMasterVolume = nullptr; + QLabel *soundMasterVolumeValue = nullptr; + QComboBox *soundVolumeSelect = nullptr; + QSlider *soundSelectedVolume = nullptr; + QLabel *soundSelectedVolumeValue = nullptr; + QSlider *soundBufferSize = nullptr; + QLabel *soundBufferSizeValue = nullptr; + QComboBox *soundChannels = nullptr; + QComboBox *soundStereoSeparation = nullptr; + QComboBox *soundInterpolation = nullptr; + QComboBox *soundFrequency = nullptr; + QComboBox *soundSwap = nullptr; + QComboBox *soundStereoDelay = nullptr; + QComboBox *soundFilter = nullptr; + QComboBox *floppySoundDrive = nullptr; + QComboBox *floppySoundType = nullptr; + QSlider *floppySoundEmptyVolume = nullptr; + QLabel *floppySoundEmptyVolumeValue = nullptr; + QSlider *floppySoundDiskVolume = nullptr; + QLabel *floppySoundDiskVolumeValue = nullptr; + int soundVolumeAttenuation[SoundVolumeCount] = {}; + int floppySoundTypeValue[FloppySoundDriveCount] = {}; + int floppySoundEmptyAttenuation[FloppySoundDriveCount] = {}; + int floppySoundDiskAttenuation[FloppySoundDriveCount] = {}; + int currentSoundVolume = 0; + int currentFloppySoundDrive = 0; + bool soundVolumeUpdating = false; + bool floppySoundUpdating = false; + QComboBox *portDevice[4] = {}; + QComboBox *portAutofire[2] = {}; + QComboBox *portMode[2] = {}; + QString joyportCustom[MaxJoyportCustomSlots]; + QCheckBox *portAutoswitch = nullptr; + QSpinBox *mouseSpeed = nullptr; + QCheckBox *virtualMouseDriver = nullptr; + QComboBox *mouseUntrapMode = nullptr; + QComboBox *magicMouseCursor = nullptr; + QCheckBox *tabletLibrary = nullptr; + QComboBox *tabletMode = nullptr; + QComboBox *inputType = nullptr; + QComboBox *inputDevice = nullptr; + QCheckBox *inputDeviceEnabled = nullptr; + QTreeWidget *inputMappingList = nullptr; + QComboBox *inputSubEvent = nullptr; + QComboBox *inputAmigaEvent = nullptr; + QPushButton *inputRemapButton = nullptr; + QPushButton *inputCopyButton = nullptr; + QPushButton *inputSwapButton = nullptr; + QSpinBox *inputDeadzone = nullptr; + QSpinBox *inputAutofireRate = nullptr; + QSpinBox *inputJoyMouseDigital = nullptr; + QSpinBox *inputJoyMouseAnalog = nullptr; + QComboBox *inputCopyFrom = nullptr; + QCheckBox *inputPageUpEnd = nullptr; + QCheckBox *inputSwapBackslashF11 = nullptr; + QMap inputMappingSettings; + QStringList inputOwnedMappingKeys; + bool inputMappingUpdating = false; + QComboBox *printerPort = nullptr; + QComboBox *printerType = nullptr; + QSpinBox *printerAutoFlush = nullptr; + QLineEdit *ghostscriptParams = nullptr; + QComboBox *samplerDevice = nullptr; + QCheckBox *samplerStereo = nullptr; + QComboBox *serialPort = nullptr; + QCheckBox *serialShared = nullptr; + QCheckBox *serialCtsRts = nullptr; + QCheckBox *serialDirect = nullptr; + QCheckBox *serialCrlf = nullptr; + QCheckBox *uaeSerial = nullptr; + QCheckBox *serialStatus = nullptr; + QCheckBox *serialRingIndicator = nullptr; + QComboBox *midiOut = nullptr; + QComboBox *midiIn = nullptr; + QCheckBox *midiRouter = nullptr; + QComboBox *protectionDongle = nullptr; + QLineEdit *romsPath = nullptr; + QLineEdit *configsPath = nullptr; + QLineEdit *nvramPath = nullptr; + QLineEdit *screenshotsPath = nullptr; + QLineEdit *stateFilesPath = nullptr; + QLineEdit *videosPath = nullptr; + QLineEdit *saveImagesPath = nullptr; + QLineEdit *ripsPath = nullptr; + QLineEdit *dataPath = nullptr; + QLineEdit *logPath = nullptr; + QCheckBox *recursiveRoms = nullptr; + QCheckBox *cacheConfigurations = nullptr; + QCheckBox *cacheBoxArt = nullptr; + QCheckBox *saveImageOriginalPath = nullptr; + QCheckBox *relativePaths = nullptr; + QCheckBox *portableMode = nullptr; + QCheckBox *fullLogging = nullptr; + QCheckBox *logWindow = nullptr; + QComboBox *pathDefaultType = nullptr; + QComboBox *logSelect = nullptr; + QListWidget *miscOptionList = nullptr; + QMap miscOptionItems; + QComboBox *miscScsiMode = nullptr; + QComboBox *miscWindowedStyle = nullptr; + QComboBox *miscVideoApi = nullptr; + QComboBox *miscVideoApiOptions = nullptr; + QComboBox *miscLanguage = nullptr; + QComboBox *miscGuiSize = nullptr; + QCheckBox *miscGuiResize = nullptr; + QCheckBox *miscGuiFullscreen = nullptr; + QCheckBox *miscGuiDarkMode = nullptr; + QString miscGuiFontConfig; + QComboBox *stateFileName = nullptr; + QCheckBox *stateFileClear = nullptr; + QComboBox *keyboardLed[3] = {}; + QCheckBox *keyboardLedUsb = nullptr; + QComboBox *extensionActivePriority = nullptr; + QCheckBox *extensionActivePause = nullptr; + QCheckBox *extensionActiveNoSound = nullptr; + QCheckBox *extensionActiveNoJoy = nullptr; + QCheckBox *extensionActiveNoKeyboard = nullptr; + QComboBox *extensionInactivePriority = nullptr; + QCheckBox *extensionInactivePause = nullptr; + QCheckBox *extensionInactiveNoSound = nullptr; + QCheckBox *extensionInactiveNoJoy = nullptr; + QComboBox *extensionMinimizedPriority = nullptr; + QCheckBox *extensionMinimizedPause = nullptr; + QCheckBox *extensionMinimizedNoSound = nullptr; + QCheckBox *extensionMinimizedNoJoy = nullptr; + QTreeWidget *extensionAssociationList = nullptr; + QTreeWidget *frontendConfigList = nullptr; + QLabel *frontendScreenshot = nullptr; + QLabel *frontendInfo = nullptr; + WinUaeQtConfig loadedConfig; + WinUaeQtLauncherResult result; + + QTreeWidgetItem *addFolder(const QString &title, QTreeWidgetItem *parent = nullptr) + { + QTreeWidgetItem *item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem(navigation); + item->setText(0, title); + item->setIcon(0, resourceIcon(QStringLiteral("folder.ico"))); + QFont font = item->font(0); + font.setBold(true); + item->setFont(0, font); + item->setExpanded(true); + return item; + } + + QTreeWidgetItem *addPage(const QString &title, const QString &icon, QWidget *page, QTreeWidgetItem *parent = nullptr) + { + QWidget *stackPage = page; + if (!page->findChild()) { + if (QLayout *layout = page->layout()) { + layout->setSizeConstraint(QLayout::SetMinimumSize); + } + page->setMinimumSize(page->minimumSizeHint()); + QScrollArea *scroll = new QScrollArea; + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidgetResizable(true); + scroll->setWidget(page); + stackPage = scroll; + } + const int index = pageStack->addWidget(stackPage); + QTreeWidgetItem *item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem(navigation); + item->setText(0, title); + item->setIcon(0, resourceIcon(icon)); + item->setData(0, Qt::UserRole, index); + return item; + } + + QWidget *makePage() + { + QWidget *page = new QWidget; + page->setObjectName(QStringLiteral("page")); + return page; + } + + void openHelp() + { + if (!QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.winuae.net/help/")))) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not open WinUAE help.")); + } + } + + QWidget *makeConfigurationsPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(4); + + configTree = new QTreeWidget; + configTree->setHeaderHidden(true); + configTree->setRootIsDecorated(true); + root->addWidget(configTree, 1); + + QGridLayout *search = new QGridLayout; + search->setColumnStretch(1, 1); + search->setColumnStretch(4, 1); + configSearch = new QLineEdit; + configFilter = combo({ QStringLiteral("All configurations"), QStringLiteral("Host"), QStringLiteral("Hardware") }); + search->addWidget(label(QStringLiteral("Search:")), 0, 0); + search->addWidget(configSearch, 0, 1); + QPushButton *clearSearch = smallButton(QStringLiteral("X")); + search->addWidget(clearSearch, 0, 2); + search->addWidget(label(QStringLiteral("Filter:")), 0, 3); + search->addWidget(configFilter, 0, 4); + root->addLayout(search); + + configName = pathCombo(); + configDescription = new QLineEdit; + configPath = new QLineEdit; + configPath->setReadOnly(true); + + QGridLayout *details = new QGridLayout; + details->setColumnStretch(1, 1); + details->addWidget(label(QStringLiteral("Name:")), 0, 0); + details->addWidget(configName, 0, 1); + details->addWidget(configPath, 0, 2); + details->addWidget(label(QStringLiteral("Description:")), 1, 0); + details->addWidget(configDescription, 1, 1, 1, 2); + root->addLayout(details); + + QHBoxLayout *buttons = new QHBoxLayout; + QPushButton *quickLoad = new QPushButton(QStringLiteral("Load")); + QPushButton *quickSave = new QPushButton(QStringLiteral("Save")); + QPushButton *load = new QPushButton(QStringLiteral("Load From...")); + QPushButton *save = new QPushButton(QStringLiteral("Save As...")); + QPushButton *deleteConfig = new QPushButton(QStringLiteral("Delete")); + buttons->addWidget(quickLoad); + buttons->addWidget(quickSave); + buttons->addStretch(); + buttons->addWidget(load); + buttons->addWidget(save); + buttons->addWidget(deleteConfig); + root->addLayout(buttons); + + connect(configTree, &QTreeWidget::itemSelectionChanged, this, [this]() { selectConfigFromTree(); }); + connect(configTree, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem *, int) { loadSelectedConfig(); }); + connect(configSearch, &QLineEdit::textChanged, this, [this]() { refreshConfigList(); }); + connect(configFilter, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { refreshConfigList(); }); + connect(clearSearch, &QPushButton::clicked, configSearch, &QLineEdit::clear); + connect(quickLoad, &QPushButton::clicked, this, [this]() { loadSelectedConfig(); }); + connect(quickSave, &QPushButton::clicked, this, [this]() { saveNamedConfig(); }); + connect(load, &QPushButton::clicked, this, [this]() { loadConfigDialog(); }); + connect(save, &QPushButton::clicked, this, [this]() { saveConfigDialog(); }); + connect(deleteConfig, &QPushButton::clicked, this, [this]() { deleteSelectedConfig(); }); + return page; + } + + QWidget *makeQuickstartPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + quickModel = combo(quickstartModelItems(), QStringLiteral("A1200")); + quickConfiguration = combo({}); + ntsc = new QCheckBox(QStringLiteral("NTSC")); + + QGridLayout *hardware = new QGridLayout; + hardware->setColumnStretch(1, 1); + hardware->addWidget(label(QStringLiteral("Model:")), 0, 0); + hardware->addWidget(quickModel, 0, 1); + hardware->addWidget(ntsc, 0, 2); + hardware->addWidget(label(QStringLiteral("Configuration:")), 1, 0); + hardware->addWidget(quickConfiguration, 1, 1, 1, 2); + root->addWidget(groupBox(QStringLiteral("Emulated Hardware"), hardware)); + + compatibility = new QSlider(Qt::Horizontal); + compatibility->setRange(0, 4); + compatibility->setTickInterval(1); + compatibility->setTickPosition(QSlider::TicksAbove); + compatibility->setValue(1); + + QHBoxLayout *compat = new QHBoxLayout; + compat->addWidget(new QLabel(QStringLiteral("Best compatibility"))); + compat->addWidget(compatibility, 1); + compat->addWidget(new QLabel(QStringLiteral("Low compatibility"))); + root->addWidget(groupBox(QStringLiteral("Compatibility vs Required CPU Power"), compat)); + + quickHostConfiguration = combo({ + QStringLiteral("Default"), + QStringLiteral("Windowed"), + QStringLiteral("Fullscreen") + }, QStringLiteral("Default")); + QGridLayout *host = new QGridLayout; + host->setColumnStretch(1, 1); + host->addWidget(label(QStringLiteral("Configuration:")), 0, 0); + host->addWidget(quickHostConfiguration, 0, 1); + root->addWidget(groupBox(QStringLiteral("Host Configuration"), host)); + + quickstartSetConfig = new QPushButton(QStringLiteral("Set configuration")); + quickstartMode = new QCheckBox(QStringLiteral("Start in Quickstart mode")); + QHBoxLayout *mode = new QHBoxLayout; + mode->addWidget(quickstartSetConfig); + mode->addStretch(); + mode->addWidget(quickstartMode); + root->addWidget(groupBox(QStringLiteral("Mode"), mode)); + + QVBoxLayout *drives = new QVBoxLayout; + drives->setSpacing(10); + addQuickDriveRow(drives, 0); + addQuickDriveRow(drives, 1); + drives->addStretch(); + root->addWidget(groupBox(QStringLiteral("Emulated Drives"), drives), 1); + + connect(quickModel, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + refreshQuickstartConfigurationChoices(); + applyQuickstartSelectionToUi(); + }); + connect(quickConfiguration, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { applyQuickstartSelectionToUi(); }); + connect(compatibility, &QSlider::valueChanged, this, [this]() { applyQuickstartCompatibilityToUi(); }); + connect(quickstartSetConfig, &QPushButton::clicked, this, [this]() { applyQuickstartSelectionToUi(); }); + connect(quickstartMode, &QCheckBox::toggled, this, [this](bool checked) { + quickstartSetConfig->setVisible(!checked); + applyQuickstartSelectionToUi(); + }); + refreshQuickstartConfigurationChoices(); + return page; + } + + void refreshQuickstartConfigurationChoices(int requestedIndex = -1) + { + if (!quickConfiguration || !quickModel) { + return; + } + const QSignalBlocker blocker(quickConfiguration); + const int oldIndex = quickConfiguration->currentIndex(); + quickConfiguration->clear(); + const QuickstartModelChoice *choice = quickstartModelChoiceByDisplay(quickModel->currentText()); + if (!choice) { + return; + } + quickConfiguration->addItems(quickstartConfigItems(*choice)); + const int selected = requestedIndex >= 0 ? requestedIndex : oldIndex; + quickConfiguration->setCurrentIndex(qBound(0, selected, qMax(0, quickConfiguration->count() - 1))); + compatibility->setEnabled(choice->compatibilityLevels > 1); + compatibility->setRange(0, qMax(1, choice->compatibilityLevels - 1)); + compatibility->setValue(qBound(0, compatibility->value(), compatibility->maximum())); + } + + QString quickstartConfigValue() const + { + const QuickstartModelChoice *choice = quickstartModelChoiceByDisplay(quickModel->currentText()); + if (!choice) { + return QString(); + } + return QStringLiteral("%1,%2").arg(QString::fromLatin1(choice->configValue)).arg(qMax(0, quickConfiguration->currentIndex())); + } + + void clearQuickstartCd32ExpansionBoards() + { + expansionBoardStates.remove(expansionBoardConfigName(QStringLiteral("cd32fmv"), 0)); + expansionBoardStates.remove(expansionBoardConfigName(QStringLiteral("cubo"), 0)); + } + + void setQuickstartExpansionBoard(const QString &boardKey, const QString &romFile) + { + const QString configName = expansionBoardConfigName(boardKey, 0); + if (configName.isEmpty()) { + return; + } + WinUaeQtExpansionBoardState state = expansionBoardStates.value(configName); + state.present = true; + state.romFile = romFile; + expansionBoardStates.insert(configName, state); + } + + QString cuboNvramPath() const + { + const QString configured = nvramPath ? expandedPathText(nvramPath->text()) : QString(); + const QString root = configured.isEmpty() + ? expandedPathText(unixDefaultDataSubPath(QStringLiteral("NVRAMs"))) + : configured; + return QDir(root).filePath(QStringLiteral("cd32cubo.nvr")); + } + + void applyQuickstartExpansionPreset(const QString &model, int config) + { + clearQuickstartCd32ExpansionBoards(); + const bool explicitPreset = !quickstartMode || !quickstartMode->isChecked(); + const QString generatedCuboNvram = cuboNvramPath(); + if (flashFile && flashFile->text().trimmed() == generatedCuboNvram + && (model != QStringLiteral("CD32") || config <= 1)) { + flashFile->clear(); + } + + if (model != QStringLiteral("CD32")) { + refreshExpansionBoardChoiceLabels(); + refreshHardwareInfoPage(); + return; + } + + if (config == 1 && quickstartMode && !quickstartMode->isChecked()) { + const QString fmvRom = cartFile ? cartFile->currentText().trimmed() : QString(); + if (!fmvRom.isEmpty()) { + setQuickstartExpansionBoard(QStringLiteral("cd32fmv"), fmvRom); + } + } else if (config > 1) { + setQuickstartExpansionBoard(QStringLiteral("cubo"), QStringLiteral(":ENABLED")); + if (explicitPreset && flashFile && flashFile->text().trimmed().isEmpty()) { + flashFile->setText(generatedCuboNvram); + } + } + + refreshExpansionBoardChoiceLabels(); + refreshHardwareInfoPage(); + } + + void applyQuickstartSelectionToUi() + { + if (quickstartUpdating) { + return; + } + const QuickstartModelChoice *choice = quickstartModelChoiceByDisplay(quickModel->currentText()); + if (!choice) { + return; + } + + quickstartUpdating = true; + const int config = qMax(0, quickConfiguration->currentIndex()); + QString compatible = QString::fromLatin1(choice->compatible); + if (quickModel->currentText() == QStringLiteral("CDTV") && config >= 2) { + compatible = QStringLiteral("CDTV-CR"); + } + if (quickModel->currentText() == QStringLiteral("A1000") && config >= 3) { + compatible = QStringLiteral("Velvet"); + } + + applyModelPreset(compatible); + applyAdvancedChipsetPreset(compatible); + applyQuickstartConfigurationMemory(quickModel->currentText(), config); + applyQuickstartExpansionPreset(quickModel->currentText(), config); + applyQuickstartCompatibilityToUi(); + quickstartUpdating = false; + } + + void applyQuickstartConfigurationMemory(const QString &model, int config) + { + z2Fast->setCurrentText(QStringLiteral("None")); + z3Fast->setCurrentText(QStringLiteral("None")); + z3ChipMem->setCurrentText(QStringLiteral("None")); + processorSlotMem->setCurrentText(QStringLiteral("None")); + z3Mapping->setCurrentText(QStringLiteral("Automatic")); + rtgMem->setCurrentText(QStringLiteral("None")); + + if (model == QStringLiteral("A500")) { + setCpuButton(68000); + setFpuButton(0); + const bool oneMegChip = config == 2; + const bool noSlow = config == 2 || config == 3 || config == 4; + chipMem->setCurrentText(oneMegChip ? QStringLiteral("1 MB") : QStringLiteral("512 KB")); + slowMem->setCurrentText(noSlow ? QStringLiteral("None") : QStringLiteral("512 KB")); + chipset->setCurrentText((config == 1 || config == 2) ? QStringLiteral("ECS") : QStringLiteral("OCS")); + } else if (model == QStringLiteral("A500+") || model == QStringLiteral("A600")) { + setCpuButton(68000); + setFpuButton(0); + chipMem->setCurrentText(config == 1 ? QStringLiteral("2 MB") : QStringLiteral("1 MB")); + slowMem->setCurrentText(QStringLiteral("None")); + if (config == 2) { + z2Fast->setCurrentText(QStringLiteral("4 MB")); + } + } else if (model == QStringLiteral("A1200")) { + if (config == 2) { + setCpuButton(68030); + setFpuButton(68882); + } else if (config == 3) { + setCpuButton(68040); + setFpuButton(68040); + } else if (config >= 4) { + setCpuButton(68060); + setFpuButton(68060); + } else { + setCpuButton(68020); + setFpuButton(0); + } + chipMem->setCurrentText(QStringLiteral("2 MB")); + slowMem->setCurrentText(QStringLiteral("None")); + if (config == 1) { + z2Fast->setCurrentText(QStringLiteral("4 MB")); + } else if (config >= 2) { + processorSlotMem->setCurrentText(config >= 5 ? QStringLiteral("128 MB") : QStringLiteral("32 MB")); + } + } else if (model == QStringLiteral("A3000")) { + setCpuButton(68030); + setFpuButton(68882); + chipMem->setCurrentText(QStringLiteral("2 MB")); + slowMem->setCurrentText(QStringLiteral("None")); + processorSlotMem->setCurrentText(QStringLiteral("8 MB")); + } else if (model == QStringLiteral("A4000")) { + if (config == 1) { + setCpuButton(68040); + setFpuButton(68040); + } else if (config >= 2) { + setCpuButton(68060); + setFpuButton(68060); + } else { + setCpuButton(68030); + setFpuButton(68882); + } + chipMem->setCurrentText(QStringLiteral("2 MB")); + slowMem->setCurrentText(QStringLiteral("None")); + processorSlotMem->setCurrentText(QStringLiteral("8 MB")); + } else if (model == QStringLiteral("CD32")) { + setCpuButton(68020); + setFpuButton(0); + chipMem->setCurrentText(QStringLiteral("2 MB")); + slowMem->setCurrentText(QStringLiteral("None")); + } else if (model == QStringLiteral("CDTV")) { + setCpuButton(68000); + setFpuButton(0); + chipMem->setCurrentText(QStringLiteral("1 MB")); + slowMem->setCurrentText(QStringLiteral("None")); + } + + if (model == QStringLiteral("CD32") || (model == QStringLiteral("CDTV") && config == 0)) { + for (int i = 0; i < 2; i++) { + dfEnable[i]->setChecked(false); + quickDfEnable[i]->setChecked(false); + } + } else { + dfEnable[0]->setChecked(true); + quickDfEnable[0]->setChecked(true); + dfEnable[1]->setChecked(false); + quickDfEnable[1]->setChecked(false); + } + } + + void applyQuickstartCompatibilityToUi() + { + if (!compatibility) { + return; + } + const int level = compatibility->value(); + const int cpu = selectedCpuModel(); + const bool exact = level == 0 && cpu <= 68030; + const bool memoryExact = level <= 1 && cpu <= 68030; + if (QAbstractButton *button = cpuSpeedButtons->button(level >= 3 ? 1 : 0)) { + button->setChecked(true); + } + chipsetCycleExact->setChecked(exact); + chipsetCycleExactMemory->setChecked(memoryExact); + moreCompatible->setChecked(level <= 2); + if (cpu >= 68030) { + cpu24Bit->setChecked(false); + } + updateCpuControlState(); + } + + void addQuickDriveRow(QVBoxLayout *layout, int drive) + { + quickDfEnable[drive] = new QCheckBox(QStringLiteral("Floppy drive DF%1:").arg(drive)); + QPushButton *select = new QPushButton(QStringLiteral("Select image file")); + quickDfType[drive] = combo(floppyTypeItems(drive, true)); + quickDfWriteProtect[drive] = new QCheckBox; + QPushButton *info = smallButton(QStringLiteral("?")); + QPushButton *eject = new QPushButton(QStringLiteral("Eject")); + quickDfPath[drive] = pathCombo(); + quickDfPath[drive]->setMinimumWidth(300); + QWidget *driveWidget = new QWidget; + QVBoxLayout *driveLayout = new QVBoxLayout(driveWidget); + driveLayout->setContentsMargins(0, 0, 0, 0); + driveLayout->setSpacing(4); + + QHBoxLayout *controls = new QHBoxLayout; + controls->setContentsMargins(0, 0, 0, 0); + controls->setSpacing(6); + controls->addWidget(quickDfEnable[drive]); + controls->addWidget(select); + controls->addWidget(quickDfType[drive]); + controls->addWidget(new QLabel(QStringLiteral("Write-protected"))); + controls->addWidget(quickDfWriteProtect[drive]); + controls->addWidget(info); + controls->addWidget(eject); + controls->addStretch(); + driveLayout->addLayout(controls); + driveLayout->addWidget(quickDfPath[drive]); + layout->addWidget(driveWidget); + quickDfEnable[drive]->setChecked(drive == 0); + connect(info, &QPushButton::clicked, this, [this, drive]() { + showFloppyInfo(quickDfPath[drive] ? quickDfPath[drive]->currentText() : QString(), drive); + }); + connect(select, &QPushButton::clicked, this, [this, drive]() { + addBrowse(quickDfPath[drive], this, QStringLiteral("Select floppy image"), floppyDiskImageFilter()); + }); + connect(eject, &QPushButton::clicked, this, [this, drive]() { + quickDfPath[drive]->setCurrentText(QString()); + }); + } + + QWidget *makeCpuPage() + { + QWidget *page = makePage(); + QHBoxLayout *root = new QHBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + QVBoxLayout *left = new QVBoxLayout; + left->setSpacing(6); + cpuButtons = new QButtonGroup(this); + QGridLayout *cpu = new QGridLayout; + cpu->setHorizontalSpacing(12); + cpu->setVerticalSpacing(5); + cpu->setColumnStretch(0, 1); + cpu->setColumnStretch(1, 1); + const QStringList cpus = { QStringLiteral("68000"), QStringLiteral("68010"), QStringLiteral("68020"), QStringLiteral("68030"), QStringLiteral("68040"), QStringLiteral("68060") }; + for (int i = 0; i < cpus.size(); i++) { + const QString &name = cpus[i]; + QRadioButton *button = new QRadioButton(name); + cpu->addWidget(button, i / 2, i % 2); + cpuButtons->addButton(button, name.mid(2).toInt() + 68000); + connect(button, &QRadioButton::clicked, this, [this]() { + updateFpuControls(); + updateCpuControlState(); + }); + if (name == QStringLiteral("68020")) { + button->setChecked(true); + } + } + cpu24Bit = new QCheckBox(QStringLiteral("24-bit addressing")); + moreCompatible = new QCheckBox(QStringLiteral("More compatible")); + cpuDataCache = new QCheckBox(QStringLiteral("Data cache emulation")); + jit = new QCheckBox(QStringLiteral("JIT")); + if (!unixJitBackendAvailable()) { + jit->setToolTip(QStringLiteral("JIT is not enabled in this Unix build yet.")); + } + cpuUnimplemented = new QCheckBox(QStringLiteral("Unimplemented CPU emu")); + cpu->addWidget(cpu24Bit, 3, 0, 1, 2); + cpu->addWidget(moreCompatible, 4, 0, 1, 2); + cpu->addWidget(cpuDataCache, 5, 0, 1, 2); + cpu->addWidget(jit, 6, 0, 1, 2); + cpu->addWidget(cpuUnimplemented, 7, 0, 1, 2); + left->addWidget(groupBox(QStringLiteral("CPU"), cpu)); + + mmuButtons = new QButtonGroup(this); + QHBoxLayout *mmu = new QHBoxLayout; + const QStringList mmus = { QStringLiteral("None"), QStringLiteral("MMU"), QStringLiteral("EC") }; + for (int i = 0; i < mmus.size(); i++) { + QRadioButton *button = new QRadioButton(mmus[i]); + mmu->addWidget(button); + mmuButtons->addButton(button, i); + if (i == 0) { + button->setChecked(true); + } + } + left->addWidget(groupBox(QStringLiteral("MMU"), mmu)); + + fpuButtons = new QButtonGroup(this); + QVBoxLayout *fpu = new QVBoxLayout; + fpu->setSpacing(5); + const QStringList fpus = { QStringLiteral("None"), QStringLiteral("68881"), QStringLiteral("68882"), QStringLiteral("CPU internal") }; + for (int i = 0; i < fpus.size(); i++) { + QRadioButton *button = new QRadioButton(fpus[i]); + fpu->addWidget(button); + const int id = i == 1 ? 68881 : (i == 2 ? 68882 : (i == 3 ? FpuInternal : 0)); + fpuButtons->addButton(button, id); + if (i == 0) { + button->setChecked(true); + } + } + fpuStrict = new QCheckBox(QStringLiteral("More compatible")); + fpuUnimplemented = new QCheckBox(QStringLiteral("Unimplemented FPU emu")); + fpuMode = combo({ + QStringLiteral("Host (64-bit)"), + QStringLiteral("Host (80-bit)"), + QStringLiteral("Softfloat (80-bit)") + }, QStringLiteral("Host (64-bit)")); + fpu->addWidget(fpuStrict); + fpu->addWidget(fpuUnimplemented); + fpu->addWidget(fpuMode); + left->addWidget(groupBox(QStringLiteral("FPU"), fpu)); + left->addStretch(); + + QVBoxLayout *right = new QVBoxLayout; + QGridLayout *speed = new QGridLayout; + cpuSpeedButtons = new QButtonGroup(this); + QRadioButton *fastest = new QRadioButton(QStringLiteral("Fastest possible")); + QRadioButton *approx = new QRadioButton(QStringLiteral("Approximate A500/A1200 or cycle-exact")); + approx->setChecked(true); + cpuSpeedButtons->addButton(fastest, 1); + cpuSpeedButtons->addButton(approx, 0); + speed->addWidget(fastest, 0, 0, 1, 2); + speed->addWidget(approx, 1, 0, 1, 2); + cpuSpeed = new QSlider(Qt::Horizontal); + cpuSpeed->setRange(-9, 50); + cpuSpeed->setTickInterval(1); + cpuSpeed->setTickPosition(QSlider::TicksAbove); + cpuSpeedLabel = new QLineEdit; + cpuSpeedLabel->setReadOnly(true); + cpuSpeedLabel->setAlignment(Qt::AlignCenter); + cpuSpeedLabel->setMinimumWidth(60); + speed->addWidget(label(QStringLiteral("CPU Speed")), 2, 0); + speed->addWidget(cpuSpeed, 2, 1); + speed->addWidget(cpuSpeedLabel, 2, 2); + right->addWidget(groupBox(QStringLiteral("CPU Emulation Speed"), speed)); + + QGridLayout *cycle = new QGridLayout; + cpuFrequency = combo({ + QStringLiteral("1x"), + QStringLiteral("2x (A500)"), + QStringLiteral("4x (A1200)"), + QStringLiteral("8x"), + QStringLiteral("16x"), + QStringLiteral("Custom") + }, QStringLiteral("4x (A1200)")); + cpuFrequencyCustom = new QLineEdit; + cpuFrequencyCustom->setPlaceholderText(QStringLiteral("MHz")); + cycle->addWidget(label(QStringLiteral("CPU Frequency")), 0, 0); + cycle->addWidget(cpuFrequency, 0, 1); + cycle->addWidget(cpuFrequencyCustom, 0, 2); + right->addWidget(groupBox(QStringLiteral("Cycle-exact CPU Emulation Speed"), cycle)); + + QGridLayout *jitBox = new QGridLayout; + jitCache = new QSlider(Qt::Horizontal); + jitCache->setRange(0, 8); + jitCache->setTickInterval(1); + jitCache->setTickPosition(QSlider::TicksAbove); + jitCacheLabel = new QLineEdit; + jitCacheLabel->setReadOnly(true); + jitCacheLabel->setAlignment(Qt::AlignCenter); + jitCacheLabel->setMinimumWidth(60); + jitFpu = new QCheckBox(QStringLiteral("FPU support")); + jitConstJump = new QCheckBox(QStringLiteral("Constant jump")); + jitHardFlush = new QCheckBox(QStringLiteral("Hard flush")); + jitTrust = new QButtonGroup(this); + QRadioButton *jitDirect = new QRadioButton(QStringLiteral("Direct")); + QRadioButton *jitIndirect = new QRadioButton(QStringLiteral("Indirect")); + jitTrust->addButton(jitDirect, 0); + jitTrust->addButton(jitIndirect, 1); + jitDirect->setChecked(true); + jitNoFlags = new QCheckBox(QStringLiteral("No flags")); + jitCatchFault = new QCheckBox(QStringLiteral("Catch unexpected exceptions")); + jitBox->addWidget(label(QStringLiteral("Cache size:")), 0, 0); + jitBox->addWidget(jitCache, 0, 1); + jitBox->addWidget(jitCacheLabel, 0, 2); + jitBox->addWidget(jitFpu, 1, 0); + jitBox->addWidget(jitConstJump, 1, 1); + jitBox->addWidget(jitHardFlush, 1, 2); + jitBox->addWidget(jitDirect, 2, 0); + jitBox->addWidget(jitIndirect, 2, 1); + jitBox->addWidget(jitNoFlags, 2, 2); + jitBox->addWidget(jitCatchFault, 3, 0, 1, 3); + right->addWidget(groupBox(QStringLiteral("Advanced JIT Settings"), jitBox)); + right->addStretch(); + + connect(cpu24Bit, &QCheckBox::toggled, this, [this]() { updateCpuControlState(); }); + connect(moreCompatible, &QCheckBox::toggled, this, [this]() { updateCpuControlState(); }); + connect(jit, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + if (jitCache && jitCacheSizeFromPosition(jitCache->value()) <= 0) { + jitCache->setValue(jitCachePositionFromSize(defaultJitCacheSize())); + } + setCheckBoxIfChanged(chipsetCycleExact, false); + setCheckBoxIfChanged(chipsetCycleExactMemory, false); + } + updateCpuControlState(); + }); + connect(fpuButtons, QOverload::of(&QButtonGroup::buttonClicked), this, [this]() { updateCpuControlState(); }); + connect(cpuSpeed, &QSlider::valueChanged, this, [this]() { updateCpuSpeedLabel(); }); + connect(jitCache, &QSlider::valueChanged, this, [this]() { + if (jit && jit->isChecked() && jitCacheSizeFromPosition(jitCache->value()) <= 0) { + jit->setChecked(false); + } + updateJitCacheLabel(); + updateCpuControlState(); + }); + updateFpuControls(); + updateCpuControlState(); + + root->addLayout(left, 2); + root->addLayout(right, 3); + return page; + } + + QWidget *makeChipsetPage() + { + QWidget *page = makePage(); + QGridLayout *root = new QGridLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setColumnStretch(0, 1); + root->setColumnStretch(1, 1); + + chipset = combo({ + QStringLiteral("OCS"), + QStringLiteral("ECS Agnus"), + QStringLiteral("ECS Denise"), + QStringLiteral("ECS"), + QStringLiteral("AGA"), + QStringLiteral("A1000"), + QStringLiteral("A1000 (No EHB)") + }, QStringLiteral("AGA")); + chipsetNtsc = new QCheckBox(QStringLiteral("NTSC")); + chipsetCycleExact = new QCheckBox(QStringLiteral("Cycle-exact (Full)")); + chipsetCycleExactMemory = new QCheckBox(QStringLiteral("Cycle-exact (DMA/Memory accesses)")); + chipsetCompatible = combo({ + QStringLiteral("-"), + QStringLiteral("Generic"), + QStringLiteral("CDTV"), + QStringLiteral("CDTV-CR"), + QStringLiteral("CD32"), + QStringLiteral("A500"), + QStringLiteral("A500+"), + QStringLiteral("A600"), + QStringLiteral("A1000"), + QStringLiteral("A1200"), + QStringLiteral("A2000"), + QStringLiteral("A3000"), + QStringLiteral("A3000T"), + QStringLiteral("A4000"), + QStringLiteral("A4000T"), + QStringLiteral("Velvet"), + QStringLiteral("Casablanca"), + QStringLiteral("DraCo") + }, QStringLiteral("A1200")); + QVBoxLayout *basic = new QVBoxLayout; + basic->setSpacing(6); + QGridLayout *chipsetSelection = new QGridLayout; + chipsetSelection->setHorizontalSpacing(8); + chipsetSelection->setVerticalSpacing(5); + chipsetSelection->setColumnStretch(1, 1); + chipsetSelection->addWidget(label(QStringLiteral("Chipset:")), 0, 0); + chipsetSelection->addWidget(chipset, 0, 1); + chipsetSelection->addWidget(chipsetNtsc, 0, 2); + chipsetSelection->addWidget(label(QStringLiteral("Chipset Extra:")), 1, 0); + chipsetSelection->addWidget(chipsetCompatible, 1, 1, 1, 2); + basic->addLayout(chipsetSelection); + basic->addWidget(chipsetCycleExact); + basic->addWidget(chipsetCycleExactMemory); + root->addWidget(groupBox(QStringLiteral("Chipset"), basic), 0, 0); + + immediateBlits = new QCheckBox(QStringLiteral("Immediate Blitter")); + waitingBlits = new QCheckBox(QStringLiteral("Wait for Blitter")); + displayOptimization = combo({ + QStringLiteral("Full"), + QStringLiteral("Partial"), + QStringLiteral("None") + }, QStringLiteral("Full")); + chipsetSyncMode = combo(configChoiceDisplays(hvSyncChoices, int(sizeof(hvSyncChoices) / sizeof(hvSyncChoices[0])))); + QGridLayout *options = new QGridLayout; + options->setColumnStretch(1, 1); + options->addWidget(immediateBlits, 0, 0, 1, 2); + options->addWidget(waitingBlits, 1, 0, 1, 2); + options->addWidget(label(QStringLiteral("Optimizations:")), 2, 0); + options->addWidget(displayOptimization, 2, 1); + options->addWidget(label(QStringLiteral("H/V sync mode:")), 3, 0); + options->addWidget(chipsetSyncMode, 3, 1); + root->addWidget(groupBox(QStringLiteral("Options"), options), 0, 1); + + collisionButtons = new QButtonGroup(this); + QGridLayout *collision = new QGridLayout; + const QStringList collisions = { + QStringLiteral("None"), + QStringLiteral("Sprites only"), + QStringLiteral("Sprites and Sprites vs. Playfield"), + QStringLiteral("Full") + }; + for (int i = 0; i < collisions.size(); i++) { + QRadioButton *button = new QRadioButton(collisions[i]); + collisionButtons->addButton(button, i); + collision->addWidget(button, i / 2, i % 2); + if (i == 3) { + button->setChecked(true); + } + } + root->addWidget(groupBox(QStringLiteral("Collision Level"), collision), 1, 0, 1, 2); + + keyboardMode = combo(configChoiceDisplays(keyboardModeChoices, int(sizeof(keyboardModeChoices) / sizeof(keyboardModeChoices[0]))), QStringLiteral("UAE High level emulation")); + keyboardNkro = new QCheckBox(QStringLiteral("Keyboard N-key rollover")); + QGridLayout *keyboard = new QGridLayout; + keyboard->setColumnStretch(0, 1); + keyboard->addWidget(keyboardMode, 0, 0); + keyboard->addWidget(keyboardNkro, 1, 0); + root->addWidget(groupBox(QStringLiteral("Keyboard"), keyboard), 2, 0, 1, 2); + + genlockConnected = new QCheckBox(QStringLiteral("Genlock connected")); + genlockMode = combo(configChoiceDisplays(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])))); + genlockMix = combo({ + QStringLiteral("100%"), + QStringLiteral("90%"), + QStringLiteral("80%"), + QStringLiteral("70%"), + QStringLiteral("60%"), + QStringLiteral("50%"), + QStringLiteral("40%"), + QStringLiteral("30%"), + QStringLiteral("20%"), + QStringLiteral("10%"), + QStringLiteral("0%") + }); + genlockAlpha = new QCheckBox(QStringLiteral("Include alpha channel in screenshots and video captures")); + genlockKeepAspect = new QCheckBox(QStringLiteral("Keep aspect ratio")); + genlockFile = pathCombo(); + genlockFileBrowse = smallButton(QStringLiteral("...")); + QGridLayout *genlock = new QGridLayout; + genlock->setColumnStretch(1, 1); + genlock->addWidget(genlockConnected, 0, 0); + genlock->addWidget(genlockMode, 0, 1); + genlock->addWidget(genlockMix, 0, 2); + genlock->addWidget(genlockAlpha, 1, 0, 1, 3); + genlock->addWidget(genlockKeepAspect, 2, 0, 1, 3); + genlock->addWidget(genlockFile, 3, 0, 1, 2); + genlock->addWidget(genlockFileBrowse, 3, 2); + root->addWidget(groupBox(QStringLiteral("Genlock"), genlock), 3, 0, 1, 2); + root->setRowStretch(4, 1); + + connect(chipsetNtsc, &QCheckBox::toggled, this, [this](bool checked) { + setCheckBoxIfChanged(ntsc, checked); + }); + connect(ntsc, &QCheckBox::toggled, this, [this](bool checked) { + setCheckBoxIfChanged(chipsetNtsc, checked); + }); + connect(chipsetCycleExact, &QCheckBox::toggled, this, [this](bool checked) { + if (checked && jit && jit->isChecked()) { + setCheckBoxIfChanged(chipsetCycleExact, false); + return; + } + if (checked) { + chipsetCycleExactMemory->setChecked(true); + } + updateCpuControlState(); + }); + connect(chipsetCycleExactMemory, &QCheckBox::toggled, this, [this](bool checked) { + if (checked && jit && jit->isChecked()) { + setCheckBoxIfChanged(chipsetCycleExactMemory, false); + return; + } + if (!checked) { + chipsetCycleExact->setChecked(false); + } + updateCpuControlState(); + }); + connect(immediateBlits, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + waitingBlits->setChecked(false); + } + }); + connect(waitingBlits, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + immediateBlits->setChecked(false); + } + }); + connect(genlockConnected, &QCheckBox::toggled, this, [this]() { updateGenlockControlState(); }); + connect(genlockMode, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { updateGenlockControlState(); }); + connect(genlockFile, &QComboBox::currentTextChanged, this, [this]() { storeGenlockFilePath(); }); + connect(genlockFileBrowse, &QPushButton::clicked, this, [this]() { + const QString mode = configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText()); + if (genlockModeUsesImageFile(mode)) { + addBrowse(genlockFile, this, QStringLiteral("Select genlock image"), QStringLiteral("Images (*.png *.bmp *.jpg *.jpeg);;All files (*)")); + } else if (genlockModeUsesVideoFile(mode)) { + addBrowse(genlockFile, this, QStringLiteral("Select genlock video"), QStringLiteral("Video files (*.avi *.mp4 *.mov *.mkv);;All files (*)")); + } + storeGenlockFilePath(); + }); + updateGenlockControlState(); + return page; + } + + int genlockMixConfigValue() const + { + if (!genlockMix) { + return 0; + } + const int index = genlockMix->currentIndex(); + return index >= 10 ? 255 : qBound(0, index, 10) * 25; + } + + void setGenlockMixConfigValue(int value) + { + if (!genlockMix) { + return; + } + const int index = value >= 250 ? 10 : qBound(0, value / 25, 10); + genlockMix->setCurrentIndex(index); + } + + void storeGenlockFilePath() + { + if (genlockUpdating || !genlockMode || !genlockFile) { + return; + } + const QString mode = configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText()); + if (genlockModeUsesImageFile(mode)) { + genlockImagePath = genlockFile->currentText().trimmed(); + } else if (genlockModeUsesVideoFile(mode)) { + genlockVideoPath = genlockFile->currentText().trimmed(); + } + } + + void updateGenlockControlState() + { + if (!genlockConnected || !genlockMode || !genlockMix || !genlockAlpha || !genlockKeepAspect || !genlockFile || !genlockFileBrowse) { + return; + } + + const bool connected = genlockConnected->isChecked(); + const QString mode = configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText()); + const bool usesImage = genlockModeUsesImageFile(mode); + const bool usesVideo = genlockModeUsesVideoFile(mode); + const bool usesFile = connected && (usesImage || usesVideo); + + genlockMode->setEnabled(connected); + genlockMix->setEnabled(connected); + genlockAlpha->setEnabled(connected); + genlockKeepAspect->setEnabled(connected); + genlockFile->setEnabled(usesFile); + genlockFileBrowse->setEnabled(usesFile); + + genlockUpdating = true; + setPathComboText(genlockFile, usesImage ? genlockImagePath : (usesVideo ? genlockVideoPath : QString())); + genlockUpdating = false; + } + + QWidget *makeAdvancedChipsetPage() + { + QWidget *page = makePage(); + QVBoxLayout *outer = new QVBoxLayout(page); + outer->setContentsMargins(0, 0, 0, 0); + + QWidget *content = new QWidget; + QVBoxLayout *root = new QVBoxLayout(content); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + advancedCompatible = new QCheckBox(QStringLiteral("Compatible Settings")); + root->addWidget(advancedCompatible); + + advancedRtcAdjust = new QLineEdit; + advancedRtcAdjust->setFixedWidth(64); + advancedRtcAdjust->setValidator(new QIntValidator(-100000, 100000, this)); + QGridLayout *rtc = new QGridLayout; + advancedRtcButtons = radioGroup(rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), 0, this, rtc); + rtc->addWidget(label(QStringLiteral("Adjust:")), 0, 4); + rtc->addWidget(advancedRtcAdjust, 0, 5); + rtc->setColumnStretch(6, 1); + root->addWidget(groupBox(QStringLiteral("RTC"), rtc)); + + QGridLayout *tod = new QGridLayout; + advancedCiaTodButtons = radioGroup(ciaTodChoices, int(sizeof(ciaTodChoices) / sizeof(ciaTodChoices[0])), 0, this, tod); + tod->setColumnStretch(3, 1); + root->addWidget(groupBox(QStringLiteral("CIA-A TOD"), tod)); + + advancedCheckBoxes.clear(); + auto featureCheck = [this](const QString &text, const QString &key) { + QCheckBox *box = new QCheckBox(text); + advancedCheckBoxes.insert(key, box); + return box; + }; + auto disabledFeature = [](const QString &text) { + QCheckBox *box = new QCheckBox(text); + disableUnavailable(box, QStringLiteral("This hardware option is not implemented by the Unix chipset backend yet.")); + return box; + }; + + advancedIdeA600A1200 = new QCheckBox(QStringLiteral("A600/A1200 IDE")); + advancedIdeA4000 = new QCheckBox(QStringLiteral("A4000/A4000T IDE")); + advancedCia391078 = new QCheckBox(QStringLiteral("CIA 391078-01")); + QGridLayout *features = new QGridLayout; + features->setColumnStretch(0, 1); + features->setColumnStretch(1, 1); + features->setColumnStretch(2, 1); + features->addWidget(featureCheck(QStringLiteral("CIA ROM Overlay"), QStringLiteral("cia_overlay")), 0, 0); + features->addWidget(featureCheck(QStringLiteral("CD32 CD"), QStringLiteral("cd32cd")), 1, 0); + features->addWidget(featureCheck(QStringLiteral("CDTV CD"), QStringLiteral("cdtvcd")), 2, 0); + features->addWidget(advancedIdeA600A1200, 3, 0); + features->addWidget(featureCheck(QStringLiteral("ROM Mirror (E0)"), QStringLiteral("ksmirror_e0")), 4, 0); + features->addWidget(featureCheck(QStringLiteral("KB Reset Warning"), QStringLiteral("resetwarning")), 5, 0); + features->addWidget(featureCheck(QStringLiteral("CIA TOD bug"), QStringLiteral("cia_todbug")), 6, 0); + features->addWidget(featureCheck(QStringLiteral("1M Chip / 0.5M+0.5M"), QStringLiteral("1mchipjumper")), 7, 0); + features->addWidget(featureCheck(QStringLiteral("Toshiba Gary"), QStringLiteral("toshiba_gary")), 8, 0); + features->addWidget(featureCheck(QStringLiteral("A1000 Boot RAM/ROM"), QStringLiteral("a1000ram")), 0, 1); + features->addWidget(featureCheck(QStringLiteral("CD32 C2P"), QStringLiteral("cd32c2p")), 1, 1); + features->addWidget(featureCheck(QStringLiteral("CDTV SRAM"), QStringLiteral("cdtvram")), 2, 1); + features->addWidget(advancedIdeA4000, 3, 1); + features->addWidget(featureCheck(QStringLiteral("ROM Mirror (A8)"), QStringLiteral("ksmirror_a8")), 4, 1); + features->addWidget(featureCheck(QStringLiteral("Z3 Autoconfig"), QStringLiteral("z3_autoconfig")), 5, 1); + features->addWidget(disabledFeature(QStringLiteral("Custom register byte write bug")), 6, 1); + features->addWidget(featureCheck(QStringLiteral("KS ROM has Chip RAM speed"), QStringLiteral("rom_is_slow")), 7, 1); + features->addWidget(featureCheck(QStringLiteral("DF0: ID Hardware"), QStringLiteral("df0idhw")), 0, 2); + features->addWidget(featureCheck(QStringLiteral("CD32 NVRAM"), QStringLiteral("cd32nvram")), 1, 2); + features->addWidget(featureCheck(QStringLiteral("CDTV-CR"), QStringLiteral("cdtv-cr")), 2, 2); + features->addWidget(featureCheck(QStringLiteral("PCMCIA"), QStringLiteral("pcmcia")), 3, 2); + features->addWidget(featureCheck(QStringLiteral("Composite color burst"), QStringLiteral("color_burst")), 4, 2); + features->addWidget(advancedCia391078, 5, 2); + features->addWidget(featureCheck(QStringLiteral("Power up memory pattern"), QStringLiteral("memory_pattern")), 6, 2); + root->addWidget(groupBox(QStringLiteral("Chipset Features"), features)); + + advancedUnmappedAddress = combo(configChoiceDisplays(unmappedAddressChoices, int(sizeof(unmappedAddressChoices) / sizeof(unmappedAddressChoices[0]))), QStringLiteral("Floating")); + advancedCiaSync = combo(configChoiceDisplays(ciaSyncChoices, int(sizeof(ciaSyncChoices) / sizeof(ciaSyncChoices[0]))), QStringLiteral("Autoselect")); + advancedScsiA3000 = new QCheckBox(QStringLiteral("A3000 WD33C93 SCSI")); + advancedScsiA4000T = new QCheckBox(QStringLiteral("A4000T NCR53C710 SCSI")); + QGridLayout *lowLevel = new QGridLayout; + lowLevel->setColumnStretch(1, 1); + lowLevel->setColumnStretch(3, 1); + lowLevel->addWidget(label(QStringLiteral("Unmapped address space:")), 0, 0); + lowLevel->addWidget(advancedUnmappedAddress, 0, 1); + lowLevel->addWidget(label(QStringLiteral("CIA E-Clock Sync:")), 0, 2); + lowLevel->addWidget(advancedCiaSync, 0, 3); + lowLevel->addWidget(label(QStringLiteral("Internal SCSI Hardware:")), 1, 0); + lowLevel->addWidget(advancedScsiA3000, 1, 1); + lowLevel->addWidget(advancedScsiA4000T, 1, 2, 1, 2); + root->addWidget(groupBox(QStringLiteral("Low Level"), lowLevel)); + + advancedRamsey = new QCheckBox(QStringLiteral("Ramsey revision:")); + advancedRamseyRevision = new QLineEdit; + advancedRamseyRevision->setFixedWidth(45); + advancedFatGary = new QCheckBox(QStringLiteral("Fat Gary revision:")); + advancedFatGaryRevision = new QLineEdit; + advancedFatGaryRevision->setFixedWidth(45); + QRegularExpressionValidator *hexByte = new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9A-Fa-f]{0,2}")), this); + advancedRamseyRevision->setValidator(hexByte); + advancedFatGaryRevision->setValidator(hexByte); + advancedAgnusModel = combo(configChoiceDisplays(agnusModelChoices, int(sizeof(agnusModelChoices) / sizeof(agnusModelChoices[0]))), QStringLiteral("Auto")); + advancedAgnusSize = combo(configChoiceDisplays(agnusSizeChoices, int(sizeof(agnusSizeChoices) / sizeof(agnusSizeChoices[0]))), QStringLiteral("Auto")); + advancedDeniseModel = combo(configChoiceDisplays(deniseModelChoices, int(sizeof(deniseModelChoices) / sizeof(deniseModelChoices[0]))), QStringLiteral("Auto")); + QGridLayout *revision = new QGridLayout; + revision->setColumnStretch(5, 1); + revision->addWidget(advancedRamsey, 0, 0); + revision->addWidget(advancedRamseyRevision, 0, 1); + revision->addWidget(label(QStringLiteral("Agnus/Alice model:")), 0, 2); + revision->addWidget(advancedAgnusModel, 0, 3); + revision->addWidget(advancedAgnusSize, 0, 4); + revision->addWidget(advancedFatGary, 1, 0); + revision->addWidget(advancedFatGaryRevision, 1, 1); + revision->addWidget(label(QStringLiteral("Denise/Lisa model:")), 1, 2); + revision->addWidget(advancedDeniseModel, 1, 3, 1, 2); + root->addWidget(groupBox(QStringLiteral("Chipset Revision"), revision)); + root->addStretch(1); + + QScrollArea *scroll = new QScrollArea; + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidgetResizable(true); + scroll->setWidget(content); + outer->addWidget(scroll); + + connect(advancedCompatible, &QCheckBox::toggled, this, [this](bool checked) { + if (checked && chipsetCompatible->currentText() == QStringLiteral("-")) { + chipsetCompatible->setCurrentText(QStringLiteral("Generic")); + } else if (!checked && chipsetCompatible->currentText() != QStringLiteral("-")) { + chipsetCompatible->setCurrentText(QStringLiteral("-")); + } + }); + connect(chipsetCompatible, &QComboBox::currentTextChanged, this, [this](const QString &text) { + setCheckBoxIfChanged(advancedCompatible, text != QStringLiteral("-")); + applyAdvancedChipsetPreset(text); + }); + connect(advancedIdeA600A1200, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + advancedIdeA4000->setChecked(false); + } + }); + connect(advancedIdeA4000, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + advancedIdeA600A1200->setChecked(false); + } + }); + auto updateRevisionState = [this]() { + advancedRamseyRevision->setEnabled(advancedRamsey->isChecked()); + advancedFatGaryRevision->setEnabled(advancedFatGary->isChecked()); + }; + connect(advancedRamsey, &QCheckBox::toggled, this, updateRevisionState); + connect(advancedFatGary, &QCheckBox::toggled, this, updateRevisionState); + + applyAdvancedChipsetPreset(chipsetCompatible->currentText()); + updateRevisionState(); + return page; + } + + QWidget *makeRomPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + romFile = pathCombo(); + extendedRomFile = pathCombo(); + mapRom = new QCheckBox(QStringLiteral("MapROM emulation")); + kickShifter = new QCheckBox(QStringLiteral("ShapeShifter support")); + QVBoxLayout *system = new QVBoxLayout; + system->setSpacing(5); + + QPushButton *romBrowse = smallButton(QStringLiteral("...")); + QHBoxLayout *romRow = new QHBoxLayout; + romRow->setContentsMargins(0, 0, 0, 0); + romRow->addWidget(romFile, 1); + romRow->addWidget(romBrowse); + system->addWidget(new QLabel(QStringLiteral("Main ROM file:"))); + system->addLayout(romRow); + connect(romBrowse, &QPushButton::clicked, this, [this]() { + addBrowse(romFile, this, QStringLiteral("Select main ROM file"), QStringLiteral("ROM files (*.rom *.bin);;All files (*)")); + }); + + QPushButton *extendedRomBrowse = smallButton(QStringLiteral("...")); + QHBoxLayout *extendedRomRow = new QHBoxLayout; + extendedRomRow->setContentsMargins(0, 0, 0, 0); + extendedRomRow->addWidget(extendedRomFile, 1); + extendedRomRow->addWidget(extendedRomBrowse); + system->addWidget(new QLabel(QStringLiteral("Extended ROM file:"))); + system->addLayout(extendedRomRow); + connect(extendedRomBrowse, &QPushButton::clicked, this, [this]() { + addBrowse(extendedRomFile, this, QStringLiteral("Select extended ROM file"), QStringLiteral("ROM files (*.rom *.bin);;All files (*)")); + }); + + QHBoxLayout *romOptions = new QHBoxLayout; + romOptions->setContentsMargins(0, 2, 0, 0); + romOptions->addStretch(); + romOptions->addWidget(mapRom); + romOptions->addWidget(kickShifter); + romOptions->addStretch(); + system->addLayout(romOptions); + root->addWidget(groupBox(QStringLiteral("System ROM Settings"), system)); + + QGridLayout *advanced = new QGridLayout; + customRomSelect = combo({}); + for (int i = 0; i < MaxRomBoards; i++) { + customRomSelect->addItem(QStringLiteral("ROM #%1").arg(i + 1)); + } + customRomStart = new QLineEdit; + customRomEnd = new QLineEdit; + customRomFile = new QLineEdit; + QPushButton *customRomBrowse = smallButton(QStringLiteral("...")); + advanced->setColumnStretch(3, 1); + advanced->addWidget(customRomSelect, 0, 0); + advanced->addWidget(label(QStringLiteral("Address range")), 0, 1); + advanced->addWidget(customRomStart, 0, 2); + advanced->addWidget(customRomEnd, 0, 3); + advanced->addWidget(customRomFile, 1, 0, 1, 4); + advanced->addWidget(customRomBrowse, 1, 4); + root->addWidget(groupBox(QStringLiteral("Advanced Custom ROM Settings"), advanced)); + + cartFile = pathCombo(); + flashFile = new QLineEdit; + rtcFile = new QLineEdit; + QGridLayout *misc = new QGridLayout; + misc->setColumnStretch(1, 1); + addPathRow(misc, 0, QStringLiteral("Cartridge ROM file:"), cartFile, QStringLiteral("Select cartridge ROM file"), QStringLiteral("ROM files (*.rom *.bin);;All files (*)")); + addLineBrowseRow(misc, 2, QStringLiteral("Flash RAM or A2286/A2386SX BIOS CMOS RAM file:"), flashFile); + addLineBrowseRow(misc, 3, QStringLiteral("Real Time Clock file"), rtcFile); + root->addWidget(groupBox(QStringLiteral("Miscellaneous"), misc), 1); + + uaeBoardType = combo({ + QStringLiteral("ROM disabled"), + QStringLiteral("Original UAE (FS + F0 ROM)"), + QStringLiteral("New UAE (64k + F0 ROM)"), + QStringLiteral("New UAE (128k, ROM, Direct)"), + QStringLiteral("New UAE (128k, ROM, Indirect)") + }, QStringLiteral("Original UAE (FS + F0 ROM)")); + QGridLayout *uaeBoard = new QGridLayout; + uaeBoard->setColumnStretch(1, 1); + uaeBoard->addWidget(label(QStringLiteral("Board type:")), 0, 0); + uaeBoard->addWidget(uaeBoardType, 0, 1); + root->addWidget(groupBox(QStringLiteral("Advanced UAE expansion board/Boot ROM Settings"), uaeBoard)); + + connect(customRomSelect, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (customRomUpdating) { + return; + } + storeCurrentCustomRomBoard(); + currentCustomRomBoard = qBound(0, index, MaxRomBoards - 1); + loadCurrentCustomRomBoard(); + }); + connect(customRomStart, &QLineEdit::textChanged, this, [this](const QString &) { storeCurrentCustomRomBoard(); }); + connect(customRomEnd, &QLineEdit::textChanged, this, [this](const QString &) { storeCurrentCustomRomBoard(); }); + connect(customRomFile, &QLineEdit::textChanged, this, [this](const QString &) { storeCurrentCustomRomBoard(); }); + connect(customRomBrowse, &QPushButton::clicked, this, [this]() { + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select custom ROM file"), fileDialogInitialPath(customRomFile->text()), QStringLiteral("ROM files (*.rom *.bin);;All files (*)")); + if (selected.isEmpty()) { + return; + } + customRomFile->setText(selected); + bool ok = false; + QString startText = customRomStart->text().trimmed(); + if (startText.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive)) { + startText = startText.mid(2); + } + const quint32 start = startText.toUInt(&ok, 16); + if (ok && customRomEnd->text().trimmed().isEmpty()) { + const qint64 size = QFileInfo(selected).size(); + if (size > 0) { + const quint32 end = ((start + quint32(size) - 1) & ~quint32(0xffff)) | quint32(0xffff); + customRomEnd->setText(QStringLiteral("%1").arg(end, 8, 16, QLatin1Char('0'))); + } + } + storeCurrentCustomRomBoard(); + }); + clearCustomRomBoards(); + return page; + } + + QWidget *makeMemoryPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + + chipMem = combo({ QStringLiteral("512 KB"), QStringLiteral("1 MB"), QStringLiteral("2 MB"), QStringLiteral("4 MB"), QStringLiteral("8 MB") }, QStringLiteral("2 MB")); + z2Fast = combo({ QStringLiteral("None"), QStringLiteral("1 MB"), QStringLiteral("2 MB"), QStringLiteral("4 MB"), QStringLiteral("8 MB"), QStringLiteral("16 MB") }); + slowMem = combo({ QStringLiteral("None"), QStringLiteral("512 KB"), QStringLiteral("1 MB"), QStringLiteral("1.5 MB"), QStringLiteral("1.8 MB") }); + z3Fast = combo({ QStringLiteral("None"), QStringLiteral("1 MB"), QStringLiteral("2 MB"), QStringLiteral("4 MB"), QStringLiteral("8 MB"), QStringLiteral("16 MB"), QStringLiteral("32 MB"), QStringLiteral("64 MB"), QStringLiteral("128 MB"), QStringLiteral("256 MB"), QStringLiteral("512 MB"), QStringLiteral("1024 MB") }); + z3ChipMem = combo({ QStringLiteral("None"), QStringLiteral("16 MB"), QStringLiteral("32 MB"), QStringLiteral("64 MB"), QStringLiteral("128 MB"), QStringLiteral("256 MB"), QStringLiteral("384 MB"), QStringLiteral("512 MB"), QStringLiteral("768 MB"), QStringLiteral("1024 MB") }); + processorSlotMem = combo({ QStringLiteral("None"), QStringLiteral("1 MB"), QStringLiteral("2 MB"), QStringLiteral("4 MB"), QStringLiteral("8 MB"), QStringLiteral("16 MB"), QStringLiteral("32 MB"), QStringLiteral("64 MB"), QStringLiteral("128 MB") }); + + QGridLayout *settings = new QGridLayout; + settings->setColumnStretch(1, 1); + settings->setColumnStretch(3, 1); + settings->addWidget(label(QStringLiteral("Chip:")), 0, 0); + settings->addWidget(chipMem, 0, 1); + settings->addWidget(label(QStringLiteral("Slow:")), 0, 2); + settings->addWidget(slowMem, 0, 3); + settings->addWidget(label(QStringLiteral("Z2 Fast:")), 1, 0); + settings->addWidget(z2Fast, 1, 1); + settings->addWidget(label(QStringLiteral("Z3 Fast:")), 1, 2); + settings->addWidget(z3Fast, 1, 3); + settings->addWidget(label(QStringLiteral("Processor slot:")), 2, 0); + settings->addWidget(processorSlotMem, 2, 1); + settings->addWidget(label(QStringLiteral("32-bit Chip:")), 2, 2); + settings->addWidget(z3ChipMem, 2, 3); + root->addWidget(groupBox(QStringLiteral("Memory Settings"), settings)); + + QGridLayout *advanced = new QGridLayout; + z3Mapping = combo(configChoiceDisplays(z3MappingChoices, int(sizeof(z3MappingChoices) / sizeof(z3MappingChoices[0])))); + advanced->setColumnStretch(1, 1); + advanced->setColumnStretch(3, 1); + advanced->addWidget(combo({ QStringLiteral("None"), QStringLiteral("Custom memory") }), 0, 0, 1, 2); + advanced->addWidget(label(QStringLiteral("Z3 mapping mode:")), 0, 2); + advanced->addWidget(z3Mapping, 0, 3); + advanced->addWidget(label(QStringLiteral("Manufacturer")), 1, 0); + advanced->addWidget(new QLineEdit, 1, 1); + advanced->addWidget(label(QStringLiteral("Product")), 1, 2); + advanced->addWidget(new QLineEdit, 1, 3); + advanced->addWidget(new QCheckBox(QStringLiteral("Manual configuration")), 2, 1); + advanced->addWidget(new QCheckBox(QStringLiteral("DMA Capable")), 2, 2); + root->addWidget(groupBox(QStringLiteral("Advanced Memory Settings"), advanced), 1); + return page; + } + + QWidget *makeFloppyPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + QVBoxLayout *drives = new QVBoxLayout; + drives->setSpacing(10); + for (int i = 0; i < 4; i++) { + addFloppyRow(drives, i); + } + drives->addStretch(); + root->addWidget(groupBox(QStringLiteral("Floppy Drives"), drives), 1); + floppySpeed = new QSlider(Qt::Horizontal); + floppySpeed->setRange(0, 4); + floppySpeed->setTickInterval(1); + floppySpeed->setTickPosition(QSlider::TicksAbove); + floppySpeedLabel = new QLineEdit; + floppySpeedLabel->setReadOnly(true); + floppySpeedLabel->setAlignment(Qt::AlignCenter); + floppySpeedLabel->setMinimumWidth(130); + QGridLayout *speed = new QGridLayout; + speed->addWidget(floppySpeed, 0, 0); + speed->addWidget(floppySpeedLabel, 0, 1); + root->addWidget(groupBox(QStringLiteral("Floppy Drive Emulation Speed"), speed)); + connect(floppySpeed, &QSlider::valueChanged, this, [this]() { + updateFloppySpeedLabel(); + }); + return page; + } + + void addFloppyRow(QVBoxLayout *layout, int drive) + { + dfEnable[drive] = new QCheckBox(QStringLiteral("DF%1:").arg(drive)); + dfType[drive] = combo(floppyTypeItems(drive, false)); + dfPath[drive] = pathCombo(); + dfWriteProtect[drive] = new QCheckBox; + QPushButton *info = smallButton(QStringLiteral("?")); + QPushButton *eject = new QPushButton(QStringLiteral("Eject")); + QPushButton *browse = smallButton(QStringLiteral("...")); + + QWidget *driveWidget = new QWidget; + QVBoxLayout *driveLayout = new QVBoxLayout(driveWidget); + driveLayout->setContentsMargins(0, 0, 0, 0); + driveLayout->setSpacing(4); + + QHBoxLayout *controls = new QHBoxLayout; + controls->setContentsMargins(0, 0, 0, 0); + controls->setSpacing(6); + controls->addWidget(dfEnable[drive]); + controls->addWidget(dfType[drive]); + controls->addWidget(new QLabel(QStringLiteral("Write-protected"))); + controls->addWidget(dfWriteProtect[drive]); + controls->addWidget(info); + controls->addWidget(eject); + controls->addWidget(browse); + controls->addStretch(); + driveLayout->addLayout(controls); + driveLayout->addWidget(dfPath[drive]); + layout->addWidget(driveWidget); + dfEnable[drive]->setChecked(drive == 0); + connect(info, &QPushButton::clicked, this, [this, drive]() { + showFloppyInfo(dfPath[drive] ? dfPath[drive]->currentText() : QString(), drive); + }); + connect(browse, &QPushButton::clicked, this, [this, drive]() { + addBrowse(dfPath[drive], this, QStringLiteral("Select floppy image"), floppyDiskImageFilter()); + }); + connect(eject, &QPushButton::clicked, this, [this, drive]() { + dfPath[drive]->setCurrentText(QString()); + }); + connect(dfPath[drive], &QComboBox::currentTextChanged, this, [this](const QString &) { + updateDiskSwapperDriveColumn(); + }); + if (drive < 2 && quickDfPath[drive]) { + connect(dfPath[drive], &QComboBox::currentTextChanged, this, [this, drive](const QString &text) { + if (quickDfPath[drive]->currentText() != text) { + setPathComboText(quickDfPath[drive], text); + } + }); + connect(quickDfPath[drive], &QComboBox::currentTextChanged, this, [this, drive](const QString &text) { + if (dfPath[drive]->currentText() != text) { + setPathComboText(dfPath[drive], text); + } + }); + connect(dfEnable[drive], &QCheckBox::toggled, quickDfEnable[drive], &QCheckBox::setChecked); + connect(quickDfEnable[drive], &QCheckBox::toggled, dfEnable[drive], &QCheckBox::setChecked); + connect(dfType[drive], QOverload::of(&QComboBox::currentIndexChanged), this, [this, drive]() { + syncFloppyDriveToQuick(drive); + }); + connect(quickDfType[drive], QOverload::of(&QComboBox::currentIndexChanged), this, [this, drive]() { + syncQuickDriveToFloppy(drive); + }); + connect(dfWriteProtect[drive], &QCheckBox::toggled, this, [this, drive]() { + syncFloppyDriveToQuick(drive); + }); + connect(quickDfWriteProtect[drive], &QCheckBox::toggled, this, [this, drive]() { + syncQuickDriveToFloppy(drive); + }); + } + } + + QString floppyInfoText(const QString &path, int drive) const + { + QStringList lines; + lines << QStringLiteral("'%1'").arg(path); + + QFileInfo info(path); + if (!info.exists()) { + lines << QStringLiteral("Disk readable: No"); + lines << QStringLiteral("Error: File does not exist"); + return lines.join(QLatin1Char('\n')); + } + + lines << QStringLiteral("Drive: DF%1").arg(drive); + lines << QStringLiteral("Size: %1").arg(formatByteSize(info.size())); + lines << QStringLiteral("Modified: %1").arg(QLocale().toString(info.lastModified(), QLocale::ShortFormat)); + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + lines << QStringLiteral("Disk readable: No"); + lines << QStringLiteral("Error: %1").arg(file.errorString()); + return lines.join(QLatin1Char('\n')); + } + + lines << QStringLiteral("Disk readable: Yes"); + + quint32 imageCrc = 0; + QString imageCrcError; + if (crc32ForFile(path, &imageCrc, &imageCrcError)) { + lines << QStringLiteral("Disk CRC32: %1").arg(hex32(imageCrc)); + } else { + lines << QStringLiteral("Disk CRC32: unavailable (%1)").arg(imageCrcError); + } + + if (!isPlainSectorFloppyImage(info)) { + lines << QStringLiteral("Boot block: unsupported image container"); + return lines.join(QLatin1Char('\n')); + } + + const QByteArray bootBlock = file.read(1024); + if (bootBlock.size() < 1024) { + lines << QStringLiteral("Boot block: unavailable"); + return lines.join(QLatin1Char('\n')); + } + + lines << QStringLiteral("Boot block CRC32: %1").arg(hex32(crc32ForBytes(bootBlock))); + lines << QStringLiteral("Boot block checksum valid: %1").arg(amigaBootBlockChecksumValid(bootBlock) ? QStringLiteral("Yes") : QStringLiteral("No")); + lines << QStringLiteral("Boot block type: %1").arg(amigaBootBlockType(bootBlock)); + + const quint32 rootBlock = readBe32(bootBlock, 8); + if (rootBlock > 0 && file.seek(qint64(rootBlock) * 512)) { + const QString label = amigaRootBlockLabel(file.read(512)); + if (!label.isEmpty()) { + lines << QStringLiteral("Label: '%1'").arg(label); + } + } + + lines << QString(); + for (int i = 0; i < bootBlock.size(); i += 32) { + QString hex; + QString ascii; + for (int j = 0; j < 32; j++) { + const int index = i + j; + if (index >= bootBlock.size()) { + hex += QStringLiteral(" "); + ascii += QLatin1Char(' '); + continue; + } + const quint8 value = quint8(bootBlock[index]); + hex += QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0')).toUpper(); + ascii += (value >= 32 && value < 127) ? QChar(ushort(value)) : QLatin1Char('.'); + } + lines << QStringLiteral("%1 %2").arg(hex, -64).arg(ascii); + } + + return lines.join(QLatin1Char('\n')); + } + + void showFloppyInfo(const QString &rawPath, int drive) + { + const QString path = rawPath.trimmed(); + if (path.isEmpty()) { + QMessageBox::information(this, windowTitle(), QStringLiteral("No disk image is selected for DF%1.").arg(drive)); + return; + } + + QDialog dialog(this); + dialog.setWindowTitle(QStringLiteral("Disk information")); + dialog.resize(760, 540); + + QVBoxLayout *layout = new QVBoxLayout(&dialog); + QPlainTextEdit *text = new QPlainTextEdit(&dialog); + text->setReadOnly(true); + text->setLineWrapMode(QPlainTextEdit::NoWrap); + text->setPlainText(floppyInfoText(path, drive)); + QFont fixed = QFontDatabase::systemFont(QFontDatabase::FixedFont); + if (fixed.pointSize() > 0) { + fixed.setPointSize(fixed.pointSize() + 1); + } + text->setFont(fixed); + layout->addWidget(text, 1); + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + layout->addWidget(buttons); + + dialog.exec(); + } + + QWidget *makeDiskSwapperPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + diskSwapperList = new QTableWidget(MaxDiskSwapperSlots, 3); + diskSwapperList->setSelectionBehavior(QAbstractItemView::SelectRows); + diskSwapperList->setSelectionMode(QAbstractItemView::SingleSelection); + diskSwapperList->setEditTriggers(QAbstractItemView::NoEditTriggers); + diskSwapperList->verticalHeader()->hide(); + diskSwapperList->setHorizontalHeaderLabels({ QStringLiteral("#"), QStringLiteral("Disk image"), QStringLiteral("Drive") }); + diskSwapperList->horizontalHeader()->setStretchLastSection(false); + diskSwapperList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + diskSwapperList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + diskSwapperList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + for (int row = 0; row < MaxDiskSwapperSlots; row++) { + QTableWidgetItem *slot = new QTableWidgetItem(QString::number(row + 1)); + slot->setFlags(slot->flags() & ~Qt::ItemIsEditable); + diskSwapperList->setItem(row, 0, slot); + setDiskSwapperPath(row, QString()); + } + root->addWidget(diskSwapperList, 1); + + QHBoxLayout *pathRow = new QHBoxLayout; + diskSwapperPath = pathCombo(); + QPushButton *browse = smallButton(QStringLiteral("...")); + pathRow->addWidget(diskSwapperPath, 1); + pathRow->addWidget(browse); + root->addLayout(pathRow); + + QHBoxLayout *buttons = new QHBoxLayout; + diskSwapperInsertButton = new QPushButton(QStringLiteral("Insert floppy disk image")); + diskSwapperRemoveButton = new QPushButton(QStringLiteral("Remove floppy disk image")); + diskSwapperRemoveAllButton = new QPushButton(QStringLiteral("Remove all")); + buttons->addWidget(diskSwapperInsertButton); + buttons->addWidget(diskSwapperRemoveButton); + buttons->addWidget(diskSwapperRemoveAllButton); + root->addLayout(buttons); + + connect(diskSwapperList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [this](const QModelIndex &, const QModelIndex &) { + updateDiskSwapperSelection(); + }); + connect(diskSwapperList, &QTableWidget::cellDoubleClicked, this, [this](int row, int column) { + if (column == 1) { + browseDiskSwapperImage(row); + } + }); + connect(browse, &QPushButton::clicked, this, [this]() { + browseDiskSwapperImage(selectedDiskSwapperSlot()); + }); + connect(diskSwapperInsertButton, &QPushButton::clicked, this, [this]() { + setDiskSwapperPath(selectedDiskSwapperSlot(), diskSwapperPath->currentText().trimmed()); + }); + connect(diskSwapperRemoveButton, &QPushButton::clicked, this, [this]() { + setDiskSwapperPath(selectedDiskSwapperSlot(), QString()); + diskSwapperPath->clearEditText(); + }); + connect(diskSwapperRemoveAllButton, &QPushButton::clicked, this, [this]() { + for (int row = 0; row < MaxDiskSwapperSlots; row++) { + setDiskSwapperPath(row, QString()); + } + diskSwapperPath->clearEditText(); + }); + diskSwapperList->selectRow(0); + updateDiskSwapperSelection(); + return page; + } + + int selectedDiskSwapperSlot() const + { + if (!diskSwapperList || diskSwapperList->currentRow() < 0) { + return 0; + } + return qBound(0, diskSwapperList->currentRow(), MaxDiskSwapperSlots - 1); + } + + QString diskSwapperPathAt(int slot) const + { + if (!diskSwapperList || slot < 0 || slot >= MaxDiskSwapperSlots) { + return QString(); + } + QTableWidgetItem *item = diskSwapperList->item(slot, 1); + return item ? item->data(Qt::UserRole).toString() : QString(); + } + + void setDiskSwapperPath(int slot, const QString &path) + { + if (!diskSwapperList || slot < 0 || slot >= MaxDiskSwapperSlots) { + return; + } + const QString trimmed = path.trimmed(); + QTableWidgetItem *image = diskSwapperList->item(slot, 1); + if (!image) { + image = new QTableWidgetItem; + image->setFlags(image->flags() & ~Qt::ItemIsEditable); + diskSwapperList->setItem(slot, 1, image); + } + image->setData(Qt::UserRole, trimmed); + image->setText(trimmed.isEmpty() ? QString() : QFileInfo(trimmed).fileName()); + image->setToolTip(trimmed); + QTableWidgetItem *drive = diskSwapperList->item(slot, 2); + if (!drive) { + drive = new QTableWidgetItem; + drive->setFlags(drive->flags() & ~Qt::ItemIsEditable); + diskSwapperList->setItem(slot, 2, drive); + } + updateDiskSwapperDriveColumn(); + } + + void updateDiskSwapperSelection() + { + if (!diskSwapperPath) { + return; + } + diskSwapperPath->setCurrentText(diskSwapperPathAt(selectedDiskSwapperSlot())); + } + + void updateDiskSwapperDriveColumn() + { + if (!diskSwapperList) { + return; + } + for (int slot = 0; slot < MaxDiskSwapperSlots; slot++) { + QTableWidgetItem *drive = diskSwapperList->item(slot, 2); + if (!drive) { + continue; + } + QString driveText; + const QString image = diskSwapperPathAt(slot); + if (!image.isEmpty()) { + for (int i = 0; i < 4; i++) { + if (dfPath[i] && dfPath[i]->currentText() == image) { + driveText = QStringLiteral("DF%1:").arg(i); + break; + } + } + } + drive->setText(driveText); + } + } + + void browseDiskSwapperImage(int slot) + { + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select floppy image"), fileDialogInitialPath(diskSwapperPathAt(slot)), floppyDiskImageFilter()); + if (!selected.isEmpty()) { + setPathComboText(diskSwapperPath, selected); + setDiskSwapperPath(slot, selected); + } + } + + QWidget *makeHardDrivesPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + mountedDrives = new QTreeWidget; + mountedDrives->setRootIsDecorated(false); + mountedDrives->setSelectionMode(QAbstractItemView::SingleSelection); + mountedDrives->setDragDropMode(QAbstractItemView::InternalMove); + mountedDrives->setDragEnabled(true); + mountedDrives->setAcceptDrops(true); + mountedDrives->viewport()->setAcceptDrops(true); + mountedDrives->setDefaultDropAction(Qt::MoveAction); + mountedDrives->setDropIndicatorShown(true); + mountedDrives->setHeaderLabels({ + QStringLiteral("*"), + QStringLiteral("Device"), + QStringLiteral("Volume"), + QStringLiteral("Path"), + QStringLiteral("RW"), + QStringLiteral("Block size"), + QStringLiteral("Size"), + QStringLiteral("BootPri") + }); + QVBoxLayout *volumeLayout = new QVBoxLayout; + volumeLayout->addWidget(mountedDrives); + root->addWidget(groupBox(QStringLiteral("Mounted drives"), volumeLayout), 1); + QGridLayout *buttons = new QGridLayout; + addDirectoryMountButton = new QPushButton(QStringLiteral("Add Directory or Archive...")); + addHardfileMountButton = new QPushButton(QStringLiteral("Add Hardfile...")); + addHardDriveMountButton = new QPushButton(QStringLiteral("Add Hard Drive...")); + addCdMountButton = new QPushButton(QStringLiteral("Add SCSI/IDE CD Drive")); + addTapeMountButton = new QPushButton(QStringLiteral("Add SCSI/IDE Tape Drive")); + propertiesMountButton = new QPushButton(QStringLiteral("Properties")); + removeMountButton = new QPushButton(QStringLiteral("Remove")); + buttons->setColumnStretch(4, 1); + buttons->addWidget(addDirectoryMountButton, 0, 0); + buttons->addWidget(addHardfileMountButton, 0, 1); + buttons->addWidget(addHardDriveMountButton, 0, 2); + buttons->addWidget(addCdMountButton, 1, 0); + buttons->addWidget(addTapeMountButton, 1, 1); + buttons->addWidget(propertiesMountButton, 1, 2); + buttons->addWidget(removeMountButton, 1, 3); + root->addLayout(buttons); + + hostDriveAutomount = new QCheckBox(QStringLiteral("Add PC drives at startup")); + hostRemovableDrives = new QCheckBox(QStringLiteral("Include removable drives..")); + hostNetworkDrives = new QCheckBox(QStringLiteral("Include network drives..")); + hostCdDrives = new QCheckBox(QStringLiteral("CDFS automount CD/DVD drives")); + filesysNoFsdb = new QCheckBox(QStringLiteral("Disable UAEFSDB-support")); + hostNoRecycleBin = new QCheckBox(QStringLiteral("Don't use Windows Recycle Bin")); + hostAutomountRemovable = new QCheckBox(QStringLiteral("Automount removable drives")); + filesysLimitSize = new QCheckBox(QStringLiteral("Limit size of directory drives to 1G")); + for (QCheckBox *check : { hostDriveAutomount, hostRemovableDrives, hostNetworkDrives, hostCdDrives, hostNoRecycleBin, hostAutomountRemovable }) { + check->setEnabled(false); + check->setToolTip(QStringLiteral("Windows host-drive automount option; Unix backend not implemented yet.")); + } + filesysLimitSize->setToolTip(QStringLiteral("Workaround for old installers that calculate free space incorrectly if a directory drive is large.")); + QGridLayout *options = new QGridLayout; + options->setColumnStretch(0, 1); + options->setColumnStretch(1, 1); + options->addWidget(hostDriveAutomount, 0, 0); + options->addWidget(hostRemovableDrives, 1, 0); + options->addWidget(hostNetworkDrives, 2, 0); + options->addWidget(hostCdDrives, 3, 0); + options->addWidget(filesysNoFsdb, 0, 1); + options->addWidget(hostNoRecycleBin, 1, 1); + options->addWidget(hostAutomountRemovable, 2, 1); + options->addWidget(filesysLimitSize, 3, 1); + root->addWidget(groupBox(QStringLiteral("Options"), options)); + + cdSlotNumber = combo({}); + for (int i = 0; i < MaxCdSlots; i++) { + cdSlotNumber->addItem(QString::number(i + 1)); + } + cdSlotPath = pathCombo(); + cdSlotType = combo({ QStringLiteral("Autodetect"), QStringLiteral("Image file") }, QStringLiteral("Image file")); + QPushButton *selectCdImage = new QPushButton(QStringLiteral("Select image file")); + QPushButton *ejectCd = new QPushButton(QStringLiteral("Eject")); + cdSpeedTurbo = new QCheckBox(QStringLiteral("CDTV/CDTV-CR/CD32 turbo CD read speed")); + + QGridLayout *optical = new QGridLayout; + optical->setColumnStretch(1, 1); + optical->addWidget(new QLabel(QStringLiteral("CD drive/image")), 0, 0); + optical->addWidget(selectCdImage, 0, 2); + optical->addWidget(cdSlotType, 0, 3); + optical->addWidget(ejectCd, 0, 4); + optical->addWidget(cdSlotNumber, 1, 0); + optical->addWidget(cdSlotPath, 1, 1, 1, 4); + optical->addWidget(cdSpeedTurbo, 2, 1, 1, 4); + root->addWidget(groupBox(QStringLiteral("Optical media options"), optical)); + + connect(addDirectoryMountButton, &QPushButton::clicked, this, [this]() { addDirectoryMountDialog(); }); + connect(addHardfileMountButton, &QPushButton::clicked, this, [this]() { addHardfileMountDialog(); }); + connect(addHardDriveMountButton, &QPushButton::clicked, this, [this]() { addHardDriveMountDialog(); }); + connect(addCdMountButton, &QPushButton::clicked, this, [this]() { addCdDriveMountDialog(); }); + connect(addTapeMountButton, &QPushButton::clicked, this, [this]() { addTapeDriveMountDialog(); }); + connect(cdSlotNumber, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (cdSlotUpdating || index < 0 || index >= MaxCdSlots) { + return; + } + storeCurrentCdSlotFromUi(); + currentCdSlot = index; + loadCdSlotToUi(index); + }); + connect(cdSlotType, &QComboBox::currentTextChanged, this, [this](const QString &text) { + if (cdSlotUpdating) { + return; + } + if (text == QStringLiteral("Autodetect") && cdSlotPath->currentText().isEmpty()) { + setCurrentCdSlotInUse(true); + } + }); + connect(cdSlotPath, &QComboBox::currentTextChanged, this, [this](const QString &text) { + if (cdSlotUpdating) { + return; + } + if (!text.isEmpty()) { + setComboTextIfChanged(cdSlotType, QStringLiteral("Image file")); + setCurrentCdSlotInUse(true); + } + }); + connect(selectCdImage, &QPushButton::clicked, this, [this]() { + const QString initialPath = fileDialogInitialPath(cdSlotPath->currentText()); + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select CD image"), initialPath, cdImageFilter()); + if (!selected.isEmpty()) { + setPathComboText(cdSlotPath, selected); + setComboTextIfChanged(cdSlotType, QStringLiteral("Image file")); + setCurrentCdSlotInUse(true); + } + }); + connect(ejectCd, &QPushButton::clicked, this, [this]() { + cdSlotPath->setCurrentText(QString()); + setComboTextIfChanged(cdSlotType, QStringLiteral("Image file")); + setCurrentCdSlotInUse(false); + }); + connect(propertiesMountButton, &QPushButton::clicked, this, [this]() { openSelectedMountProperties(); }); + connect(removeMountButton, &QPushButton::clicked, this, [this]() { removeSelectedMount(); }); + connect(mountedDrives, &QTreeWidget::itemSelectionChanged, this, [this]() { updateMountButtons(); }); + connect(mountedDrives, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem *, int) { openSelectedMountProperties(); }); + QShortcut *propertiesShortcut = new QShortcut(QKeySequence(Qt::Key_Return), mountedDrives); + connect(propertiesShortcut, &QShortcut::activated, this, [this]() { openSelectedMountProperties(); }); + QShortcut *propertiesEnterShortcut = new QShortcut(QKeySequence(Qt::Key_Enter), mountedDrives); + connect(propertiesEnterShortcut, &QShortcut::activated, this, [this]() { openSelectedMountProperties(); }); + QShortcut *removeShortcut = new QShortcut(QKeySequence::Delete, mountedDrives); + connect(removeShortcut, &QShortcut::activated, this, [this]() { removeSelectedMount(); }); + QShortcut *moveUpShortcut = new QShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Up), mountedDrives); + connect(moveUpShortcut, &QShortcut::activated, this, [this]() { moveSelectedMount(-1); }); + QShortcut *moveDownShortcut = new QShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Down), mountedDrives); + connect(moveDownShortcut, &QShortcut::activated, this, [this]() { moveSelectedMount(1); }); + updateMountButtons(); + return page; + } + + QWidget *makeExpansionPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + + rtgMem = combo({ QStringLiteral("None"), QStringLiteral("1 MB"), QStringLiteral("2 MB"), QStringLiteral("4 MB"), QStringLiteral("8 MB"), QStringLiteral("16 MB"), QStringLiteral("32 MB"), QStringLiteral("64 MB"), QStringLiteral("128 MB"), QStringLiteral("256 MB") }, QStringLiteral("None")); + QStringList rtgTypes; + for (const WinUaeQtRtgBoardCatalogItem &board : boardCatalog.rtgBoards) { + rtgTypes.append(board.configValue); + } + if (rtgTypes.isEmpty()) { + rtgTypes = { QStringLiteral("ZorroII"), QStringLiteral("ZorroIII") }; + } + rtgType = combo(rtgTypes, QStringLiteral("ZorroIII")); + rtgMonitor = combo({ QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4") }, QStringLiteral("1")); + disableUnavailable(rtgMonitor, QStringLiteral("The Unix uaegfx.card backend currently exposes a single RTG monitor.")); + rtgScale = new QCheckBox(QStringLiteral("Scale if smaller than display size setting")); + rtgScaleAllow = new QCheckBox(QStringLiteral("Always scale in windowed mode")); + rtgScaleAllow->setEnabled(false); + rtgScaleAllow->setToolTip(QStringLiteral("Windows display-backend option; Unix scaling is controlled by the RTG filter settings.")); + rtgCenter = new QCheckBox(QStringLiteral("Always center")); + rtgIntegerScale = new QCheckBox(QStringLiteral("Integer scaling")); + rtgMultithread = new QCheckBox(QStringLiteral("Multithreaded")); + rtgMultithread->setToolTip(QStringLiteral("Render RTG frame conversion on a Unix worker thread.")); + rtgHardwareSprite = new QCheckBox(QStringLiteral("Hardware sprite emulation")); + rtgHardwareVBlank = new QCheckBox(QStringLiteral("Hardware vertical blank interrupt")); + rtgAutoswitch = new QCheckBox(QStringLiteral("Native/RTG autoswitch")); + rtgInitialMonitor = new QCheckBox(QStringLiteral("Override initial native chipset display")); + disableUnavailable(rtgInitialMonitor, QStringLiteral("Initial RTG monitor override needs multi-monitor RTG support in the Unix backend.")); + rtg8Bit = combo({ QStringLiteral("(8bit)"), QStringLiteral("8-bit (*)") }, QStringLiteral("8-bit (*)")); + rtg16Bit = combo({ QStringLiteral("(15/16bit)"), QStringLiteral("All 15/16-bit"), QStringLiteral("R5G6B5PC (*)"), QStringLiteral("R5G5B5PC"), QStringLiteral("R5G6B5"), QStringLiteral("R5G5B5"), QStringLiteral("B5G6R5PC"), QStringLiteral("B5G5R5PC") }, QStringLiteral("R5G6B5PC (*)")); + rtg24Bit = combo({ QStringLiteral("(24bit)"), QStringLiteral("All 24-bit"), QStringLiteral("R8G8B8"), QStringLiteral("B8G8R8") }, QStringLiteral("(24bit)")); + rtg32Bit = combo({ QStringLiteral("(32bit)"), QStringLiteral("All 32-bit"), QStringLiteral("A8R8G8B8"), QStringLiteral("A8B8G8R8"), QStringLiteral("R8G8B8A8"), QStringLiteral("B8G8R8A8 (*)") }, QStringLiteral("B8G8R8A8 (*)")); + rtgDisplay = combo({ QStringLiteral("Default display") }, QStringLiteral("Default display")); + rtgDisplay->setEnabled(false); + rtgDisplay->setToolTip(QStringLiteral("Use the Display page for the shared host display setting; separate RTG monitor placement is not connected yet.")); + rtgRefreshRate = combo({ QStringLiteral("Chipset"), QStringLiteral("Default"), QStringLiteral("50"), QStringLiteral("60"), QStringLiteral("70"), QStringLiteral("75") }, QStringLiteral("Chipset")); + rtgRefreshRate->setEditable(true); + rtgBuffers = combo({ QStringLiteral("Double"), QStringLiteral("Triple") }, QStringLiteral("Double")); + rtgBuffers->setToolTip(QStringLiteral("Controls the Unix SDL RTG presentation texture queue.")); + rtgAspectRatio = combo(configChoiceDisplays(rtgAspectRatioChoices, int(sizeof(rtgAspectRatioChoices) / sizeof(rtgAspectRatioChoices[0]))), QStringLiteral("Automatic")); + + QGridLayout *rtg = new QGridLayout; + rtg->setColumnStretch(1, 2); + rtg->setColumnStretch(3, 1); + rtg->addWidget(label(QStringLiteral("Board:")), 0, 0); + rtg->addWidget(rtgType, 0, 1); + rtg->addWidget(label(QStringLiteral("Monitor:")), 0, 2); + rtg->addWidget(rtgMonitor, 0, 3); + rtg->addWidget(label(QStringLiteral("VRAM size:")), 1, 0); + rtg->addWidget(rtgMem, 1, 1); + rtg->addWidget(rtgAutoswitch, 2, 0, 1, 2); + rtg->addWidget(rtgScale, 3, 0, 1, 2); + rtg->addWidget(rtgScaleAllow, 4, 0, 1, 2); + rtg->addWidget(rtgCenter, 5, 0, 1, 2); + rtg->addWidget(rtgIntegerScale, 6, 0, 1, 2); + rtg->addWidget(rtgMultithread, 3, 2, 1, 2); + rtg->addWidget(rtgHardwareSprite, 4, 2, 1, 2); + rtg->addWidget(rtgHardwareVBlank, 5, 2, 1, 2); + rtg->addWidget(label(QStringLiteral("8-bit:")), 7, 0); + rtg->addWidget(rtg8Bit, 7, 1); + rtg->addWidget(label(QStringLiteral("16-bit:")), 7, 2); + rtg->addWidget(rtg16Bit, 7, 3); + rtg->addWidget(label(QStringLiteral("24-bit:")), 8, 0); + rtg->addWidget(rtg24Bit, 8, 1); + rtg->addWidget(label(QStringLiteral("32-bit:")), 8, 2); + rtg->addWidget(rtg32Bit, 8, 3); + rtg->addWidget(rtgDisplay, 9, 0, 1, 4); + rtg->addWidget(label(QStringLiteral("Refresh rate:")), 10, 0); + rtg->addWidget(rtgRefreshRate, 10, 1); + rtg->addWidget(label(QStringLiteral("Buffer mode:")), 10, 2); + rtg->addWidget(rtgBuffers, 10, 3); + rtg->addWidget(label(QStringLiteral("Aspect ratio:")), 11, 0); + rtg->addWidget(rtgAspectRatio, 11, 1); + rtg->addWidget(rtgInitialMonitor, 11, 2, 1, 2); + root->addWidget(groupBox(QStringLiteral("RTG Graphics Card"), rtg)); + + connect(rtgScale, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + rtgCenter->setChecked(false); + rtgIntegerScale->setChecked(false); + } + }); + connect(rtgCenter, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + rtgScale->setChecked(false); + rtgIntegerScale->setChecked(false); + } + }); + connect(rtgIntegerScale, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + rtgScale->setChecked(false); + rtgCenter->setChecked(false); + } + }); + connect(rtgMem, &QComboBox::currentTextChanged, this, [this]() { updateCpuControlState(); }); + connect(rtgType, &QComboBox::currentTextChanged, this, [this]() { updateCpuControlState(); }); + return page; + } + + QWidget *makeExpansionsPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + + QGridLayout *board = new QGridLayout; + expansionRomCategory = combo(expansionBoardCategoryItems(boardCatalog)); + expansionRomBoard = combo({}); + expansionRomBoard->setEditable(false); + expansionRomBoard->setInsertPolicy(QComboBox::NoInsert); + expansionRomSubtype = combo({ QStringLiteral("Default") }); + expansionRomSubtype->setEnabled(false); + expansionRomSlot = combo({ QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4") }); + expansionRomId = combo({ QStringLiteral("0"), QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"), QStringLiteral("6"), QStringLiteral("7") }, QStringLiteral("7")); + expansionRomFile = pathCombo(); + expansionBoardOption = combo({ QStringLiteral("None") }); + expansionBoardOption->setEnabled(false); + expansionBoardSelector = combo({ QStringLiteral("None") }); + expansionBoardSelector->setEnabled(false); + expansionBoardSelector->setVisible(false); + expansionBoardString = new QLineEdit; + expansionBoardString->setEnabled(false); + expansionBoardString->setVisible(false); + expansionRom24BitDma = new QCheckBox(QStringLiteral("24-bit DMA")); + expansionRomEnabled = new QCheckBox(QStringLiteral("Enable board")); + expansionRomEnabled->setToolTip(QStringLiteral("Adds the selected expansion board to the configuration.")); + expansionRomAutobootDisabled = new QCheckBox(QStringLiteral("Autoboot disabled")); + expansionRomPcmciaInserted = new QCheckBox(QStringLiteral("PCMCIA inserted")); + expansionBoardOptionCheck = new QCheckBox(QStringLiteral("Enable option")); + expansionBoardOptionCheck->setToolTip(QStringLiteral("Applies the selected board option.")); + expansionBoardOptionCheck->setEnabled(false); + expansionRomBrowse = smallButton(QStringLiteral("...")); + board->setColumnStretch(2, 1); + board->addWidget(expansionRomCategory, 0, 0, 1, 2); + board->addWidget(expansionRom24BitDma, 0, 2); + board->addWidget(label(QStringLiteral("Controller ID:")), 0, 3); + board->addWidget(expansionRomId, 0, 4); + board->addWidget(expansionRomBoard, 1, 0); + board->addWidget(expansionRomSlot, 1, 1); + board->addWidget(expansionRomFile, 1, 2, 1, 2); + board->addWidget(expansionRomBrowse, 1, 4); + board->addWidget(expansionRomSubtype, 2, 0, 1, 2); + board->addWidget(expansionRomAutobootDisabled, 2, 2); + board->addWidget(expansionRomPcmciaInserted, 2, 3, 1, 2); + board->addWidget(expansionBoardOption, 3, 0, 1, 2); + board->addWidget(expansionBoardSelector, 3, 2, 1, 2); + board->addWidget(expansionBoardString, 3, 2, 1, 2); + board->addWidget(expansionBoardOptionCheck, 3, 2, 1, 2); + board->addWidget(expansionRomEnabled, 3, 4); + root->addWidget(groupBox(QStringLiteral("Expansion Board Settings"), board)); + populateExpansionBoardChoices(); + loadExpansionBoardUiState(QString(), 0); + + connect(expansionRomCategory, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + storeCurrentExpansionBoardUiState(); + populateExpansionBoardChoices(); + loadExpansionBoardUiState(selectedExpansionBoardKey(), selectedExpansionBoardSlot()); + }); + connect(expansionRomBoard, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + storeCurrentExpansionBoardUiState(); + loadExpansionBoardUiState(selectedExpansionBoardKey(), selectedExpansionBoardSlot()); + }); + connect(expansionRomBoard, &QComboBox::currentTextChanged, this, [this]() { + if (!expansionBoardUpdating) { + storeCurrentExpansionBoardUiState(); + loadExpansionBoardUiState(selectedExpansionBoardKey(), selectedExpansionBoardSlot()); + } + }); + connect(expansionRomSlot, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + storeCurrentExpansionBoardUiState(); + loadExpansionBoardUiState(selectedExpansionBoardKey(), selectedExpansionBoardSlot()); + }); + connect(expansionRomFile, &QComboBox::currentTextChanged, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRomEnabled, &QCheckBox::toggled, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRomAutobootDisabled, &QCheckBox::toggled, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRom24BitDma, &QCheckBox::toggled, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRomPcmciaInserted, &QCheckBox::toggled, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRomId, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRomSubtype, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionBoardOption, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + updateExpansionBoardOptionCheck(); + }); + connect(expansionBoardSelector, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionBoardString, &QLineEdit::textChanged, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionBoardOptionCheck, &QCheckBox::toggled, this, [this]() { storeCurrentExpansionBoardUiState(); }); + connect(expansionRomBrowse, &QPushButton::clicked, this, [this]() { + addBrowse(expansionRomFile, this, QStringLiteral("Expansion board ROM file"), QStringLiteral("ROM images (*.rom *.bin);;All files (*)")); + if (!expansionRomFile->currentText().trimmed().isEmpty()) { + expansionRomEnabled->setChecked(true); + } + storeCurrentExpansionBoardUiState(); + }); + + QGridLayout *accelerator = new QGridLayout; + cpuBoardType = combo({}); + cpuBoardSubtype = combo({ QStringLiteral("Default") }); + cpuBoardRom = pathCombo(); + acceleratorOption = combo({ QStringLiteral("Settings preserved") }); + acceleratorSelector = combo({ QStringLiteral("None") }); + cpuBoardMem = combo({ QStringLiteral("None") }); + acceleratorOptionCheck = new QCheckBox(QStringLiteral("Enabled")); + cpuBoardBrowse = new QPushButton(QStringLiteral("...")); + acceleratorOption->setEnabled(false); + acceleratorSelector->setEnabled(false); + acceleratorOptionCheck->setEnabled(false); + accelerator->setColumnStretch(2, 1); + accelerator->addWidget(cpuBoardType, 0, 0); + accelerator->addWidget(cpuBoardSubtype, 1, 0); + accelerator->addWidget(new QLabel(QStringLiteral("Accelerator board ROM file:")), 0, 2, 1, 2); + accelerator->addWidget(cpuBoardRom, 1, 2); + accelerator->addWidget(cpuBoardBrowse, 1, 3); + accelerator->addWidget(label(QStringLiteral("Accelerator board memory:")), 2, 1); + accelerator->addWidget(cpuBoardMem, 2, 2, 1, 2); + accelerator->addWidget(acceleratorOption, 3, 0); + accelerator->addWidget(acceleratorSelector, 3, 2); + accelerator->addWidget(acceleratorOptionCheck, 3, 3); + root->addWidget(groupBox(QStringLiteral("Accelerator Board Settings"), accelerator)); + populateCpuBoardTypeChoices(); + populateCpuBoardSubtypeChoices(); + updateCpuBoardControls(); + + connect(cpuBoardType, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + if (!cpuBoardUpdating) { + cpuBoardSettingsRaw.clear(); + } + populateCpuBoardSubtypeChoices(); + updateCpuBoardControls(); + }); + connect(cpuBoardSubtype, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + if (!cpuBoardUpdating) { + cpuBoardSettingsRaw.clear(); + } + updateCpuBoardControls(); + }); + connect(cpuBoardBrowse, &QPushButton::clicked, this, [this]() { + addBrowse(cpuBoardRom, this, QStringLiteral("Accelerator board ROM file"), QStringLiteral("ROM images (*.rom *.bin);;All files (*)")); + }); + connect(acceleratorOption, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + updateCpuBoardOptionControls(); + }); + connect(acceleratorSelector, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + storeCpuBoardOptionFromUi(); + }); + connect(acceleratorOptionCheck, &QCheckBox::toggled, this, [this]() { + storeCpuBoardOptionFromUi(); + }); + + QGridLayout *misc = new QGridLayout; + expansionBsdsocket = new QCheckBox(QStringLiteral("bsdsocket.library")); + expansionScsiDevice = new QCheckBox(QStringLiteral("uaescsi.device")); + expansionSana2 = new QCheckBox(QStringLiteral("uaenet.device")); +#if !UAE_UNIX_WITH_BSDSOCKET + expansionBsdsocket->setEnabled(false); + expansionBsdsocket->setToolTip(QStringLiteral("Unix bsdsocket.library backend is not enabled in this build yet.")); +#endif +#if !UAE_UNIX_WITH_UAESCSI + expansionScsiDevice->setEnabled(false); + expansionScsiDevice->setToolTip(QStringLiteral("Unix uaescsi.device backend is not enabled in this build yet.")); +#endif +#if !UAE_UNIX_WITH_SANA2 + expansionSana2->setEnabled(false); + expansionSana2->setToolTip(QStringLiteral("Unix SANA-II backend is not enabled in this build yet.")); +#endif + misc->addWidget(expansionBsdsocket, 0, 0); + misc->addWidget(expansionScsiDevice, 1, 0); + misc->addWidget(expansionSana2, 0, 1); + misc->setColumnStretch(0, 1); + misc->setColumnStretch(1, 1); + root->addWidget(groupBox(QStringLiteral("Miscellaneous Expansions"), misc)); + root->addStretch(); + return page; + } + + QString selectedExpansionBoardKey() const + { + if (!expansionRomBoard) { + return QString(); + } + const QString data = expansionRomBoard->currentData().toString(); + if (!data.isEmpty()) { + return data; + } + const QString text = expansionRomBoard->currentText().trimmed(); + if (text.isEmpty() || text == QStringLiteral("None")) { + return QString(); + } + if (const WinUaeQtExpansionBoardCatalogItem *choice = expansionBoardChoiceByDisplay(boardCatalog, text)) { + return choice->key; + } + return text; + } + + int selectedExpansionBoardSlot() const + { + if (!expansionRomSlot) { + return 0; + } + bool ok = false; + const int slot = expansionRomSlot->currentText().toInt(&ok); + return ok ? qBound(0, slot - 1, 3) : 0; + } + + int expansionBoardEnabledCount(const QString &boardKey) const + { + int count = 0; + for (auto it = expansionBoardStates.constBegin(); it != expansionBoardStates.constEnd(); ++it) { + int slot = 0; + const QString key = expansionBoardBaseKey(it.key(), &slot); + if (it.value().present && key.compare(boardKey, Qt::CaseInsensitive) == 0) { + count++; + } + } + return count; + } + + QString expansionBoardComboText(const WinUaeQtExpansionBoardCatalogItem &choice) const + { + const int count = expansionBoardEnabledCount(choice.key); + if (count == 1) { + return QStringLiteral("* %1").arg(choice.display); + } + if (count > 1) { + return QStringLiteral("[%1] %2").arg(count).arg(choice.display); + } + return choice.display; + } + + void refreshExpansionBoardChoiceLabels() + { + if (!expansionRomBoard) { + return; + } + + const QSignalBlocker blocker(expansionRomBoard); + for (int i = 0; i < expansionRomBoard->count(); i++) { + const QString boardKey = expansionRomBoard->itemData(i).toString(); + if (const WinUaeQtExpansionBoardCatalogItem *choice = expansionBoardChoiceByKey(boardCatalog, boardKey)) { + expansionRomBoard->setItemText(i, expansionBoardComboText(*choice)); + } + } + } + + void populateExpansionBoardChoices(const QString &requestedKey = QString()) + { + if (!expansionRomBoard || !expansionRomCategory) { + return; + } + + const QString oldKey = requestedKey.isEmpty() ? selectedExpansionBoardKey() : requestedKey; + const WinUaeQtExpansionCategoryChoice *category = expansionBoardCategoryByDisplay(expansionRomCategory->currentText()); + const QSignalBlocker blocker(expansionRomBoard); + expansionRomBoard->clear(); + expansionRomBoard->addItem(QStringLiteral("None"), QString()); + if (category) { + QVector choices; + for (const WinUaeQtExpansionBoardCatalogItem &choice : boardCatalog.expansionBoards) { + if (expansionBoardMatchesCategory(choice, *category)) { + choices.append(&choice); + } + } + std::sort(choices.begin(), choices.end(), [](const WinUaeQtExpansionBoardCatalogItem *a, const WinUaeQtExpansionBoardCatalogItem *b) { + const QString aSort = expansionBoardSortText(a->display); + const QString bSort = expansionBoardSortText(b->display); + const int byName = QString::compare(aSort, bSort, Qt::CaseInsensitive); + if (byName != 0) { + return byName < 0; + } + return QString::compare(a->display, b->display, Qt::CaseInsensitive) < 0; + }); + for (const WinUaeQtExpansionBoardCatalogItem *choice : choices) { + expansionRomBoard->addItem(expansionBoardComboText(*choice), choice->key); + } + } + + int selectedIndex = 0; + for (int i = 0; i < expansionRomBoard->count(); i++) { + if (expansionRomBoard->itemData(i).toString().compare(oldKey, Qt::CaseInsensitive) == 0) { + selectedIndex = i; + break; + } + } + expansionRomBoard->setCurrentIndex(selectedIndex); + } + + void populateExpansionSubtypeChoices(const QString &boardKey, const QString &requestedValue) + { + if (!expansionRomSubtype) { + return; + } + + const QSignalBlocker blocker(expansionRomSubtype); + expansionRomSubtype->clear(); + int selectedIndex = -1; + if (const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, boardKey)) { + for (const WinUaeQtBoardSubtype &choice : board->subtypes) { + expansionRomSubtype->addItem(choice.display, choice.configValue); + if (requestedValue.compare(choice.configValue, Qt::CaseInsensitive) == 0) { + selectedIndex = expansionRomSubtype->count() - 1; + } + } + } + + if (expansionRomSubtype->count() == 0) { + expansionRomSubtype->addItem(QStringLiteral("Default"), QString()); + expansionRomSubtype->setCurrentIndex(0); + expansionRomSubtype->setEnabled(false); + return; + } + + if (selectedIndex < 0 && !requestedValue.isEmpty()) { + expansionRomSubtype->addItem(requestedValue, requestedValue); + selectedIndex = expansionRomSubtype->count() - 1; + } + expansionRomSubtype->setCurrentIndex(selectedIndex >= 0 ? selectedIndex : 0); + expansionRomSubtype->setEnabled(true); + } + + bool expansionBoardOptionIsMulti(const WinUaeQtBoardSetting *choice) const + { + return choice + && choice->type == WinUaeQtBoardSettingType::Multi + && !choice->multiValues.isEmpty(); + } + + bool expansionBoardMultiValueConfigured(const WinUaeQtExpansionBoardState &state, const WinUaeQtBoardSetting &choice) const + { + if (state.optionValues.contains(choice.configValue)) { + return true; + } + for (const QString &value : choice.multiValues) { + if (expansionOptionContains(state.rawOptions, value)) { + return true; + } + } + return false; + } + + QString expansionBoardSelectedMultiValue(const WinUaeQtExpansionBoardState &state, const WinUaeQtBoardSetting *choice) const + { + if (!choice) { + return QString(); + } + const QString stored = state.optionValues.value(choice->configValue); + if (!stored.isEmpty()) { + return stored; + } + for (const QString &value : choice->multiValues) { + if (expansionOptionContains(state.rawOptions, value)) { + return value; + } + } + return choice->multiValues.value(0); + } + + QString expansionBoardStringValue(const WinUaeQtExpansionBoardState &state, const WinUaeQtBoardSetting *choice) const + { + if (!choice) { + return QString(); + } + if (state.optionValues.contains(choice->configValue)) { + return state.optionValues.value(choice->configValue); + } + return expansionOptionValue(state.rawOptions, choice->configValue); + } + + bool expansionBoardSettingHasStoredValue(const WinUaeQtExpansionBoardState &state, const WinUaeQtBoardSetting &choice) const + { + if (choice.type == WinUaeQtBoardSettingType::CheckBox) { + return state.optionBools.value(choice.configValue, false); + } + if (choice.type == WinUaeQtBoardSettingType::String) { + return !expansionBoardStringValue(state, &choice).isEmpty(); + } + return expansionBoardMultiValueConfigured(state, choice); + } + + void populateExpansionOptionChoices(const QString &boardKey) + { + if (!expansionBoardOption) { + return; + } + + const QString requested = expansionBoardOption->currentData().toString(); + const WinUaeQtExpansionBoardState state = expansionBoardStates.value(currentExpansionBoardConfigName); + const QSignalBlocker blocker(expansionBoardOption); + expansionBoardOption->clear(); + expansionBoardOption->addItem(QStringLiteral("None"), QString()); + int selectedIndex = 0; + if (const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, boardKey)) { + for (const WinUaeQtBoardSetting &choice : board->settings) { + expansionBoardOption->addItem(choice.display, choice.configValue); + if (requested.compare(choice.configValue, Qt::CaseInsensitive) == 0) { + selectedIndex = expansionBoardOption->count() - 1; + } else if (selectedIndex == 0 && requested.isEmpty() && expansionBoardSettingHasStoredValue(state, choice)) { + selectedIndex = expansionBoardOption->count() - 1; + } + } + } + expansionBoardOption->setCurrentIndex(selectedIndex); + expansionBoardOption->setEnabled(expansionBoardOption->count() > 1); + updateExpansionBoardOptionCheck(); + } + + void updateExpansionBoardOptionCheck() + { + if (!expansionBoardOption || !expansionBoardOptionCheck || !expansionBoardSelector || !expansionBoardString) { + return; + } + const QString option = expansionBoardOption->currentData().toString(); + int slot = 0; + const QString boardKey = expansionBoardBaseKey(currentExpansionBoardConfigName, &slot); + const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, boardKey); + const WinUaeQtBoardSetting *choice = board ? boardSettingChoiceByConfig(board->settings, option) : nullptr; + const bool enabled = !expansionBoardUpdating + && !currentExpansionBoardConfigName.isEmpty() + && board + && choice; + const WinUaeQtExpansionBoardState state = expansionBoardStates.value(currentExpansionBoardConfigName); + const QSignalBlocker checkBlocker(expansionBoardOptionCheck); + const QSignalBlocker selectorBlocker(expansionBoardSelector); + const QSignalBlocker stringBlocker(expansionBoardString); + + expansionBoardOptionCheck->setVisible(choice && choice->type == WinUaeQtBoardSettingType::CheckBox); + expansionBoardSelector->setVisible(expansionBoardOptionIsMulti(choice)); + expansionBoardString->setVisible(choice && choice->type == WinUaeQtBoardSettingType::String); + + expansionBoardOptionCheck->setEnabled(enabled && choice->type == WinUaeQtBoardSettingType::CheckBox); + expansionBoardOptionCheck->setChecked(enabled && choice->type == WinUaeQtBoardSettingType::CheckBox && state.optionBools.value(option, false)); + + expansionBoardSelector->clear(); + if (enabled && expansionBoardOptionIsMulti(choice)) { + const QStringList displays = choice->multiDisplays; + const QStringList values = choice->multiValues; + for (int i = 0; i < values.size(); i++) { + expansionBoardSelector->addItem(displays.value(i, values[i]), values[i]); + } + const QString selected = expansionBoardSelectedMultiValue(state, choice); + const int index = expansionBoardSelector->findData(selected); + expansionBoardSelector->setCurrentIndex(index >= 0 ? index : 0); + } else { + expansionBoardSelector->addItem(QStringLiteral("None"), QString()); + expansionBoardSelector->setCurrentIndex(0); + } + expansionBoardSelector->setEnabled(enabled && expansionBoardOptionIsMulti(choice)); + + expansionBoardString->setText(enabled && choice->type == WinUaeQtBoardSettingType::String + ? expansionBoardStringValue(state, choice) + : QString()); + expansionBoardString->setEnabled(enabled && choice->type == WinUaeQtBoardSettingType::String); + } + + void loadExpansionBoardUiState(const QString &boardKey, int slot) + { + if (!expansionRomEnabled) { + return; + } + + expansionBoardUpdating = true; + const QString configName = expansionBoardConfigName(boardKey, slot); + currentExpansionBoardConfigName = configName; + const WinUaeQtExpansionBoardState state = expansionBoardStates.value(configName); + const bool hasBoard = !boardKey.isEmpty(); + const WinUaeQtExpansionBoardCatalogItem *choice = expansionBoardChoiceByKey(boardCatalog, boardKey); + const bool supported = hasBoard && choice; + const bool noRomFile = expansionBoardNoRomFile(choice, state.subtype); + + const QList controls { + expansionRomSlot, + expansionRomId, + expansionRomFile, + expansionRomBrowse, + expansionRom24BitDma, + expansionRomEnabled, + expansionRomAutobootDisabled, + expansionRomPcmciaInserted + }; + for (QWidget *control : controls) { + if (control) { + control->setEnabled(supported); + control->setToolTip(QString()); + } + } + if (expansionRomSlot) { + expansionRomSlot->setEnabled(supported && ((choice->zorro >= 2 && !choice->singleOnly) || choice->clockPort)); + } + if (expansionRom24BitDma) { + expansionRom24BitDma->setEnabled(supported && choice->dma24Bit); + } + if (expansionRomPcmciaInserted) { + expansionRomPcmciaInserted->setEnabled(supported && choice->pcmcia); + } + if (expansionRomAutobootDisabled) { + expansionRomAutobootDisabled->setEnabled(supported && choice->autobootJumper); + } + if (expansionRomId) { + expansionRomId->setEnabled(supported && choice->idJumper); + } + populateExpansionSubtypeChoices(hasBoard ? boardKey : QString(), state.subtype); + expansionRomSubtype->setEnabled(supported && expansionRomSubtype->count() > 1); + populateExpansionOptionChoices(hasBoard ? boardKey : QString()); + expansionBoardOption->setEnabled(supported && expansionBoardOption->count() > 1); + + if (expansionRomFile) { + expansionRomFile->setVisible(!noRomFile); + expansionRomFile->setEnabled(supported && !noRomFile); + } + if (expansionRomBrowse) { + expansionRomBrowse->setVisible(!noRomFile); + expansionRomBrowse->setEnabled(supported && !noRomFile); + } + if (expansionRomEnabled) { + expansionRomEnabled->setText(noRomFile ? QStringLiteral("Enabled") : QStringLiteral("Enable board")); + } + setPathComboText(expansionRomFile, noRomFile || state.romFile == QStringLiteral(":ENABLED") ? QString() : state.romFile); + expansionRomEnabled->setChecked(hasBoard && state.present); + expansionRomAutobootDisabled->setChecked(hasBoard && state.autobootDisabled); + expansionRom24BitDma->setChecked(hasBoard && state.dma24Bit); + expansionRomPcmciaInserted->setChecked(hasBoard && state.inserted); + expansionRomId->setCurrentText(QString::number(qBound(0, state.id, 7))); + expansionBoardUpdating = false; + updateExpansionBoardOptionCheck(); + } + + void storeCurrentExpansionBoardUiState() + { + if (expansionBoardUpdating || currentExpansionBoardConfigName.isEmpty() || !expansionRomEnabled) { + return; + } + + WinUaeQtExpansionBoardState state = expansionBoardStates.value(currentExpansionBoardConfigName); + state.autobootDisabled = expansionRomAutobootDisabled->isChecked(); + state.dma24Bit = expansionRom24BitDma->isChecked(); + state.inserted = expansionRomPcmciaInserted->isChecked(); + state.id = expansionRomId->currentText().toInt(); + state.subtype = expansionRomSubtype && expansionRomSubtype->isEnabled() + ? expansionRomSubtype->currentData().toString() + : QString(); + int currentSlot = 0; + const QString currentBoardKey = expansionBoardBaseKey(currentExpansionBoardConfigName, ¤tSlot); + const WinUaeQtExpansionBoardCatalogItem *currentBoard = expansionBoardChoiceByKey(boardCatalog, currentBoardKey); + const bool noRomFile = expansionBoardNoRomFile(currentBoard, state.subtype); + if (noRomFile) { + state.present = expansionRomEnabled->isChecked(); + state.romFile = state.present ? QStringLiteral(":ENABLED") : QString(); + } else { + state.present = expansionRomEnabled->isChecked() || !expansionRomFile->currentText().trimmed().isEmpty(); + state.romFile = expansionRomFile->currentText().trimmed(); + } + if (expansionBoardOption && expansionBoardOptionCheck && expansionBoardSelector && expansionBoardString) { + const QString option = expansionBoardOption->currentData().toString(); + int slot = 0; + const QString boardKey = expansionBoardBaseKey(currentExpansionBoardConfigName, &slot); + const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, boardKey); + const WinUaeQtBoardSetting *choice = board ? boardSettingChoiceByConfig(board->settings, option) : nullptr; + if (choice && choice->type == WinUaeQtBoardSettingType::CheckBox) { + state.optionBools.insert(option, expansionBoardOptionCheck->isChecked()); + } else if (choice && choice->type == WinUaeQtBoardSettingType::Multi) { + state.optionValues.insert(option, expansionBoardSelector->currentData().toString()); + } else if (choice && choice->type == WinUaeQtBoardSettingType::String) { + state.optionValues.insert(option, expansionBoardString->text().trimmed()); + } + } + if (state.present) { + expansionBoardStates.insert(currentExpansionBoardConfigName, state); + } else { + expansionBoardStates.remove(currentExpansionBoardConfigName); + } + refreshExpansionBoardChoiceLabels(); + refreshHardwareInfoPage(); + } + + void clearExpansionBoardStates() + { + expansionBoardStates.clear(); + currentExpansionBoardConfigName.clear(); + if (expansionRomCategory) { + expansionRomCategory->setCurrentIndex(0); + } + if (expansionRomBoard) { + populateExpansionBoardChoices(); + loadExpansionBoardUiState(QString(), 0); + } + } + + bool setExpansionBoardFromConfigName(const QString &configName) + { + int slot = 0; + const QString boardKey = expansionBoardBaseKey(configName, &slot); + const WinUaeQtExpansionBoardCatalogItem *choice = expansionBoardChoiceByKey(boardCatalog, boardKey); + if (!choice || !expansionRomCategory || !expansionRomBoard || !expansionRomSlot) { + return false; + } + + QString categoryDisplay; + for (const WinUaeQtExpansionCategoryChoice *category = expansionBoardCategoryChoices; category->display; category++) { + if (expansionBoardMatchesCategory(*choice, *category)) { + categoryDisplay = QString::fromLatin1(category->display); + break; + } + } + if (!categoryDisplay.isEmpty() && expansionRomCategory->currentText() != categoryDisplay) { + expansionRomCategory->setCurrentText(categoryDisplay); + } + populateExpansionBoardChoices(boardKey); + for (int i = 0; i < expansionRomBoard->count(); i++) { + if (expansionRomBoard->itemData(i).toString().compare(boardKey, Qt::CaseInsensitive) == 0) { + expansionRomBoard->setCurrentIndex(i); + break; + } + } + expansionRomSlot->setCurrentText(QString::number(qBound(0, slot, 3) + 1)); + loadExpansionBoardUiState(boardKey, slot); + return true; + } + + bool applyExpansionBoardSetting(const QString &key, const QString &value) + { + QString configName; + bool isOptions = false; + if (key.endsWith(QStringLiteral("_rom_file"))) { + configName = key.left(key.size() - int(QStringLiteral("_rom_file").size())); + } else if (key.endsWith(QStringLiteral("_rom_options"))) { + configName = key.left(key.size() - int(QStringLiteral("_rom_options").size())); + isOptions = true; + } else { + return false; + } + + int slot = 0; + const QString boardKey = expansionBoardBaseKey(configName, &slot); + const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, boardKey); + if (!board) { + return false; + } + + WinUaeQtExpansionBoardState state = expansionBoardStates.value(configName); + if (isOptions) { + state.rawOptions = value; + state.subtype = expansionOptionValue(value, QStringLiteral("subtype")); + state.autobootDisabled = expansionOptionBool(value, QStringLiteral("autoboot_disabled")); + state.dma24Bit = expansionOptionBool(value, QStringLiteral("dma24bit")); + state.inserted = expansionOptionBool(value, QStringLiteral("inserted")); + const QString id = expansionOptionValue(value, QStringLiteral("id")); + if (!id.isEmpty()) { + state.id = qBound(0, id.toInt(), 7); + } + for (const WinUaeQtBoardSetting &choice : board->settings) { + if (choice.type == WinUaeQtBoardSettingType::CheckBox) { + const QString option = choice.configValue; + state.optionBools.insert(option, expansionOptionBool(value, option)); + } else if (choice.type == WinUaeQtBoardSettingType::String) { + state.optionValues.insert(choice.configValue, expansionOptionValue(value, choice.configValue)); + } else if (choice.type == WinUaeQtBoardSettingType::Multi) { + for (const QString &option : choice.multiValues) { + if (expansionOptionContains(value, option)) { + state.optionValues.insert(choice.configValue, option); + break; + } + } + } + } + } else { + state.present = !value.trimmed().isEmpty(); + state.romFile = value.trimmed(); + } + expansionBoardStates.insert(configName, state); + return setExpansionBoardFromConfigName(configName); + } + + void insertExpansionBoardSettings(WinUaeQtConfig::Settings &settings) const + { + for (auto it = expansionBoardStates.constBegin(); it != expansionBoardStates.constEnd(); ++it) { + const WinUaeQtExpansionBoardState &state = it.value(); + if (!state.present) { + continue; + } + int slot = 0; + const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, expansionBoardBaseKey(it.key(), &slot)); + if (!board) { + continue; + } + const QString romFile = expansionBoardNoRomFile(board, state.subtype) + ? QStringLiteral(":ENABLED") + : (state.romFile.trimmed().isEmpty() ? QStringLiteral(":ENABLED") : state.romFile.trimmed()); + settings.insert(it.key() + QStringLiteral("_rom_file"), romFile); + const QString options = expansionBoardOptionsValue(state, board); + if (!options.isEmpty()) { + settings.insert(it.key() + QStringLiteral("_rom_options"), options); + } + } + } + + WinUaeQtConfig::Settings currentExpansionBoardSettings() const + { + WinUaeQtConfig::Settings settings; + insertExpansionBoardSettings(settings); + return settings; + } + + QStringList expansionBoardOwnedKeys() const + { + QStringList keys; + for (const WinUaeQtExpansionBoardCatalogItem &choice : boardCatalog.expansionBoards) { + for (int slot = 0; slot < 4; slot++) { + const QString name = expansionBoardConfigName(choice.key, slot); + keys.append(name + QStringLiteral("_rom_file")); + keys.append(name + QStringLiteral("_rom_options")); + } + } + return keys; + } + + QString selectedCpuBoardType() const + { + if (!cpuBoardType) { + return QString(); + } + const QString data = cpuBoardType->currentData().toString(); + if (!data.isEmpty()) { + return data; + } + const QString text = cpuBoardType->currentText().trimmed(); + return text == QStringLiteral("None") ? QString() : text; + } + + QString selectedCpuBoardConfigValue() const + { + if (!cpuBoardSubtype || !cpuBoardSubtype->isEnabled()) { + return QString(); + } + return cpuBoardSubtype->currentData().toString(); + } + + const WinUaeQtCpuBoardCatalogItem *selectedCpuBoardChoice() const + { + return cpuBoardSubtypeChoiceByConfig(boardCatalog, selectedCpuBoardConfigValue()); + } + + QStringList cpuBoardMemoryItems(int maxMemoryMb) const + { + QStringList items { QStringLiteral("None") }; + for (int mb : { 1, 2, 4, 8, 16, 32, 64, 128, 256 }) { + if (maxMemoryMb <= 0 || mb <= maxMemoryMb) { + items.append(QStringLiteral("%1 MB").arg(mb)); + } + } + return items; + } + + bool cpuBoardOptionIsMulti(const WinUaeQtBoardSetting *choice) const + { + return choice + && choice->type == WinUaeQtBoardSettingType::Multi + && !choice->multiValues.isEmpty(); + } + + QStringList cpuBoardOptionTokens(const WinUaeQtBoardSetting *choice) const + { + if (!choice) { + return {}; + } + if (cpuBoardOptionIsMulti(choice)) { + return choice->multiValues; + } + return { choice->configValue }; + } + + bool cpuBoardSettingsContainToken(const QString &token) const + { + for (const QString &field : expansionOptionTokens(cpuBoardSettingsRaw)) { + if (field.compare(token, Qt::CaseInsensitive) == 0) { + return true; + } + } + return false; + } + + QString cpuBoardSelectedMultiValue(const WinUaeQtBoardSetting *choice) const + { + const QStringList values = cpuBoardOptionTokens(choice); + for (const QString &value : values) { + if (cpuBoardSettingsContainToken(value)) { + return value; + } + } + return values.value(0); + } + + void setCpuBoardSettingsToken(const WinUaeQtBoardSetting *choice, const QString &selectedValue, bool enabled) + { + if (!choice) { + return; + } + const QStringList removeTokens = cpuBoardOptionTokens(choice); + QStringList tokens; + for (const QString &token : expansionOptionTokens(cpuBoardSettingsRaw)) { + bool remove = false; + for (const QString &known : removeTokens) { + if (token.compare(known, Qt::CaseInsensitive) == 0) { + remove = true; + break; + } + } + if (!remove) { + tokens.append(token); + } + } + if (cpuBoardOptionIsMulti(choice)) { + if (!selectedValue.isEmpty()) { + tokens.append(selectedValue); + } + } else if (enabled) { + tokens.append(choice->configValue); + } + cpuBoardSettingsRaw = tokens.join(QLatin1Char(',')); + } + + void populateCpuBoardTypeChoices() + { + if (!cpuBoardType) { + return; + } + const QSignalBlocker blocker(cpuBoardType); + const QString selected = selectedCpuBoardType(); + cpuBoardType->clear(); + cpuBoardType->addItem(QStringLiteral("None"), QString()); + int selectedIndex = 0; + QStringList seenTypes; + for (const WinUaeQtCpuBoardCatalogItem &choice : boardCatalog.cpuBoards) { + const QString typeText = choice.type; + if (seenTypes.contains(typeText) || !cpuBoardTypeHasSubtypes(boardCatalog, typeText)) { + continue; + } + seenTypes.append(typeText); + cpuBoardType->addItem(typeText, typeText); + if (selected == typeText) { + selectedIndex = cpuBoardType->count() - 1; + } + } + cpuBoardType->setCurrentIndex(selectedIndex); + } + + void populateCpuBoardSubtypeChoices(const QString &requestedConfig = QString()) + { + if (!cpuBoardSubtype) { + return; + } + const QString selectedType = selectedCpuBoardType(); + const QString currentConfig = requestedConfig.isEmpty() ? selectedCpuBoardConfigValue() : requestedConfig; + const QSignalBlocker blocker(cpuBoardSubtype); + cpuBoardSubtype->clear(); + int selectedIndex = -1; + for (const WinUaeQtCpuBoardCatalogItem &choice : boardCatalog.cpuBoards) { + if (selectedType != choice.type) { + continue; + } + cpuBoardSubtype->addItem(choice.display, choice.configValue); + if (currentConfig.compare(choice.configValue, Qt::CaseInsensitive) == 0) { + selectedIndex = cpuBoardSubtype->count() - 1; + } + } + if (cpuBoardSubtype->count() == 0) { + cpuBoardSubtype->addItem(QStringLiteral("Default"), QString()); + cpuBoardSubtype->setCurrentIndex(0); + cpuBoardSubtype->setEnabled(false); + return; + } + cpuBoardSubtype->setCurrentIndex(selectedIndex >= 0 ? selectedIndex : 0); + cpuBoardSubtype->setEnabled(true); + } + + void setCpuBoardMemoryMb(int memoryMb) + { + if (!cpuBoardMem) { + return; + } + const QString text = memoryMb > 0 ? QStringLiteral("%1 MB").arg(memoryMb) : QStringLiteral("None"); + if (cpuBoardMem->findText(text) < 0) { + cpuBoardMem->addItem(text); + } + cpuBoardMem->setCurrentText(text); + } + + void updateCpuBoardControls() + { + if (!cpuBoardSubtype || !cpuBoardRom || !cpuBoardMem || !cpuBoardBrowse) { + return; + } + const WinUaeQtCpuBoardCatalogItem *choice = selectedCpuBoardChoice(); + const bool hasBoard = choice != nullptr; + cpuBoardSubtype->setEnabled(!selectedCpuBoardType().isEmpty() && cpuBoardSubtype->count() > 0); + cpuBoardRom->setEnabled(hasBoard); + cpuBoardBrowse->setEnabled(hasBoard); + + const int currentMemory = megabytesFromText(cpuBoardMem->currentText()); + const QSignalBlocker blocker(cpuBoardMem); + cpuBoardMem->clear(); + cpuBoardMem->addItems(cpuBoardMemoryItems(choice ? choice->maxMemoryMb : 0)); + setCpuBoardMemoryMb(hasBoard ? qMin(currentMemory, choice->maxMemoryMb) : 0); + cpuBoardMem->setEnabled(hasBoard); + updateCpuBoardOptionChoices(); + } + + void selectCpuBoardConfigValue(const QString &configValue) + { + const WinUaeQtCpuBoardCatalogItem *choice = cpuBoardSubtypeChoiceByConfig(boardCatalog, configValue); + cpuBoardUpdating = true; + if (!choice) { + if (cpuBoardType) { + cpuBoardType->setCurrentIndex(0); + } + populateCpuBoardSubtypeChoices(); + updateCpuBoardControls(); + cpuBoardUpdating = false; + return; + } + if (cpuBoardType) { + cpuBoardType->setCurrentText(choice->type); + } + populateCpuBoardSubtypeChoices(choice->configValue); + updateCpuBoardControls(); + cpuBoardUpdating = false; + } + + void updateCpuBoardOptionChoices() + { + if (!acceleratorOption) { + return; + } + const QString requested = acceleratorOption->currentData().toString(); + const WinUaeQtCpuBoardCatalogItem *board = selectedCpuBoardChoice(); + const QSignalBlocker blocker(acceleratorOption); + acceleratorOption->clear(); + acceleratorOption->addItem(QStringLiteral("None"), QString()); + int selectedIndex = 0; + if (board) { + for (const WinUaeQtBoardSetting &choice : board->settings) { + if (choice.type == WinUaeQtBoardSettingType::String) { + continue; + } + acceleratorOption->addItem(choice.display, choice.configValue); + if (requested.compare(choice.configValue, Qt::CaseInsensitive) == 0) { + selectedIndex = acceleratorOption->count() - 1; + } else if (selectedIndex == 0 && requested.isEmpty()) { + for (const QString &token : cpuBoardOptionTokens(&choice)) { + if (cpuBoardSettingsContainToken(token)) { + selectedIndex = acceleratorOption->count() - 1; + break; + } + } + } + } + } + acceleratorOption->setCurrentIndex(selectedIndex); + acceleratorOption->setEnabled(acceleratorOption->count() > 1); + updateCpuBoardOptionControls(); + } + + void updateCpuBoardOptionControls() + { + if (!acceleratorOption || !acceleratorSelector || !acceleratorOptionCheck) { + return; + } + const QString option = acceleratorOption->currentData().toString(); + const WinUaeQtCpuBoardCatalogItem *board = selectedCpuBoardChoice(); + const WinUaeQtBoardSetting *choice = board ? boardSettingChoiceByConfig(board->settings, option) : nullptr; + const bool isMulti = cpuBoardOptionIsMulti(choice); + + const QSignalBlocker selectorBlocker(acceleratorSelector); + const QSignalBlocker checkBlocker(acceleratorOptionCheck); + acceleratorSelector->clear(); + if (isMulti) { + const QStringList displays = choice->multiDisplays; + const QStringList values = choice->multiValues; + for (int i = 0; i < values.size(); i++) { + acceleratorSelector->addItem(displays.value(i, values[i]), values[i]); + } + const QString selected = cpuBoardSelectedMultiValue(choice); + const int index = acceleratorSelector->findData(selected); + acceleratorSelector->setCurrentIndex(index >= 0 ? index : 0); + } else { + acceleratorSelector->addItem(QStringLiteral("None"), QString()); + acceleratorSelector->setCurrentIndex(0); + } + acceleratorSelector->setEnabled(isMulti); + acceleratorOptionCheck->setEnabled(choice && !isMulti); + acceleratorOptionCheck->setChecked(choice && !isMulti && cpuBoardSettingsContainToken(choice->configValue)); + } + + void storeCpuBoardOptionFromUi() + { + if (cpuBoardUpdating || !acceleratorOption || !acceleratorSelector || !acceleratorOptionCheck) { + return; + } + const QString option = acceleratorOption->currentData().toString(); + const WinUaeQtCpuBoardCatalogItem *board = selectedCpuBoardChoice(); + const WinUaeQtBoardSetting *choice = board ? boardSettingChoiceByConfig(board->settings, option) : nullptr; + if (!choice) { + return; + } + if (cpuBoardOptionIsMulti(choice)) { + setCpuBoardSettingsToken(choice, acceleratorSelector->currentData().toString(), true); + } else { + setCpuBoardSettingsToken(choice, QString(), acceleratorOptionCheck->isChecked()); + } + } + + QWidget *makeHardwareInfoPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + + hardwareBoardList = new QTreeWidget; + hardwareBoardList->setRootIsDecorated(false); + hardwareBoardList->setAlternatingRowColors(true); + hardwareBoardList->setSelectionMode(QAbstractItemView::SingleSelection); + hardwareBoardList->setEditTriggers(QAbstractItemView::NoEditTriggers); + hardwareBoardList->setHeaderLabels({ + QStringLiteral("Type"), + QStringLiteral("Name"), + QStringLiteral("Start"), + QStringLiteral("End"), + QStringLiteral("Size"), + QStringLiteral("ID") + }); + root->addWidget(hardwareBoardList, 1); + + QHBoxLayout *actions = new QHBoxLayout; + hardwareCustomBoardOrder = new QCheckBox(QStringLiteral("Custom board order")); + hardwareMoveUp = new QPushButton(QStringLiteral("Move up")); + hardwareMoveDown = new QPushButton(QStringLiteral("Move down")); + if (!hasHardwareInfoProvider()) { + disableUnavailable(hardwareMoveUp, QStringLiteral("Hardware board reordering needs the integrated Unix preferences model.")); + disableUnavailable(hardwareMoveDown, QStringLiteral("Hardware board reordering needs the integrated Unix preferences model.")); + } + actions->addWidget(hardwareCustomBoardOrder); + actions->addStretch(); + actions->addWidget(hardwareMoveUp); + actions->addWidget(hardwareMoveDown); + root->addLayout(actions); + + connect(hardwareCustomBoardOrder, &QCheckBox::toggled, this, [this](bool enabled) { + if (hardwareProvider.setCustomOrder) { + hardwareProvider.setCustomOrder(hardwareProvider.context, enabled); + } + refreshHardwareInfoPage(); + }); + connect(hardwareBoardList, &QTreeWidget::itemSelectionChanged, this, [this]() { updateHardwareMoveButtons(); }); + connect(hardwareMoveUp, &QPushButton::clicked, this, [this]() { moveSelectedHardwareBoard(-1); }); + connect(hardwareMoveDown, &QPushButton::clicked, this, [this]() { moveSelectedHardwareBoard(1); }); + return page; + } + + quint64 bytesFromMemoryText(QString text) const + { + text = text.trimmed(); + if (text == QStringLiteral("None") || text.isEmpty()) { + return 0; + } + if (text.endsWith(QStringLiteral("KB"))) { + text.chop(2); + return quint64(text.trimmed().toDouble() * 1024.0); + } + if (text.endsWith(QStringLiteral("MB"))) { + text.chop(2); + return quint64(text.trimmed().toDouble() * 1024.0 * 1024.0); + } + return 0; + } + + QString hardwareSizeText(quint64 bytes) const + { + if (!bytes) { + return QStringLiteral("-"); + } + return QStringLiteral("0x%1").arg(bytes, 8, 16, QLatin1Char('0')); + } + + void addHardwareBoardRow( + const QString &type, + const QString &name, + const QString &start, + const QString &end, + const QString &size, + const QString &id, + int boardIndex = -1, + bool movable = false) + { + if (!hardwareBoardList) { + return; + } + QTreeWidgetItem *item = new QTreeWidgetItem(hardwareBoardList); + item->setText(0, type); + item->setText(1, name); + item->setText(2, start); + item->setText(3, end); + item->setText(4, size); + item->setText(5, id); + item->setData(0, HardwareBoardIndexRole, boardIndex); + item->setData(0, Qt::UserRole + 1, movable); + } + + bool hasHardwareInfoProvider() const + { + return hardwareProvider.context && hardwareProvider.boards; + } + + bool hardwareCustomOrderEnabled() const + { + if (hardwareProvider.customOrder && hardwareProvider.context) { + return hardwareProvider.customOrder(hardwareProvider.context); + } + return hardwareCustomBoardOrder && hardwareCustomBoardOrder->isChecked(); + } + + void updateHardwareMoveButtons() + { + bool moveUp = false; + bool moveDown = false; + if (hasHardwareInfoProvider() && hardwareCustomOrderEnabled() && hardwareBoardList && hardwareBoardList->currentItem()) { + const int index = hardwareBoardList->currentItem()->data(0, HardwareBoardIndexRole).toInt(); + if (hardwareProvider.canMove) { + moveUp = hardwareProvider.canMove(hardwareProvider.context, index, -1); + moveDown = hardwareProvider.canMove(hardwareProvider.context, index, 1); + } + } + if (hardwareMoveUp) { + hardwareMoveUp->setEnabled(moveUp); + } + if (hardwareMoveDown) { + hardwareMoveDown->setEnabled(moveDown); + } + } + + void moveSelectedHardwareBoard(int direction) + { + if (!hasHardwareInfoProvider() || !hardwareProvider.move || !hardwareBoardList || !hardwareBoardList->currentItem()) { + return; + } + const int index = hardwareBoardList->currentItem()->data(0, HardwareBoardIndexRole).toInt(); + const int newIndex = hardwareProvider.move(hardwareProvider.context, index, direction); + refreshHardwareInfoPage(); + if (newIndex >= 0) { + for (int row = 0; row < hardwareBoardList->topLevelItemCount(); row++) { + QTreeWidgetItem *item = hardwareBoardList->topLevelItem(row); + if (item && item->data(0, HardwareBoardIndexRole).toInt() == newIndex) { + hardwareBoardList->setCurrentItem(item); + break; + } + } + } + updateHardwareMoveButtons(); + } + + void mergeHardwareOrderSettings(WinUaeQtConfig::Settings &settings) const + { + if (!hardwareProvider.orderSettings || !hardwareProvider.context) { + return; + } + const WinUaeQtConfig::Settings orders = hardwareProvider.orderSettings(hardwareProvider.context); + for (auto it = orders.constBegin(); it != orders.constEnd(); ++it) { + if (!hardwareOrderOwnedKeys.contains(it.key())) { + hardwareOrderOwnedKeys.append(it.key()); + } + if (it.key() == QStringLiteral("board_custom_order")) { + continue; + } + const QString order = expansionOptionValue(it.value(), QStringLiteral("order")); + if (order.isEmpty()) { + continue; + } + settings.insert(it.key(), expansionOptionsWithValue(settings.value(it.key()), QStringLiteral("order"), order)); + } + } + + void trackHardwareOrderSetting(const QString &key, const QString &value) + { + if (!hardwareOrderOwnedKeys.contains(key) && !expansionOptionValue(value, QStringLiteral("order")).isEmpty()) { + hardwareOrderOwnedKeys.append(key); + } + } + + void refreshHardwareInfoPage() + { + if (!hardwareBoardList) { + return; + } + hardwareBoardList->clear(); + + if (hasHardwareInfoProvider()) { + if (hardwareProvider.applyConfig) { + hardwareProvider.applyConfig(hardwareProvider.context, mergedConfig()); + } + if (hardwareCustomBoardOrder) { + QSignalBlocker blocker(hardwareCustomBoardOrder); + hardwareCustomBoardOrder->setChecked(hardwareCustomOrderEnabled()); + hardwareCustomBoardOrder->setEnabled(true); + } + const QVector boards = hardwareProvider.boards(hardwareProvider.context); + for (const WinUaeQtHardwareBoard &board : boards) { + addHardwareBoardRow(board.type, board.name, board.start, board.end, board.size, board.id, board.index, board.movable); + } + for (int i = 0; i < hardwareBoardList->columnCount(); i++) { + hardwareBoardList->resizeColumnToContents(i); + } + updateHardwareMoveButtons(); + return; + } + + const quint64 chipBytes = bytesFromMemoryText(chipMem ? chipMem->currentText() : QString()); + if (chipBytes) { + addHardwareBoardRow( + QStringLiteral("-"), + QStringLiteral("Chip memory"), + QStringLiteral("0x00000000"), + QStringLiteral("0x%1").arg(chipBytes - 1, 8, 16, QLatin1Char('0')), + hardwareSizeText(chipBytes), + QStringLiteral("-")); + } + + const quint64 slowBytes = bytesFromMemoryText(slowMem ? slowMem->currentText() : QString()); + if (slowBytes) { + addHardwareBoardRow(QStringLiteral("-"), QStringLiteral("Slow memory"), QStringLiteral("-"), QStringLiteral("-"), hardwareSizeText(slowBytes), QStringLiteral("-")); + } + + const quint64 z2Bytes = bytesFromMemoryText(z2Fast ? z2Fast->currentText() : QString()); + if (z2Bytes) { + addHardwareBoardRow(QStringLiteral("Z2"), QStringLiteral("Fast memory"), QStringLiteral("-"), QStringLiteral("-"), hardwareSizeText(z2Bytes), QStringLiteral("-")); + } + + const quint64 z3Bytes = bytesFromMemoryText(z3Fast ? z3Fast->currentText() : QString()); + if (z3Bytes) { + addHardwareBoardRow(QStringLiteral("Z3"), QStringLiteral("Fast memory"), QStringLiteral("-"), QStringLiteral("-"), hardwareSizeText(z3Bytes), QStringLiteral("-")); + } + + const quint64 z3ChipBytes = bytesFromMemoryText(z3ChipMem ? z3ChipMem->currentText() : QString()); + if (z3ChipBytes) { + addHardwareBoardRow(QStringLiteral("Z3"), QStringLiteral("Z3 chip memory"), QStringLiteral("-"), QStringLiteral("-"), hardwareSizeText(z3ChipBytes), QStringLiteral("-")); + } + + const quint64 processorBytes = bytesFromMemoryText(processorSlotMem ? processorSlotMem->currentText() : QString()); + if (processorBytes) { + addHardwareBoardRow(QStringLiteral("-"), QStringLiteral("Processor slot memory"), QStringLiteral("-"), QStringLiteral("-"), hardwareSizeText(processorBytes), QStringLiteral("-")); + } + + const quint64 rtgBytes = bytesFromMemoryText(rtgMem ? rtgMem->currentText() : QString()); + if (rtgBytes) { + addHardwareBoardRow( + rtgBusName(rtgType ? rtgType->currentText() : QString()), + QStringLiteral("RTG graphics card"), + QStringLiteral("-"), + QStringLiteral("-"), + hardwareSizeText(rtgBytes), + QStringLiteral("gfxcard")); + } + + storeCurrentCustomRomBoard(); + for (int i = 0; i < customRomBoards.size(); i++) { + const WinUaeQtRomBoard &board = customRomBoards[i]; + if (board.start.isEmpty() || board.end.isEmpty()) { + continue; + } + bool startOk = false; + bool endOk = false; + const quint64 start = board.start.toULongLong(&startOk, 16); + const quint64 end = board.end.toULongLong(&endOk, 16); + const quint64 size = startOk && endOk && end >= start ? end - start + 1 : 0; + addHardwareBoardRow( + QStringLiteral("-"), + QStringLiteral("Custom ROM board #%1").arg(i + 1), + QStringLiteral("0x%1").arg(start, 8, 16, QLatin1Char('0')), + QStringLiteral("0x%1").arg(end, 8, 16, QLatin1Char('0')), + hardwareSizeText(size), + board.path.isEmpty() ? QStringLiteral("-") : QFileInfo(board.path).fileName()); + } + + for (int i = 0; i < hardwareBoardList->columnCount(); i++) { + hardwareBoardList->resizeColumnToContents(i); + } + if (hardwareMoveUp) { + hardwareMoveUp->setEnabled(false); + } + if (hardwareMoveDown) { + hardwareMoveDown->setEnabled(false); + } + } + + QWidget *makeDisplayPage() + { + QWidget *page = makePage(); + QHBoxLayout *root = new QHBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + + QVBoxLayout *left = new QVBoxLayout; + windowWidth = new QLineEdit(QStringLiteral("720")); + windowHeight = new QLineEdit(QStringLiteral("568")); + windowResize = new QCheckBox(QStringLiteral("Window resize")); + fullscreenResolution = combo({ QStringLiteral("Native"), QStringLiteral("640x480"), QStringLiteral("800x600"), QStringLiteral("1024x768"), QStringLiteral("1280x720"), QStringLiteral("1920x1080") }); + fullscreenResolution->setEditable(true); + displayRefreshRate = combo({ QStringLiteral("Default"), QStringLiteral("50"), QStringLiteral("60"), QStringLiteral("70"), QStringLiteral("75"), QStringLiteral("120"), QStringLiteral("144") }, QStringLiteral("Default")); + displayBufferCount = combo({ QStringLiteral("Double"), QStringLiteral("Triple") }, QStringLiteral("Double")); + hostDisplay = new QComboBox; + const QVector> displays = displayDeviceChoices(); + for (const QPair &display : displays) { + hostDisplay->addItem(display.first, display.second); + hostDisplay->setItemData(hostDisplay->count() - 1, displayFriendlyName(display.first), Qt::UserRole + 1); + } + QGridLayout *screen = new QGridLayout; + screen->setColumnStretch(1, 1); + screen->addWidget(hostDisplay, 0, 0, 1, 4); + screen->addWidget(label(QStringLiteral("Fullscreen:")), 1, 0); + screen->addWidget(fullscreenResolution, 1, 1, 1, 2); + screen->addWidget(displayRefreshRate, 1, 3); + screen->addWidget(label(QStringLiteral("Windowed:")), 2, 0); + screen->addWidget(windowWidth, 2, 1); + screen->addWidget(windowHeight, 2, 2); + screen->addWidget(displayBufferCount, 2, 3); + screen->addWidget(windowResize, 3, 1, 1, 3); + left->addWidget(groupBox(QStringLiteral("Screen"), screen)); + + nativeMode = combo({ QStringLiteral("Windowed"), QStringLiteral("Fullscreen"), QStringLiteral("Full-window") }, QStringLiteral("Windowed")); + nativeVsync = combo({ + QStringLiteral("No vsync"), + QStringLiteral("VSync (Busy wait)"), + QStringLiteral("VSync autoswitch (Busy wait)"), + QStringLiteral("VSync"), + QStringLiteral("VSync autoswitch") + }, QStringLiteral("No vsync")); + nativeFrameSlices = combo({ QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4") }, QStringLiteral("4")); + rtgMode = combo({ QStringLiteral("Windowed"), QStringLiteral("Fullscreen"), QStringLiteral("Full-window") }, QStringLiteral("Windowed")); + rtgVsync = combo({ QStringLiteral("-"), QStringLiteral("VSync (Busy wait)") }, QStringLiteral("-")); + displayResolution = combo({ QStringLiteral("lores"), QStringLiteral("hires"), QStringLiteral("superhires") }, QStringLiteral("hires")); + displayOverscan = combo({ + QStringLiteral("TV (narrow)"), + QStringLiteral("TV (standard)"), + QStringLiteral("TV (wide)"), + QStringLiteral("Overscan"), + QStringLiteral("Overscan+"), + QStringLiteral("Extreme"), + QStringLiteral("Ultra extreme debug"), + QStringLiteral("Ultra extreme debug (HV)"), + QStringLiteral("Ultra extreme debug (C)") + }, QStringLiteral("Overscan")); + displayAutoResolution = combo({ QStringLiteral("Disabled"), QStringLiteral("Always on"), QStringLiteral("10%"), QStringLiteral("33%"), QStringLiteral("66%"), QStringLiteral("100%") }, QStringLiteral("Disabled")); + displayFrameRate = new QSpinBox; + displayFrameRate->setRange(1, 20); + displayFlickerFixer = new QCheckBox(QStringLiteral("Remove interlace artifacts")); + displayLoresSmoothed = new QCheckBox(QStringLiteral("Filtered low resolution")); + displayBlackerThanBlack = new QCheckBox(QStringLiteral("Blacker than black")); + displayMonochrome = new QCheckBox(QStringLiteral("Monochrome video out")); + displayAutoResolutionVga = new QCheckBox(QStringLiteral("VGA mode resolution autoswitch")); + displayResyncBlank = new QCheckBox(QStringLiteral("Display resync blanking")); + QGridLayout *settings = new QGridLayout; + settings->setColumnStretch(1, 1); + settings->addWidget(label(QStringLiteral("Native:")), 0, 0); + settings->addWidget(nativeMode, 0, 1); + settings->addWidget(nativeVsync, 0, 2); + settings->addWidget(nativeFrameSlices, 0, 3); + settings->addWidget(label(QStringLiteral("RTG:")), 1, 0); + settings->addWidget(rtgMode, 1, 1); + settings->addWidget(rtgVsync, 1, 2, 1, 2); + settings->addWidget(label(QStringLiteral("Resolution:")), 2, 0); + settings->addWidget(displayResolution, 2, 1); + settings->addWidget(label(QStringLiteral("Overscan:")), 2, 2); + settings->addWidget(displayOverscan, 2, 3); + settings->addWidget(label(QStringLiteral("Resolution autoswitch:")), 3, 0); + settings->addWidget(displayAutoResolution, 3, 1); + settings->addWidget(label(QStringLiteral("Refresh:")), 3, 2); + settings->addWidget(displayFrameRate, 3, 3); + settings->addWidget(displayBlackerThanBlack, 4, 1); + settings->addWidget(displayLoresSmoothed, 4, 2, 1, 2); + settings->addWidget(displayFlickerFixer, 5, 1); + settings->addWidget(displayAutoResolutionVga, 5, 2, 1, 2); + settings->addWidget(displayMonochrome, 6, 1); + settings->addWidget(displayResyncBlank, 6, 2, 1, 2); + left->addWidget(groupBox(QStringLiteral("Settings"), settings), 1); + + QVBoxLayout *right = new QVBoxLayout; + QVBoxLayout *center = new QVBoxLayout; + displayCenterHorizontal = new QCheckBox(QStringLiteral("Horizontal")); + displayCenterVertical = new QCheckBox(QStringLiteral("Vertical")); + center->addWidget(displayCenterHorizontal); + center->addWidget(displayCenterVertical); + right->addWidget(groupBox(QStringLiteral("Centering"), center)); + QVBoxLayout *aspect = new QVBoxLayout; + displayKeepAspect = new QCheckBox(QStringLiteral("Automatic integer scaling")); + aspect->addWidget(displayKeepAspect); + right->addWidget(groupBox(QStringLiteral("Aspect ratio"), aspect)); + QVBoxLayout *lineMode = new QVBoxLayout; + displayLineModeButtons = new QButtonGroup(this); + QRadioButton *singleLine = new QRadioButton(QStringLiteral("Single")); + QRadioButton *doubleLine = new QRadioButton(QStringLiteral("Double")); + QRadioButton *scanlines = new QRadioButton(QStringLiteral("Scanlines")); + QRadioButton *doubleFields = new QRadioButton(QStringLiteral("Double, fields")); + QRadioButton *doubleFieldsPlus = new QRadioButton(QStringLiteral("Double, fields+")); + displayLineModeButtons->addButton(singleLine, 0); + displayLineModeButtons->addButton(doubleLine, 1); + displayLineModeButtons->addButton(scanlines, 2); + displayLineModeButtons->addButton(doubleFields, 3); + displayLineModeButtons->addButton(doubleFieldsPlus, 4); + doubleLine->setChecked(true); + lineMode->addWidget(singleLine); + lineMode->addWidget(doubleLine); + lineMode->addWidget(scanlines); + lineMode->addWidget(doubleFields); + lineMode->addWidget(doubleFieldsPlus); + right->addWidget(groupBox(QStringLiteral("Line mode"), lineMode)); + QVBoxLayout *interlacedLineMode = new QVBoxLayout; + displayInterlacedLineModeButtons = new QButtonGroup(this); + QRadioButton *interlacedSingle = new QRadioButton(QStringLiteral("Single")); + QRadioButton *interlacedFrames = new QRadioButton(QStringLiteral("Double, frames")); + QRadioButton *interlacedFields = new QRadioButton(QStringLiteral("Double, fields")); + QRadioButton *interlacedFieldsPlus = new QRadioButton(QStringLiteral("Double, fields+")); + displayInterlacedLineModeButtons->addButton(interlacedSingle, 0); + displayInterlacedLineModeButtons->addButton(interlacedFrames, 1); + displayInterlacedLineModeButtons->addButton(interlacedFields, 2); + displayInterlacedLineModeButtons->addButton(interlacedFieldsPlus, 3); + interlacedFrames->setChecked(true); + interlacedLineMode->addWidget(interlacedSingle); + interlacedLineMode->addWidget(interlacedFrames); + interlacedLineMode->addWidget(interlacedFields); + interlacedLineMode->addWidget(interlacedFieldsPlus); + right->addWidget(groupBox(QStringLiteral("Interlaced line mode"), interlacedLineMode)); + right->addStretch(); + + root->addLayout(left, 3); + root->addLayout(right, 1); + return page; + } + + QWidget *makeFilterPage() + { + QWidget *page = makePage(); + QVBoxLayout *outer = new QVBoxLayout(page); + outer->setContentsMargins(0, 0, 0, 0); + + QWidget *content = new QWidget; + QVBoxLayout *root = new QVBoxLayout(content); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + auto doubleSpin = [](double minimum, double maximum, double step, int decimals) { + QDoubleSpinBox *spin = new QDoubleSpinBox; + spin->setRange(minimum, maximum); + spin->setSingleStep(step); + spin->setDecimals(decimals); + spin->setAlignment(Qt::AlignRight); + return spin; + }; + auto intSpin = [](int minimum, int maximum) { + QSpinBox *spin = new QSpinBox; + spin->setRange(minimum, maximum); + spin->setAlignment(Qt::AlignRight); + return spin; + }; + + filterTarget = combo(configChoiceDisplays(filterTargetChoices, int(sizeof(filterTargetChoices) / sizeof(filterTargetChoices[0]))), QStringLiteral("Native")); + filterEnable = new QCheckBox(QStringLiteral("Enabled")); + filterMode = combo(configChoiceDisplays(filterModeChoices, int(sizeof(filterModeChoices) / sizeof(filterModeChoices[0]))), QStringLiteral("None")); + filterModeH = combo(configChoiceDisplays(filterModeHChoices, int(sizeof(filterModeHChoices) / sizeof(filterModeHChoices[0]))), QStringLiteral("1x")); + filterModeV = combo(configChoiceDisplays(filterModeVChoices, int(sizeof(filterModeVChoices) / sizeof(filterModeVChoices[0]))), QStringLiteral("-")); + if (!unixShaderPipelineAvailable()) { + const QString reason = QStringLiteral("Host shader/filter mode selection is not implemented by the Unix graphics backend yet."); + disableUnavailable(filterMode, reason); + disableUnavailable(filterModeH, reason); + disableUnavailable(filterModeV, reason); + } + filterAutoscale = combo({}); + filterIntegerLimit = combo(configChoiceDisplays(filterIntegerLimitChoices, int(sizeof(filterIntegerLimitChoices) / sizeof(filterIntegerLimitChoices[0]))), QStringLiteral("1/1")); + QPushButton *reset = new QPushButton(QStringLiteral("Reset to defaults")); + + QGridLayout *settings = new QGridLayout; + settings->setColumnStretch(1, 1); + settings->setColumnStretch(3, 1); + settings->addWidget(label(QStringLiteral("Target:")), 0, 0); + settings->addWidget(filterTarget, 0, 1); + settings->addWidget(filterEnable, 0, 2); + settings->addWidget(reset, 0, 3); + settings->addWidget(label(QStringLiteral("Filter:")), 1, 0); + settings->addWidget(filterMode, 1, 1); + settings->addWidget(label(QStringLiteral("H/V mode:")), 1, 2); + QHBoxLayout *modes = new QHBoxLayout; + modes->setContentsMargins(0, 0, 0, 0); + modes->addWidget(filterModeH); + modes->addWidget(filterModeV); + settings->addLayout(modes, 1, 3); + settings->addWidget(label(QStringLiteral("Autoscale:")), 2, 0); + settings->addWidget(filterAutoscale, 2, 1); + settings->addWidget(label(QStringLiteral("Integer limit:")), 2, 2); + settings->addWidget(filterIntegerLimit, 2, 3); + root->addWidget(groupBox(QStringLiteral("Filter Settings"), settings)); + + filterHorizZoomMult = doubleSpin(0.25, 8.0, 0.25, 2); + filterVertZoomMult = doubleSpin(0.25, 8.0, 0.25, 2); + filterHorizZoom = doubleSpin(-9999.0, 9999.0, 1.0, 0); + filterVertZoom = doubleSpin(-9999.0, 9999.0, 1.0, 0); + filterHorizOffset = doubleSpin(-9999.0, 9999.0, 1.0, 0); + filterVertOffset = doubleSpin(-9999.0, 9999.0, 1.0, 0); + QGridLayout *geometry = new QGridLayout; + geometry->setColumnStretch(1, 1); + geometry->setColumnStretch(3, 1); + geometry->addWidget(label(QStringLiteral("Horiz. size:")), 0, 0); + geometry->addWidget(filterHorizZoomMult, 0, 1); + geometry->addWidget(filterHorizZoom, 0, 2); + geometry->addWidget(label(QStringLiteral("Vert. size:")), 1, 0); + geometry->addWidget(filterVertZoomMult, 1, 1); + geometry->addWidget(filterVertZoom, 1, 2); + geometry->addWidget(label(QStringLiteral("Horiz. position:")), 0, 3); + geometry->addWidget(filterHorizOffset, 0, 4); + geometry->addWidget(label(QStringLiteral("Vert. position:")), 1, 3); + geometry->addWidget(filterVertOffset, 1, 4); + root->addWidget(groupBox(QStringLiteral("Size and Position"), geometry)); + + filterKeepAutoscaleAspect = new QCheckBox(QStringLiteral("Keep autoscale aspect")); + filterKeepAspect = new QCheckBox(QStringLiteral("Keep aspect ratio")); + filterAspect = combo(configChoiceDisplays(filterAspectChoices, int(sizeof(filterAspectChoices) / sizeof(filterAspectChoices[0]))), QStringLiteral("VGA")); + filterNtscPixels = new QCheckBox(QStringLiteral("Always stretch NTSC mode")); + QGridLayout *aspect = new QGridLayout; + aspect->setColumnStretch(2, 1); + aspect->addWidget(filterKeepAutoscaleAspect, 0, 0, 1, 2); + aspect->addWidget(filterKeepAspect, 1, 0); + aspect->addWidget(filterAspect, 1, 1); + aspect->addWidget(filterNtscPixels, 2, 0, 1, 2); + root->addWidget(groupBox(QStringLiteral("Aspect Ratio Correction"), aspect)); + + filterBilinear = new QCheckBox(QStringLiteral("Point/Bilinear")); + filterScanlines = intSpin(0, 1000); + filterScanlineLevel = intSpin(0, 1000); + filterScanlineOffset = intSpin(-1000, 1000); + filterLuminance = intSpin(-1000, 1000); + filterContrast = intSpin(-1000, 1000); + filterSaturation = intSpin(-1000, 1000); + filterGamma = intSpin(-1000, 1000); + filterBlur = intSpin(0, 1000); + filterNoise = intSpin(0, 1000); + if (!unixShaderPipelineAvailable()) { + const QString reason = QStringLiteral("This color/noise filter control is not implemented by the Unix graphics backend yet."); + for (QWidget *widget : { static_cast(filterLuminance), static_cast(filterContrast), static_cast(filterSaturation), static_cast(filterGamma), static_cast(filterBlur), static_cast(filterNoise) }) { + disableUnavailable(widget, reason); + } + } + QGridLayout *extra = new QGridLayout; + extra->setColumnStretch(1, 1); + extra->setColumnStretch(3, 1); + extra->addWidget(filterBilinear, 0, 0, 1, 2); + extra->addWidget(label(QStringLiteral("Scanline opacity:")), 1, 0); + extra->addWidget(filterScanlines, 1, 1); + extra->addWidget(label(QStringLiteral("Scanline level:")), 1, 2); + extra->addWidget(filterScanlineLevel, 1, 3); + extra->addWidget(label(QStringLiteral("Scanline offset:")), 2, 0); + extra->addWidget(filterScanlineOffset, 2, 1); + extra->addWidget(label(QStringLiteral("Brightness:")), 3, 0); + extra->addWidget(filterLuminance, 3, 1); + extra->addWidget(label(QStringLiteral("Contrast:")), 3, 2); + extra->addWidget(filterContrast, 3, 3); + extra->addWidget(label(QStringLiteral("Saturation:")), 4, 0); + extra->addWidget(filterSaturation, 4, 1); + extra->addWidget(label(QStringLiteral("Gamma:")), 4, 2); + extra->addWidget(filterGamma, 4, 3); + extra->addWidget(label(QStringLiteral("Blurriness:")), 5, 0); + extra->addWidget(filterBlur, 5, 1); + extra->addWidget(label(QStringLiteral("Noise:")), 5, 2); + extra->addWidget(filterNoise, 5, 3); + root->addWidget(groupBox(QStringLiteral("Extra Settings"), extra)); + + QGridLayout *presets = new QGridLayout; + filterPresetName = combo({ QStringLiteral("") }); + filterPresetName->setEditable(true); + filterPresetName->setInsertPolicy(QComboBox::NoInsert); + filterPresetName->lineEdit()->setClearButtonEnabled(true); + QPushButton *load = new QPushButton(QStringLiteral("Load")); + QPushButton *save = new QPushButton(QStringLiteral("Save")); + QPushButton *deletePreset = new QPushButton(QStringLiteral("Delete")); + presets->setColumnStretch(0, 1); + presets->addWidget(filterPresetName, 0, 0); + presets->addWidget(load, 0, 1); + presets->addWidget(save, 0, 2); + presets->addWidget(deletePreset, 0, 3); + root->addWidget(groupBox(QStringLiteral("Presets"), presets)); + root->addStretch(1); + + QScrollArea *scroll = new QScrollArea; + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidgetResizable(true); + scroll->setWidget(content); + outer->addWidget(scroll); + + connect(filterTarget, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (filterUpdating || index < 0 || index > 2) { + return; + } + storeFilterUiToState(); + currentFilterTarget = index; + loadFilterStateToUi(index); + }); + connect(filterKeepAspect, &QCheckBox::toggled, this, [this]() { updateFilterControlState(); }); + connect(reset, &QPushButton::clicked, this, [this]() { + filterStates[currentFilterTarget] = defaultFilterState(currentFilterTarget); + loadFilterStateToUi(currentFilterTarget); + }); + connect(load, &QPushButton::clicked, this, [this]() { loadFilterPreset(); }); + connect(save, &QPushButton::clicked, this, [this]() { saveFilterPreset(); }); + connect(deletePreset, &QPushButton::clicked, this, [this]() { deleteFilterPreset(); }); + + resetFilterStates(); + refreshFilterPresetList(); + return page; + } + + QWidget *makeOutputPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + outputFile = new QLineEdit; + outputFile->setReadOnly(true); + QPushButton *browse = smallButton(QStringLiteral("...")); + outputAudio = new QCheckBox(QStringLiteral("Audio")); + outputVideo = new QCheckBox(QStringLiteral("Video")); + outputAudioCodec = combo(QStringList{ + QStringLiteral("PCM in AVI (internal)"), + QStringLiteral("Wave file (internal)") + }); + outputVideoCodec = combo(QStringList{ QStringLiteral("DIB RGB (internal)") }); + outputFrameLimiter = new QCheckBox(QStringLiteral("Disable frame rate limit")); + outputOriginalSize = new QCheckBox(QStringLiteral("Capture before filtering")); + outputNoSound = new QCheckBox(QStringLiteral("Disable sound output")); + outputNoSoundSync = new QCheckBox(QStringLiteral("Disable sound sync")); + outputEnabled = new QCheckBox(QStringLiteral("AVI output enabled")); + + QGridLayout *properties = new QGridLayout; + properties->setColumnStretch(1, 1); + properties->addWidget(outputFile, 0, 0, 1, 3); + properties->addWidget(browse, 0, 3); + properties->addWidget(outputAudio, 1, 0); + properties->addWidget(outputAudioCodec, 1, 1, 1, 3); + properties->addWidget(outputVideo, 2, 0); + properties->addWidget(outputVideoCodec, 2, 1, 1, 3); + properties->addWidget(outputFrameLimiter, 3, 0, 1, 2); + properties->addWidget(outputOriginalSize, 3, 2, 1, 2); + properties->addWidget(outputNoSound, 4, 0, 1, 2); + properties->addWidget(outputNoSoundSync, 4, 2, 1, 2); + properties->addWidget(outputEnabled, 5, 0, 1, 2); + root->addWidget(groupBox(QStringLiteral("Output Properties"), properties)); + + QPushButton *saveScreenshot = new QPushButton(QStringLiteral("Save screenshot")); + QPushButton *proWizard = new QPushButton(QStringLiteral("Pro Wizard 1.62")); + QCheckBox *sampleRipper = new QCheckBox(QStringLiteral("Sample ripper")); + if (hardwareProvider.saveScreenshot) { + connect(saveScreenshot, &QPushButton::clicked, this, [this]() { + hardwareProvider.saveScreenshot(hardwareProvider.context); + if (status) { + status->setText(QStringLiteral("Screenshot requested.")); + } + }); + } else { + disableUnavailable(saveScreenshot, QStringLiteral("Screenshot capture is only available from the integrated runtime UI.")); + } + if (hardwareProvider.runProWizard) { + connect(proWizard, &QPushButton::clicked, this, [this]() { + hardwareProvider.runProWizard(hardwareProvider.context); + if (status) { + status->setText(QStringLiteral("Pro Wizard scan finished.")); + } + }); + } else { + disableUnavailable(proWizard, QStringLiteral("Pro Wizard is only available from the integrated runtime UI when Unix is built with Pro-Wizard support.")); + } + if (hardwareProvider.sampleRipperEnabled && hardwareProvider.setSampleRipperEnabled) { + sampleRipper->setChecked(hardwareProvider.sampleRipperEnabled(hardwareProvider.context)); + connect(sampleRipper, &QCheckBox::toggled, this, [this](bool enabled) { + hardwareProvider.setSampleRipperEnabled(hardwareProvider.context, enabled); + if (status) { + status->setText(enabled ? QStringLiteral("Sample ripper enabled.") : QStringLiteral("Sample ripper disabled.")); + } + }); + } else { + disableUnavailable(sampleRipper, QStringLiteral("Sample ripper integration is only available from the integrated emulator UI.")); + } + screenshotOriginalSize = new QCheckBox(QStringLiteral("Take screenshot before filtering")); + screenshotPaletted = new QCheckBox(QStringLiteral("Create 256 color palette indexed screenshot if possible")); + screenshotClip = new QCheckBox(QStringLiteral("Autoclip screenshot")); + screenshotAuto = new QCheckBox(QStringLiteral("Continuous screenshots")); + QGridLayout *ripper = new QGridLayout; + ripper->setColumnStretch(3, 1); + ripper->addWidget(saveScreenshot, 0, 0); + ripper->addWidget(proWizard, 0, 1); + ripper->addWidget(sampleRipper, 0, 2); + ripper->addWidget(screenshotOriginalSize, 1, 0, 1, 2); + ripper->addWidget(screenshotClip, 1, 2, 1, 2); + ripper->addWidget(screenshotPaletted, 2, 0, 1, 2); + ripper->addWidget(screenshotAuto, 2, 2, 1, 2); + root->addWidget(groupBox(QStringLiteral("Ripper"), ripper)); + + QCheckBox *statePlay = new QCheckBox(QStringLiteral("Play recording")); + QCheckBox *stateRecord = new QCheckBox(QStringLiteral("Re-recording enabled")); + stateReplayAutoplay = new QCheckBox(QStringLiteral("Automatic replay")); + QPushButton *stateSave = new QPushButton(QStringLiteral("Save recording")); + stateReplayRate = combo({ QStringLiteral("-"), QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("5"), QStringLiteral("10"), QStringLiteral("20"), QStringLiteral("30"), QStringLiteral("60") }, QStringLiteral("5")); + stateReplayRate->setEditable(true); + stateReplayBuffers = combo({ QStringLiteral("50"), QStringLiteral("100"), QStringLiteral("500"), QStringLiteral("1000"), QStringLiteral("10000") }, QStringLiteral("100")); + stateReplayBuffers->setEditable(true); + const bool hasStateRecorder = hardwareProvider.statePlaybackEnabled + && hardwareProvider.stateRecordingEnabled + && hardwareProvider.canSaveStateRecording + && hardwareProvider.setStatePlayback + && hardwareProvider.toggleStateRecording + && hardwareProvider.saveStateRecording; + auto updateStateRecorderControls = [this, statePlay, stateRecord, stateSave, hasStateRecorder]() { + if (!hasStateRecorder) { + return; + } + const QSignalBlocker playBlocker(statePlay); + const QSignalBlocker recordBlocker(stateRecord); + statePlay->setChecked(hardwareProvider.statePlaybackEnabled(hardwareProvider.context)); + stateRecord->setChecked(hardwareProvider.stateRecordingEnabled(hardwareProvider.context)); + stateSave->setEnabled(hardwareProvider.canSaveStateRecording(hardwareProvider.context)); + }; + if (hasStateRecorder) { + connect(statePlay, &QCheckBox::clicked, this, [this, statePlay, updateStateRecorderControls](bool checked) { + if (checked) { + const QString selected = QFileDialog::getOpenFileName( + this, + QStringLiteral("Select input recording"), + expandedPathText(unixDefaultDataSubPath(QStringLiteral("Save States"))), + QStringLiteral("Input recordings (*.inp);;All files (*)")); + if (!selected.isEmpty()) { + const QByteArray path = selected.toLocal8Bit(); + if (hardwareProvider.setStatePlayback(hardwareProvider.context, true, path.constData())) { + if (status) { + status->setText(QStringLiteral("State recording playback requested.")); + } + } else if (status) { + status->setText(QStringLiteral("State recording playback failed.")); + } + } + } else { + hardwareProvider.setStatePlayback(hardwareProvider.context, false, nullptr); + if (status) { + status->setText(QStringLiteral("State recording playback stopped.")); + } + } + updateStateRecorderControls(); + }); + connect(stateRecord, &QCheckBox::clicked, this, [this, updateStateRecorderControls]() { + hardwareProvider.toggleStateRecording(hardwareProvider.context); + updateStateRecorderControls(); + if (status) { + status->setText(hardwareProvider.stateRecordingEnabled(hardwareProvider.context) + ? QStringLiteral("State recording enabled.") + : QStringLiteral("State recording disabled.")); + } + }); + connect(stateSave, &QPushButton::clicked, this, [this, updateStateRecorderControls]() { + if (!hardwareProvider.canSaveStateRecording(hardwareProvider.context)) { + if (status) { + status->setText(QStringLiteral("No re-recording is available to save.")); + } + updateStateRecorderControls(); + return; + } + const QString selected = QFileDialog::getSaveFileName( + this, + QStringLiteral("Save input recording"), + QDir(expandedPathText(unixDefaultDataSubPath(QStringLiteral("Save States")))).filePath(QStringLiteral("recording.inp")), + QStringLiteral("Input recordings (*.inp);;All files (*)")); + if (!selected.isEmpty()) { + QDir().mkpath(QFileInfo(selected).absolutePath()); + const QByteArray path = selected.toLocal8Bit(); + if (hardwareProvider.saveStateRecording(hardwareProvider.context, path.constData())) { + if (status) { + status->setText(QStringLiteral("State recording saved.")); + } + } else if (status) { + status->setText(QStringLiteral("State recording save failed.")); + } + } + updateStateRecorderControls(); + }); + updateStateRecorderControls(); + } else { + disableUnavailable(statePlay, QStringLiteral("State recording playback is only available from the integrated runtime UI.")); + disableUnavailable(stateRecord, QStringLiteral("State recording is only available from the integrated runtime UI.")); + disableUnavailable(stateSave, QStringLiteral("State recording export is only available from the integrated runtime UI.")); + } + QGridLayout *recorder = new QGridLayout; + recorder->setColumnStretch(4, 1); + recorder->addWidget(statePlay, 0, 0); + recorder->addWidget(stateRecord, 0, 2); + recorder->addWidget(stateReplayAutoplay, 1, 0); + recorder->addWidget(stateSave, 1, 2); + recorder->addWidget(label(QStringLiteral("Recording rate (seconds):")), 2, 0); + recorder->addWidget(stateReplayRate, 2, 1); + recorder->addWidget(label(QStringLiteral("Recording buffers:")), 2, 2); + recorder->addWidget(stateReplayBuffers, 2, 3); + root->addWidget(groupBox(QStringLiteral("Re-recorder"), recorder)); + root->addStretch(1); + + connect(browse, &QPushButton::clicked, this, [this]() { + const QString selected = QFileDialog::getSaveFileName(this, QStringLiteral("Select output file"), fileDialogInitialPath(outputFile->text()), QStringLiteral("Video Clip (*.avi);;Wave Sound (*.wav);;All files (*)")); + if (!selected.isEmpty()) { + outputFile->setText(selected); + } + }); + connect(outputAudio, &QCheckBox::toggled, this, [this](bool) { updateOutputControlState(); }); + connect(outputVideo, &QCheckBox::toggled, this, [this](bool) { updateOutputControlState(); }); + connect(outputAudioCodec, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int) { updateOutputControlState(); }); + connect(outputFrameLimiter, &QCheckBox::toggled, this, [this](bool) { updateOutputControlState(); }); + connect(screenshotOriginalSize, &QCheckBox::toggled, this, [this](bool) { updateOutputControlState(); }); + updateOutputControlState(); + return page; + } + + void updateOutputControlState() + { + if (!screenshotOriginalSize || !screenshotClip || !outputFrameLimiter || !outputNoSound || + !outputAudio || !outputVideo || !outputEnabled || !outputAudioCodec || !outputVideoCodec) { + return; + } + const bool waveOutput = outputAudio->isChecked() && outputAudioCodec->currentIndex() == 1; + outputAudioCodec->setEnabled(outputAudio->isChecked()); + outputVideoCodec->setEnabled(outputVideo->isChecked() && !waveOutput); + outputVideo->setEnabled(!waveOutput); + if (waveOutput && outputVideo->isChecked()) { + outputVideo->setChecked(false); + } + const bool hasOutputStream = outputAudio->isChecked() || outputVideo->isChecked(); + outputEnabled->setEnabled(hasOutputStream); + if (!hasOutputStream) { + outputEnabled->setChecked(false); + } + outputNoSound->setEnabled(!outputFrameLimiter->isChecked()); + if (outputFrameLimiter->isChecked()) { + outputNoSound->setChecked(true); + } + screenshotClip->setEnabled(screenshotOriginalSize->isChecked()); + if (!screenshotOriginalSize->isChecked()) { + screenshotClip->setChecked(false); + } + } + + QWidget *makeSoundPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + soundDevice = new QComboBox; + for (const auto &choice : soundDeviceChoices()) { + soundDevice->addItem(choice.first, choice.second); + } + root->addWidget(soundDevice); + + QHBoxLayout *top = new QHBoxLayout; + QVBoxLayout *emulation = new QVBoxLayout; + soundOutputButtons = new QButtonGroup(this); + QRadioButton *soundDisabled = new QRadioButton(QStringLiteral("Disabled")); + QRadioButton *soundEmulated = new QRadioButton(QStringLiteral("Disabled, but emulated")); + QRadioButton *soundEnabled = new QRadioButton(QStringLiteral("Enabled")); + soundOutputButtons->addButton(soundDisabled, 0); + soundOutputButtons->addButton(soundEmulated, 1); + soundOutputButtons->addButton(soundEnabled, 2); + soundEnabled->setChecked(true); + soundAutomatic = new QCheckBox(QStringLiteral("Automatic switching")); + emulation->addWidget(soundDisabled); + emulation->addWidget(soundEmulated); + emulation->addWidget(soundEnabled); + emulation->addSpacing(8); + emulation->addWidget(soundAutomatic); + top->addWidget(groupBox(QStringLiteral("Sound Emulation"), emulation), 1); + + QGridLayout *volume = new QGridLayout; + volume->setColumnStretch(1, 1); + soundMasterVolume = new QSlider(Qt::Horizontal); + soundMasterVolume->setRange(0, 100); + soundMasterVolumeValue = new QLabel; + soundMasterVolumeValue->setMinimumWidth(44); + soundVolumeSelect = combo({ QStringLiteral("Paula"), QStringLiteral("CD"), QStringLiteral("AHI"), QStringLiteral("MIDI"), QStringLiteral("Genlock") }, QStringLiteral("Paula")); + soundSelectedVolume = new QSlider(Qt::Horizontal); + soundSelectedVolume->setRange(0, 100); + soundSelectedVolumeValue = new QLabel; + soundSelectedVolumeValue->setMinimumWidth(44); + volume->addWidget(label(QStringLiteral("Master")), 0, 0); + volume->addWidget(soundMasterVolume, 0, 1); + volume->addWidget(soundMasterVolumeValue, 0, 2); + volume->addWidget(soundVolumeSelect, 1, 0); + volume->addWidget(soundSelectedVolume, 1, 1); + volume->addWidget(soundSelectedVolumeValue, 1, 2); + top->addWidget(groupBox(QStringLiteral("Volume"), volume), 2); + + QGridLayout *buffer = new QGridLayout; + buffer->setColumnStretch(0, 1); + soundBufferSize = new QSlider(Qt::Horizontal); + soundBufferSize->setRange(0, 10); + soundBufferSizeValue = new QLabel; + soundBufferSizeValue->setMinimumWidth(44); + buffer->addWidget(soundBufferSize, 0, 0); + buffer->addWidget(soundBufferSizeValue, 0, 1); + top->addWidget(groupBox(QStringLiteral("Sound Buffer Size"), buffer), 1); + root->addLayout(top); + + soundChannels = combo(soundChannelItems(), QStringLiteral("Stereo")); + soundStereoSeparation = combo({}); + for (int i = 10; i >= 0; i--) { + soundStereoSeparation->addItem(QStringLiteral("%1%").arg(i * 10)); + } + soundInterpolation = combo({ QStringLiteral("Disabled"), QStringLiteral("Anti"), QStringLiteral("Sinc"), QStringLiteral("RH"), QStringLiteral("Crux") }, QStringLiteral("Anti")); + soundFrequency = combo({ QStringLiteral("11025"), QStringLiteral("15000"), QStringLiteral("22050"), QStringLiteral("32000"), QStringLiteral("44100"), QStringLiteral("48000") }, QStringLiteral("44100")); + soundFrequency->setEditable(true); + soundSwap = combo({ QStringLiteral("-"), QStringLiteral("Paula only"), QStringLiteral("AHI only"), QStringLiteral("Both") }, QStringLiteral("-")); + soundStereoDelay = combo({ QStringLiteral("-"), QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"), QStringLiteral("6"), QStringLiteral("7"), QStringLiteral("8"), QStringLiteral("9"), QStringLiteral("10") }, QStringLiteral("-")); + soundFilter = combo({ QStringLiteral("Always off"), QStringLiteral("Emulated (A500)"), QStringLiteral("Emulated (A1200)"), QStringLiteral("Always on (A500)"), QStringLiteral("Always on (A1200)") }, QStringLiteral("Emulated (A500)")); + + QGridLayout *settings = new QGridLayout; + settings->setColumnStretch(1, 1); + settings->setColumnStretch(3, 1); + settings->setColumnStretch(5, 1); + settings->addWidget(label(QStringLiteral("Channel mode:")), 0, 0); + settings->addWidget(soundChannels, 0, 1); + settings->addWidget(label(QStringLiteral("Stereo separation:")), 0, 2); + settings->addWidget(soundStereoSeparation, 0, 3); + settings->addWidget(label(QStringLiteral("Interpolation:")), 0, 4); + settings->addWidget(soundInterpolation, 0, 5); + settings->addWidget(label(QStringLiteral("Frequency:")), 1, 0); + settings->addWidget(soundFrequency, 1, 1); + settings->addWidget(label(QStringLiteral("Swap channels:")), 1, 2); + settings->addWidget(soundSwap, 1, 3); + settings->addWidget(label(QStringLiteral("Stereo delay:")), 1, 4); + settings->addWidget(soundStereoDelay, 1, 5); + settings->addWidget(label(QStringLiteral("Audio filter:")), 2, 4); + settings->addWidget(soundFilter, 2, 5); + root->addWidget(groupBox(QStringLiteral("Settings"), settings)); + + QHBoxLayout *bottom = new QHBoxLayout; + QGridLayout *floppy = new QGridLayout; + floppy->setColumnStretch(1, 1); + floppySoundEmptyVolume = new QSlider(Qt::Horizontal); + floppySoundEmptyVolume->setRange(0, 100); + floppySoundEmptyVolumeValue = new QLabel; + floppySoundEmptyVolumeValue->setMinimumWidth(44); + floppySoundDiskVolume = new QSlider(Qt::Horizontal); + floppySoundDiskVolume->setRange(0, 100); + floppySoundDiskVolumeValue = new QLabel; + floppySoundDiskVolumeValue->setMinimumWidth(44); + floppySoundType = combo({ QStringLiteral("No sound"), QStringLiteral("Built-in A500") }, QStringLiteral("No sound")); + floppySoundDrive = combo({ QStringLiteral("DF0:"), QStringLiteral("DF1:"), QStringLiteral("DF2:"), QStringLiteral("DF3:") }, QStringLiteral("DF0:")); + floppy->addWidget(label(QStringLiteral("Empty drive")), 0, 0); + floppy->addWidget(floppySoundEmptyVolume, 0, 1); + floppy->addWidget(floppySoundEmptyVolumeValue, 0, 2); + floppy->addWidget(label(QStringLiteral("Disk in drive")), 1, 0); + floppy->addWidget(floppySoundDiskVolume, 1, 1); + floppy->addWidget(floppySoundDiskVolumeValue, 1, 2); + floppy->addWidget(floppySoundType, 2, 0, 1, 2); + floppy->addWidget(floppySoundDrive, 2, 2); + bottom->addWidget(groupBox(QStringLiteral("Floppy Drive Sound Emulation"), floppy), 3); + + QVBoxLayout *drivers = new QVBoxLayout; + QCheckBox *sdlDriver = new QCheckBox(QStringLiteral("SDL")); + sdlDriver->setChecked(true); + sdlDriver->setEnabled(false); + drivers->addWidget(sdlDriver); + drivers->addStretch(); + bottom->addWidget(groupBox(QStringLiteral("Drivers"), drivers), 1); + root->addLayout(bottom); + + connect(soundOutputButtons, &QButtonGroup::idClicked, this, [this](int) { updateSoundControlState(); }); + connect(soundMasterVolume, &QSlider::valueChanged, this, [this](int) { updateSoundVolumeLabels(); }); + connect(soundVolumeSelect, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (soundVolumeUpdating) { + return; + } + storeSelectedSoundVolume(); + currentSoundVolume = qBound(0, index, SoundVolumeCount - 1); + loadSelectedSoundVolume(); + }); + connect(soundSelectedVolume, &QSlider::valueChanged, this, [this](int) { updateSoundVolumeLabels(); }); + connect(soundBufferSize, &QSlider::valueChanged, this, [this](int) { updateSoundBufferLabel(); }); + connect(soundChannels, &QComboBox::currentTextChanged, this, [this](const QString &) { updateSoundControlState(); }); + connect(floppySoundDrive, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (floppySoundUpdating) { + return; + } + storeSelectedFloppySound(); + currentFloppySoundDrive = qBound(0, index, FloppySoundDriveCount - 1); + loadSelectedFloppySound(); + }); + connect(floppySoundType, &QComboBox::currentTextChanged, this, [this](const QString &) { storeSelectedFloppySound(); }); + connect(floppySoundEmptyVolume, &QSlider::valueChanged, this, [this](int) { + updateFloppySoundVolumeLabels(); + storeSelectedFloppySound(); + }); + connect(floppySoundDiskVolume, &QSlider::valueChanged, this, [this](int) { + updateFloppySoundVolumeLabels(); + storeSelectedFloppySound(); + }); + updateSoundControlState(); + return page; + } + + int soundVolumeAttenuationValue(int index) const + { + if (index == currentSoundVolume && soundSelectedVolume) { + return 100 - soundSelectedVolume->value(); + } + return soundVolumeAttenuation[qBound(0, index, SoundVolumeCount - 1)]; + } + + int floppySoundTypeConfigValue(int drive) const + { + if (drive == currentFloppySoundDrive && floppySoundType) { + return floppySoundType->currentIndex(); + } + return floppySoundTypeValue[qBound(0, drive, FloppySoundDriveCount - 1)]; + } + + int floppySoundEmptyAttenuationValue(int drive) const + { + if (drive == currentFloppySoundDrive && floppySoundEmptyVolume) { + return 100 - floppySoundEmptyVolume->value(); + } + return floppySoundEmptyAttenuation[qBound(0, drive, FloppySoundDriveCount - 1)]; + } + + int floppySoundDiskAttenuationValue(int drive) const + { + if (drive == currentFloppySoundDrive && floppySoundDiskVolume) { + return 100 - floppySoundDiskVolume->value(); + } + return floppySoundDiskAttenuation[qBound(0, drive, FloppySoundDriveCount - 1)]; + } + + void updateSoundControlState() + { + const bool enabled = soundOutputButtons && soundOutputButtons->checkedId() != 0; + const bool stereo = enabled && soundChannels && soundChannels->currentText() != QStringLiteral("Mono"); + const QList widgets = { + soundMasterVolume, + soundVolumeSelect, + soundSelectedVolume, + soundBufferSize, + soundDevice, + soundChannels, + soundInterpolation, + soundFrequency, + soundSwap, + soundFilter, + floppySoundDrive, + floppySoundType, + floppySoundEmptyVolume, + floppySoundDiskVolume + }; + for (QWidget *widget : widgets) { + if (widget) { + widget->setEnabled(enabled); + } + } + if (soundStereoSeparation) { + soundStereoSeparation->setEnabled(stereo); + } + if (soundStereoDelay) { + soundStereoDelay->setEnabled(stereo); + } + updateSoundVolumeLabels(); + updateSoundBufferLabel(); + updateFloppySoundVolumeLabels(); + } + + void updateSoundVolumeLabels() + { + if (soundMasterVolumeValue && soundMasterVolume) { + soundMasterVolumeValue->setText(QStringLiteral("%1%").arg(soundMasterVolume->value())); + } + if (soundSelectedVolumeValue && soundSelectedVolume) { + soundSelectedVolumeValue->setText(QStringLiteral("%1%").arg(soundSelectedVolume->value())); + } + } + + void updateSoundBufferLabel() + { + if (!soundBufferSizeValue || !soundBufferSize) { + return; + } + const int index = soundBufferSize->value(); + soundBufferSizeValue->setText(index <= 0 ? QStringLiteral("Min") : QString::number(index)); + } + + void storeSelectedSoundVolume() + { + if (!soundSelectedVolume || soundVolumeUpdating) { + return; + } + soundVolumeAttenuation[qBound(0, currentSoundVolume, SoundVolumeCount - 1)] = 100 - soundSelectedVolume->value(); + } + + void loadSelectedSoundVolume() + { + if (!soundSelectedVolume) { + return; + } + QSignalBlocker blocker(soundSelectedVolume); + soundVolumeUpdating = true; + soundSelectedVolume->setValue(100 - soundVolumeAttenuation[qBound(0, currentSoundVolume, SoundVolumeCount - 1)]); + soundVolumeUpdating = false; + updateSoundVolumeLabels(); + } + + void updateFloppySoundVolumeLabels() + { + if (floppySoundEmptyVolumeValue && floppySoundEmptyVolume) { + floppySoundEmptyVolumeValue->setText(QStringLiteral("%1%").arg(floppySoundEmptyVolume->value())); + } + if (floppySoundDiskVolumeValue && floppySoundDiskVolume) { + floppySoundDiskVolumeValue->setText(QStringLiteral("%1%").arg(floppySoundDiskVolume->value())); + } + } + + void storeSelectedFloppySound() + { + if (!floppySoundType || !floppySoundEmptyVolume || !floppySoundDiskVolume || floppySoundUpdating) { + return; + } + const int drive = qBound(0, currentFloppySoundDrive, FloppySoundDriveCount - 1); + floppySoundTypeValue[drive] = floppySoundType->currentIndex(); + floppySoundEmptyAttenuation[drive] = 100 - floppySoundEmptyVolume->value(); + floppySoundDiskAttenuation[drive] = 100 - floppySoundDiskVolume->value(); + } + + void loadSelectedFloppySound() + { + if (!floppySoundType || !floppySoundEmptyVolume || !floppySoundDiskVolume) { + return; + } + const int drive = qBound(0, currentFloppySoundDrive, FloppySoundDriveCount - 1); + floppySoundUpdating = true; + floppySoundType->setCurrentIndex(qBound(0, floppySoundTypeValue[drive], 1)); + floppySoundEmptyVolume->setValue(100 - floppySoundEmptyAttenuation[drive]); + floppySoundDiskVolume->setValue(100 - floppySoundDiskAttenuation[drive]); + floppySoundUpdating = false; + updateFloppySoundVolumeLabels(); + } + + void selectHostDisplayByIndex(int index) + { + if (hostDisplay && index >= 0 && index < hostDisplay->count()) { + hostDisplay->setCurrentIndex(index); + } + } + + void selectHostDisplayByName(const QString &value) + { + if (!hostDisplay || value.trimmed().isEmpty()) { + return; + } + const QString wanted = value.trimmed(); + for (int i = 0; i < hostDisplay->count(); i++) { + const QString configName = hostDisplay->itemData(i).toString(); + const QString friendlyName = hostDisplay->itemData(i, Qt::UserRole + 1).toString(); + const QString displayName = hostDisplay->itemText(i); + if (configName.compare(wanted, Qt::CaseInsensitive) == 0 + || friendlyName.compare(wanted, Qt::CaseInsensitive) == 0 + || displayName.compare(wanted, Qt::CaseInsensitive) == 0) { + hostDisplay->setCurrentIndex(i); + return; + } + } + } + + void selectSoundDeviceByConfigName(const QString &value) + { + if (!soundDevice || value.trimmed().isEmpty()) { + return; + } + const QString wanted = value.trimmed(); + for (int i = 0; i < soundDevice->count(); i++) { + const QString configName = soundDevice->itemData(i).toString(); + const QString displayName = soundDevice->itemText(i); + if (configName.compare(wanted, Qt::CaseInsensitive) == 0 + || displayName.compare(wanted, Qt::CaseInsensitive) == 0 + || configName.mid(4).compare(wanted, Qt::CaseInsensitive) == 0) { + soundDevice->setCurrentIndex(i); + return; + } + } + } + + void selectSamplerDeviceByConfigName(const QString &value) + { + if (!samplerDevice || value.trimmed().isEmpty()) { + return; + } + const QString wanted = value.trimmed(); + for (int i = 0; i < samplerDevice->count(); i++) { + const QString configName = samplerDevice->itemData(i).toString(); + const QString displayName = samplerDevice->itemText(i); + if (configName.compare(wanted, Qt::CaseInsensitive) == 0 + || displayName.compare(wanted, Qt::CaseInsensitive) == 0 + || configName.mid(4).compare(wanted, Qt::CaseInsensitive) == 0) { + samplerDevice->setCurrentIndex(i); + updateIoPortsState(); + return; + } + } + } + + void selectMidiOutByDeviceId(int deviceId) + { + if (!midiOut) { + return; + } + for (int i = 0; i < midiOut->count(); i++) { + if (midiOut->itemData(i).toInt() == deviceId) { + midiOut->setCurrentIndex(i); + updateIoPortsState(); + return; + } + } + if (deviceId < -1) { + midiOut->setCurrentIndex(0); + updateIoPortsState(); + } + } + + void selectMidiOutByConfigName(const QString &value) + { + if (!midiOut || value.trimmed().isEmpty()) { + return; + } + const QString wanted = value.trimmed(); + if (wanted.compare(QStringLiteral("none"), Qt::CaseInsensitive) == 0) { + selectMidiOutByDeviceId(-2); + return; + } + for (int i = 0; i < midiOut->count(); i++) { + const QString configName = midiOut->itemData(i, Qt::UserRole + 1).toString(); + const QString displayName = midiOut->itemText(i); + if (configName.compare(wanted, Qt::CaseInsensitive) == 0 + || displayName.compare(wanted, Qt::CaseInsensitive) == 0) { + midiOut->setCurrentIndex(i); + updateIoPortsState(); + return; + } + } + } + + void selectMidiInByDeviceId(int deviceId) + { + if (!midiIn) { + return; + } + for (int i = 0; i < midiIn->count(); i++) { + if (midiIn->itemData(i).toInt() == deviceId) { + midiIn->setCurrentIndex(i); + updateIoPortsState(); + return; + } + } + midiIn->setCurrentIndex(0); + updateIoPortsState(); + } + + void selectMidiInByConfigName(const QString &value) + { + if (!midiIn || value.trimmed().isEmpty()) { + return; + } + const QString wanted = value.trimmed(); + if (wanted.compare(QStringLiteral("none"), Qt::CaseInsensitive) == 0) { + selectMidiInByDeviceId(-1); + return; + } + for (int i = 0; i < midiIn->count(); i++) { + const QString configName = midiIn->itemData(i, Qt::UserRole + 1).toString(); + const QString displayName = midiIn->itemText(i); + if (configName.compare(wanted, Qt::CaseInsensitive) == 0 + || displayName.compare(wanted, Qt::CaseInsensitive) == 0) { + midiIn->setCurrentIndex(i); + updateIoPortsState(); + return; + } + } + } + + void setSoundSwapBit(bool paula, bool enabled) + { + if (!soundSwap) { + return; + } + const int index = soundSwap->currentIndex(); + bool paulaEnabled = (index & 1) != 0; + bool ahiEnabled = (index & 2) != 0; + if (paula) { + paulaEnabled = enabled; + } else { + ahiEnabled = enabled; + } + soundSwap->setCurrentText(soundSwapText(paulaEnabled, ahiEnabled)); + } + + QWidget *makeGamePortsPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + portDevice[0] = combo(primaryPortDeviceItems(), QStringLiteral("Mouse")); + portDevice[1] = combo(primaryPortDeviceItems(), QStringLiteral("Keyboard Layout A")); + portDevice[2] = combo(parallelPortDeviceItems(), QStringLiteral("")); + portDevice[3] = combo(parallelPortDeviceItems(), QStringLiteral("")); + for (int i = 0; i < 2; i++) { + portAutofire[i] = combo({ + QStringLiteral("No autofire (normal)"), + QStringLiteral("Autofire"), + QStringLiteral("Autofire (toggle)"), + QStringLiteral("Autofire (always)"), + QStringLiteral("No autofire (toggle)") + }, QStringLiteral("No autofire (normal)")); + portMode[i] = combo({ + QStringLiteral("Default"), + QStringLiteral("Wheel Mouse"), + QStringLiteral("Mouse"), + QStringLiteral("Joystick"), + QStringLiteral("Gamepad"), + QStringLiteral("Analog joystick"), + QStringLiteral("CDTV remote mouse"), + QStringLiteral("CD32 pad"), + QStringLiteral("Generic light pen/gun") + }, QStringLiteral("Default")); + } + portAutoswitch = new QCheckBox(QStringLiteral("Mouse/Joystick autoswitching")); + QPushButton *port0Remap = new QPushButton(QStringLiteral("Remap / Test")); + QPushButton *port1Remap = new QPushButton(QStringLiteral("Remap / Test")); + QPushButton *port2Remap = new QPushButton(QStringLiteral("Remap / Test")); + QPushButton *port3Remap = new QPushButton(QStringLiteral("Remap / Test")); + const auto wireRemapButton = [this](QPushButton *button, int port) { + connect(button, &QPushButton::clicked, this, [this, port]() { + openInputMapDialog(port); + }); + }; + wireRemapButton(port0Remap, 0); + wireRemapButton(port1Remap, 1); + wireRemapButton(port2Remap, 2); + wireRemapButton(port3Remap, 3); + + QGridLayout *ports = new QGridLayout; + ports->setColumnStretch(1, 1); + ports->addWidget(label(QStringLiteral("Port 1:")), 0, 0); + ports->addWidget(portDevice[0], 0, 1, 1, 3); + ports->addWidget(portAutofire[0], 1, 1); + ports->addWidget(portMode[0], 1, 2); + ports->addWidget(port0Remap, 1, 3); + ports->addWidget(label(QStringLiteral("Port 2:")), 2, 0); + ports->addWidget(portDevice[1], 2, 1, 1, 3); + ports->addWidget(portAutofire[1], 3, 1); + ports->addWidget(portMode[1], 3, 2); + ports->addWidget(port1Remap, 3, 3); + + QPushButton *swapPorts = new QPushButton(QStringLiteral("Swap ports")); + ports->addWidget(swapPorts, 4, 1); + ports->addWidget(portAutoswitch, 4, 2, 1, 2); + ports->addWidget(label(QStringLiteral("Emulated parallel port joystick adapter")), 5, 0, 1, 4, Qt::AlignLeft | Qt::AlignVCenter); + ports->addWidget(label(QStringLiteral("Port 1:")), 6, 0); + ports->addWidget(portDevice[2], 6, 1, 1, 3); + ports->addWidget(port2Remap, 7, 3); + ports->addWidget(label(QStringLiteral("Port 2:")), 8, 0); + ports->addWidget(portDevice[3], 8, 1, 1, 3); + ports->addWidget(port3Remap, 9, 3); + root->addWidget(groupBox(QStringLiteral("Mouse and Joystick settings"), ports)); + + connect(swapPorts, &QPushButton::clicked, this, [this]() { + const QString port0Text = portDevice[0]->currentText(); + const QString port0Autofire = portAutofire[0]->currentText(); + const QString port0Mode = portMode[0]->currentText(); + portDevice[0]->setCurrentText(portDevice[1]->currentText()); + portAutofire[0]->setCurrentText(portAutofire[1]->currentText()); + portMode[0]->setCurrentText(portMode[1]->currentText()); + portDevice[1]->setCurrentText(port0Text); + portAutofire[1]->setCurrentText(port0Autofire); + portMode[1]->setCurrentText(port0Mode); + }); + const auto updateRemapButtons = [this, port0Remap, port1Remap, port2Remap, port3Remap]() { + QPushButton *buttons[] = { port0Remap, port1Remap, port2Remap, port3Remap }; + for (int i = 0; i < 4; i++) { + const QString device = portDevice[i] ? portDevice[i]->currentText().trimmed() : QString(); + const bool enabled = !device.isEmpty() && device != QStringLiteral(""); + buttons[i]->setEnabled(enabled); + buttons[i]->setToolTip(enabled + ? QStringLiteral("Open input remap/test dialog.") + : QStringLiteral("Select a host input device first.")); + } + }; + for (QComboBox *device : portDevice) { + connect(device, &QComboBox::currentTextChanged, this, [updateRemapButtons](const QString &) { + updateRemapButtons(); + }); + } + updateRemapButtons(); + + QGridLayout *mouse = new QGridLayout; + mouse->setColumnStretch(1, 1); + mouseSpeed = new QSpinBox; + mouseSpeed->setRange(1, 1000); + mouseSpeed->setValue(100); + virtualMouseDriver = new QCheckBox(QStringLiteral("Install virtual mouse driver")); + mouseUntrapMode = combo({ + QStringLiteral("None (Alt-Tab)"), + QStringLiteral("Middle button"), + QStringLiteral("Magic mouse"), + QStringLiteral("Both") + }, QStringLiteral("Middle button")); + magicMouseCursor = combo({ + QStringLiteral("Show both cursors"), + QStringLiteral("Show native cursor only"), + QStringLiteral("Show host cursor only") + }, QStringLiteral("Show both cursors")); + tabletLibrary = new QCheckBox(QStringLiteral("Tablet.library emulation")); + tabletMode = combo({ QStringLiteral("-"), QStringLiteral("Tablet emulation") }, QStringLiteral("-")); + if (!unixTabletBackendAvailable()) { + const QString reason = QStringLiteral("Native tablet input is not implemented by the Unix input backend yet."); + disableUnavailable(tabletLibrary, reason); + disableUnavailable(tabletMode, reason); + } + + mouse->addWidget(label(QStringLiteral("Mouse speed:")), 0, 0); + mouse->addWidget(mouseSpeed, 0, 1); + mouse->addWidget(label(QStringLiteral("Mouse untrap mode:")), 0, 2); + mouse->addWidget(mouseUntrapMode, 0, 3); + mouse->addWidget(virtualMouseDriver, 1, 0, 1, 2); + mouse->addWidget(label(QStringLiteral("Magic Mouse cursor mode:")), 1, 2); + mouse->addWidget(magicMouseCursor, 1, 3); + mouse->addWidget(tabletLibrary, 2, 0, 1, 2); + mouse->addWidget(label(QStringLiteral("Tablet mode:")), 2, 2); + mouse->addWidget(tabletMode, 2, 3); + root->addWidget(groupBox(QStringLiteral("Mouse extra settings"), mouse), 1); + + connect(virtualMouseDriver, &QCheckBox::toggled, this, [this](bool) { updateMouseExtraState(); }); + connect(tabletMode, &QComboBox::currentTextChanged, this, [this](const QString &) { updateMouseExtraState(); }); + updateMouseExtraState(); + return page; + } + + void openInputMapDialog(int port) + { + QString context; + int customSlot = -1; + if (port >= 0 && port < 4) { + context = QStringLiteral("Port %1: %2").arg(port + 1).arg(portDevice[port] ? portDevice[port]->currentText() : QStringLiteral("")); + if (port < 2) { + context += QStringLiteral("\nMode: %1\nAutofire: %2") + .arg(portMode[port] ? portMode[port]->currentText() : QStringLiteral("Default")) + .arg(portAutofire[port] ? portAutofire[port]->currentText() : QStringLiteral("No autofire (normal)")); + } + customSlot = qBound(0, port, MaxJoyportCustomSlots - 1); + const QString device = portDevice[port] ? portDevice[port]->currentText() : QString(); + if (device.startsWith(QStringLiteral("Custom "))) { + bool ok = false; + const int slot = device.mid(7).toInt(&ok) - 1; + if (ok && slot >= 0 && slot < MaxJoyportCustomSlots) { + customSlot = slot; + } + } + } else { + context = QStringLiteral("Input: %1").arg(inputDevice ? inputDevice->currentText() : QStringLiteral("Device")); + } + UnixQtInputMapDialog dialog(port, context, customSlot >= 0 ? joyportCustom[customSlot] : QString(), this); + if (dialog.exec() == QDialog::Accepted && dialog.hasChanges() && customSlot >= 0) { + joyportCustom[customSlot] = dialog.customConfig(); + if (portDevice[port]) { + portDevice[port]->setCurrentText(QStringLiteral("Custom %1").arg(customSlot + 1)); + } + } + } + + void updateMouseExtraState() + { + const bool tabletBackend = unixTabletBackendAvailable(); + const bool virtualMouseEnabled = virtualMouseDriver && virtualMouseDriver->isChecked(); + const bool tabletModeSelected = tabletMode && tabletMode->currentText() == QStringLiteral("Tablet emulation"); + const bool absoluteMouseEnabled = virtualMouseEnabled || (tabletBackend && tabletModeSelected); + const bool tabletEnabled = tabletBackend && absoluteMouseEnabled; + if (magicMouseCursor) { + magicMouseCursor->setEnabled(absoluteMouseEnabled); + } + if (tabletLibrary) { + tabletLibrary->setEnabled(tabletEnabled); + if (!tabletBackend || !tabletEnabled) { + tabletLibrary->setChecked(false); + } + } + if (tabletMode) { + tabletMode->setEnabled(tabletBackend && absoluteMouseEnabled); + if (!tabletBackend) { + tabletMode->setCurrentText(QStringLiteral("-")); + } + } + } + + void setMouseUntrapBit(bool middle, bool enabled) + { + if (!mouseUntrapMode) { + return; + } + const int index = mouseUntrapMode->currentIndex(); + bool middleEnabled = index == 1 || index == 3; + bool magicEnabled = index == 2 || index == 3; + if (middle) { + middleEnabled = enabled; + } else { + magicEnabled = enabled; + } + const int nextIndex = (middleEnabled ? 1 : 0) + (magicEnabled ? 2 : 0); + mouseUntrapMode->setCurrentIndex(qBound(0, nextIndex, 3)); + } + + QWidget *makeIoPortsPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + QGridLayout *parallel = new QGridLayout; + parallel->setHorizontalSpacing(8); + parallel->setVerticalSpacing(4); + parallel->setColumnStretch(1, 1); + printerPort = combo({ QStringLiteral("") }); + printerType = combo(configChoiceDisplays(printerTypeChoices, int(sizeof(printerTypeChoices) / sizeof(printerTypeChoices[0])))); + QPushButton *flushPrinter = new QPushButton(QStringLiteral("Flush print job")); + printerAutoFlush = new QSpinBox; + printerAutoFlush->setRange(0, 3600); + ghostscriptParams = new QLineEdit; + samplerDevice = new QComboBox; + for (const auto &choice : samplerDeviceChoices()) { + samplerDevice->addItem(choice.first, choice.second); + } + samplerStereo = new QCheckBox(QStringLiteral("Stereo sampler")); + parallel->addWidget(label(QStringLiteral("Printer:")), 0, 0); + parallel->addWidget(printerPort, 0, 1, 1, 3); + parallel->addWidget(label(QStringLiteral("Type:")), 1, 0); + parallel->addWidget(printerType, 1, 1, 1, 3); + parallel->addWidget(flushPrinter, 2, 1); + parallel->addWidget(label(QStringLiteral("Autoflush:")), 2, 2); + parallel->addWidget(printerAutoFlush, 2, 3); + parallel->addWidget(label(QStringLiteral("Ghostscript extra parameters:")), 3, 0); + parallel->addWidget(ghostscriptParams, 3, 1, 1, 3); + parallel->addWidget(label(QStringLiteral("Sampler:")), 4, 0); + parallel->addWidget(samplerDevice, 4, 1, 1, 3); + parallel->addWidget(samplerStereo, 5, 1, 1, 3); + root->addWidget(groupBox(QStringLiteral("Parallel Port"), parallel)); + + QGridLayout *serial = new QGridLayout; + serial->setHorizontalSpacing(8); + serial->setVerticalSpacing(4); + serial->setColumnStretch(0, 1); + serial->setColumnStretch(1, 1); + serial->setColumnStretch(2, 1); + serial->setColumnStretch(3, 1); + serialPort = combo(unixSerialPortItems()); + serialPort->setEditable(true); + serialPort->setInsertPolicy(QComboBox::NoInsert); + serialPort->lineEdit()->setClearButtonEnabled(true); + serialShared = new QCheckBox(QStringLiteral("Shared")); + serialCtsRts = new QCheckBox(QStringLiteral("Host RTS/CTS")); + serialDirect = new QCheckBox(QStringLiteral("Direct")); + serialCrlf = new QCheckBox(QStringLiteral("CR/LF conversion")); + uaeSerial = new QCheckBox(QStringLiteral("uaeserial.device")); + serialStatus = new QCheckBox(QStringLiteral("Serial status (RTS/CTS/DTR/DTE/CD)")); + serialRingIndicator = new QCheckBox(QStringLiteral("Serial status: Ring Indicator")); + serialDirect->setToolTip(QStringLiteral("Unix serial uses direct host device/TCP I/O; this keeps the Windows-compatible config flag enabled for that path.")); + serial->addWidget(serialPort, 0, 0, 1, 4); + serial->addWidget(serialShared, 1, 0); + serial->addWidget(serialCtsRts, 1, 1); + serial->addWidget(serialDirect, 1, 2); + serial->addWidget(uaeSerial, 1, 3); + serial->addWidget(serialStatus, 2, 0, 1, 2); + serial->addWidget(serialRingIndicator, 2, 2); + serial->addWidget(serialCrlf, 2, 3); + root->addWidget(groupBox(QStringLiteral("Serial Port"), serial)); + + QGridLayout *midi = new QGridLayout; + midi->setHorizontalSpacing(8); + midi->setVerticalSpacing(4); + midi->setColumnStretch(1, 1); + midi->setColumnStretch(3, 1); + midiOut = new QComboBox; + midiOut->addItem(QStringLiteral(""), -2); + for (const WinUaeQtMidiChoice &choice : midiOutputChoices()) { + midiOut->addItem(choice.display, choice.deviceId); + midiOut->setItemData(midiOut->count() - 1, choice.configName, Qt::UserRole + 1); + } + midiIn = new QComboBox; + midiIn->addItem(QStringLiteral(""), -1); + for (const WinUaeQtMidiChoice &choice : midiInputChoices()) { + midiIn->addItem(choice.display, choice.deviceId); + midiIn->setItemData(midiIn->count() - 1, choice.configName, Qt::UserRole + 1); + } + midiRouter = new QCheckBox(QStringLiteral("Route MIDI In to MIDI Out")); + midi->addWidget(label(QStringLiteral("Out:")), 0, 0); + midi->addWidget(midiOut, 0, 1); + midi->addWidget(label(QStringLiteral("In:")), 0, 2); + midi->addWidget(midiIn, 0, 3); + midi->addWidget(midiRouter, 1, 1, 1, 3); + root->addWidget(groupBox(QStringLiteral("MIDI"), midi)); + + QGridLayout *dongle = new QGridLayout; + dongle->setHorizontalSpacing(8); + dongle->setVerticalSpacing(4); + dongle->setColumnStretch(1, 1); + protectionDongle = combo(configChoiceDisplays(dongleChoices, int(sizeof(dongleChoices) / sizeof(dongleChoices[0])))); + dongle->addWidget(label(QStringLiteral("Dongle:")), 0, 0); + dongle->addWidget(protectionDongle, 0, 1); + root->addWidget(groupBox(QStringLiteral("Protection Dongle"), dongle)); + root->addStretch(1); + + connect(serialPort, &QComboBox::currentTextChanged, this, [this](const QString &) { updateIoPortsState(); }); + connect(samplerDevice, &QComboBox::currentTextChanged, this, [this](const QString &) { updateIoPortsState(); }); + connect(midiOut, &QComboBox::currentTextChanged, this, [this](const QString &) { updateIoPortsState(); }); + connect(midiIn, &QComboBox::currentTextChanged, this, [this](const QString &) { updateIoPortsState(); }); + updateIoPortsState(); + return page; + } + + void updateIoPortsState() + { + const QString serial = serialPort ? serialPort->currentText().trimmed() : QString(); + const bool serialEnabled = !serial.isEmpty() + && serial != QStringLiteral("") + && serial.compare(QStringLiteral("none"), Qt::CaseInsensitive) != 0; + if (serialShared) { + serialShared->setEnabled(serialEnabled); + } + if (serialCtsRts) { + serialCtsRts->setEnabled(serialEnabled); + } + if (serialDirect) { + serialDirect->setEnabled(serialEnabled); + } + if (serialStatus) { + serialStatus->setEnabled(serialEnabled); + } + if (serialRingIndicator) { + serialRingIndicator->setEnabled(serialEnabled); + } + if (samplerStereo) { + samplerStereo->setEnabled(samplerDevice && samplerDevice->currentText() != QStringLiteral("")); + } + const bool midiActive = midiOut && midiIn + && midiOut->currentText() != QStringLiteral("") + && midiIn->currentText() != QStringLiteral(""); + if (midiRouter) { + midiRouter->setEnabled(midiActive); + } + } + + bool inputGamePortsMode() const + { + return !inputType || inputType->currentText() == QStringLiteral("Game Ports"); + } + + int selectedInputConfigLine() const + { + if (inputGamePortsMode()) { + return GamePortsInputConfigLine; + } + return qBound(1, inputType->currentIndex() + 1, CustomInputConfigSlots); + } + + QString selectedInputDeviceType() const + { + const QString device = inputDevice ? inputDevice->currentText() : QStringLiteral("Joystick"); + if (device == QStringLiteral("Mouse")) { + return QStringLiteral("mouse"); + } + if (device == QStringLiteral("Keyboard")) { + return QStringLiteral("keyboard"); + } + return QStringLiteral("joystick"); + } + + void markInputOwnedKey(const QString &key) + { + if (!inputOwnedMappingKeys.contains(key)) { + inputOwnedMappingKeys.append(key); + } + } + + QString inputDeviceMetadataKey(const QString &suffix, int device = 0) const + { + return QStringLiteral("input.%1.%2.%3.%4") + .arg(selectedInputConfigLine()) + .arg(selectedInputDeviceType()) + .arg(device) + .arg(suffix); + } + + QString selectedInputMappingKey() const + { + QTreeWidgetItem *item = inputMappingList ? inputMappingList->currentItem() : nullptr; + return item ? item->data(0, InputMappingKeyRole).toString() : QString(); + } + + QList inputDeviceIndices(const QString &type, int configLine) const + { + QList indices; + if (type != QStringLiteral("keyboard")) { + indices.append(0); + } + const QString prefix = QStringLiteral("input.%1.%2.").arg(configLine).arg(type); + for (auto it = inputMappingSettings.constBegin(); it != inputMappingSettings.constEnd(); ++it) { + if (!it.key().startsWith(prefix)) { + continue; + } + const QStringList parts = it.key().split(QLatin1Char('.')); + bool ok = false; + const int index = parts.value(3).toInt(&ok); + if (ok && !indices.contains(index)) { + indices.append(index); + } + } + std::sort(indices.begin(), indices.end()); + return indices; + } + + QVector currentInputRows() const + { + QVector rows; + const QString type = selectedInputDeviceType(); + const int configLine = selectedInputConfigLine(); + const QList deviceIndices = inputDeviceIndices(type, configLine); + const bool showDeviceNumber = deviceIndices.size() > 1 || deviceIndices.value(0, 0) != 0; + const auto rowLabel = [showDeviceNumber](const QString &base, int device) { + return showDeviceNumber ? QStringLiteral("Device %1 %2").arg(device + 1).arg(base) : base; + }; + + if (type == QStringLiteral("joystick")) { + for (int device : deviceIndices) { + for (int axis = 0; axis < 6; axis++) { + rows.append({ + rowLabel(axis == 0 ? QStringLiteral("X Axis") + : axis == 1 ? QStringLiteral("Y Axis") + : QStringLiteral("Axis %1").arg(axis + 1), device), + QStringLiteral("input.%1.joystick.%2.axis.%3").arg(configLine).arg(device).arg(axis), + true + }); + } + for (int button = 0; button < 12; button++) { + rows.append({ + rowLabel(QStringLiteral("Button %1").arg(button + 1), device), + QStringLiteral("input.%1.joystick.%2.button.%3").arg(configLine).arg(device).arg(button), + true + }); + } + } + } else if (type == QStringLiteral("mouse")) { + for (int device : deviceIndices) { + rows.append({ rowLabel(QStringLiteral("X Axis"), device), QStringLiteral("input.%1.mouse.%2.axis.0").arg(configLine).arg(device), true }); + rows.append({ rowLabel(QStringLiteral("Y Axis"), device), QStringLiteral("input.%1.mouse.%2.axis.1").arg(configLine).arg(device), true }); + rows.append({ rowLabel(QStringLiteral("Wheel"), device), QStringLiteral("input.%1.mouse.%2.axis.2").arg(configLine).arg(device), true }); + for (int button = 0; button < 5; button++) { + rows.append({ + rowLabel(QStringLiteral("Button %1").arg(button + 1), device), + QStringLiteral("input.%1.mouse.%2.button.%3").arg(configLine).arg(device).arg(button), + true + }); + } + } + } else { + QStringList keyboardKeys; + const QString prefix = QStringLiteral("input.%1.keyboard.").arg(configLine); + for (auto it = inputMappingSettings.constBegin(); it != inputMappingSettings.constEnd(); ++it) { + if (winUaeQtIsInputWidgetMappingKey(it.key()) && it.key().startsWith(prefix)) { + keyboardKeys.append(it.key()); + } + } + std::sort(keyboardKeys.begin(), keyboardKeys.end()); + QStringList generatedKeys; + for (const WinUaeQtKeyboardChoice &choice : keyboardChoices) { + const QString key = QStringLiteral("input.%1.keyboard.0.button.%2.KEY_%2") + .arg(configLine) + .arg(choice.scancode); + generatedKeys.append(key); + rows.append({ + QString::fromLatin1(choice.display), + key, + true + }); + } + for (const QString &key : keyboardKeys) { + if (generatedKeys.contains(key)) { + continue; + } + const QStringList parts = key.split(QLatin1Char('.')); + rows.append({ + QStringLiteral("Keyboard %1 %2").arg(parts.value(3), parts.mid(4).join(QLatin1Char(' '))), + key, + true + }); + } + } + return rows; + } + + QVector inputSlotsForKey(const QString &key) const + { + QVector slotList; + const QString value = inputMappingSettings.value(key).trimmed(); + if (value.isEmpty()) { + return slotList; + } + for (const QString &field : winUaeQtInputSlotFields(value)) { + slotList.append(winUaeQtParseInputSlot(field)); + } + return slotList; + } + + WinUaeQtInputSlot selectedInputSlotForKey(const QString &key) const + { + const QVector slotList = inputSlotsForKey(key); + const int sub = inputSubEvent ? inputSubEvent->currentIndex() : 0; + return sub >= 0 && sub < slotList.size() ? slotList[sub] : WinUaeQtInputSlot(); + } + + void insertInputMetadataIfMissing(const QString &key, const QString &value) + { + if (!inputMappingSettings.contains(key)) { + inputMappingSettings.insert(key, value); + markInputOwnedKey(key); + } + } + + void ensureInputDeviceMetadataForMappingKey(const QString &key) + { + const QStringList parts = key.split(QLatin1Char('.')); + if (parts.size() < 5) { + return; + } + const QString prefix = QStringLiteral("input.%1.%2.%3.") + .arg(parts.value(1), parts.value(2), parts.value(3)); + if (parts.value(2) == QStringLiteral("keyboard")) { + insertInputMetadataIfMissing(prefix + QStringLiteral("friendlyname"), QStringLiteral("Unix Keyboard")); + insertInputMetadataIfMissing(prefix + QStringLiteral("name"), QStringLiteral("unix.keyboard")); + } else if (parts.value(2) == QStringLiteral("mouse")) { + insertInputMetadataIfMissing(prefix + QStringLiteral("friendlyname"), QStringLiteral("Unix Mouse")); + insertInputMetadataIfMissing(prefix + QStringLiteral("name"), QStringLiteral("unix.mouse")); + } + insertInputMetadataIfMissing(prefix + QStringLiteral("empty"), QStringLiteral("false")); + insertInputMetadataIfMissing(prefix + QStringLiteral("disabled"), QStringLiteral("false")); + } + + void setInputSlotForKey(const QString &key, int sub, WinUaeQtInputSlot slot) + { + if (key.isEmpty() || sub < 0 || sub >= MaxInputSubEventSlots) { + return; + } + QVector slotList = inputSlotsForKey(key); + while (slotList.size() <= sub) { + slotList.append(WinUaeQtInputSlot()); + } + slotList[sub] = slot; + while (!slotList.isEmpty()) { + const WinUaeQtInputSlot &last = slotList.last(); + if (!last.event.trimmed().isEmpty() || last.flags != 0 || !last.qualifiers.isEmpty() || !last.suffix.isEmpty()) { + break; + } + slotList.removeLast(); + } + + markInputOwnedKey(key); + if (slotList.isEmpty()) { + inputMappingSettings.remove(key); + return; + } + QStringList formatted; + for (const WinUaeQtInputSlot &entry : slotList) { + formatted.append(winUaeQtFormatInputSlot(entry)); + } + ensureInputDeviceMetadataForMappingKey(key); + inputMappingSettings.insert(key, formatted.join(QLatin1Char(','))); + } + + int inputMappingSubEventCount(const QString &key) const + { + int count = 0; + for (const WinUaeQtInputSlot &slot : inputSlotsForKey(key)) { + if (!slot.event.trimmed().isEmpty()) { + count++; + } + } + return count; + } + + QString inputFlagText(const WinUaeQtInputSlot &slot, int flag) const + { + if (slot.event.trimmed().isEmpty()) { + return QStringLiteral("-"); + } + return (slot.flags & flag) ? QStringLiteral("YES") : QStringLiteral("NO"); + } + + QString inputAutofireText(const WinUaeQtInputSlot &slot) const + { + if (slot.event.trimmed().isEmpty()) { + return QStringLiteral("-"); + } + if (slot.flags & InputFlagInvertToggle) { + return QStringLiteral("ON"); + } + return (slot.flags & InputFlagAutofire) ? QStringLiteral("YES") : QStringLiteral("NO"); + } + + void populateInputEventChoices(const WinUaeQtInputSlot &slot, bool editable) + { + if (!inputAmigaEvent) { + return; + } + const QSignalBlocker blocker(inputAmigaEvent); + inputMappingUpdating = true; + inputAmigaEvent->clear(); + inputAmigaEvent->addItem(QStringLiteral(""), QString()); + inputAmigaEvent->addItem(QStringLiteral(""), QStringLiteral("__custom__")); + for (const WinUaeQtInputEventChoice &choice : inputEventChoices) { + inputAmigaEvent->addItem(QString::fromLatin1(choice.display), QString::fromLatin1(choice.config)); + } + for (const WinUaeQtKeyboardChoice &choice : keyboardChoices) { + if (choice.defaultEvent[0]) { + inputAmigaEvent->addItem(QString::fromLatin1(choice.display), QString::fromLatin1(choice.defaultEvent)); + } + } + if (!slot.event.isEmpty()) { + const QString data = slot.custom ? QStringLiteral("__current_custom__") : slot.event; + const QString text = slot.custom + ? QStringLiteral(": %1").arg(slot.event) + : winUaeQtInputEventDisplayName(slot.event); + if (inputAmigaEvent->findData(data) < 0) { + inputAmigaEvent->insertItem(1, text, data); + } + inputAmigaEvent->setCurrentIndex(qMax(0, inputAmigaEvent->findData(data))); + } else { + inputAmigaEvent->setCurrentIndex(0); + } + inputAmigaEvent->setEnabled(editable); + inputMappingUpdating = false; + } + + void updateInputDeviceEnabledCheck() + { + if (!inputDeviceEnabled) { + return; + } + const QSignalBlocker blocker(inputDeviceEnabled); + const QString disabled = inputMappingSettings.value(inputDeviceMetadataKey(QStringLiteral("disabled"))); + inputDeviceEnabled->setChecked(!configBoolValue(disabled)); + } + + void updateInputControlState() + { + const QString key = selectedInputMappingKey(); + QTreeWidgetItem *item = inputMappingList ? inputMappingList->currentItem() : nullptr; + const bool editable = item && item->data(0, InputMappingEditableRole).toBool() && !inputGamePortsMode(); + const WinUaeQtInputSlot slot = editable ? selectedInputSlotForKey(key) : WinUaeQtInputSlot(); + populateInputEventChoices(slot, editable); + if (inputDeviceEnabled) { + inputDeviceEnabled->setEnabled(!inputGamePortsMode()); + updateInputDeviceEnabledCheck(); + } + if (inputRemapButton) { + const bool canCapture = editable && selectedInputDeviceType() == QStringLiteral("joystick"); + inputRemapButton->setEnabled(canCapture); + inputRemapButton->setToolTip(canCapture + ? QStringLiteral("Capture an SDL joystick/gamepad control for the selected mapping.") + : QStringLiteral("Capture is currently available for joystick/gamepad mappings.")); + } + if (inputCopyButton) { + inputCopyButton->setEnabled(!inputGamePortsMode()); + } + if (inputCopyFrom) { + inputCopyFrom->setEnabled(!inputGamePortsMode()); + } + if (inputSwapButton) { + inputSwapButton->setEnabled(!inputGamePortsMode()); + } + } + + void setSelectedInputEvent(const QString &eventData) + { + if (inputMappingUpdating || inputGamePortsMode()) { + return; + } + const QString key = selectedInputMappingKey(); + QTreeWidgetItem *item = inputMappingList ? inputMappingList->currentItem() : nullptr; + if (key.isEmpty() || !item || !item->data(0, InputMappingEditableRole).toBool()) { + return; + } + WinUaeQtInputSlot slot = selectedInputSlotForKey(key); + QString event = eventData; + bool custom = false; + if (eventData == QStringLiteral("__current_custom__")) { + return; + } + if (eventData == QStringLiteral("__custom__")) { + bool ok = false; + event = QInputDialog::getText(this, QStringLiteral("Custom Event"), QStringLiteral("Event:"), QLineEdit::Normal, slot.custom ? slot.event : QString(), &ok).trimmed(); + if (!ok) { + updateInputControlState(); + return; + } + custom = true; + } + slot.event = event; + slot.custom = custom; + if (slot.event.isEmpty()) { + slot.flags = 0; + slot.qualifiers.clear(); + slot.suffix.clear(); + slot.custom = false; + } + setInputSlotForKey(key, inputSubEvent ? inputSubEvent->currentIndex() : 0, slot); + refreshInputMappingList(key); + } + + void toggleSelectedInputFlag(int flag) + { + if (inputGamePortsMode()) { + return; + } + const QString key = selectedInputMappingKey(); + if (key.isEmpty()) { + return; + } + WinUaeQtInputSlot slot = selectedInputSlotForKey(key); + if (slot.event.trimmed().isEmpty()) { + return; + } + slot.flags ^= flag; + setInputSlotForKey(key, inputSubEvent ? inputSubEvent->currentIndex() : 0, slot); + refreshInputMappingList(key); + } + + void toggleSelectedInputAutofire() + { + if (inputGamePortsMode()) { + return; + } + const QString key = selectedInputMappingKey(); + if (key.isEmpty()) { + return; + } + WinUaeQtInputSlot slot = selectedInputSlotForKey(key); + if (slot.event.trimmed().isEmpty()) { + return; + } + if ((slot.flags & InputFlagAutofire) && (slot.flags & InputFlagInvertToggle)) { + slot.flags &= ~(InputFlagAutofire | InputFlagInvertToggle); + } else if (slot.flags & InputFlagAutofire) { + slot.flags |= InputFlagInvertToggle; + slot.flags &= ~InputFlagToggle; + } else { + slot.flags |= InputFlagAutofire; + } + setInputSlotForKey(key, inputSubEvent ? inputSubEvent->currentIndex() : 0, slot); + refreshInputMappingList(key); + } + + void toggleSelectedInputSetMode() + { + if (inputGamePortsMode()) { + return; + } + const QString key = selectedInputMappingKey(); + if (key.isEmpty()) { + return; + } + WinUaeQtInputSlot slot = selectedInputSlotForKey(key); + if (slot.event.trimmed().isEmpty()) { + return; + } + if (slot.flags & InputFlagSetOnOffVal2) { + slot.flags &= ~(InputFlagSetOnOff | InputFlagSetOnOffVal1 | InputFlagSetOnOffVal2); + } else if (slot.flags & InputFlagSetOnOffVal1) { + slot.flags &= ~InputFlagSetOnOffVal1; + slot.flags |= InputFlagSetOnOffVal2; + } else if (slot.flags & InputFlagSetOnOff) { + slot.flags |= InputFlagSetOnOffVal1; + } else { + slot.flags |= InputFlagSetOnOff; + } + setInputSlotForKey(key, inputSubEvent ? inputSubEvent->currentIndex() : 0, slot); + refreshInputMappingList(key); + } + + bool inputKeyMatchesCurrentDevice(const QString &key, int configLine) const + { + const QString prefix = QStringLiteral("input.%1.%2.").arg(configLine).arg(selectedInputDeviceType()); + return key.startsWith(prefix); + } + + void copyInputMappings() + { + if (inputGamePortsMode()) { + return; + } + const int dest = selectedInputConfigLine(); + const int sourceIndex = inputCopyFrom ? inputCopyFrom->currentIndex() : -1; + if (sourceIndex < 0) { + return; + } + if (sourceIndex < CustomInputConfigSlots && sourceIndex + 1 == dest) { + return; + } + const QString type = selectedInputDeviceType(); + const QString destPrefix = QStringLiteral("input.%1.%2.").arg(dest).arg(type); + const QString sourcePrefix = QStringLiteral("input.%1.%2.").arg(sourceIndex + 1).arg(type); + const QStringList keys = inputMappingSettings.keys(); + for (const QString &key : keys) { + if (key.startsWith(destPrefix)) { + inputMappingSettings.remove(key); + markInputOwnedKey(key); + } + } + if (sourceIndex < CustomInputConfigSlots && sourceIndex + 1 != dest) { + for (const QString &key : keys) { + if (!key.startsWith(sourcePrefix)) { + continue; + } + const QString destKey = destPrefix + key.mid(sourcePrefix.size()); + inputMappingSettings.insert(destKey, inputMappingSettings.value(key)); + markInputOwnedKey(destKey); + } + } + refreshInputMappingList(); + } + + void swapInputMappings() + { + if (inputGamePortsMode()) { + return; + } + const QStringList keys = inputMappingSettings.keys(); + const int configLine = selectedInputConfigLine(); + for (const QString &key : keys) { + if (!inputKeyMatchesCurrentDevice(key, configLine) || !winUaeQtIsInputWidgetMappingKey(key)) { + continue; + } + QVector slotList = inputSlotsForKey(key); + bool changed = false; + for (WinUaeQtInputSlot &slot : slotList) { + const QString swapped = winUaeQtSwapInputEventPorts(slot.event); + if (swapped != slot.event) { + slot.event = swapped; + changed = true; + } + } + if (!changed) { + continue; + } + QStringList formatted; + for (const WinUaeQtInputSlot &slot : slotList) { + formatted.append(winUaeQtFormatInputSlot(slot)); + } + inputMappingSettings.insert(key, formatted.join(QLatin1Char(','))); + markInputOwnedKey(key); + } + refreshInputMappingList(); + } + + void setInputDeviceEnabled(bool enabled) + { + if (inputMappingUpdating || inputGamePortsMode()) { + return; + } + const QString key = inputDeviceMetadataKey(QStringLiteral("disabled")); + inputMappingSettings.insert(key, enabled ? QStringLiteral("false") : QStringLiteral("true")); + markInputOwnedKey(key); + } + + void openInputRemapCapture() + { + const QString key = selectedInputMappingKey(); + if (key.isEmpty() || selectedInputDeviceType() != QStringLiteral("joystick")) { + return; + } + WinUaeQtInputSlot slot = selectedInputSlotForKey(key); + const QString event = inputAmigaEvent ? inputAmigaEvent->currentData().toString() : slot.event; + if (!event.isEmpty() && !event.startsWith(QStringLiteral("__"))) { + slot.event = event; + slot.custom = false; + } + if (slot.event.trimmed().isEmpty()) { + QMessageBox::information(this, windowTitle(), QStringLiteral("Select an Amiga event before capturing a host control.")); + return; + } + + const QString prompt = QStringLiteral("Press input for %1.").arg(winUaeQtInputEventDisplayName(slot.event)); + UnixQtInputMapDialog dialog(-1, prompt, QString(), this, true); + if (dialog.exec() != QDialog::Accepted || !dialog.hasCapturedInput()) { + return; + } + const QString capturedKey = QStringLiteral("input.%1.joystick.%2.%3.%4") + .arg(selectedInputConfigLine()) + .arg(dialog.capturedDevice()) + .arg(dialog.capturedWidgetType() == QLatin1Char('b') ? QStringLiteral("button") : QStringLiteral("axis")) + .arg(dialog.capturedWidgetMappingIndex()); + inputMappingSettings.insert(QStringLiteral("input.%1.joystick.%2.friendlyname") + .arg(selectedInputConfigLine()).arg(dialog.capturedDevice()), + dialog.capturedDeviceDisplayName()); + inputMappingSettings.insert(QStringLiteral("input.%1.joystick.%2.name") + .arg(selectedInputConfigLine()).arg(dialog.capturedDevice()), + dialog.capturedDeviceConfigName().isEmpty() ? dialog.capturedDeviceDisplayName() : dialog.capturedDeviceConfigName()); + inputMappingSettings.insert(QStringLiteral("input.%1.joystick.%2.empty") + .arg(selectedInputConfigLine()).arg(dialog.capturedDevice()), + QStringLiteral("false")); + inputMappingSettings.insert(QStringLiteral("input.%1.joystick.%2.disabled") + .arg(selectedInputConfigLine()).arg(dialog.capturedDevice()), + QStringLiteral("false")); + markInputOwnedKey(QStringLiteral("input.%1.joystick.%2.friendlyname").arg(selectedInputConfigLine()).arg(dialog.capturedDevice())); + markInputOwnedKey(QStringLiteral("input.%1.joystick.%2.name").arg(selectedInputConfigLine()).arg(dialog.capturedDevice())); + markInputOwnedKey(QStringLiteral("input.%1.joystick.%2.empty").arg(selectedInputConfigLine()).arg(dialog.capturedDevice())); + markInputOwnedKey(QStringLiteral("input.%1.joystick.%2.disabled").arg(selectedInputConfigLine()).arg(dialog.capturedDevice())); + setInputSlotForKey(capturedKey, inputSubEvent ? inputSubEvent->currentIndex() : 0, slot); + refreshInputMappingList(capturedKey); + } + + QWidget *makeInputPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + + QGridLayout *top = new QGridLayout; + top->setColumnStretch(1, 1); + inputType = combo({ + QStringLiteral("Configuration #1"), + QStringLiteral("Configuration #2"), + QStringLiteral("Configuration #3"), + QStringLiteral("Game Ports") + }, QStringLiteral("Game Ports")); + inputDevice = combo({ + QStringLiteral("Keyboard"), + QStringLiteral("Mouse"), + QStringLiteral("Joystick") + }); + inputDeviceEnabled = new QCheckBox(QStringLiteral("Device enabled")); + top->addWidget(inputType, 0, 0); + top->addWidget(inputDevice, 0, 1); + top->addWidget(inputDeviceEnabled, 0, 2, Qt::AlignRight | Qt::AlignVCenter); + root->addLayout(top); + + inputMappingList = new QTreeWidget; + inputMappingList->setRootIsDecorated(false); + inputMappingList->setAlternatingRowColors(true); + inputMappingList->setSelectionMode(QAbstractItemView::SingleSelection); + inputMappingList->setHeaderLabels({ + QStringLiteral("Host widget"), + QStringLiteral("Amiga event"), + QStringLiteral("Autofire"), + QStringLiteral("Toggle"), + QStringLiteral("Invert"), + QStringLiteral("Qualifier"), + QStringLiteral("#") + }); + inputMappingList->header()->setStretchLastSection(false); + inputMappingList->header()->setSectionResizeMode(0, QHeaderView::Stretch); + for (int i = 1; i < inputMappingList->columnCount(); i++) { + inputMappingList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + root->addWidget(inputMappingList, 1); + + QHBoxLayout *eventRow = new QHBoxLayout; + inputSubEvent = combo({ QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5"), QStringLiteral("6"), QStringLiteral("7"), QStringLiteral("8") }); + inputAmigaEvent = new QComboBox; + inputAmigaEvent->setEditable(false); + QPushButton *inputTest = new QPushButton(QStringLiteral("Test")); + inputRemapButton = new QPushButton(QStringLiteral("Remap")); + connect(inputTest, &QPushButton::clicked, this, [this]() { openInputMapDialog(-1); }); + connect(inputRemapButton, &QPushButton::clicked, this, [this]() { openInputRemapCapture(); }); + eventRow->addWidget(inputSubEvent); + eventRow->addWidget(inputAmigaEvent, 1); + eventRow->addWidget(inputTest); + eventRow->addWidget(inputRemapButton); + root->addLayout(eventRow); + + QGridLayout *bottom = new QGridLayout; + bottom->setColumnStretch(1, 1); + bottom->setColumnStretch(3, 1); + inputDeadzone = new QSpinBox; + inputDeadzone->setRange(0, 100); + inputDeadzone->setSuffix(QStringLiteral("%")); + inputAutofireRate = new QSpinBox; + inputAutofireRate->setRange(1, 10000); + inputJoyMouseDigital = new QSpinBox; + inputJoyMouseDigital->setRange(1, 1000); + inputJoyMouseAnalog = new QSpinBox; + inputJoyMouseAnalog->setRange(1, 1000); + inputCopyFrom = combo({ + QStringLiteral("Custom #1"), + QStringLiteral("Custom #2"), + QStringLiteral("Custom #3"), + QStringLiteral("Default"), + QStringLiteral("Default (PC KB)") + }); + inputCopyButton = new QPushButton(QStringLiteral("Copy from:")); + inputSwapButton = new QPushButton(QStringLiteral("Swap 1<>2")); + inputPageUpEnd = new QCheckBox(QStringLiteral("Page Up = End")); + inputSwapBackslashF11 = new QCheckBox(QStringLiteral("Swap Backslash/F11")); + inputSwapBackslashF11->setTristate(true); + + bottom->addWidget(label(QStringLiteral("Joystick dead zone (%):")), 0, 0); + bottom->addWidget(inputDeadzone, 0, 1); + bottom->addWidget(label(QStringLiteral("Digital joy-mouse speed:")), 0, 2); + bottom->addWidget(inputJoyMouseDigital, 0, 3); + bottom->addWidget(inputCopyButton, 0, 4); + bottom->addWidget(label(QStringLiteral("Autofire rate (lines):")), 1, 0); + bottom->addWidget(inputAutofireRate, 1, 1); + bottom->addWidget(label(QStringLiteral("Analog joy-mouse speed:")), 1, 2); + bottom->addWidget(inputJoyMouseAnalog, 1, 3); + bottom->addWidget(inputCopyFrom, 1, 4); + bottom->addWidget(inputPageUpEnd, 2, 1); + bottom->addWidget(inputSwapBackslashF11, 2, 2, 1, 2); + bottom->addWidget(inputSwapButton, 2, 4); + root->addLayout(bottom); + + connect(inputType, &QComboBox::currentTextChanged, this, [this](const QString &) { refreshInputMappingList(); }); + connect(inputDevice, &QComboBox::currentTextChanged, this, [this](const QString &) { refreshInputMappingList(); }); + connect(inputSubEvent, &QComboBox::currentIndexChanged, this, [this](int) { refreshInputMappingList(selectedInputMappingKey()); }); + connect(inputAmigaEvent, &QComboBox::currentIndexChanged, this, [this](int) { + setSelectedInputEvent(inputAmigaEvent->currentData().toString()); + }); + connect(inputMappingList, &QTreeWidget::currentItemChanged, this, [this](QTreeWidgetItem *, QTreeWidgetItem *) { + updateInputControlState(); + }); + connect(inputMappingList, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem *, int column) { + if (column == 1) { + toggleSelectedInputSetMode(); + } else if (column == 2) { + toggleSelectedInputAutofire(); + } else if (column == 3) { + toggleSelectedInputFlag(InputFlagToggle); + } else if (column == 4) { + toggleSelectedInputFlag(InputFlagInvert); + } else if (column == 6 && inputSubEvent) { + inputSubEvent->setCurrentIndex((inputSubEvent->currentIndex() + 1) % MaxInputSubEventSlots); + } + }); + connect(inputCopyButton, &QPushButton::clicked, this, [this]() { copyInputMappings(); }); + connect(inputSwapButton, &QPushButton::clicked, this, [this]() { swapInputMappings(); }); + connect(inputDeviceEnabled, &QCheckBox::toggled, this, [this](bool enabled) { setInputDeviceEnabled(enabled); }); + + refreshInputMappingList(); + return page; + } + + void refreshInputMappingList(const QString &preferredKey = QString()) + { + if (!inputMappingList || !inputDevice) { + return; + } + const QString selectedKey = preferredKey.isEmpty() ? selectedInputMappingKey() : preferredKey; + const QSignalBlocker blocker(inputMappingList); + inputMappingList->clear(); + + for (const WinUaeQtInputRow &row : currentInputRows()) { + const WinUaeQtInputSlot slot = selectedInputSlotForKey(row.key); + QTreeWidgetItem *item = new QTreeWidgetItem(inputMappingList); + item->setText(0, row.label); + item->setText(1, slot.custom ? QStringLiteral(": %1").arg(slot.event) : winUaeQtInputEventDisplayName(slot.event)); + item->setText(2, inputAutofireText(slot)); + item->setText(3, inputFlagText(slot, InputFlagToggle)); + item->setText(4, inputFlagText(slot, InputFlagInvert)); + item->setText(5, slot.qualifiers.isEmpty() ? QStringLiteral("-") : slot.qualifiers); + item->setText(6, row.key.isEmpty() ? QStringLiteral("-") : QString::number(qMax(1, inputMappingSubEventCount(row.key)))); + item->setData(0, InputMappingKeyRole, row.key); + item->setData(0, InputMappingEditableRole, row.editable); + if (!row.editable || inputGamePortsMode()) { + item->setDisabled(true); + } + if (!selectedKey.isEmpty() && row.key == selectedKey) { + inputMappingList->setCurrentItem(item); + } + } + if (!inputMappingList->currentItem() && inputMappingList->topLevelItemCount() > 0) { + inputMappingList->setCurrentItem(inputMappingList->topLevelItem(0)); + } + updateInputControlState(); + } + + QWidget *makePathsPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + QGridLayout *paths = new QGridLayout; + paths->setColumnStretch(1, 1); + romsPath = new QLineEdit; + configsPath = new QLineEdit; + nvramPath = new QLineEdit; + screenshotsPath = new QLineEdit; + stateFilesPath = new QLineEdit; + videosPath = new QLineEdit; + saveImagesPath = new QLineEdit; + ripsPath = new QLineEdit; + recursiveRoms = new QCheckBox(QStringLiteral("Scan subfolders")); + cacheConfigurations = new QCheckBox(QStringLiteral("Cache Configuration files")); + cacheBoxArt = new QCheckBox(QStringLiteral("Cache Boxart files")); + saveImageOriginalPath = new QCheckBox(QStringLiteral("Use original image's path")); + paths->addWidget(recursiveRoms, 0, 1); + addLineBrowseRow(paths, 1, QStringLiteral("System ROMs:"), romsPath, true); + QHBoxLayout *cacheOptions = new QHBoxLayout; + cacheOptions->setContentsMargins(0, 0, 0, 0); + cacheOptions->addWidget(cacheConfigurations); + cacheOptions->addWidget(cacheBoxArt); + cacheOptions->addStretch(); + paths->addLayout(cacheOptions, 2, 1, 1, 2); + addLineBrowseRow(paths, 3, QStringLiteral("Configuration files:"), configsPath, true); + addLineBrowseRow(paths, 4, QStringLiteral("NVRAM files:"), nvramPath, true); + addLineBrowseRow(paths, 5, QStringLiteral("Screenshots:"), screenshotsPath, true); + addLineBrowseRow(paths, 6, QStringLiteral("State files:"), stateFilesPath, true); + addLineBrowseRow(paths, 7, QStringLiteral("Videos:"), videosPath, true); + paths->addWidget(saveImageOriginalPath, 8, 1); + addLineBrowseRow(paths, 9, QStringLiteral("Saveimages:"), saveImagesPath, true); + addLineBrowseRow(paths, 10, QStringLiteral("Rips:"), ripsPath, true); + root->addLayout(paths); + + QHBoxLayout *actions = new QHBoxLayout; + QPushButton *setPath = new QPushButton(QStringLiteral("Set Path")); + pathDefaultType = combo({ QStringLiteral("Application directory"), QStringLiteral("User data directory"), QStringLiteral("Custom data directory") }, QStringLiteral("User data directory")); + actions->addWidget(setPath); + actions->addWidget(pathDefaultType); + actions->addStretch(); + relativePaths = new QCheckBox(QStringLiteral("Use relative paths")); + disableUnavailable(relativePaths, QStringLiteral("Windows-style relative path saving is not implemented in the Unix configuration backend yet.")); + portableMode = new QCheckBox(QStringLiteral("Portable mode")); + actions->addWidget(relativePaths); + actions->addWidget(portableMode); + root->addLayout(actions); + + QGridLayout *data = new QGridLayout; + data->setColumnStretch(1, 1); + dataPath = new QLineEdit; + addLineBrowseRow(data, 0, QStringLiteral("Data path:"), dataPath, true); + QPushButton *rescanRoms = new QPushButton(QStringLiteral("Rescan ROMs")); + QPushButton *clearDiskHistory = new QPushButton(QStringLiteral("Clear disk history")); + QPushButton *clearRegistry = new QPushButton(QStringLiteral("Clear registry")); + disableUnavailable(clearRegistry, QStringLiteral("Windows registry reset does not apply to the Unix configuration backend.")); + data->addWidget(rescanRoms, 1, 0); + data->addWidget(clearDiskHistory, 1, 1); + data->addWidget(clearRegistry, 1, 2); + root->addLayout(data); + + QGridLayout *logging = new QGridLayout; + logging->setColumnStretch(0, 1); + logSelect = combo({ QStringLiteral("winuaebootlog.txt"), QStringLiteral("winuaelog.txt"), QStringLiteral("Current configuration") }); + fullLogging = new QCheckBox(QStringLiteral("Enable full logging")); + logWindow = new QCheckBox(QStringLiteral("Log window")); + QPushButton *saveAllLogs = new QPushButton(QStringLiteral("Save All")); + QPushButton *openLog = new QPushButton(QStringLiteral("Open")); + logPath = new QLineEdit; + logPath->setReadOnly(true); + logging->addWidget(logSelect, 0, 0); + logging->addWidget(fullLogging, 0, 1); + logging->addWidget(logWindow, 0, 2); + logging->addWidget(saveAllLogs, 0, 3); + logging->addWidget(openLog, 1, 3); + logging->addWidget(logPath, 1, 0, 1, 3); + root->addWidget(groupBox(QStringLiteral("Debug logging"), logging)); + root->addStretch(1); + connect(configsPath, &QLineEdit::textChanged, this, [this]() { refreshConfigList(); }); + connect(configsPath, &QLineEdit::textChanged, this, [this]() { refreshFrontendPage(); }); + connect(configsPath, &QLineEdit::textChanged, this, [this]() { updateLogPathText(); }); + connect(setPath, &QPushButton::clicked, this, [this]() { applySelectedPathDefaults(); }); + connect(logSelect, &QComboBox::currentTextChanged, this, [this](const QString &) { updateLogPathText(); }); + connect(rescanRoms, &QPushButton::clicked, this, [this]() { rescanRomPathCandidates(true); }); + connect(clearDiskHistory, &QPushButton::clicked, this, [this]() { clearDiskHistoryCombos(); }); + connect(saveAllLogs, &QPushButton::clicked, this, [this]() { saveAllDebugLogs(); }); + connect(openLog, &QPushButton::clicked, this, [this]() { openSelectedLog(); }); + if (configName) { + connect(configName, &QComboBox::currentTextChanged, this, [this](const QString &) { updateLogPathText(); }); + } + return page; + } + + bool isRomCandidateFile(const QFileInfo &info) const + { + if (!info.isFile() || info.size() <= 0 || info.size() >= 10 * 1024 * 1024) { + return false; + } + const QString suffix = info.suffix().toLower(); + return suffix == QStringLiteral("rom") + || suffix == QStringLiteral("bin") + || suffix == QStringLiteral("kick") + || suffix == QStringLiteral("a500") + || suffix == QStringLiteral("a600") + || suffix == QStringLiteral("a1200") + || suffix == QStringLiteral("a4000") + || suffix == QStringLiteral("cd32") + || suffix == QStringLiteral("cdtv"); + } + + void collectRomCandidates(const QDir &dir, int depth, QStringList *paths) const + { + if (!dir.exists() || !paths) { + return; + } + const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase); + for (const QFileInfo &info : files) { + if (isRomCandidateFile(info)) { + paths->append(info.absoluteFilePath()); + } + } + + const bool recursive = recursiveRoms && recursiveRoms->isChecked(); + if (!recursive || depth >= 2) { + return; + } + const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable, QDir::Name | QDir::IgnoreCase); + for (const QFileInfo &info : dirs) { + collectRomCandidates(QDir(info.absoluteFilePath()), depth + 1, paths); + } + } + + void replacePathComboItems(QComboBox *field, const QStringList &paths) + { + if (!field) { + return; + } + const QString current = field->currentText(); + QSignalBlocker blocker(field); + field->clear(); + field->addItems(paths); + if (!current.isEmpty() && field->findText(current) < 0) { + field->insertItem(0, current); + } + field->setCurrentText(current); + } + + void clearPathComboHistory(QComboBox *field) + { + if (!field) { + return; + } + const QString current = field->currentText(); + QSignalBlocker blocker(field); + field->clear(); + if (!current.isEmpty()) { + field->addItem(current); + } + field->setCurrentText(current); + } + + void clearDiskHistoryCombos() + { + for (int i = 0; i < 4; i++) { + clearPathComboHistory(dfPath[i]); + } + for (int i = 0; i < 2; i++) { + clearPathComboHistory(quickDfPath[i]); + } + clearPathComboHistory(diskSwapperPath); + clearPathComboHistory(cdSlotPath); + clearPathComboHistory(stateFileName); + clearPathComboHistory(genlockFile); + status->setText(QStringLiteral("Disk history cleared")); + } + + void rescanRomPathCandidates(bool showResult) + { + const QString root = romsPath ? expandedPathText(romsPath->text()) : QString(); + QStringList candidates; + if (!root.isEmpty()) { + collectRomCandidates(QDir(root), 0, &candidates); + } + candidates.removeDuplicates(); + candidates.sort(Qt::CaseInsensitive); + + replacePathComboItems(romFile, candidates); + replacePathComboItems(extendedRomFile, candidates); + replacePathComboItems(cartFile, candidates); + + if (showResult) { + if (candidates.isEmpty()) { + QMessageBox::information(this, windowTitle(), QStringLiteral("No ROM files were found in the configured System ROMs path.")); + } else { + status->setText(QStringLiteral("Found %1 ROM file%2").arg(candidates.size()).arg(candidates.size() == 1 ? QString() : QStringLiteral("s"))); + } + } + } + + void applySelectedPathDefaults() + { + QString base; + if (pathDefaultType && pathDefaultType->currentText() == QStringLiteral("Application directory")) { + base = QCoreApplication::applicationDirPath(); + } else if (pathDefaultType && pathDefaultType->currentText() == QStringLiteral("Custom data directory")) { + base = dataPath ? dataPath->text().trimmed() : QString(); + } + if (base.isEmpty()) { + base = unixDefaultDataPath(); + } + if (dataPath) { + dataPath->setText(base); + } + const QDir dir(base); + if (romsPath) { + romsPath->setText(dir.filePath(QStringLiteral("Kickstarts"))); + } + if (configsPath) { + configsPath->setText(dir.filePath(QStringLiteral("Configuration"))); + } + if (nvramPath) { + nvramPath->setText(dir.filePath(QStringLiteral("NVRAMs"))); + } + if (screenshotsPath) { + screenshotsPath->setText(dir.filePath(QStringLiteral("Screenshots"))); + } + if (stateFilesPath) { + stateFilesPath->setText(dir.filePath(QStringLiteral("Save States"))); + } + if (videosPath) { + videosPath->setText(dir.filePath(QStringLiteral("Videos"))); + } + if (saveImagesPath) { + saveImagesPath->setText(dir.filePath(QStringLiteral("SaveImages"))); + } + if (ripsPath) { + ripsPath->setText(dir.filePath(QStringLiteral("Rips"))); + } + refreshConfigList(); + } + + QByteArray currentConfigText() const + { + QString text = QStringLiteral("; WinUAE Unix Qt current configuration\n"); + const WinUaeQtConfig config = mergedConfig(); + for (const WinUaeQtConfig::Setting &setting : config.orderedSettings()) { + if (!setting.value.isEmpty()) { + text += QStringLiteral("%1=%2\n").arg(setting.key, setting.value); + } + } + return text.toUtf8(); + } + + void updateLogPathText() + { + if (!logSelect || !logPath) { + return; + } + const QString selected = logSelect->currentText(); + if (selected == QStringLiteral("Current configuration")) { + logPath->setText(currentConfigReportPath()); + } else { + logPath->setText(logFilePath(selected)); + } + } + + QString logFilePath(const QString &name) const + { + return QDir::temp().filePath(name); + } + + QString currentConfigReportPath() const + { + return QDir::temp().filePath(QStringLiteral("winuae_config_%1.%2.%3.txt") + .arg(WINUAE_UNIX_VERSION_MAJOR) + .arg(WINUAE_UNIX_VERSION_MINOR) + .arg(WINUAE_UNIX_VERSION_REVISION)); + } + + QString selectedLogPath() const + { + if (!logSelect) { + return QString(); + } + if (logSelect->currentText() == QStringLiteral("Current configuration")) { + return currentConfigReportPath(); + } + return logPath ? logPath->text().trimmed() : QString(); + } + + bool writeCurrentConfigReport(const QString &path) + { + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not write %1:\n%2").arg(path, file.errorString())); + return false; + } + const QByteArray text = currentConfigText(); + if (file.write(text) != text.size()) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not write %1:\n%2").arg(path, file.errorString())); + return false; + } + return true; + } + + void openSelectedLog() + { + const bool currentConfigSelected = logSelect && logSelect->currentText() == QStringLiteral("Current configuration"); + const QString path = selectedLogPath(); + if (path.isEmpty()) { + return; + } + if (currentConfigSelected) { + if (!writeCurrentConfigReport(path)) { + return; + } + } else if (!QFileInfo::exists(path)) { + QMessageBox::information(this, windowTitle(), QStringLiteral("The selected log file does not exist yet:\n%1").arg(path)); + return; + } + + if (!QDesktopServices::openUrl(QUrl::fromLocalFile(path))) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not open %1.").arg(path)); + } + } + + void addLogZipEntry(QVector *entries, const QString &entryName, const QString &path) const + { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + return; + } + entries->append({ entryName.toUtf8(), file.readAll() }); + } + + void saveAllDebugLogs() + { + const QString defaultName = QStringLiteral("winuae_debug_%1.%2.%3.zip") + .arg(WINUAE_UNIX_VERSION_MAJOR) + .arg(WINUAE_UNIX_VERSION_MINOR) + .arg(WINUAE_UNIX_VERSION_REVISION); + const QString path = QFileDialog::getSaveFileName( + this, + QStringLiteral("Save debug information"), + fileDialogInitialSavePath(QString(), defaultName), + QStringLiteral("Zip files (*.zip);;All files (*)")); + if (path.isEmpty()) { + return; + } + + QVector entries; + addLogZipEntry(&entries, QStringLiteral("winuaebootlog.txt"), logFilePath(QStringLiteral("winuaebootlog.txt"))); + addLogZipEntry(&entries, QStringLiteral("winuaelog.txt"), logFilePath(QStringLiteral("winuaelog.txt"))); + entries.append({ QByteArray("config.uae"), currentConfigText() }); + + QString error; + if (!writeStoredZip(path, entries, &error)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not save %1:\n%2").arg(path, error)); + } + } + + QWidget *makeMiscPage() + { + QWidget *page = makePage(); + QHBoxLayout *root = new QHBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + miscOptionList = new QListWidget; + miscOptionList->setAlternatingRowColors(true); + miscOptionItems.clear(); + for (const MiscCheckChoice &choice : miscCheckChoices) { + const QString key = QString::fromLatin1(choice.key); + QListWidgetItem *item = new QListWidgetItem(QString::fromLatin1(choice.display), miscOptionList); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(choice.defaultChecked ? Qt::Checked : Qt::Unchecked); + if (!miscCheckChoiceEnabled(key)) { + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + item->setCheckState(Qt::Unchecked); + item->setToolTip(miscCheckChoiceDisabledReason(key)); + } + miscOptionItems.insert(key, item); + } + root->addWidget(miscOptionList, 2); + + QVBoxLayout *right = new QVBoxLayout; + right->setSpacing(6); + QGridLayout *miscOptions = new QGridLayout; + miscOptions->setHorizontalSpacing(8); + miscOptions->setVerticalSpacing(5); + miscOptions->setColumnStretch(1, 1); + miscScsiMode = combo(configChoiceDisplays(scsiModeChoices, int(sizeof(scsiModeChoices) / sizeof(scsiModeChoices[0])))); + miscWindowedStyle = combo({ QStringLiteral("Borderless"), QStringLiteral("Minimal"), QStringLiteral("Standard"), QStringLiteral("Extended") }); + miscVideoApi = combo({ QStringLiteral("Unix video backend") }); + miscVideoApiOptions = combo({ QStringLiteral("Default") }); + disableUnavailable(miscWindowedStyle, QStringLiteral("Windows window-style selector does not apply to the Unix SDL/Qt window backend.")); + disableUnavailable(miscVideoApi, QStringLiteral("Unix video backend selection is not configurable from the Qt frontend yet.")); + disableUnavailable(miscVideoApiOptions, QStringLiteral("Unix video backend options are not configurable from the Qt frontend yet.")); + miscOptions->addWidget(label(QStringLiteral("SCSI and CD/DVD access:")), 0, 0); + miscOptions->addWidget(miscScsiMode, 0, 1); + miscOptions->addWidget(label(QStringLiteral("Windowed style:")), 1, 0); + miscOptions->addWidget(miscWindowedStyle, 1, 1); + miscOptions->addWidget(miscVideoApi, 2, 1); + miscOptions->addWidget(miscVideoApiOptions, 3, 1); + right->addWidget(groupBox(QStringLiteral("Miscellaneous Options"), miscOptions)); + + QVBoxLayout *gui = new QVBoxLayout; + gui->setSpacing(5); + miscLanguage = combo({ QStringLiteral("Autodetect"), QStringLiteral("English (built-in)") }); + QPushButton *guiFont = new QPushButton(QStringLiteral("GUI Font...")); + QPushButton *osdFont = new QPushButton(QStringLiteral("OSD Font...")); + QPushButton *setDefault = new QPushButton(QStringLiteral("Set default")); + miscGuiSize = combo({ + QStringLiteral("Select..."), + QStringLiteral("200%"), + QStringLiteral("190%"), + QStringLiteral("180%"), + QStringLiteral("170%"), + QStringLiteral("160%"), + QStringLiteral("150%"), + QStringLiteral("140%"), + QStringLiteral("130%"), + QStringLiteral("120%"), + QStringLiteral("110%"), + QStringLiteral("100%"), + QStringLiteral("90%"), + QStringLiteral("80%"), + QStringLiteral("70%"), + QStringLiteral("60%") + }); + QPushButton *resetLists = new QPushButton(QStringLiteral("Reset list customizations")); + miscGuiResize = new QCheckBox(QStringLiteral("Resizeable GUI")); + miscGuiFullscreen = new QCheckBox(QStringLiteral("Fullscreen GUI")); + miscGuiDarkMode = new QCheckBox(QStringLiteral("Dark mode")); + miscGuiDarkMode->setTristate(true); + disableUnavailable(osdFont, QStringLiteral("OSD font selection is not implemented yet.")); + disableUnavailable(resetLists, QStringLiteral("List customization storage is not implemented in the Unix Qt frontend yet.")); + miscGuiDarkMode->setToolTip(QStringLiteral("Matches Windows: unchecked is light, checked is dark, mixed follows the system appearance.")); + + QHBoxLayout *fontRow = new QHBoxLayout; + fontRow->setContentsMargins(0, 0, 0, 0); + fontRow->setSpacing(6); + fontRow->addWidget(guiFont); + fontRow->addWidget(osdFont); + + QHBoxLayout *scaleRow = new QHBoxLayout; + scaleRow->setContentsMargins(0, 0, 0, 0); + scaleRow->setSpacing(6); + scaleRow->addWidget(setDefault); + scaleRow->addWidget(miscGuiSize, 1); + + gui->addWidget(miscLanguage); + gui->addLayout(fontRow); + gui->addLayout(scaleRow); + gui->addWidget(resetLists); + gui->addWidget(miscGuiResize); + gui->addWidget(miscGuiFullscreen); + gui->addWidget(miscGuiDarkMode); + right->addWidget(groupBox(QStringLiteral("GUI"), gui)); + + QGridLayout *stateFiles = new QGridLayout; + stateFiles->setColumnStretch(0, 1); + stateFileName = pathCombo(); + stateFileClear = new QCheckBox; + QPushButton *loadState = new QPushButton(QStringLiteral("Load state...")); + QPushButton *saveState = new QPushButton(QStringLiteral("Save state...")); + QPushButton *browseState = smallButton(QStringLiteral("...")); + saveState->setEnabled(false); + saveState->setToolTip(QStringLiteral("Saving emulator state requires runtime save-state support in the Unix GUI.")); + stateFiles->addWidget(stateFileName, 0, 0); + stateFiles->addWidget(stateFileClear, 0, 1); + stateFiles->addWidget(browseState, 0, 2); + stateFiles->addWidget(loadState, 1, 0); + stateFiles->addWidget(saveState, 1, 1, 1, 2); + right->addWidget(groupBox(QStringLiteral("State Files"), stateFiles)); + + QGridLayout *keyboard = new QGridLayout; + for (int i = 0; i < 3; i++) { + keyboardLed[i] = combo(configChoiceDisplays(keyboardLedChoices, int(sizeof(keyboardLedChoices) / sizeof(keyboardLedChoices[0])))); + keyboard->addWidget(keyboardLed[i], 0, i); + } + keyboardLedUsb = new QCheckBox(QStringLiteral("USB mode")); + disableUnavailable(keyboardLedUsb, QStringLiteral("Native host keyboard LED output is not implemented on Unix yet.")); + keyboard->addWidget(keyboardLedUsb, 0, 3); + right->addWidget(groupBox(QStringLiteral("Keyboard LEDs"), keyboard)); + root->addLayout(right, 1); + + const auto selectStateFile = [this]() { + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select state file"), fileDialogInitialPath(stateFileName->currentText()), QStringLiteral("WinUAE state files (*.uss);;All files (*)")); + if (!selected.isEmpty()) { + setPathComboText(stateFileName, selected); + stateFileClear->setChecked(false); + status->setText(QStringLiteral("State file %1 selected for restore").arg(selected)); + } + }; + connect(loadState, &QPushButton::clicked, this, selectStateFile); + connect(browseState, &QPushButton::clicked, this, selectStateFile); + connect(stateFileName, &QComboBox::currentTextChanged, this, [this](const QString &text) { + stateFileClear->setChecked(text.trimmed().isEmpty()); + }); + connect(guiFont, &QPushButton::clicked, this, [this]() { chooseGuiFont(); }); + connect(setDefault, &QPushButton::clicked, this, [this]() { applyGuiScaleSelection(); }); + connect(miscGuiSize, &QComboBox::currentTextChanged, this, [this](const QString &text) { + if (text != QStringLiteral("Select...")) { + applyGuiScaleSelection(); + } + }); + connect(miscGuiResize, &QCheckBox::toggled, this, [this](bool) { applyGuiResizeMode(); }); + connect(miscGuiFullscreen, &QCheckBox::toggled, this, [this](bool checked) { applyGuiFullscreenMode(checked); }); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + connect(miscGuiDarkMode, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState) { applyGuiDarkModeSelection(); }); +#else + connect(miscGuiDarkMode, QOverload::of(&QCheckBox::stateChanged), this, [this](int) { applyGuiDarkModeSelection(); }); +#endif + return page; + } + + int guiScalePercent(const QString &text) const + { + QString value = text.trimmed(); + if (value.endsWith(QLatin1Char('%'))) { + value.chop(1); + } + bool ok = false; + const int percent = value.trimmed().toInt(&ok); + return ok && percent >= 60 && percent <= 200 ? percent : 100; + } + + void applyGuiResizeMode() + { + if (!miscGuiResize || !miscGuiFullscreen) { + return; + } + const bool resizable = miscGuiResize->isChecked() || miscGuiFullscreen->isChecked(); + setMinimumSize(820, 600); + setMaximumSize(resizable ? QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX) : size()); + if (!resizable) { + setFixedSize(size()); + } + } + + void applyGuiScaleSelection(bool exitFullscreen = true) + { + if (!miscGuiSize) { + return; + } + const int percent = guiScalePercent(miscGuiSize->currentText()); + const QSize baseSize(880, 640); + const QSize minSize(820, 600); + const QSize scaled(qMax(minSize.width(), baseSize.width() * percent / 100), + qMax(minSize.height(), baseSize.height() * percent / 100)); + if (miscGuiFullscreen && miscGuiFullscreen->isChecked()) { + if (!exitFullscreen) { + return; + } + showNormal(); + miscGuiFullscreen->setChecked(false); + } + setMinimumSize(820, 600); + setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + resize(scaled); + applyGuiResizeMode(); + } + + void applyGuiFullscreenMode(bool fullscreen) + { + if (fullscreen) { + setMinimumSize(820, 600); + setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + showFullScreen(); + } else { + showNormal(); + applyGuiResizeMode(); + } + } + + QString guiDarkModeConfigValue() const + { + if (!miscGuiDarkMode) { + return QStringLiteral("system"); + } + switch (miscGuiDarkMode->checkState()) { + case Qt::Checked: + return QStringLiteral("dark"); + case Qt::Unchecked: + return QStringLiteral("light"); + case Qt::PartiallyChecked: + return QStringLiteral("system"); + } + return QStringLiteral("system"); + } + + void setGuiDarkModeFromConfig(const QString &value) + { + if (!miscGuiDarkMode) { + return; + } + const QString normalized = value.trimmed().toLower(); + QSignalBlocker blocker(miscGuiDarkMode); + if (normalized == QStringLiteral("dark") || normalized == QStringLiteral("true") || normalized == QStringLiteral("1")) { + miscGuiDarkMode->setCheckState(Qt::Checked); + } else if (normalized == QStringLiteral("light") || normalized == QStringLiteral("false") || normalized == QStringLiteral("0")) { + miscGuiDarkMode->setCheckState(Qt::Unchecked); + } else { + miscGuiDarkMode->setCheckState(Qt::PartiallyChecked); + } + applyGuiDarkModeSelection(); + } + + void applyGuiDarkModeSelection() + { + if (!miscGuiDarkMode || !qApp) { + return; + } + bool dark = false; + if (miscGuiDarkMode->checkState() == Qt::Checked) { + dark = true; + } else if (miscGuiDarkMode->checkState() == Qt::PartiallyChecked) { + dark = systemPrefersDarkMode(); + } + applyApplicationColors(*qApp, dark); + } + + void applyGuiFont(const QFont &font, const QString &config) + { + miscGuiFontConfig = config; + if (QApplication *app = qobject_cast(QApplication::instance())) { + app->setFont(font); + } + setFont(font); + } + + void applyGuiFontConfig(const QString &config) + { + QFont font; + if (font.fromString(config)) { + applyGuiFont(font, config); + } + } + + void chooseGuiFont() + { + bool ok = false; + const QFont selected = QFontDialog::getFont(&ok, font(), this, QStringLiteral("Select GUI Font")); + if (ok) { + applyGuiFont(selected, selected.toString()); + } + } + + bool miscOptionChecked(const QString &key) const + { + QListWidgetItem *item = miscOptionItems.value(key, nullptr); + return item && item->checkState() == Qt::Checked; + } + + void setMiscOptionChecked(const QString &key, bool checked) + { + if (QListWidgetItem *item = miscOptionItems.value(key, nullptr)) { + if (!miscCheckChoiceEnabled(key)) { + item->setCheckState(Qt::Unchecked); + return; + } + item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + } + } + + QGroupBox *makeActivityGroup( + const QString &title, + QComboBox **priority, + QCheckBox **pause, + QCheckBox **noSound, + QCheckBox **noJoy, + QCheckBox **noKeyboard = nullptr) + { + QGridLayout *layout = new QGridLayout; + layout->setColumnStretch(0, 1); + *priority = combo(activityPriorityDisplays(), QStringLiteral("Normal")); + *pause = new QCheckBox(QStringLiteral("Pause emulation")); + *noSound = new QCheckBox(QStringLiteral("Disable sound")); + *noJoy = new QCheckBox(QStringLiteral("Disable game controllers")); + layout->addWidget(label(QStringLiteral("Run at priority:")), 0, 0); + layout->addWidget(*priority, 1, 0); + layout->addWidget(*pause, 3, 0); + layout->addWidget(*noSound, 4, 0); + layout->addWidget(*noJoy, 5, 0); + if (noKeyboard) { + *noKeyboard = new QCheckBox(QStringLiteral("Disable keyboard")); + layout->addWidget(label(QStringLiteral("Mouse uncaptured:")), 2, 0); + layout->addWidget(*noKeyboard, 6, 0); + } + return groupBox(title, layout); + } + + QWidget *makeExtensionsPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(6); + + QHBoxLayout *activity = new QHBoxLayout; + activity->setSpacing(6); + activity->addWidget(makeActivityGroup( + QStringLiteral("When Active"), + &extensionActivePriority, + &extensionActivePause, + &extensionActiveNoSound, + &extensionActiveNoJoy, + &extensionActiveNoKeyboard)); + activity->addWidget(makeActivityGroup( + QStringLiteral("When Inactive"), + &extensionInactivePriority, + &extensionInactivePause, + &extensionInactiveNoSound, + &extensionInactiveNoJoy)); + activity->addWidget(makeActivityGroup( + QStringLiteral("When Minimized"), + &extensionMinimizedPriority, + &extensionMinimizedPause, + &extensionMinimizedNoSound, + &extensionMinimizedNoJoy)); + root->addLayout(activity); + + QVBoxLayout *associations = new QVBoxLayout; + extensionAssociationList = new QTreeWidget; + extensionAssociationList->setRootIsDecorated(false); + extensionAssociationList->setAlternatingRowColors(true); + extensionAssociationList->setSelectionMode(QAbstractItemView::SingleSelection); + extensionAssociationList->setEditTriggers(QAbstractItemView::NoEditTriggers); + extensionAssociationList->setHeaderLabels({ QStringLiteral("Extension"), QStringLiteral("Associated") }); + for (int i = 0; i < int(sizeof(associationChoices) / sizeof(associationChoices[0])); i++) { + QTreeWidgetItem *item = new QTreeWidgetItem(extensionAssociationList); + const QString extension = QString::fromLatin1(associationChoices[i].extension); + item->setText(0, extension); + if (extension == QStringLiteral(".uae")) { + item->setText(1, QStringLiteral("Declared by app bundle")); + item->setToolTip(1, QStringLiteral("The macOS app bundle declares .uae files and the Qt frontend handles document-open events.")); + } else { + item->setText(1, QStringLiteral("Deferred")); + item->setToolTip(1, QStringLiteral("Windows starts these files through extension-specific command lines; Unix needs matching startup mapping before declaring them.")); + } + } + extensionAssociationList->resizeColumnToContents(0); + associations->addWidget(extensionAssociationList, 1); + + QHBoxLayout *associationActions = new QHBoxLayout; + associationActions->addStretch(); + QPushButton *associateAll = new QPushButton(QStringLiteral("Associate all")); + QPushButton *deassociateAll = new QPushButton(QStringLiteral("Deassociate all")); + disableUnavailable(associateAll, QStringLiteral("Unix file-association install/uninstall is not implemented; macOS uses bundle document declarations.")); + disableUnavailable(deassociateAll, QStringLiteral("Unix file-association install/uninstall is not implemented; macOS uses bundle document declarations.")); + associationActions->addWidget(associateAll); + associationActions->addWidget(deassociateAll); + associationActions->addStretch(); + associations->addLayout(associationActions); + root->addWidget(groupBox(QStringLiteral("File Extension Associations"), associations), 1); + + const QList boxes = { + extensionActivePause, + extensionActiveNoSound, + extensionActiveNoJoy, + extensionActiveNoKeyboard, + extensionInactivePause, + extensionInactiveNoSound, + extensionInactiveNoJoy, + extensionMinimizedPause, + extensionMinimizedNoSound, + extensionMinimizedNoJoy + }; + for (QCheckBox *box : boxes) { + connect(box, &QCheckBox::toggled, this, [this]() { updateExtensionActivityState(); }); + } + return page; + } + + void updateExtensionActivityState() + { + if (!extensionActivePause || !extensionInactivePause || !extensionMinimizedPause) { + return; + } + + bool paused = extensionActivePause->isChecked(); + bool noSound = extensionActiveNoSound->isChecked(); + bool noJoy = extensionActiveNoJoy->isChecked(); + + if (paused) { + setCheckBoxIfChanged(extensionActiveNoSound, true); + setCheckBoxIfChanged(extensionActiveNoJoy, true); + setCheckBoxIfChanged(extensionActiveNoKeyboard, true); + noSound = true; + noJoy = true; + } + extensionActiveNoSound->setEnabled(!paused); + extensionActiveNoJoy->setEnabled(!paused); + extensionActiveNoKeyboard->setEnabled(!paused); + + if (paused) { + setCheckBoxIfChanged(extensionInactivePause, true); + } + if (paused || noSound) { + setCheckBoxIfChanged(extensionInactiveNoSound, true); + } + if (paused || noJoy) { + setCheckBoxIfChanged(extensionInactiveNoJoy, true); + } + extensionInactivePause->setEnabled(!paused); + extensionInactiveNoSound->setEnabled(!paused && !noSound); + extensionInactiveNoJoy->setEnabled(!paused && !noJoy); + + paused = paused || extensionInactivePause->isChecked(); + noSound = noSound || extensionInactiveNoSound->isChecked(); + noJoy = noJoy || extensionInactiveNoJoy->isChecked(); + + if (paused) { + setCheckBoxIfChanged(extensionMinimizedPause, true); + } + if (paused || noSound) { + setCheckBoxIfChanged(extensionMinimizedNoSound, true); + } + if (paused || noJoy) { + setCheckBoxIfChanged(extensionMinimizedNoJoy, true); + } + extensionMinimizedPause->setEnabled(!paused); + extensionMinimizedNoSound->setEnabled(!paused && !noSound); + extensionMinimizedNoJoy->setEnabled(!paused && !noJoy); + } + + void setExtensionPriorityValue(QComboBox *box, int value) + { + if (box) { + box->setCurrentText(activityPriorityText(value)); + } + } + + int extensionPriorityValue(QComboBox *box) const + { + return box ? activityPriorityValue(box->currentText()) : 0; + } + + QWidget *makeFrontendPage() + { + QWidget *page = makePage(); + QHBoxLayout *root = new QHBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->setSpacing(8); + + frontendConfigList = new QTreeWidget; + frontendConfigList->setRootIsDecorated(false); + frontendConfigList->setAlternatingRowColors(true); + frontendConfigList->setSelectionMode(QAbstractItemView::SingleSelection); + frontendConfigList->setEditTriggers(QAbstractItemView::NoEditTriggers); + frontendConfigList->setHeaderLabels({ QStringLiteral("Configuration"), QStringLiteral("Description") }); + root->addWidget(frontendConfigList, 3); + + QVBoxLayout *right = new QVBoxLayout; + QVBoxLayout *screenshotLayout = new QVBoxLayout; + frontendScreenshot = new QLabel; + frontendScreenshot->setFrameShape(QFrame::Box); + frontendScreenshot->setAlignment(Qt::AlignCenter); + frontendScreenshot->setMinimumSize(160, 128); + screenshotLayout->addWidget(frontendScreenshot, 1); + right->addWidget(groupBox(QString(), screenshotLayout), 3); + + QVBoxLayout *infoLayout = new QVBoxLayout; + frontendInfo = new QLabel; + frontendInfo->setAlignment(Qt::AlignTop | Qt::AlignLeft); + frontendInfo->setWordWrap(true); + frontendInfo->setTextInteractionFlags(Qt::TextSelectableByMouse); + infoLayout->addWidget(frontendInfo, 1); + right->addWidget(groupBox(QString(), infoLayout), 2); + root->addLayout(right, 2); + + connect(frontendConfigList, &QTreeWidget::itemSelectionChanged, this, [this]() { updateFrontendSelection(); }); + connect(frontendConfigList, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem *item, int) { + const QString path = item ? item->data(0, Qt::UserRole).toString() : QString(); + if (!path.isEmpty()) { + loadConfig(path); + } + }); + return page; + } + + QString frontendMediaArtDirectory(const QString &mediaPath) const + { + if (mediaPath.trimmed().isEmpty()) { + return QString(); + } + QDir dir(QFileInfo(mediaPath).absolutePath()); + if (QFileInfo::exists(dir.filePath(QStringLiteral("___Title.png")))) { + return dir.absolutePath(); + } + return QString(); + } + + QString frontendArtDirectory(const WinUaeQtConfig &config) const + { + const QString floppy = config.value(QStringLiteral("floppy0")); + QString path = frontendMediaArtDirectory(floppy); + if (!path.isEmpty()) { + return path; + } + + const QString cd = config.value(QStringLiteral("cdimage0")); + if (!cd.isEmpty() + && cd.compare(QStringLiteral("autodetect"), Qt::CaseInsensitive) != 0 + && cd.compare(QStringLiteral("empty"), Qt::CaseInsensitive) != 0) { + const QStringList fields = winUaeQtConfigFieldList(cd); + path = frontendMediaArtDirectory(fields.value(0)); + if (!path.isEmpty()) { + return path; + } + } + return QString(); + } + + QString frontendArtImage(const QString &artDirectory) const + { + if (artDirectory.isEmpty()) { + return QString(); + } + const QStringList names = { + QStringLiteral("___Title.png"), + QStringLiteral("___SShot.png"), + QStringLiteral("___Boxart.png") + }; + const QDir dir(artDirectory); + for (const QString &name : names) { + const QString path = dir.filePath(name); + if (QFileInfo::exists(path)) { + return path; + } + } + return QString(); + } + + void refreshFrontendPage() + { + if (!frontendConfigList) { + return; + } + const QString selectedPath = frontendConfigList->currentItem() + ? frontendConfigList->currentItem()->data(0, Qt::UserRole).toString() + : (configPath ? configPath->text() : QString()); + QSignalBlocker blocker(frontendConfigList); + frontendConfigList->clear(); + + QDir dir(configurationDirectory()); + const QFileInfoList files = dir.entryInfoList({ QStringLiteral("*.uae") }, QDir::Files, QDir::Name | QDir::IgnoreCase); + QTreeWidgetItem *selected = nullptr; + for (const QFileInfo &info : files) { + WinUaeQtConfig config; + config.load(info.absoluteFilePath()); + const QString description = config.value(QStringLiteral("config_description")); + const QString artDirectory = frontendArtDirectory(config); + QTreeWidgetItem *item = new QTreeWidgetItem(frontendConfigList); + item->setText(0, info.completeBaseName()); + item->setText(1, description); + item->setData(0, Qt::UserRole, info.absoluteFilePath()); + item->setData(0, Qt::UserRole + 1, description); + item->setData(0, Qt::UserRole + 2, artDirectory); + if (info.absoluteFilePath() == selectedPath) { + selected = item; + } + } + for (int i = 0; i < frontendConfigList->columnCount(); i++) { + frontendConfigList->resizeColumnToContents(i); + } + if (selected) { + frontendConfigList->setCurrentItem(selected); + } else if (frontendConfigList->topLevelItemCount() > 0) { + frontendConfigList->setCurrentItem(frontendConfigList->topLevelItem(0)); + } + updateFrontendSelection(); + } + + void updateFrontendSelection() + { + if (!frontendConfigList || !frontendScreenshot || !frontendInfo) { + return; + } + QTreeWidgetItem *item = frontendConfigList->currentItem(); + if (!item) { + frontendScreenshot->clear(); + frontendInfo->clear(); + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); + const QString description = item->data(0, Qt::UserRole + 1).toString(); + const QString artImage = frontendArtImage(item->data(0, Qt::UserRole + 2).toString()); + if (!artImage.isEmpty()) { + QPixmap image(artImage); + frontendScreenshot->setPixmap(image.scaled(frontendScreenshot->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } else { + frontendScreenshot->clear(); + } + + QStringList lines; + lines.append(QFileInfo(path).completeBaseName()); + if (!description.isEmpty()) { + lines.append(description); + } + lines.append(path); + frontendInfo->setText(lines.join(QLatin1Char('\n'))); + } + + QWidget *makeAboutPage() + { + QWidget *page = makePage(); + QVBoxLayout *root = new QVBoxLayout(page); + root->setContentsMargins(4, 4, 4, 4); + root->addStretch(); + QLabel *title = new QLabel(QStringLiteral("WinUAE")); + QFont font = title->font(); + font.setPointSize(22); + font.setBold(true); + title->setFont(font); + title->setAlignment(Qt::AlignCenter); + root->addWidget(title); + QLabel *version = new QLabel(versionString()); + QFont versionFont = version->font(); + versionFont.setPointSize(13); + version->setFont(versionFont); + version->setAlignment(Qt::AlignCenter); + root->addWidget(version); + QLabel *subtitle = new QLabel(QStringLiteral("Unix Qt configuration frontend")); + subtitle->setAlignment(Qt::AlignCenter); + root->addWidget(subtitle); + QPushButton *contributors = new QPushButton(QStringLiteral("Contributors")); + contributors->setFixedWidth(120); + connect(contributors, &QPushButton::clicked, this, [this]() { showContributors(); }); + QHBoxLayout *buttonRow = new QHBoxLayout; + buttonRow->addStretch(); + buttonRow->addWidget(contributors); + buttonRow->addStretch(); + root->addLayout(buttonRow); + QGridLayout *links = new QGridLayout; + links->setHorizontalSpacing(24); + links->setVerticalSpacing(16); + for (int i = 0; i < int(sizeof(aboutLinks) / sizeof(aboutLinks[0])); i++) { + QLabel *link = new QLabel(QStringLiteral("%2") + .arg(QString::fromLatin1(aboutLinks[i].url), QString::fromLatin1(aboutLinks[i].display))); + link->setAlignment(Qt::AlignCenter); + link->setOpenExternalLinks(true); + link->setTextInteractionFlags(Qt::TextBrowserInteraction); + links->addWidget(link, i / 3, i % 3); + } + root->addSpacing(20); + root->addLayout(links); + root->addStretch(); + return page; + } + + void showContributors() + { + QDialog dialog(this); + dialog.setWindowTitle(QStringLiteral("UAE Authors and Contributors...")); + dialog.resize(620, 420); + + QVBoxLayout *root = new QVBoxLayout(&dialog); + QListWidget *list = new QListWidget; + list->addItems(contributorLines()); + root->addWidget(list, 1); + + QPushButton *ok = new QPushButton(QStringLiteral("Ok")); + ok->setDefault(true); + connect(ok, &QPushButton::clicked, &dialog, &QDialog::accept); + + QHBoxLayout *buttons = new QHBoxLayout; + buttons->addStretch(); + buttons->addWidget(ok); + buttons->addStretch(); + root->addLayout(buttons); + + dialog.exec(); + } + + void addPathRow(QGridLayout *layout, int row, const QString &caption, QComboBox *field, const QString &dialogTitle, const QString &filter) + { + QPushButton *browse = smallButton(QStringLiteral("...")); + layout->addWidget(new QLabel(caption), row * 2, 0, 1, 2); + layout->addWidget(field, row * 2 + 1, 0, 1, 2); + layout->addWidget(browse, row * 2 + 1, 2); + connect(browse, &QPushButton::clicked, this, [this, field, dialogTitle, filter]() { + addBrowse(field, this, dialogTitle, filter); + }); + } + + void addLineBrowseRow(QGridLayout *layout, int row, const QString &caption, QLineEdit *field, bool directory = false) + { + QPushButton *browse = smallButton(QStringLiteral("...")); + layout->addWidget(label(caption), row, 0); + layout->addWidget(field, row, 1); + layout->addWidget(browse, row, 2); + connect(browse, &QPushButton::clicked, this, [this, field, directory, caption]() { + const QString initialPath = directory + ? fileDialogInitialDirectory(field->text()) + : fileDialogInitialPath(field->text()); + QString path; + if (directory) { + path = QFileDialog::getExistingDirectory(this, caption, initialPath); + } else { + path = QFileDialog::getOpenFileName(this, caption, initialPath, QStringLiteral("All files (*)")); + } + if (!path.isEmpty()) { + field->setText(path); + } + }); + } + + void ensureCdSlots() + { + if (cdSlots.size() == MaxCdSlots) { + return; + } + cdSlots = QVector(MaxCdSlots); + for (WinUaeQtCdSlot &slot : cdSlots) { + slot.type = QStringLiteral("Image file"); + } + } + + void clearCdSlots() + { + ensureCdSlots(); + for (WinUaeQtCdSlot &slot : cdSlots) { + slot = WinUaeQtCdSlot(); + slot.type = QStringLiteral("Image file"); + } + currentCdSlot = 0; + loadCdSlotToUi(currentCdSlot); + } + + void setCurrentCdSlotInUse(bool inUse) + { + ensureCdSlots(); + if (currentCdSlot >= 0 && currentCdSlot < cdSlots.size()) { + cdSlots[currentCdSlot].inUse = inUse; + } + } + + void storeCurrentCdSlotFromUi() + { + ensureCdSlots(); + if (!cdSlotPath || !cdSlotType || currentCdSlot < 0 || currentCdSlot >= cdSlots.size()) { + return; + } + WinUaeQtCdSlot &slot = cdSlots[currentCdSlot]; + slot.path = cdSlotPath->currentText().trimmed(); + slot.type = cdSlotType->currentText(); + if (!slot.path.isEmpty()) { + slot.inUse = true; + } else if (slot.type == QStringLiteral("Image file")) { + slot.inUse = false; + } + } + + WinUaeQtCdSlot cdSlotState(int index) const + { + WinUaeQtCdSlot slot = index >= 0 && index < cdSlots.size() ? cdSlots.value(index) : WinUaeQtCdSlot(); + if (index == currentCdSlot && cdSlotPath && cdSlotType) { + slot.path = cdSlotPath->currentText().trimmed(); + slot.type = cdSlotType->currentText(); + if (!slot.path.isEmpty()) { + slot.inUse = true; + } else if (slot.type == QStringLiteral("Image file")) { + slot.inUse = false; + } + } + return slot; + } + + void loadCdSlotToUi(int index) + { + ensureCdSlots(); + if (!cdSlotNumber || !cdSlotPath || !cdSlotType || index < 0 || index >= cdSlots.size()) { + return; + } + cdSlotUpdating = true; + QSignalBlocker numberBlocker(cdSlotNumber); + QSignalBlocker pathBlocker(cdSlotPath); + QSignalBlocker typeBlocker(cdSlotType); + cdSlotNumber->setCurrentIndex(index); + cdSlotPath->setCurrentText(cdSlots[index].path); + cdSlotType->setCurrentText(cdSlots[index].type.isEmpty() ? QStringLiteral("Image file") : cdSlots[index].type); + cdSlotUpdating = false; + } + + void ensureCustomRomBoards() + { + if (customRomBoards.size() == MaxRomBoards) { + return; + } + customRomBoards.clear(); + customRomBoards.resize(MaxRomBoards); + currentCustomRomBoard = 0; + } + + void clearCustomRomBoards() + { + ensureCustomRomBoards(); + for (WinUaeQtRomBoard &board : customRomBoards) { + board = WinUaeQtRomBoard(); + } + currentCustomRomBoard = 0; + if (customRomSelect) { + QSignalBlocker blocker(customRomSelect); + customRomSelect->setCurrentIndex(0); + } + loadCurrentCustomRomBoard(); + } + + void storeCurrentCustomRomBoard() + { + if (customRomUpdating || !customRomStart || !customRomEnd || !customRomFile) { + return; + } + ensureCustomRomBoards(); + if (currentCustomRomBoard < 0 || currentCustomRomBoard >= customRomBoards.size()) { + return; + } + WinUaeQtRomBoard &board = customRomBoards[currentCustomRomBoard]; + board.start = normalizedRomAddress(customRomStart->text(), false); + board.end = normalizedRomAddress(customRomEnd->text(), true); + board.path = customRomFile->text().trimmed(); + } + + void loadCurrentCustomRomBoard() + { + if (!customRomStart || !customRomEnd || !customRomFile) { + return; + } + ensureCustomRomBoards(); + currentCustomRomBoard = qBound(0, currentCustomRomBoard, MaxRomBoards - 1); + customRomUpdating = true; + QSignalBlocker startBlocker(customRomStart); + QSignalBlocker endBlocker(customRomEnd); + QSignalBlocker fileBlocker(customRomFile); + if (customRomSelect) { + QSignalBlocker selectBlocker(customRomSelect); + customRomSelect->setCurrentIndex(currentCustomRomBoard); + } + const WinUaeQtRomBoard &board = customRomBoards[currentCustomRomBoard]; + customRomStart->setText(board.start); + customRomEnd->setText(board.end); + customRomFile->setText(board.path); + customRomUpdating = false; + } + + void applyCustomRomBoard(int index, const QString &value) + { + if (index < 0 || index >= MaxRomBoards) { + return; + } + ensureCustomRomBoards(); + customRomBoards[index] = romBoardFromConfigValue(value); + if (index == currentCustomRomBoard) { + loadCurrentCustomRomBoard(); + } + } + + void updateFloppySpeedLabel() + { + if (!floppySpeed || !floppySpeedLabel) { + return; + } + floppySpeedLabel->setText(floppySpeedText(floppySpeedConfigValue(floppySpeed->value()))); + } + + void updateCpuSpeedLabel() + { + if (!cpuSpeed || !cpuSpeedLabel) { + return; + } + const int value = cpuSpeed->value() * 10; + cpuSpeedLabel->setText(QStringLiteral("%1%2%").arg(value >= 0 ? QStringLiteral("+") : QString()).arg(value)); + } + + void updateJitCacheLabel() + { + if (!jitCache || !jitCacheLabel) { + return; + } + jitCacheLabel->setText(jitCacheText(jit->isChecked() ? jitCacheSizeFromPosition(jitCache->value()) : 0)); + } + + void updateCpuControlState() + { + const int cpu = selectedCpuModel(); + const int fpu = fpuModelConfigValue(cpu); + const bool z3Rtg = rtgNeeds32BitAddressSpace(); + bool jitEnabled = jit && jit->isChecked(); + if (cpu24Bit) { + cpu24Bit->setEnabled(cpu <= 68030 && !z3Rtg); + cpu24Bit->setToolTip(z3Rtg ? QStringLiteral("Zorro III RTG requires a 32-bit address space.") : QString()); + if (z3Rtg && cpu24Bit->isChecked()) { + cpu24Bit->setChecked(false); + } + } + if (jit) { + jit->setEnabled(unixJitBackendAvailable() && cpu >= 68020 && !cpu24Bit->isChecked()); + if (!jit->isEnabled() && jit->isChecked()) { + jit->setChecked(false); + } + jitEnabled = jit->isChecked(); + } + const bool cycleExactEnabled = !jitEnabled; + if (chipsetCycleExact) { + chipsetCycleExact->setEnabled(cycleExactEnabled); + chipsetCycleExact->setToolTip(jitEnabled ? QStringLiteral("Cycle-exact emulation is not compatible with JIT.") : QString()); + } + if (chipsetCycleExactMemory) { + chipsetCycleExactMemory->setEnabled(cycleExactEnabled); + chipsetCycleExactMemory->setToolTip(jitEnabled ? QStringLiteral("Cycle-exact emulation is not compatible with JIT.") : QString()); + } + if (cpuDataCache) { + cpuDataCache->setEnabled(cpu >= 68030 && moreCompatible->isChecked() && !jitEnabled); + } + if (cpuUnimplemented) { + cpuUnimplemented->setEnabled(cpu == 68060 && !jitEnabled); + } + if (mmuButtons) { + const bool mmuEnabled = cpu >= 68030 && !jitEnabled; + for (QAbstractButton *button : mmuButtons->buttons()) { + button->setEnabled(button == mmuButtons->button(0) || mmuEnabled); + } + if (!mmuEnabled && mmuButtons->checkedId() != 0) { + mmuButtons->button(0)->setChecked(true); + } + } + const bool hasFpu = fpu != 0; + if (fpuStrict) { + fpuStrict->setEnabled(hasFpu); + } + if (fpuUnimplemented) { + fpuUnimplemented->setEnabled(hasFpu && !jitEnabled); + } + if (fpuMode) { + fpuMode->setEnabled(hasFpu); + } + const bool jitOptions = jitEnabled && cpu >= 68020 && !cpu24Bit->isChecked(); + for (QWidget *widget : { static_cast(jitCache), static_cast(jitCacheLabel), static_cast(jitConstJump), static_cast(jitHardFlush), static_cast(jitNoFlags), static_cast(jitCatchFault) }) { + if (widget) { + widget->setEnabled(jitOptions); + } + } + if (jitFpu) { + jitFpu->setEnabled(jitOptions && hasFpu); + } + if (jitTrust) { + for (QAbstractButton *button : jitTrust->buttons()) { + button->setEnabled(jitOptions); + } + } + updateJitCacheLabel(); + } + + bool rtgNeeds32BitAddressSpace() const + { + const int configType = rtgConfigType(rtgType ? rtgType->currentText() : QString()); + return rtgMem && rtgType + && rtgMem->currentText() != QStringLiteral("None") + && (configType == 3 || configType == 7); + } + + int rtgConfigType(const QString &type) const + { + for (const WinUaeQtRtgBoardCatalogItem &board : boardCatalog.rtgBoards) { + if (type.compare(board.configValue, Qt::CaseInsensitive) == 0) { + return board.configType; + } + } + if (type.compare(QStringLiteral("ZorroII"), Qt::CaseInsensitive) == 0 + || type.endsWith(QStringLiteral("_Z2"), Qt::CaseInsensitive)) { + return 2; + } + if (type.compare(QStringLiteral("ZorroIII"), Qt::CaseInsensitive) == 0 + || type.endsWith(QStringLiteral("_Z3"), Qt::CaseInsensitive)) { + return 3; + } + if (type.endsWith(QStringLiteral("_PCI"), Qt::CaseInsensitive)) { + return 7; + } + return 0; + } + + QString rtgBusName(const QString &type) const + { + switch (rtgConfigType(type)) { + case 2: + return QStringLiteral("Z2"); + case 3: + return QStringLiteral("Z3"); + case 7: + return QStringLiteral("PCI"); + default: + break; + } + return QStringLiteral("-"); + } + + WinUaeQtFilterState defaultFilterState(int target) const + { + WinUaeQtFilterState state; + if (target == 1) { + state.autoscale = QStringLiteral("resize"); + } else if (target == 2) { + state.enable = false; + } + return state; + } + + void resetFilterStates() + { + for (int i = 0; i < 3; i++) { + filterStates[i] = defaultFilterState(i); + } + currentFilterTarget = 0; + if (filterTarget) { + filterTarget->setCurrentIndex(0); + loadFilterStateToUi(0); + } + if (filterNtscPixels) { + filterNtscPixels->setChecked(true); + } + } + + void updateFilterAutoscaleItems(int target) + { + if (!filterAutoscale) { + return; + } + const QString current = filterAutoscale->currentText(); + filterAutoscale->clear(); + if (target == 1) { + filterAutoscale->addItems(configChoiceDisplays(filterAutoscaleRtgChoices, int(sizeof(filterAutoscaleRtgChoices) / sizeof(filterAutoscaleRtgChoices[0])))); + } else { + filterAutoscale->addItems(configChoiceDisplays(filterAutoscaleChoices, int(sizeof(filterAutoscaleChoices) / sizeof(filterAutoscaleChoices[0])))); + } + if (!current.isEmpty()) { + setComboTextIfChanged(filterAutoscale, current); + } + } + + WinUaeQtFilterState filterStateFromUi(int target) const + { + WinUaeQtFilterState state = filterStates[qBound(0, target, 2)]; + if (!filterTarget || target != currentFilterTarget) { + return state; + } + state.enable = filterEnable->isChecked(); + state.filter = configChoiceValue(filterModeChoices, int(sizeof(filterModeChoices) / sizeof(filterModeChoices[0])), filterMode->currentText()); + state.modeH = configChoiceValue(filterModeHChoices, int(sizeof(filterModeHChoices) / sizeof(filterModeHChoices[0])), filterModeH->currentText()); + state.modeV = configChoiceValue(filterModeVChoices, int(sizeof(filterModeVChoices) / sizeof(filterModeVChoices[0])), filterModeV->currentText()); + state.autoscale = filterAutoscaleConfigValue(filterAutoscale->currentText(), target); + state.integerLimit = configChoiceValue(filterIntegerLimitChoices, int(sizeof(filterIntegerLimitChoices) / sizeof(filterIntegerLimitChoices[0])), filterIntegerLimit->currentText()); + state.keepAspect = filterKeepAspect->isChecked() + ? configChoiceValue(filterAspectChoices, int(sizeof(filterAspectChoices) / sizeof(filterAspectChoices[0])), filterAspect->currentText()) + : QStringLiteral("none"); + state.keepAutoscaleAspect = filterKeepAutoscaleAspect->isChecked(); + state.bilinear = filterBilinear->isChecked(); + state.horizZoom = filterHorizZoom->value(); + state.vertZoom = filterVertZoom->value(); + state.horizZoomMult = filterHorizZoomMult->value(); + state.vertZoomMult = filterVertZoomMult->value(); + state.horizOffset = filterHorizOffset->value(); + state.vertOffset = filterVertOffset->value(); + state.scanlines = filterScanlines->value(); + state.scanlineLevel = filterScanlineLevel->value(); + state.scanlineOffset = filterScanlineOffset->value(); + state.luminance = filterLuminance->value(); + state.contrast = filterContrast->value(); + state.saturation = filterSaturation->value(); + state.gamma = filterGamma->value(); + state.blur = filterBlur->value(); + state.noise = filterNoise->value(); + return state; + } + + void storeFilterUiToState() + { + if (!filterTarget || filterUpdating) { + return; + } + filterStates[currentFilterTarget] = filterStateFromUi(currentFilterTarget); + } + + void updateFilterControlState() + { + if (!filterTarget) { + return; + } + const int target = filterTarget->currentIndex(); + filterEnable->setEnabled(target == 2); + filterAspect->setEnabled(filterKeepAspect->isChecked()); + } + + void loadFilterStateToUi(int target) + { + if (!filterTarget) { + return; + } + filterUpdating = true; + const WinUaeQtFilterState state = filterStates[qBound(0, target, 2)]; + updateFilterAutoscaleItems(target); + filterEnable->setChecked(state.enable); + filterMode->setCurrentText(configChoiceDisplay(filterModeChoices, int(sizeof(filterModeChoices) / sizeof(filterModeChoices[0])), state.filter)); + filterModeH->setCurrentText(configChoiceDisplay(filterModeHChoices, int(sizeof(filterModeHChoices) / sizeof(filterModeHChoices[0])), state.modeH)); + filterModeV->setCurrentText(configChoiceDisplay(filterModeVChoices, int(sizeof(filterModeVChoices) / sizeof(filterModeVChoices[0])), state.modeV)); + filterAutoscale->setCurrentText(filterAutoscaleDisplay(state.autoscale, target)); + filterIntegerLimit->setCurrentText(configChoiceDisplay(filterIntegerLimitChoices, int(sizeof(filterIntegerLimitChoices) / sizeof(filterIntegerLimitChoices[0])), state.integerLimit)); + filterKeepAspect->setChecked(state.keepAspect != QStringLiteral("none")); + filterAspect->setCurrentText(configChoiceDisplay(filterAspectChoices, int(sizeof(filterAspectChoices) / sizeof(filterAspectChoices[0])), state.keepAspect)); + filterKeepAutoscaleAspect->setChecked(state.keepAutoscaleAspect); + filterBilinear->setChecked(state.bilinear); + filterHorizZoom->setValue(state.horizZoom); + filterVertZoom->setValue(state.vertZoom); + filterHorizZoomMult->setValue(state.horizZoomMult); + filterVertZoomMult->setValue(state.vertZoomMult); + filterHorizOffset->setValue(state.horizOffset); + filterVertOffset->setValue(state.vertOffset); + filterScanlines->setValue(state.scanlines); + filterScanlineLevel->setValue(state.scanlineLevel); + filterScanlineOffset->setValue(state.scanlineOffset); + filterLuminance->setValue(state.luminance); + filterContrast->setValue(state.contrast); + filterSaturation->setValue(state.saturation); + filterGamma->setValue(state.gamma); + filterBlur->setValue(state.blur); + filterNoise->setValue(state.noise); + filterUpdating = false; + updateFilterControlState(); + } + + QMap filterPresetSettings(const WinUaeQtFilterState &state, int target) const + { + QMap settings; + settings.insert(QStringLiteral("gfx_filter_mode"), state.modeH); + settings.insert(QStringLiteral("gfx_filter_mode2"), state.modeV); + settings.insert(QStringLiteral("gfx_filter_horiz_zoomf"), QString::number(state.horizZoom, 'f', 1)); + settings.insert(QStringLiteral("gfx_filter_vert_zoomf"), QString::number(state.vertZoom, 'f', 1)); + settings.insert(QStringLiteral("gfx_filter_horiz_zoom_multf"), QString::number(state.horizZoomMult, 'f', 2)); + settings.insert(QStringLiteral("gfx_filter_vert_zoom_multf"), QString::number(state.vertZoomMult, 'f', 2)); + settings.insert(QStringLiteral("gfx_filter_horiz_offsetf"), QString::number(state.horizOffset, 'f', 1)); + settings.insert(QStringLiteral("gfx_filter_vert_offsetf"), QString::number(state.vertOffset, 'f', 1)); + settings.insert(QStringLiteral("gfx_filter_keep_aspect"), state.keepAspect); + settings.insert(QStringLiteral("gfx_filter_keep_autoscale_aspect"), state.keepAutoscaleAspect ? QStringLiteral("1") : QStringLiteral("0")); + settings.insert(QStringLiteral("gfx_filter_autoscale"), target == 1 && !isRtgAutoscaleValue(state.autoscale) ? QStringLiteral("resize") : state.autoscale); + settings.insert(QStringLiteral("gfx_filter_autoscale_limit"), state.integerLimit); + settings.insert(QStringLiteral("gfx_filter_luminance"), QString::number(state.luminance)); + settings.insert(QStringLiteral("gfx_filter_contrast"), QString::number(state.contrast)); + settings.insert(QStringLiteral("gfx_filter_saturation"), QString::number(state.saturation)); + settings.insert(QStringLiteral("gfx_filter_gamma"), QString::number(state.gamma)); + settings.insert(QStringLiteral("gfx_filter_blur"), QString::number(state.blur)); + settings.insert(QStringLiteral("gfx_filter_noise"), QString::number(state.noise)); + settings.insert(QStringLiteral("gfx_filter_bilinear"), state.bilinear ? QStringLiteral("1") : QStringLiteral("0")); + settings.insert(QStringLiteral("gfx_filter_scanlines"), QString::number(state.scanlines)); + settings.insert(QStringLiteral("gfx_filter_scanlinelevel"), QString::number(state.scanlineLevel)); + settings.insert(QStringLiteral("gfx_filter_scanlineoffset"), QString::number(state.scanlineOffset)); + settings.insert(QStringLiteral("gfx_filter_enable"), state.enable ? QStringLiteral("1") : QStringLiteral("0")); + return settings; + } + + QString filterPresetDirectoryPath() const + { + QString base = dataPath ? dataPath->text().trimmed() : QString(); + if (base.isEmpty()) { + base = unixDefaultDataPath(); + } + return QDir(expandedPathText(base)).filePath(QStringLiteral("FilterPresets")); + } + + QString normalizedFilterPresetName(QString name) const + { + name = name.trimmed(); + QString result; + for (const QChar ch : name) { + result.append(ch.isLetterOrNumber() || ch == QLatin1Char(' ') || ch == QLatin1Char('_') || ch == QLatin1Char('-') + ? ch + : QLatin1Char('_')); + } + result = result.simplified(); + return result.isEmpty() ? QStringLiteral("Preset") : result.left(80); + } + + QString filterPresetPath(const QString &name) const + { + return QDir(filterPresetDirectoryPath()).filePath(normalizedFilterPresetName(name) + QStringLiteral(".filter")); + } + + void refreshFilterPresetList(const QString &preferred = QString()) + { + if (!filterPresetName) { + return; + } + const QString current = preferred.isEmpty() ? filterPresetName->currentText().trimmed() : preferred; + QSignalBlocker blocker(filterPresetName); + filterPresetName->clear(); + filterPresetName->addItem(QString()); + QDir dir(filterPresetDirectoryPath()); + const QStringList files = dir.entryList({ QStringLiteral("*.filter") }, QDir::Files, QDir::Name | QDir::IgnoreCase); + for (const QString &file : files) { + filterPresetName->addItem(QFileInfo(file).completeBaseName()); + } + filterPresetName->setCurrentText(current); + } + + bool writeFilterPresetFile(const QString &path, const QMap &settings) + { + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not write %1:\n%2").arg(path, file.errorString())); + return false; + } + QTextStream out(&file); + out << "# WinUAE Qt filter preset\n"; + for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) { + out << it.key() << "=" << it.value() << "\n"; + } + return true; + } + + void saveFilterPreset() + { + if (!filterPresetName) { + return; + } + QString name = normalizedFilterPresetName(filterPresetName->currentText()); + if (name.isEmpty()) { + name = QStringLiteral("Preset"); + } + QDir dir(filterPresetDirectoryPath()); + if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not create %1.").arg(dir.absolutePath())); + return; + } + storeFilterUiToState(); + const QString path = filterPresetPath(name); + if (writeFilterPresetFile(path, filterPresetSettings(filterStates[currentFilterTarget], currentFilterTarget))) { + refreshFilterPresetList(name); + status->setText(QStringLiteral("Filter preset saved: %1").arg(name)); + } + } + + void loadFilterPreset() + { + if (!filterPresetName) { + return; + } + const QString name = normalizedFilterPresetName(filterPresetName->currentText()); + if (name.isEmpty()) { + return; + } + QFile file(filterPresetPath(name)); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not read %1:\n%2").arg(file.fileName(), file.errorString())); + return; + } + filterStates[currentFilterTarget] = defaultFilterState(currentFilterTarget); + QTextStream in(&file); + while (!in.atEnd()) { + const QString line = in.readLine().trimmed(); + if (line.isEmpty() || line.startsWith(QLatin1Char('#'))) { + continue; + } + const int equals = line.indexOf(QLatin1Char('=')); + if (equals <= 0) { + continue; + } + const QString key = line.left(equals).trimmed(); + const QString value = line.mid(equals + 1).trimmed(); + applyFilterSetting(filterKey(key, currentFilterTarget), value); + } + loadFilterStateToUi(currentFilterTarget); + status->setText(QStringLiteral("Filter preset loaded: %1").arg(name)); + } + + void deleteFilterPreset() + { + if (!filterPresetName) { + return; + } + const QString name = normalizedFilterPresetName(filterPresetName->currentText()); + if (name.isEmpty()) { + return; + } + const QString path = filterPresetPath(name); + if (QFile::exists(path) && !QFile::remove(path)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not delete %1.").arg(path)); + return; + } + refreshFilterPresetList(); + status->setText(QStringLiteral("Filter preset deleted: %1").arg(name)); + } + + bool applyFilterSetting(const QString &key, const QString &value) + { + const QStringList bases = { + QStringLiteral("gfx_filter_mode"), + QStringLiteral("gfx_filter_mode2"), + QStringLiteral("gfx_filter_horiz_zoomf"), + QStringLiteral("gfx_filter_vert_zoomf"), + QStringLiteral("gfx_filter_horiz_zoom_multf"), + QStringLiteral("gfx_filter_vert_zoom_multf"), + QStringLiteral("gfx_filter_horiz_offsetf"), + QStringLiteral("gfx_filter_vert_offsetf"), + QStringLiteral("gfx_filter_keep_aspect"), + QStringLiteral("gfx_filter_keep_autoscale_aspect"), + QStringLiteral("gfx_filter_autoscale"), + QStringLiteral("gfx_filter_autoscale_limit"), + QStringLiteral("gfx_filter_luminance"), + QStringLiteral("gfx_filter_contrast"), + QStringLiteral("gfx_filter_saturation"), + QStringLiteral("gfx_filter_gamma"), + QStringLiteral("gfx_filter_blur"), + QStringLiteral("gfx_filter_noise"), + QStringLiteral("gfx_filter_bilinear"), + QStringLiteral("gfx_filter_scanlines"), + QStringLiteral("gfx_filter_scanlinelevel"), + QStringLiteral("gfx_filter_scanlineoffset"), + QStringLiteral("gfx_filter_enable") + }; + for (int target = 0; target < 3; target++) { + for (const QString &base : bases) { + if (key != filterKey(base, target)) { + continue; + } + WinUaeQtFilterState &state = filterStates[target]; + if (base == QStringLiteral("gfx_filter_mode")) { + state.modeH = value; + } else if (base == QStringLiteral("gfx_filter_mode2")) { + state.modeV = value; + } else if (base == QStringLiteral("gfx_filter_horiz_zoomf")) { + state.horizZoom = value.toDouble(); + } else if (base == QStringLiteral("gfx_filter_vert_zoomf")) { + state.vertZoom = value.toDouble(); + } else if (base == QStringLiteral("gfx_filter_horiz_zoom_multf")) { + state.horizZoomMult = value.toDouble(); + } else if (base == QStringLiteral("gfx_filter_vert_zoom_multf")) { + state.vertZoomMult = value.toDouble(); + } else if (base == QStringLiteral("gfx_filter_horiz_offsetf")) { + state.horizOffset = value.toDouble(); + } else if (base == QStringLiteral("gfx_filter_vert_offsetf")) { + state.vertOffset = value.toDouble(); + } else if (base == QStringLiteral("gfx_filter_keep_aspect")) { + state.keepAspect = value; + } else if (base == QStringLiteral("gfx_filter_keep_autoscale_aspect")) { + state.keepAutoscaleAspect = value.toInt() != 0 || configBoolValue(value); + } else if (base == QStringLiteral("gfx_filter_autoscale")) { + state.autoscale = target == 1 && !isRtgAutoscaleValue(value) ? QStringLiteral("resize") : value; + if (target == 1) { + applyRtgScaleValue(state.autoscale); + } + } else if (base == QStringLiteral("gfx_filter_autoscale_limit")) { + state.integerLimit = value; + } else if (base == QStringLiteral("gfx_filter_luminance")) { + state.luminance = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_contrast")) { + state.contrast = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_saturation")) { + state.saturation = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_gamma")) { + state.gamma = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_blur")) { + state.blur = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_noise")) { + state.noise = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_bilinear")) { + state.bilinear = value.toInt() != 0 || configBoolValue(value); + } else if (base == QStringLiteral("gfx_filter_scanlines")) { + state.scanlines = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_scanlinelevel")) { + state.scanlineLevel = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_scanlineoffset")) { + state.scanlineOffset = value.toInt(); + } else if (base == QStringLiteral("gfx_filter_enable")) { + state.enable = value.toInt() != 0 || configBoolValue(value); + } + if (target == currentFilterTarget) { + loadFilterStateToUi(target); + } + return true; + } + } + return false; + } + + void setAdvancedCheck(const QString &key, bool checked) + { + if (QCheckBox *box = advancedCheckBoxes.value(key, nullptr)) { + setCheckBoxIfChanged(box, checked); + } + } + + bool advancedCheck(const QString &key) const + { + if (QCheckBox *box = advancedCheckBoxes.value(key, nullptr)) { + return box->isChecked(); + } + return false; + } + + void setAdvancedRadioValue(QButtonGroup *group, const ConfigChoice *choices, int count, const QString &value) + { + if (!group) { + return; + } + if (QAbstractButton *button = group->button(configChoiceIndex(choices, count, value))) { + button->setChecked(true); + } + } + + QString advancedRadioValue(QButtonGroup *group, const ConfigChoice *choices, int count) const + { + return configChoiceValueAt(choices, count, group ? group->checkedId() : 0); + } + + void setAdvancedRevision(QCheckBox *enabled, QLineEdit *field, int value, int defaultValue) + { + if (!enabled || !field) { + return; + } + enabled->setChecked(value >= 0); + field->setText(chipsetRevisionText(value >= 0 ? value : defaultValue)); + field->setEnabled(value >= 0); + } + + void resetAdvancedChipsetBase() + { + if (!advancedRtcButtons) { + return; + } + for (const AdvancedCheckChoice &choice : advancedCheckChoices) { + setAdvancedCheck(QString::fromLatin1(choice.key), choice.defaultChecked); + } + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("none")); + setAdvancedRadioValue(advancedCiaTodButtons, ciaTodChoices, int(sizeof(ciaTodChoices) / sizeof(ciaTodChoices[0])), QStringLiteral("vblank")); + advancedRtcAdjust->setText(QStringLiteral("0")); + advancedIdeA600A1200->setChecked(false); + advancedIdeA4000->setChecked(false); + advancedScsiA3000->setChecked(false); + advancedScsiA4000T->setChecked(false); + advancedCia391078->setChecked(false); + advancedUnmappedAddress->setCurrentText(QStringLiteral("Floating")); + advancedCiaSync->setCurrentText(QStringLiteral("Autoselect")); + setAdvancedRevision(advancedRamsey, advancedRamseyRevision, -1, 0x0f); + setAdvancedRevision(advancedFatGary, advancedFatGaryRevision, -1, 0x00); + advancedAgnusModel->setCurrentText(QStringLiteral("Auto")); + advancedAgnusSize->setCurrentText(QStringLiteral("Auto")); + advancedDeniseModel->setCurrentText(QStringLiteral("Auto")); + } + + void setAdvancedCiaTodForPowerSupply() + { + setAdvancedRadioValue(advancedCiaTodButtons, ciaTodChoices, int(sizeof(ciaTodChoices) / sizeof(ciaTodChoices[0])), + (ntsc && ntsc->isChecked()) ? QStringLiteral("60hz") : QStringLiteral("50hz")); + } + + void applyAdvancedChipsetPreset(const QString &compatible) + { + if (!advancedRtcButtons) { + return; + } + + resetAdvancedChipsetBase(); + const QString value = compatible.trimmed(); + setCheckBoxIfChanged(advancedCompatible, value != QStringLiteral("-")); + if (value == QStringLiteral("-")) { + return; + } + if (value == QStringLiteral("CDTV")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("MSM6242B")); + setAdvancedCheck(QStringLiteral("cdtvcd"), true); + setAdvancedCheck(QStringLiteral("cdtvram"), true); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + } else if (value == QStringLiteral("CDTV-CR")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("MSM6242B")); + setAdvancedCheck(QStringLiteral("cdtvcd"), true); + setAdvancedCheck(QStringLiteral("cdtvram"), true); + setAdvancedCheck(QStringLiteral("cdtv-cr"), true); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + setAdvancedCheck(QStringLiteral("ksmirror_a8"), true); + setAdvancedCheck(QStringLiteral("cia_overlay"), false); + setAdvancedCheck(QStringLiteral("resetwarning"), false); + setAdvancedCheck(QStringLiteral("cia_todbug"), true); + setAdvancedCheck(QStringLiteral("pcmcia"), true); + advancedIdeA600A1200->setChecked(true); + } else if (value == QStringLiteral("CD32")) { + setAdvancedCheck(QStringLiteral("cd32cd"), true); + setAdvancedCheck(QStringLiteral("cd32c2p"), true); + setAdvancedCheck(QStringLiteral("cd32nvram"), true); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + setAdvancedCheck(QStringLiteral("ksmirror_a8"), true); + setAdvancedCheck(QStringLiteral("cia_overlay"), false); + setAdvancedCheck(QStringLiteral("resetwarning"), false); + advancedUnmappedAddress->setCurrentText(QStringLiteral("All zeros")); + advancedCiaSync->setCurrentText(QStringLiteral("Gayle")); + } else if (value == QStringLiteral("A500")) { + setAdvancedCheck(QStringLiteral("df0idhw"), false); + setAdvancedCheck(QStringLiteral("resetwarning"), false); + setAdvancedCheck(QStringLiteral("cia_todbug"), true); + } else if (value == QStringLiteral("A500+")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("MSM6242B")); + setAdvancedCheck(QStringLiteral("resetwarning"), false); + setAdvancedCheck(QStringLiteral("cia_todbug"), true); + } else if (value == QStringLiteral("A600")) { + advancedIdeA600A1200->setChecked(true); + setAdvancedCheck(QStringLiteral("pcmcia"), true); + setAdvancedCheck(QStringLiteral("ksmirror_a8"), true); + setAdvancedCheck(QStringLiteral("cia_overlay"), false); + setAdvancedCheck(QStringLiteral("resetwarning"), false); + setAdvancedCheck(QStringLiteral("cia_todbug"), true); + advancedCia391078->setChecked(true); + advancedCiaSync->setCurrentText(QStringLiteral("Gayle")); + } else if (value == QStringLiteral("A1000")) { + setAdvancedCheck(QStringLiteral("a1000ram"), true); + setAdvancedCiaTodForPowerSupply(); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + setAdvancedCheck(QStringLiteral("cia_todbug"), true); + advancedAgnusModel->setCurrentText(QStringLiteral("A1000")); + advancedDeniseModel->setCurrentText(QStringLiteral("A1000")); + } else if (value == QStringLiteral("A1200")) { + advancedIdeA600A1200->setChecked(true); + setAdvancedCheck(QStringLiteral("pcmcia"), true); + setAdvancedCheck(QStringLiteral("ksmirror_a8"), true); + setAdvancedCheck(QStringLiteral("cia_overlay"), false); + advancedCiaSync->setCurrentText(QStringLiteral("Gayle")); + } else if (value == QStringLiteral("A2000")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("MSM6242B")); + setAdvancedCiaTodForPowerSupply(); + setAdvancedCheck(QStringLiteral("cia_todbug"), true); + advancedUnmappedAddress->setCurrentText(QStringLiteral("All zeros")); + } else if (value == QStringLiteral("A3000") || value == QStringLiteral("A3000T")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("RP5C01A")); + setAdvancedRevision(advancedFatGary, advancedFatGaryRevision, 0x00, 0x00); + setAdvancedRevision(advancedRamsey, advancedRamseyRevision, 0x0d, 0x0f); + advancedScsiA3000->setChecked(true); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + setAdvancedCiaTodForPowerSupply(); + setAdvancedCheck(QStringLiteral("z3_autoconfig"), true); + advancedUnmappedAddress->setCurrentText(QStringLiteral("All zeros")); + } else if (value == QStringLiteral("A4000") || value == QStringLiteral("A4000T")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), QStringLiteral("RP5C01A")); + setAdvancedRevision(advancedFatGary, advancedFatGaryRevision, 0x00, 0x00); + setAdvancedRevision(advancedRamsey, advancedRamseyRevision, 0x0f, 0x0f); + advancedIdeA4000->setChecked(true); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + setAdvancedCheck(QStringLiteral("ksmirror_a8"), false); + setAdvancedCheck(QStringLiteral("z3_autoconfig"), true); + advancedUnmappedAddress->setCurrentText(QStringLiteral("All zeros")); + advancedCiaSync->setCurrentText(QStringLiteral("Gayle")); + if (value == QStringLiteral("A4000T")) { + advancedScsiA4000T->setChecked(true); + } + } else if (value == QStringLiteral("Velvet")) { + setAdvancedCiaTodForPowerSupply(); + setAdvancedCheck(QStringLiteral("ksmirror_e0"), false); + advancedAgnusModel->setCurrentText(QStringLiteral("A1000")); + advancedDeniseModel->setCurrentText(QStringLiteral("A1000 No-EHB")); + } + } + + void resetDefaults() + { + loadedConfig = WinUaeQtConfig(); + hardwareOrderOwnedKeys.clear(); + + if (configName) { + configName->setCurrentText(QStringLiteral("A1200 Install")); + } + if (configDescription) { + configDescription->setText(QStringLiteral("A1200, 68020, AGA, 2 MB Chip RAM")); + } + if (configPath) { + configPath->clear(); + } + + quickModel->setCurrentText(QStringLiteral("A1200")); + refreshQuickstartConfigurationChoices(1); + quickHostConfiguration->setCurrentText(QStringLiteral("Default")); + compatibility->setValue(1); + quickstartMode->setChecked(true); + ntsc->setChecked(false); + chipsetNtsc->setChecked(false); + chipsetCycleExact->setChecked(false); + chipsetCycleExactMemory->setChecked(false); + immediateBlits->setChecked(false); + waitingBlits->setChecked(false); + displayOptimization->setCurrentText(QStringLiteral("Full")); + chipsetSyncMode->setCurrentText(QStringLiteral("Combined + Blanking")); + genlockConnected->setChecked(false); + genlockMode->setCurrentIndex(0); + genlockMix->setCurrentIndex(0); + genlockAlpha->setChecked(false); + genlockKeepAspect->setChecked(false); + genlockImagePath.clear(); + genlockVideoPath.clear(); + updateGenlockControlState(); + keyboardMode->setCurrentText(QStringLiteral("UAE High level emulation")); + keyboardNkro->setChecked(true); + if (QAbstractButton *button = collisionButtons->button(3)) { + button->setChecked(true); + } + + applyQuickstartSelectionToUi(); + moreCompatible->setChecked(false); + cpuDataCache->setChecked(false); + cpuUnimplemented->setChecked(true); + if (QAbstractButton *button = mmuButtons->button(0)) { + button->setChecked(true); + } + fpuStrict->setChecked(false); + fpuUnimplemented->setChecked(true); + fpuMode->setCurrentText(QStringLiteral("Host (64-bit)")); + if (QAbstractButton *button = cpuSpeedButtons->button(0)) { + button->setChecked(true); + } + cpuSpeed->setValue(0); + updateCpuSpeedLabel(); + cpuFrequency->setCurrentText(QStringLiteral("4x (A1200)")); + cpuFrequencyCustom->clear(); + jit->setChecked(false); + jitCache->setValue(jitCachePositionFromSize(defaultJitCacheSize())); + jitFpu->setChecked(false); + jitConstJump->setChecked(true); + jitHardFlush->setChecked(false); + if (QAbstractButton *button = jitTrust->button(0)) { + button->setChecked(true); + } + jitNoFlags->setChecked(false); + jitCatchFault->setChecked(true); + updateCpuControlState(); + chipMem->setCurrentText(QStringLiteral("2 MB")); + z2Fast->setCurrentText(QStringLiteral("None")); + slowMem->setCurrentText(QStringLiteral("None")); + z3Fast->setCurrentText(QStringLiteral("None")); + z3ChipMem->setCurrentText(QStringLiteral("None")); + processorSlotMem->setCurrentText(QStringLiteral("None")); + rtgMem->setCurrentText(QStringLiteral("None")); + rtgType->setCurrentText(QStringLiteral("ZorroIII")); + rtgMonitor->setCurrentText(QStringLiteral("1")); + rtgScale->setChecked(true); + rtgCenter->setChecked(false); + rtgIntegerScale->setChecked(false); + rtgMultithread->setChecked(false); + rtgHardwareSprite->setChecked(true); + rtgHardwareVBlank->setChecked(false); + rtgAutoswitch->setChecked(true); + rtgInitialMonitor->setChecked(false); + rtg8Bit->setCurrentText(QStringLiteral("8-bit (*)")); + rtg16Bit->setCurrentText(QStringLiteral("R5G6B5PC (*)")); + rtg24Bit->setCurrentText(QStringLiteral("(24bit)")); + rtg32Bit->setCurrentText(QStringLiteral("B8G8R8A8 (*)")); + rtgRefreshRate->setCurrentText(QStringLiteral("Chipset")); + rtgBuffers->setCurrentText(QStringLiteral("Double")); + hardwareCustomBoardOrder->setChecked(false); + refreshHardwareInfoPage(); + rtgScaleAllow->setChecked(false); + rtgDisplay->setCurrentText(QStringLiteral("Default display")); + rtgAspectRatio->setCurrentText(QStringLiteral("Automatic")); + expansionBsdsocket->setChecked(false); + expansionScsiDevice->setChecked(false); + expansionSana2->setChecked(false); + clearExpansionBoardStates(); + + romFile->setCurrentText(envString("WINUAE_KICKSTART_ROM")); + extendedRomFile->setCurrentText(QString()); + cartFile->setCurrentText(QString()); + flashFile->clear(); + rtcFile->clear(); + mapRom->setChecked(false); + kickShifter->setChecked(false); + uaeBoardType->setCurrentText(QStringLiteral("Original UAE (FS + F0 ROM)")); + clearCustomRomBoards(); + + for (int i = 0; i < 4; i++) { + dfEnable[i]->setChecked(i == 0); + dfType[i]->setCurrentText(QStringLiteral("3.5 DD")); + dfWriteProtect[i]->setChecked(false); + dfPath[i]->setCurrentText(i == 0 ? envString("WINUAE_FLOPPY0") : QString()); + } + floppySpeed->setValue(floppySpeedSliderPosition(100)); + updateFloppySpeedLabel(); + for (int i = 0; i < 2; i++) { + quickDfEnable[i]->setChecked(i == 0); + quickDfType[i]->setCurrentText(QStringLiteral("3.5 DD")); + quickDfWriteProtect[i]->setChecked(false); + quickDfPath[i]->setCurrentText(i == 0 ? envString("WINUAE_FLOPPY0") : QString()); + } + for (int i = 0; i < MaxDiskSwapperSlots; i++) { + setDiskSwapperPath(i, QString()); + } + if (diskSwapperPath) { + diskSwapperPath->clearEditText(); + } + if (mountedDrives) { + mountedDrives->clear(); + updateMountButtons(); + } + clearCdSlots(); + hostDriveAutomount->setChecked(false); + hostRemovableDrives->setChecked(false); + hostNetworkDrives->setChecked(false); + hostCdDrives->setChecked(false); + filesysNoFsdb->setChecked(false); + hostNoRecycleBin->setChecked(false); + hostAutomountRemovable->setChecked(false); + filesysLimitSize->setChecked(false); + + windowWidth->setText(QStringLiteral("720")); + windowHeight->setText(QStringLiteral("568")); + windowResize->setChecked(true); + hostDisplay->setCurrentIndex(0); + fullscreenResolution->setProperty("winuae_width", QString()); + fullscreenResolution->setProperty("winuae_height", QString()); + fullscreenResolution->setCurrentText(QStringLiteral("Native")); + displayRefreshRate->setCurrentText(QStringLiteral("Default")); + displayBufferCount->setCurrentText(QStringLiteral("Double")); + nativeMode->setCurrentText(QStringLiteral("Windowed")); + nativeVsync->setCurrentText(QStringLiteral("No vsync")); + nativeFrameSlices->setCurrentText(QStringLiteral("4")); + rtgMode->setCurrentText(QStringLiteral("Windowed")); + rtgVsync->setCurrentText(QStringLiteral("-")); + displayResolution->setCurrentText(QStringLiteral("hires")); + displayOverscan->setCurrentText(QStringLiteral("Overscan")); + displayAutoResolution->setCurrentText(QStringLiteral("Disabled")); + displayFrameRate->setValue(1); + displayCenterHorizontal->setChecked(false); + displayCenterVertical->setChecked(false); + displayFlickerFixer->setChecked(false); + displayLoresSmoothed->setChecked(false); + displayBlackerThanBlack->setChecked(false); + displayMonochrome->setChecked(false); + displayAutoResolutionVga->setChecked(true); + displayResyncBlank->setChecked(false); + displayKeepAspect->setChecked(false); + if (QAbstractButton *button = displayLineModeButtons->button(1)) { + button->setChecked(true); + } + if (QAbstractButton *button = displayInterlacedLineModeButtons->button(1)) { + button->setChecked(true); + } + resetFilterStates(); + outputFile->setText(unixDefaultDataSubPath(QStringLiteral("Videos/output.avi"))); + outputAudio->setChecked(false); + outputVideo->setChecked(true); + outputEnabled->setChecked(false); + outputAudioCodec->setCurrentIndex(0); + outputVideoCodec->setCurrentIndex(0); + outputFrameLimiter->setChecked(false); + outputOriginalSize->setChecked(false); + outputNoSound->setChecked(false); + outputNoSoundSync->setChecked(false); + screenshotOriginalSize->setChecked(false); + screenshotPaletted->setChecked(false); + screenshotClip->setChecked(false); + screenshotClip->setEnabled(false); + screenshotAuto->setChecked(false); + stateReplayAutoplay->setChecked(true); + stateReplayRate->setCurrentText(QStringLiteral("5")); + stateReplayBuffers->setCurrentText(QStringLiteral("100")); + if (QAbstractButton *button = soundOutputButtons->button(2)) { + button->setChecked(true); + } + soundDevice->setCurrentIndex(0); + soundAutomatic->setChecked(false); + soundMasterVolume->setValue(100); + for (int i = 0; i < SoundVolumeCount; i++) { + soundVolumeAttenuation[i] = 0; + } + currentSoundVolume = 0; + soundVolumeSelect->setCurrentIndex(0); + loadSelectedSoundVolume(); + soundBufferSize->setValue(soundBufferIndexFromSize(16384)); + soundChannels->setCurrentText(QStringLiteral("Stereo")); + soundStereoSeparation->setCurrentText(QStringLiteral("70%")); + soundInterpolation->setCurrentText(QStringLiteral("Anti")); + soundFrequency->setCurrentText(QStringLiteral("44100")); + soundSwap->setCurrentText(QStringLiteral("-")); + soundStereoDelay->setCurrentText(QStringLiteral("-")); + soundFilter->setCurrentText(QStringLiteral("Emulated (A500)")); + for (int i = 0; i < FloppySoundDriveCount; i++) { + floppySoundTypeValue[i] = 0; + floppySoundEmptyAttenuation[i] = 33; + floppySoundDiskAttenuation[i] = 33; + } + currentFloppySoundDrive = 0; + floppySoundDrive->setCurrentIndex(0); + loadSelectedFloppySound(); + updateSoundControlState(); + cdSpeedTurbo->setChecked(false); + portDevice[0]->setCurrentText(QStringLiteral("Mouse")); + portDevice[1]->setCurrentText(QStringLiteral("Keyboard Layout A")); + portDevice[2]->setCurrentText(QStringLiteral("")); + portDevice[3]->setCurrentText(QStringLiteral("")); + for (QString &custom : joyportCustom) { + custom.clear(); + } + for (int i = 0; i < 2; i++) { + portAutofire[i]->setCurrentText(QStringLiteral("No autofire (normal)")); + portMode[i]->setCurrentText(QStringLiteral("Default")); + } + portAutoswitch->setChecked(true); + mouseSpeed->setValue(100); + inputType->setCurrentText(QStringLiteral("Game Ports")); + inputDevice->setCurrentText(QStringLiteral("Keyboard")); + inputMappingSettings.clear(); + inputOwnedMappingKeys.clear(); + inputDeviceEnabled->setChecked(true); + inputSubEvent->setCurrentText(QStringLiteral("1")); + inputAmigaEvent->setCurrentText(QStringLiteral("")); + inputDeadzone->setValue(33); + inputAutofireRate->setValue(600); + inputJoyMouseDigital->setValue(10); + inputJoyMouseAnalog->setValue(100); + inputCopyFrom->setCurrentText(QStringLiteral("Custom #1")); + inputPageUpEnd->setChecked(false); + inputSwapBackslashF11->setCheckState(Qt::Unchecked); + refreshInputMappingList(); + mouseUntrapMode->setCurrentText(QStringLiteral("Middle button")); + magicMouseCursor->setCurrentText(QStringLiteral("Show both cursors")); + virtualMouseDriver->setChecked(false); + tabletMode->setCurrentText(QStringLiteral("-")); + tabletLibrary->setChecked(false); + updateMouseExtraState(); + printerPort->setCurrentText(QStringLiteral("")); + printerType->setCurrentText(QStringLiteral("Passthrough")); + printerAutoFlush->setValue(5); + ghostscriptParams->clear(); + samplerDevice->setCurrentText(QStringLiteral("")); + samplerStereo->setChecked(false); + serialPort->setCurrentText(QStringLiteral("")); + serialShared->setChecked(false); + serialCtsRts->setChecked(true); + serialDirect->setChecked(false); + serialCrlf->setChecked(false); + uaeSerial->setChecked(false); + serialStatus->setChecked(true); + serialRingIndicator->setChecked(false); + midiOut->setCurrentText(QStringLiteral("")); + midiIn->setCurrentText(QStringLiteral("")); + midiRouter->setChecked(false); + protectionDongle->setCurrentText(QStringLiteral("None")); + updateIoPortsState(); + for (const MiscCheckChoice &choice : miscCheckChoices) { + setMiscOptionChecked(QString::fromLatin1(choice.key), choice.defaultChecked); + } + stateFileName->setCurrentText(QString()); + stateFileClear->setChecked(false); + for (int i = 0; i < 3; i++) { + keyboardLed[i]->setCurrentText(QStringLiteral("None")); + } + setExtensionPriorityValue(extensionActivePriority, 0); + extensionActivePause->setChecked(false); + extensionActiveNoSound->setChecked(false); + extensionActiveNoJoy->setChecked(false); + extensionActiveNoKeyboard->setChecked(false); + setExtensionPriorityValue(extensionInactivePriority, -1); + extensionInactivePause->setChecked(false); + extensionInactiveNoSound->setChecked(false); + extensionInactiveNoJoy->setChecked(true); + setExtensionPriorityValue(extensionMinimizedPriority, -2); + extensionMinimizedPause->setChecked(true); + extensionMinimizedNoSound->setChecked(true); + extensionMinimizedNoJoy->setChecked(true); + updateExtensionActivityState(); + romsPath->setText(unixDefaultDataSubPath(QStringLiteral("Kickstarts"))); + configsPath->setText(unixDefaultDataSubPath(QStringLiteral("Configuration"))); + nvramPath->setText(unixDefaultDataSubPath(QStringLiteral("NVRAMs"))); + screenshotsPath->setText(unixDefaultDataSubPath(QStringLiteral("Screenshots"))); + stateFilesPath->setText(unixDefaultDataSubPath(QStringLiteral("Save States"))); + videosPath->setText(unixDefaultDataSubPath(QStringLiteral("Videos"))); + saveImagesPath->setText(unixDefaultDataSubPath(QStringLiteral("SaveImages"))); + ripsPath->setText(unixDefaultDataSubPath(QStringLiteral("Rips"))); + dataPath->setText(unixDefaultDataPath()); + recursiveRoms->setChecked(false); + cacheConfigurations->setChecked(true); + cacheBoxArt->setChecked(false); + saveImageOriginalPath->setChecked(false); + relativePaths->setChecked(false); + portableMode->setChecked(false); + pathDefaultType->setCurrentText(QStringLiteral("User data directory")); + logSelect->setCurrentText(QStringLiteral("winuaebootlog.txt")); + fullLogging->setChecked(false); + logWindow->setChecked(false); + miscGuiSize->setCurrentText(QStringLiteral("Select...")); + miscGuiResize->setChecked(true); + miscGuiFullscreen->setChecked(false); + miscGuiDarkMode->setCheckState(Qt::PartiallyChecked); + applyGuiDarkModeSelection(); + miscGuiFontConfig.clear(); + applyGuiResizeMode(); + updateLogPathText(); + updateOutputControlState(); + refreshConfigList(); + status->setText(QStringLiteral("Ready")); + } + + void applyModelPreset(const QString &model) + { + if (model == QStringLiteral("A1200") || model == QStringLiteral("CD32")) { + chipset->setCurrentText(QStringLiteral("AGA")); + chipsetCompatible->setCurrentText(model); + setCpuButton(68020); + setFpuButton(0); + cpu24Bit->setChecked(false); + chipMem->setCurrentText(QStringLiteral("2 MB")); + } else if (model == QStringLiteral("A4000")) { + chipset->setCurrentText(QStringLiteral("AGA")); + chipsetCompatible->setCurrentText(QStringLiteral("A4000")); + setCpuButton(68030); + setFpuButton(68882); + cpu24Bit->setChecked(false); + chipMem->setCurrentText(QStringLiteral("2 MB")); + } else if (model == QStringLiteral("A3000")) { + chipset->setCurrentText(QStringLiteral("ECS")); + chipsetCompatible->setCurrentText(QStringLiteral("A3000")); + setCpuButton(68030); + setFpuButton(68882); + cpu24Bit->setChecked(false); + chipMem->setCurrentText(QStringLiteral("2 MB")); + } else if (model == QStringLiteral("A600")) { + chipset->setCurrentText(QStringLiteral("ECS")); + chipsetCompatible->setCurrentText(QStringLiteral("A600")); + setCpuButton(68000); + setFpuButton(0); + cpu24Bit->setChecked(true); + chipMem->setCurrentText(QStringLiteral("1 MB")); + } else if (model == QStringLiteral("A500+")) { + chipset->setCurrentText(QStringLiteral("ECS")); + chipsetCompatible->setCurrentText(QStringLiteral("A500+")); + setCpuButton(68000); + setFpuButton(0); + cpu24Bit->setChecked(true); + chipMem->setCurrentText(QStringLiteral("1 MB")); + } else if (model == QStringLiteral("A1000") || model == QStringLiteral("Velvet")) { + chipset->setCurrentText(QStringLiteral("OCS")); + chipsetCompatible->setCurrentText(model); + setCpuButton(68000); + setFpuButton(0); + cpu24Bit->setChecked(true); + chipMem->setCurrentText(QStringLiteral("512 KB")); + } else if (model == QStringLiteral("CDTV") || model == QStringLiteral("CDTV-CR")) { + chipset->setCurrentText(QStringLiteral("ECS")); + chipsetCompatible->setCurrentText(model); + setCpuButton(68000); + setFpuButton(0); + cpu24Bit->setChecked(true); + chipMem->setCurrentText(QStringLiteral("1 MB")); + } else { + chipset->setCurrentText(QStringLiteral("OCS")); + chipsetCompatible->setCurrentText(QStringLiteral("A500")); + setCpuButton(68000); + setFpuButton(0); + cpu24Bit->setChecked(true); + chipMem->setCurrentText(QStringLiteral("512 KB")); + } + } + + void setCpuButton(int model) + { + if (QAbstractButton *button = cpuButtons->button(model)) { + button->setChecked(true); + } + updateFpuControls(); + updateCpuControlState(); + } + + void setFpuButton(int model) + { + const int id = (model == 68040 || model == 68060) ? FpuInternal : model; + if (QAbstractButton *button = fpuButtons->button(id)) { + button->setChecked(true); + } + updateFpuControls(); + updateCpuControlState(); + } + + int selectedCpuModel() const + { + const int cpu = cpuButtons->checkedId(); + return cpu > 0 ? cpu : 68020; + } + + int fpuModelConfigValue(int cpu) const + { + const int fpu = fpuButtons->checkedId(); + if (fpu == FpuInternal) { + return cpu >= 68060 ? 68060 : (cpu >= 68040 ? 68040 : 68882); + } + return fpu > 0 ? fpu : 0; + } + + void updateFpuControls() + { + const int cpu = selectedCpuModel(); + if (QAbstractButton *internal = fpuButtons->button(FpuInternal)) { + internal->setEnabled(cpu >= 68040); + if (!internal->isEnabled() && internal->isChecked()) { + if (QAbstractButton *none = fpuButtons->button(0)) { + none->setChecked(true); + } + } + } + } + + int chipMemConfigValue() const + { + const QString text = chipMem->currentText(); + if (text == QStringLiteral("512 KB")) { + return 1; + } + if (text == QStringLiteral("1 MB")) { + return 2; + } + if (text == QStringLiteral("2 MB")) { + return 4; + } + if (text == QStringLiteral("4 MB")) { + return 8; + } + if (text == QStringLiteral("8 MB")) { + return 16; + } + return 4; + } + + int megabytesFromText(const QString &text) const + { + QString value = text; + value.remove(QStringLiteral(" MB")); + return value.toInt(); + } + + int slowMemConfigValue() const + { + const QString text = slowMem->currentText(); + if (text == QStringLiteral("512 KB")) { + return 2; + } + if (text == QStringLiteral("1 MB")) { + return 4; + } + if (text == QStringLiteral("1.5 MB")) { + return 6; + } + if (text == QStringLiteral("1.8 MB")) { + return 7; + } + return 0; + } + + QString nextMountDeviceName() const + { + QStringList used; + if (mountedDrives) { + for (int i = 0; i < mountedDrives->topLevelItemCount(); i++) { + const QTreeWidgetItem *item = mountedDrives->topLevelItem(i); + const QString kind = item->data(0, MountKindRole).toString(); + if (kind == QStringLiteral("dir") || kind == QStringLiteral("hdf")) { + used.append(item->data(0, MountDeviceRole).toString().toUpper()); + } + } + } + for (int i = 0; i < MaxMountEntries; i++) { + const QString device = QStringLiteral("DH%1").arg(i); + if (!used.contains(device)) { + return device; + } + } + return QStringLiteral("DH0"); + } + + void addDirectoryMountDialog() + { + WinUaeQtMountEntry entry; + entry.kind = QStringLiteral("dir"); + entry.device = nextMountDeviceName(); + if (showDirectoryMountDialog(&entry, QStringLiteral("Add Directory or Archive"))) { + addMountEntry(entry); + } + } + + void addHardfileMountDialog() + { + const QString path = QFileDialog::getOpenFileName(this, QStringLiteral("Add hardfile"), fileDialogInitialPath(QString()), hardfileImageFilter()); + if (path.isEmpty()) { + return; + } + + WinUaeQtMountEntry entry; + entry.kind = QStringLiteral("hdf"); + entry.device = nextMountDeviceName(); + entry.path = path; + entry.hardfileGeometry = QStringLiteral("32,1,2,512"); + entry.hardfileTail = QStringLiteral(",uae0"); + entry.readOnly = false; + entry.bootPri = 0; + if (showHardfileMountDialog(&entry, QStringLiteral("Hardfile Properties"))) { + addMountEntry(entry); + } + } + + int nextMountEmuUnit(const QString &kind) const + { + QList used; + if (mountedDrives) { + for (int i = 0; i < mountedDrives->topLevelItemCount(); i++) { + const QTreeWidgetItem *item = mountedDrives->topLevelItem(i); + if (item->data(0, MountKindRole).toString() == kind) { + used.append(item->data(0, MountEmuUnitRole).toInt()); + } + } + } + for (int i = 0; i < MaxControllerUnits; i++) { + if (!used.contains(i)) { + return i; + } + } + return 0; + } + + void addHardDriveMountDialog() + { + const QVector choices = nativeHardDriveChoices(); + if (choices.isEmpty()) { + QMessageBox::information( + this, + QStringLiteral("Add Hard Drive"), + QStringLiteral("No readable Unix block devices were found. On macOS and Linux, raw device access may require adjusted permissions.")); + return; + } + + QDialog dialog(this); + dialog.setWindowTitle(QStringLiteral("Add Hard Drive")); + QComboBox *drive = new QComboBox; + for (const WinUaeQtNativeDriveChoice &choice : choices) { + drive->addItem(choice.display, choice.configPath); + } + QLabel *notice = new QLabel(QStringLiteral("Native drives are opened read-only by the Unix backend.")); + notice->setWordWrap(true); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QVBoxLayout *root = new QVBoxLayout(&dialog); + QGridLayout *fields = new QGridLayout; + fields->setColumnStretch(1, 1); + fields->addWidget(label(QStringLiteral("Drive:")), 0, 0); + fields->addWidget(drive, 0, 1); + root->addLayout(fields); + root->addWidget(notice); + root->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + if (dialog.exec() != QDialog::Accepted) { + return; + } + + const int index = drive->currentIndex(); + if (index < 0 || index >= choices.size()) { + return; + } + const WinUaeQtNativeDriveChoice &choice = choices[index]; + WinUaeQtMountEntry entry; + entry.kind = QStringLiteral("hdf"); + entry.path = choice.configPath; + entry.device.clear(); + entry.bootPri = 0; + entry.readOnly = true; + entry.hardfileGeometry = QStringLiteral("0,0,0,%1").arg(qMax(1, choice.blockSize)); + entry.hardfileTail = QStringLiteral(",uae0"); + if (showHardfileMountDialog(&entry, QStringLiteral("Hard Drive Properties"))) { + addMountEntry(entry); + } + } + + void addCdDriveMountDialog() + { + WinUaeQtMountEntry entry; + entry.kind = QStringLiteral("cd"); + entry.emuUnit = nextMountEmuUnit(QStringLiteral("cd")); + entry.readOnly = true; + entry.bootPri = 0; + entry.hardfileGeometry = QStringLiteral("0,0,0,2048"); + entry.hardfileTail = QStringLiteral(",ide0"); + if (showCdDriveMountDialog(&entry, QStringLiteral("Add CD Drive"))) { + addMountEntry(entry); + } + } + + void addTapeDriveMountDialog() + { + WinUaeQtMountEntry entry; + entry.kind = QStringLiteral("tape"); + entry.emuUnit = nextMountEmuUnit(QStringLiteral("tape")); + entry.readOnly = false; + entry.bootPri = 0; + entry.hardfileGeometry = QStringLiteral("0,0,0,512"); + entry.hardfileTail = QStringLiteral(",uae0"); + if (showTapeDriveMountDialog(&entry, QStringLiteral("Add Tape Drive"))) { + addMountEntry(entry); + } + } + + void addMountEntry(const WinUaeQtMountEntry &entry) + { + if (!mountedDrives || mountedDrives->topLevelItemCount() >= MaxMountEntries) { + return; + } + QTreeWidgetItem *item = new QTreeWidgetItem(mountedDrives); + updateMountItem(item, entry); + updateMountButtons(); + } + + QString mountEntryConfigKey(const WinUaeQtMountEntry &entry, int index = 0) const + { + if (entry.kind == QStringLiteral("dir")) { + return QStringLiteral("filesystem2"); + } + if (entry.kind == QStringLiteral("hdf")) { + return QStringLiteral("hardfile2"); + } + if (entry.kind == QStringLiteral("cd") || entry.kind == QStringLiteral("tape")) { + return QStringLiteral("uaehf%1").arg(index); + } + return QString(); + } + + QString mountEntryConfigValue(const WinUaeQtMountEntry &entry) const + { + if (entry.kind == QStringLiteral("dir")) { + return serializeWinUaeQtFilesystem2MountValue(entry); + } + if (entry.kind == QStringLiteral("hdf")) { + return serializeWinUaeQtHardfile2MountValue(entry); + } + if (entry.kind == QStringLiteral("cd")) { + return serializeWinUaeQtUaehfCdMountValue(entry); + } + if (entry.kind == QStringLiteral("tape")) { + return serializeWinUaeQtUaehfTapeMountValue(entry); + } + return QString(); + } + + bool mountEntryExists(const WinUaeQtMountEntry &entry) const + { + if (!mountedDrives) { + return false; + } + const QString key = mountEntryConfigKey(entry); + const QString value = mountEntryConfigValue(entry); + if (key.isEmpty() || value.isEmpty()) { + return false; + } + for (int i = 0; i < mountedDrives->topLevelItemCount(); i++) { + const WinUaeQtMountEntry existing = mountEntryFromItem(mountedDrives->topLevelItem(i)); + if (mountEntryConfigKey(existing) == key && mountEntryConfigValue(existing) == value) { + return true; + } + } + return false; + } + + void addMountEntryIfUnique(const WinUaeQtMountEntry &entry) + { + if (!mountEntryExists(entry)) { + addMountEntry(entry); + } + } + + void updateMountItem(QTreeWidgetItem *item, const WinUaeQtMountEntry &entry) + { + if (!mountedDrives || !item) { + return; + } + item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled); + WinUaeQtMountEntry normalized = entry; + if (normalized.kind == QStringLiteral("dir")) { + normalized.device = winUaeQtSanitizedAmigaName(normalized.device, nextMountDeviceName(), true); + normalized.volume = winUaeQtSanitizedAmigaName(normalized.volume, winUaeQtDefaultVolumeName(normalized.path), false); + } else if (normalized.kind == QStringLiteral("hdf")) { + normalized.device = winUaeQtSanitizedAmigaName(normalized.device, nextMountDeviceName(), true); + } else if (normalized.kind == QStringLiteral("cd")) { + normalized.device.clear(); + normalized.volume = QStringLiteral("CD"); + normalized.readOnly = true; + normalized.bootPri = 0; + if (normalized.hardfileGeometry.isEmpty()) { + normalized.hardfileGeometry = QStringLiteral("0,0,0,2048"); + } + } else if (normalized.kind == QStringLiteral("tape")) { + normalized.device.clear(); + normalized.volume = QStringLiteral("TAPE"); + normalized.bootPri = 0; + if (normalized.hardfileGeometry.isEmpty()) { + normalized.hardfileGeometry = QStringLiteral("0,0,0,512"); + } + } + + QString deviceText = normalized.device; + QString volumeText = normalized.kind == QStringLiteral("dir") ? normalized.volume : QStringLiteral("n/a"); + QString blockSizeText = QStringLiteral("n/a"); + QString bootPriText = QString::number(normalized.bootPri); + if (normalized.kind == QStringLiteral("cd")) { + deviceText = mountControllerDisplay(boardCatalog, normalized); + volumeText = QStringLiteral("CD"); + blockSizeText = QStringLiteral("2048"); + bootPriText = QStringLiteral("n/a"); + } else if (normalized.kind == QStringLiteral("tape")) { + deviceText = mountControllerDisplay(boardCatalog, normalized); + volumeText = QStringLiteral("TAPE"); + blockSizeText = QStringLiteral("512"); + bootPriText = QStringLiteral("n/a"); + } else if (normalized.kind == QStringLiteral("hdf")) { + const QStringList geometry = normalized.hardfileGeometry.split(QLatin1Char(',')); + blockSizeText = geometry.value(3, QStringLiteral("512")); + } + + item->setText(0, QString()); + item->setText(1, deviceText); + item->setText(2, volumeText); + item->setText(3, normalized.path.isEmpty() ? QStringLiteral("-") : normalized.path); + item->setText(4, normalized.readOnly ? QStringLiteral("No") : QStringLiteral("Yes")); + item->setText(5, blockSizeText); + item->setText(6, QStringLiteral("n/a")); + item->setText(7, bootPriText); + item->setData(0, MountKindRole, entry.kind); + item->setData(0, MountDeviceRole, normalized.device); + item->setData(0, MountVolumeRole, normalized.volume); + item->setData(0, MountPathRole, normalized.path); + item->setData(0, MountReadOnlyRole, normalized.readOnly); + item->setData(0, MountBootPriRole, normalized.bootPri); + item->setData(0, MountEmuUnitRole, normalized.emuUnit); + item->setData(0, MountRawConfigRole, normalized.rawConfig); + item->setData(0, MountHardfileGeometryRole, normalized.hardfileGeometry); + item->setData(0, MountHardfileTailRole, normalized.hardfileTail); + for (int i = 0; i < mountedDrives->columnCount(); i++) { + mountedDrives->resizeColumnToContents(i); + } + } + + void removeSelectedMount() + { + if (!mountedDrives) { + return; + } + qDeleteAll(mountedDrives->selectedItems()); + updateMountButtons(); + } + + void moveSelectedMount(int delta) + { + if (!mountedDrives || delta == 0) { + return; + } + QTreeWidgetItem *item = mountedDrives->currentItem(); + if (!item) { + return; + } + const int from = mountedDrives->indexOfTopLevelItem(item); + if (from < 0) { + return; + } + const int to = qBound(0, from + delta, mountedDrives->topLevelItemCount() - 1); + if (from == to) { + return; + } + item = mountedDrives->takeTopLevelItem(from); + mountedDrives->insertTopLevelItem(to, item); + mountedDrives->setCurrentItem(item); + item->setSelected(true); + updateMountButtons(); + } + + void updateMountButtons() + { + if (!mountedDrives) { + return; + } + const bool canAdd = mountedDrives->topLevelItemCount() < MaxMountEntries; + if (addDirectoryMountButton) { + addDirectoryMountButton->setEnabled(canAdd); + } + if (addHardfileMountButton) { + addHardfileMountButton->setEnabled(canAdd); + } + if (addHardDriveMountButton) { + addHardDriveMountButton->setEnabled(canAdd); + } + if (addCdMountButton) { + addCdMountButton->setEnabled(canAdd); + } + if (addTapeMountButton) { + addTapeMountButton->setEnabled(canAdd); + } + if (propertiesMountButton) { + propertiesMountButton->setEnabled(!mountedDrives->selectedItems().isEmpty()); + } + if (removeMountButton) { + removeMountButton->setEnabled(!mountedDrives->selectedItems().isEmpty()); + } + } + + WinUaeQtMountEntry mountEntryFromItem(const QTreeWidgetItem *item) const + { + WinUaeQtMountEntry entry; + entry.kind = item->data(0, MountKindRole).toString(); + entry.device = item->data(0, MountDeviceRole).toString(); + entry.volume = item->data(0, MountVolumeRole).toString(); + entry.path = item->data(0, MountPathRole).toString(); + entry.readOnly = item->data(0, MountReadOnlyRole).toBool(); + entry.bootPri = item->data(0, MountBootPriRole).toInt(); + entry.emuUnit = item->data(0, MountEmuUnitRole).toInt(); + entry.rawConfig = item->data(0, MountRawConfigRole).toString(); + entry.hardfileGeometry = item->data(0, MountHardfileGeometryRole).toString(); + entry.hardfileTail = item->data(0, MountHardfileTailRole).toString(); + return entry; + } + + void openSelectedMountProperties() + { + if (!mountedDrives) { + return; + } + QTreeWidgetItem *item = mountedDrives->currentItem(); + if (!item) { + return; + } + + WinUaeQtMountEntry entry = mountEntryFromItem(item); + bool accepted = false; + if (entry.kind == QStringLiteral("dir")) { + accepted = showDirectoryMountDialog(&entry, QStringLiteral("Directory Properties")); + } else if (entry.kind == QStringLiteral("hdf")) { + accepted = showHardfileMountDialog(&entry, QStringLiteral("Hardfile Properties")); + } else if (entry.kind == QStringLiteral("cd")) { + accepted = showCdDriveMountDialog(&entry, QStringLiteral("CD Drive Properties")); + } else if (entry.kind == QStringLiteral("tape")) { + accepted = showTapeDriveMountDialog(&entry, QStringLiteral("Tape Drive Properties")); + } + if (accepted) { + updateMountItem(item, entry); + } + } + + bool showDirectoryMountDialog(WinUaeQtMountEntry *entry, const QString &title) + { + if (!entry) { + return false; + } + + QDialog dialog(this); + dialog.setWindowTitle(title); + + QLineEdit *path = new QLineEdit(entry->path); + QLineEdit *device = new QLineEdit(entry->device.isEmpty() ? nextMountDeviceName() : entry->device); + QLineEdit *volume = new QLineEdit(entry->volume); + QSpinBox *bootPri = new QSpinBox; + bootPri->setRange(-128, 127); + bootPri->setValue(entry->bootPri); + QCheckBox *readOnly = new QCheckBox(QStringLiteral("Read-only")); + readOnly->setChecked(entry->readOnly); + QPushButton *selectDirectory = new QPushButton(QStringLiteral("Directory...")); + QPushButton *selectArchive = new QPushButton(QStringLiteral("Archive...")); + QPushButton *eject = new QPushButton(QStringLiteral("Eject")); + + QGridLayout *fields = new QGridLayout; + fields->setColumnStretch(1, 1); + fields->addWidget(label(QStringLiteral("Path:")), 0, 0); + fields->addWidget(path, 0, 1, 1, 3); + fields->addWidget(selectDirectory, 1, 1); + if (selectArchive) { + fields->addWidget(selectArchive, 1, 2); + } + if (eject) { + fields->addWidget(eject, 1, 3); + } + fields->addWidget(label(QStringLiteral("Device:")), 2, 0); + fields->addWidget(device, 2, 1, 1, 3); + fields->addWidget(label(QStringLiteral("Volume:")), 3, 0); + fields->addWidget(volume, 3, 1, 1, 3); + fields->addWidget(label(QStringLiteral("Boot priority:")), 4, 0); + fields->addWidget(bootPri, 4, 1); + fields->addWidget(readOnly, 5, 1, 1, 3); + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QVBoxLayout *root = new QVBoxLayout(&dialog); + root->addLayout(fields); + root->addWidget(buttons); + + auto updateReadOnlyForPath = [this, path, readOnly]() { + const QString text = path->text().trimmed(); + const bool archiveFile = !text.isEmpty() && QFileInfo(expandedPathText(text)).isFile(); + if (archiveFile) { + readOnly->setChecked(true); + } + readOnly->setEnabled(!archiveFile); + }; + + connect(selectDirectory, &QPushButton::clicked, this, [this, path, volume, readOnly, updateReadOnlyForPath]() { + const QString initialPath = fileDialogInitialDirectory(path->text()); + const QString selected = QFileDialog::getExistingDirectory(this, QStringLiteral("Select directory"), initialPath); + if (!selected.isEmpty()) { + path->setText(selected); + if (volume->text().isEmpty()) { + volume->setText(winUaeQtDefaultVolumeName(selected)); + } + readOnly->setChecked(false); + updateReadOnlyForPath(); + } + }); + if (selectArchive) { + connect(selectArchive, &QPushButton::clicked, this, [this, path, volume, updateReadOnlyForPath]() { + const QString initialPath = fileDialogInitialPath(path->text()); + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select directory archive"), initialPath, directoryArchiveFilter()); + if (!selected.isEmpty()) { + path->setText(selected); + if (volume->text().isEmpty()) { + volume->setText(winUaeQtDefaultVolumeName(selected)); + } + updateReadOnlyForPath(); + } + }); + } + if (eject) { + connect(eject, &QPushButton::clicked, this, [path, volume, readOnly]() { + path->clear(); + volume->clear(); + readOnly->setEnabled(true); + }); + } + connect(path, &QLineEdit::editingFinished, this, updateReadOnlyForPath); + updateReadOnlyForPath(); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted || path->text().trimmed().isEmpty()) { + return false; + } + + entry->kind = QStringLiteral("dir"); + entry->device = winUaeQtSanitizedAmigaName(device->text(), nextMountDeviceName(), true); + entry->volume = winUaeQtSanitizedAmigaName(volume->text(), winUaeQtDefaultVolumeName(path->text()), false); + entry->path = path->text().trimmed(); + entry->bootPri = bootPri->value(); + entry->readOnly = readOnly->isChecked() || QFileInfo(expandedPathText(entry->path)).isFile(); + return true; + } + + void appendActiveMountControllerChoices(QStringList *items, WinUaeQtMountControllerBus bus) const + { + if (!items) { + return; + } + for (auto it = expansionBoardStates.constBegin(); it != expansionBoardStates.constEnd(); ++it) { + if (!it.value().present) { + continue; + } + int slot = 0; + const QString boardKey = expansionBoardBaseKey(it.key(), &slot); + const WinUaeQtExpansionBoardCatalogItem *board = expansionBoardChoiceByKey(boardCatalog, boardKey); + if (!board) { + continue; + } + const WinUaeQtMountControllerChoice choice = mountControllerChoiceForBoard(*board, bus); + if (!choice.valid) { + continue; + } + const QString display = choice.display + mountControllerDuplicateDisplaySuffix(slot); + if (!items->contains(display)) { + items->append(display); + } + } + } + + QStringList mountControllerItemsForDialog(bool includeUae, bool includeIde, bool includeScsi, const WinUaeQtMountEntry &entry, const QString &fallback) const + { + QStringList items; + if (includeUae) { + items.append(QStringLiteral("UAE (uaehf.device)")); + } + if (includeIde) { + items.append(QStringLiteral("IDE (Auto)")); + appendActiveMountControllerChoices(&items, MountControllerBusIde); + } + if (includeScsi) { + items.append(QStringLiteral("SCSI (Auto)")); + appendActiveMountControllerChoices(&items, MountControllerBusScsi); + } + + const QString current = mountControllerFamily(boardCatalog, entry, fallback); + if (!current.isEmpty() && !items.contains(current)) { + items.append(current); + } + return items; + } + + bool showHardfileMountDialog(WinUaeQtMountEntry *entry, const QString &title) + { + if (!entry) { + return false; + } + + QDialog dialog(this); + dialog.setWindowTitle(title); + dialog.resize(600, 460); + + QLineEdit *path = new QLineEdit(entry->path); + QLineEdit *geometryFile = new QLineEdit(hardfileTailGeometryFile(*entry)); + QLineEdit *filesys = new QLineEdit(mountControllerParts(entry->hardfileTail).value(0)); + QLineEdit *device = new QLineEdit(entry->device.isEmpty() ? nextMountDeviceName() : entry->device); + QSpinBox *bootPri = new QSpinBox; + bootPri->setRange(-129, 127); + bootPri->setValue(entry->bootPri); + QCheckBox *readWrite = new QCheckBox(QStringLiteral("Read/write")); + readWrite->setChecked(!entry->readOnly); + QCheckBox *autoboot = new QCheckBox(QStringLiteral("Bootable")); + QCheckBox *doNotMount = new QCheckBox(QStringLiteral("Do not mount")); + QCheckBox *rdbMode = new QCheckBox(QStringLiteral("Full drive/RDB mode")); + QCheckBox *manualGeometry = new QCheckBox(QStringLiteral("Manual geometry")); + rdbMode->setChecked(hardfileIsRdb(*entry)); + manualGeometry->setChecked(hardfileHasPhysicalGeometry(*entry)); + + const QStringList geometry = hardfileGeometryParts(*entry); + QSpinBox *surfaces = new QSpinBox; + QSpinBox *sectors = new QSpinBox; + QSpinBox *reserved = new QSpinBox; + QSpinBox *blockSize = new QSpinBox; + for (QSpinBox *spin : { surfaces, sectors, reserved }) { + spin->setRange(0, 1000000000); + } + blockSize->setRange(1, 65536); + sectors->setValue(geometry.value(0).toInt()); + surfaces->setValue(geometry.value(1).toInt()); + reserved->setValue(geometry.value(2).toInt()); + blockSize->setValue(qMax(1, geometry.value(3, QStringLiteral("512")).toInt())); + + const QStringList physicalGeometry = hardfilePhysicalGeometryParts(*entry); + QSpinBox *physicalCylinders = new QSpinBox; + QSpinBox *physicalHeads = new QSpinBox; + QSpinBox *physicalSectors = new QSpinBox; + for (QSpinBox *spin : { physicalCylinders, physicalHeads, physicalSectors }) { + spin->setRange(0, 1000000000); + } + physicalCylinders->setValue(physicalGeometry.value(0).toInt()); + physicalHeads->setValue(physicalGeometry.value(1).toInt()); + physicalSectors->setValue(physicalGeometry.value(2).toInt()); + + QComboBox *controller = combo(mountControllerItemsForDialog(true, true, true, *entry, QStringLiteral("uae0")), mountControllerFamily(boardCatalog, *entry, QStringLiteral("uae0"))); + QSpinBox *controllerUnit = new QSpinBox; + controllerUnit->setRange(0, MaxControllerUnits - 1); + controllerUnit->setValue(mountControllerUnit(*entry, QStringLiteral("uae0"))); + QComboBox *mediaType = combo({ QStringLiteral("HD"), QStringLiteral("CF") }, hardfileTailHasToken(*entry, QStringLiteral("CF")) ? QStringLiteral("CF") : QStringLiteral("HD")); + QComboBox *featureLevel = new QComboBox; + + QPushButton *browse = smallButton(QStringLiteral("...")); + QPushButton *geometryBrowse = smallButton(QStringLiteral("...")); + QPushButton *filesysBrowse = smallButton(QStringLiteral("...")); + + QGridLayout *fields = new QGridLayout; + fields->setColumnStretch(1, 1); + fields->addWidget(label(QStringLiteral("Path:")), 0, 0); + fields->addWidget(path, 0, 1, 1, 2); + fields->addWidget(browse, 0, 3); + fields->addWidget(label(QStringLiteral("Geometry:")), 1, 0); + fields->addWidget(geometryFile, 1, 1, 1, 2); + fields->addWidget(geometryBrowse, 1, 3); + fields->addWidget(label(QStringLiteral("FileSys:")), 2, 0); + fields->addWidget(filesys, 2, 1, 1, 2); + fields->addWidget(filesysBrowse, 2, 3); + fields->addWidget(label(QStringLiteral("Device:")), 3, 0); + fields->addWidget(device, 3, 1); + fields->addWidget(label(QStringLiteral("Boot priority:")), 3, 2); + fields->addWidget(bootPri, 3, 3); + fields->addWidget(readWrite, 4, 1); + fields->addWidget(autoboot, 4, 2); + fields->addWidget(doNotMount, 5, 1); + fields->addWidget(rdbMode, 6, 1); + fields->addWidget(manualGeometry, 6, 2, 1, 2); + + QGridLayout *geometryLayout = new QGridLayout; + geometryLayout->addWidget(label(QStringLiteral("Surfaces:")), 0, 0); + geometryLayout->addWidget(surfaces, 0, 1); + geometryLayout->addWidget(label(QStringLiteral("Sectors:")), 1, 0); + geometryLayout->addWidget(sectors, 1, 1); + geometryLayout->addWidget(label(QStringLiteral("Reserved:")), 2, 0); + geometryLayout->addWidget(reserved, 2, 1); + geometryLayout->addWidget(label(QStringLiteral("Block size:")), 3, 0); + geometryLayout->addWidget(blockSize, 3, 1); + geometryLayout->addWidget(label(QStringLiteral("Physical cyls:")), 0, 2); + geometryLayout->addWidget(physicalCylinders, 0, 3); + geometryLayout->addWidget(label(QStringLiteral("Physical heads:")), 1, 2); + geometryLayout->addWidget(physicalHeads, 1, 3); + geometryLayout->addWidget(label(QStringLiteral("Physical sectors:")), 2, 2); + geometryLayout->addWidget(physicalSectors, 2, 3); + + QGridLayout *controllerLayout = new QGridLayout; + controllerLayout->setColumnStretch(1, 1); + controllerLayout->addWidget(label(QStringLiteral("HD Controller:")), 0, 0); + controllerLayout->addWidget(controller, 0, 1); + controllerLayout->addWidget(label(QStringLiteral("Unit:")), 0, 2); + controllerLayout->addWidget(controllerUnit, 0, 3); + controllerLayout->addWidget(label(QStringLiteral("Type:")), 1, 0); + controllerLayout->addWidget(mediaType, 1, 1); + controllerLayout->addWidget(label(QStringLiteral("Feature level:")), 1, 2); + controllerLayout->addWidget(featureLevel, 1, 3); + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QVBoxLayout *root = new QVBoxLayout(&dialog); + root->addWidget(groupBox(QStringLiteral("Settings"), fields)); + root->addWidget(groupBox(QStringLiteral("Geometry"), geometryLayout)); + root->addWidget(groupBox(QStringLiteral("HD Controller"), controllerLayout)); + root->addWidget(buttons); + + auto updateBootChecks = [bootPri, autoboot, doNotMount]() { + QSignalBlocker blockAutoboot(autoboot); + QSignalBlocker blockDoNotMount(doNotMount); + autoboot->setChecked(bootPri->value() > -128); + doNotMount->setChecked(bootPri->value() <= -129); + }; + auto updatePhysicalControls = [manualGeometry, physicalCylinders, physicalHeads, physicalSectors]() { + const bool enabled = manualGeometry->isChecked(); + physicalCylinders->setEnabled(enabled); + physicalHeads->setEnabled(enabled); + physicalSectors->setEnabled(enabled); + }; + auto setFeatureItems = [this, controller, featureLevel, mediaType](const QString &preferred) { + const WinUaeQtMountControllerBus bus = mountControllerBusFromDisplay(boardCatalog, controller->currentText()); + QSignalBlocker blocker(featureLevel); + featureLevel->clear(); + featureLevel->addItem(QStringLiteral("Default")); + if (bus == MountControllerBusIde) { + featureLevel->addItems({ QStringLiteral("ATA-1"), QStringLiteral("ATA-2+"), QStringLiteral("ATA-2+ Strict") }); + } else if (bus == MountControllerBusScsi) { + featureLevel->addItems({ QStringLiteral("SCSI-1"), QStringLiteral("SCSI-2"), QStringLiteral("SASI"), QStringLiteral("SASI CHS") }); + } + const int index = featureLevel->findText(preferred); + featureLevel->setCurrentIndex(index >= 0 ? index : 0); + featureLevel->setEnabled(bus == MountControllerBusIde || bus == MountControllerBusScsi); + mediaType->setEnabled(bus == MountControllerBusIde); + }; + auto updateNativeDriveControls = [path, readWrite, browse]() { + const bool nativeDrive = path->text().trimmed().startsWith(QLatin1Char(':')); + if (nativeDrive) { + readWrite->setChecked(false); + } + readWrite->setEnabled(!nativeDrive); + browse->setEnabled(!nativeDrive); + }; + + updateBootChecks(); + updatePhysicalControls(); + updateControllerUnitRange(controllerUnit, controller->currentText()); + setFeatureItems(hardfileFeatureText(boardCatalog, *entry, controller->currentText())); + updateNativeDriveControls(); + + connect(browse, &QPushButton::clicked, this, [this, path]() { + const QString initialPath = fileDialogInitialPath(path->text()); + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select hardfile"), initialPath, hardfileImageFilter()); + if (!selected.isEmpty()) { + path->setText(selected); + } + }); + connect(geometryBrowse, &QPushButton::clicked, this, [this, geometryFile]() { + const QString initialPath = fileDialogInitialPath(geometryFile->text()); + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select geometry file"), initialPath, QStringLiteral("Geometry files (*.geo);;All files (*)")); + if (!selected.isEmpty()) { + geometryFile->setText(selected); + } + }); + connect(filesysBrowse, &QPushButton::clicked, this, [this, filesys]() { + const QString initialPath = fileDialogInitialPath(filesys->text()); + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select filesystem"), initialPath, QStringLiteral("Filesystem files (*.fs *.filesystem);;All files (*)")); + if (!selected.isEmpty()) { + filesys->setText(selected); + } + }); + connect(bootPri, QOverload::of(&QSpinBox::valueChanged), this, updateBootChecks); + connect(autoboot, &QCheckBox::toggled, this, [bootPri, doNotMount](bool checked) { + QSignalBlocker blocker(doNotMount); + if (checked) { + bootPri->setValue(0); + doNotMount->setChecked(false); + } else if (bootPri->value() > -128) { + bootPri->setValue(-128); + } + }); + connect(doNotMount, &QCheckBox::toggled, this, [bootPri, autoboot](bool checked) { + QSignalBlocker blocker(autoboot); + if (checked) { + bootPri->setValue(-129); + autoboot->setChecked(false); + } else if (bootPri->value() <= -129) { + bootPri->setValue(-128); + } + }); + connect(rdbMode, &QCheckBox::toggled, this, [sectors, surfaces, reserved, blockSize, device, filesys, bootPri](bool checked) { + if (checked) { + sectors->setValue(0); + surfaces->setValue(0); + reserved->setValue(0); + blockSize->setValue(512); + device->clear(); + filesys->clear(); + bootPri->setValue(0); + } else if (sectors->value() == 0 && surfaces->value() == 0 && reserved->value() == 0) { + sectors->setValue(32); + surfaces->setValue(1); + reserved->setValue(2); + blockSize->setValue(512); + } + }); + connect(manualGeometry, &QCheckBox::toggled, this, updatePhysicalControls); + connect(controller, &QComboBox::currentTextChanged, this, [this, controllerUnit, setFeatureItems](const QString &text) { + updateControllerUnitRange(controllerUnit, text); + setFeatureItems(QStringLiteral("Default")); + }); + connect(path, &QLineEdit::editingFinished, this, updateNativeDriveControls); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted || path->text().trimmed().isEmpty()) { + return false; + } + + entry->kind = QStringLiteral("hdf"); + entry->device = winUaeQtSanitizedAmigaName(device->text(), nextMountDeviceName(), true); + entry->path = path->text().trimmed(); + entry->bootPri = bootPri->value(); + entry->readOnly = !readWrite->isChecked() || entry->path.startsWith(QLatin1Char(':')); + entry->hardfileGeometry = QStringLiteral("%1,%2,%3,%4") + .arg(sectors->value()) + .arg(surfaces->value()) + .arg(reserved->value()) + .arg(blockSize->value()); + + QStringList tailFields; + tailFields.append(filesys->text().trimmed()); + tailFields.append(mountControllerConfigValue(boardCatalog, controller->currentText(), controllerUnit->value())); + if (manualGeometry->isChecked() || !geometryFile->text().trimmed().isEmpty()) { + tailFields.append(QString::number(physicalCylinders->value())); + tailFields.append(QStringLiteral("%1/%2/%3") + .arg(physicalCylinders->value()) + .arg(physicalHeads->value()) + .arg(physicalSectors->value())); + if (!geometryFile->text().trimmed().isEmpty()) { + tailFields.append(geometryFile->text().trimmed()); + } + } + if (mountControllerBusFromDisplay(boardCatalog, controller->currentText()) == MountControllerBusIde && mediaType->currentText() == QStringLiteral("CF")) { + tailFields.append(QStringLiteral("CF")); + } + const QString featureToken = hardfileFeatureToken(featureLevel->currentText()); + if (!featureToken.isEmpty()) { + tailFields.append(featureToken); + } + tailFields.append(hardfilePreservedTailExtras(*entry)); + entry->hardfileTail = winUaeQtConfigJoinFields(tailFields); + return true; + } + + void updateControllerUnitRange(QSpinBox *unit, const QString &family) + { + if (!unit) { + return; + } + unit->setMaximum(mountControllerMaxUnit(boardCatalog, family)); + } + + bool showCdDriveMountDialog(WinUaeQtMountEntry *entry, const QString &title) + { + if (!entry) { + return false; + } + + QDialog dialog(this); + dialog.setWindowTitle(title); + + QSpinBox *cdUnit = new QSpinBox; + cdUnit->setRange(0, MaxControllerUnits - 1); + cdUnit->setValue(qBound(0, entry->emuUnit, MaxControllerUnits - 1)); + QComboBox *controller = combo(mountControllerItemsForDialog(false, true, true, *entry, QStringLiteral("ide0")), mountControllerFamily(boardCatalog, *entry, QStringLiteral("ide0"))); + QSpinBox *controllerUnit = new QSpinBox; + controllerUnit->setRange(0, MaxControllerUnits - 1); + controllerUnit->setValue(mountControllerUnit(*entry, QStringLiteral("ide0"))); + updateControllerUnitRange(controllerUnit, controller->currentText()); + + QGridLayout *fields = new QGridLayout; + fields->setColumnStretch(1, 1); + fields->addWidget(label(QStringLiteral("CD unit:")), 0, 0); + fields->addWidget(cdUnit, 0, 1); + fields->addWidget(label(QStringLiteral("Controller:")), 1, 0); + fields->addWidget(controller, 1, 1); + fields->addWidget(label(QStringLiteral("Controller unit:")), 2, 0); + fields->addWidget(controllerUnit, 2, 1); + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QVBoxLayout *root = new QVBoxLayout(&dialog); + root->addLayout(fields); + root->addWidget(buttons); + + connect(controller, &QComboBox::currentTextChanged, this, [this, controllerUnit](const QString &text) { + updateControllerUnitRange(controllerUnit, text); + }); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted) { + return false; + } + + entry->kind = QStringLiteral("cd"); + entry->device.clear(); + entry->volume = QStringLiteral("CD"); + entry->emuUnit = cdUnit->value(); + entry->readOnly = true; + entry->bootPri = 0; + entry->hardfileGeometry = QStringLiteral("0,0,0,2048"); + entry->hardfileTail = mountTailWithController(*entry, mountControllerConfigValue(boardCatalog, controller->currentText(), controllerUnit->value())); + return true; + } + + bool showTapeDriveMountDialog(WinUaeQtMountEntry *entry, const QString &title) + { + if (!entry) { + return false; + } + + QDialog dialog(this); + dialog.setWindowTitle(title); + + QLineEdit *path = new QLineEdit(entry->path); + QSpinBox *tapeUnit = new QSpinBox; + tapeUnit->setRange(0, MaxControllerUnits - 1); + tapeUnit->setValue(qBound(0, entry->emuUnit, MaxControllerUnits - 1)); + QComboBox *controller = combo(mountControllerItemsForDialog(true, true, true, *entry, QStringLiteral("uae0")), mountControllerFamily(boardCatalog, *entry, QStringLiteral("uae0"))); + QSpinBox *controllerUnit = new QSpinBox; + controllerUnit->setRange(0, MaxControllerUnits - 1); + controllerUnit->setValue(mountControllerUnit(*entry, QStringLiteral("uae0"))); + updateControllerUnitRange(controllerUnit, controller->currentText()); + QCheckBox *readWrite = new QCheckBox(QStringLiteral("Read/write")); + readWrite->setChecked(!entry->readOnly); + QPushButton *selectFile = new QPushButton(QStringLiteral("File...")); + QPushButton *selectDirectory = new QPushButton(QStringLiteral("Directory...")); + QPushButton *eject = new QPushButton(QStringLiteral("Eject")); + + QGridLayout *fields = new QGridLayout; + fields->setColumnStretch(1, 1); + fields->addWidget(label(QStringLiteral("Path:")), 0, 0); + fields->addWidget(path, 0, 1, 1, 3); + fields->addWidget(selectFile, 1, 1); + fields->addWidget(selectDirectory, 1, 2); + fields->addWidget(eject, 1, 3); + fields->addWidget(label(QStringLiteral("Tape unit:")), 2, 0); + fields->addWidget(tapeUnit, 2, 1); + fields->addWidget(label(QStringLiteral("Controller:")), 3, 0); + fields->addWidget(controller, 3, 1, 1, 3); + fields->addWidget(label(QStringLiteral("Controller unit:")), 4, 0); + fields->addWidget(controllerUnit, 4, 1); + fields->addWidget(readWrite, 5, 1, 1, 3); + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QVBoxLayout *root = new QVBoxLayout(&dialog); + root->addLayout(fields); + root->addWidget(buttons); + + connect(selectFile, &QPushButton::clicked, this, [this, path]() { + const QString initialPath = fileDialogInitialPath(path->text()); + const QString selected = QFileDialog::getOpenFileName(this, QStringLiteral("Select tape image"), initialPath, QStringLiteral("Tape images (*.tap *.raw);;All files (*)")); + if (!selected.isEmpty()) { + path->setText(selected); + } + }); + connect(selectDirectory, &QPushButton::clicked, this, [this, path]() { + const QString initialPath = fileDialogInitialDirectory(path->text()); + const QString selected = QFileDialog::getExistingDirectory(this, QStringLiteral("Select tape directory"), initialPath); + if (!selected.isEmpty()) { + path->setText(selected); + } + }); + connect(eject, &QPushButton::clicked, path, &QLineEdit::clear); + connect(controller, &QComboBox::currentTextChanged, this, [this, controllerUnit](const QString &text) { + updateControllerUnitRange(controllerUnit, text); + }); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted) { + return false; + } + + entry->kind = QStringLiteral("tape"); + entry->device.clear(); + entry->volume = QStringLiteral("TAPE"); + entry->path = path->text().trimmed(); + entry->emuUnit = tapeUnit->value(); + entry->readOnly = !readWrite->isChecked(); + entry->bootPri = 0; + entry->hardfileGeometry = QStringLiteral("0,0,0,512"); + entry->hardfileTail = mountTailWithController(*entry, mountControllerConfigValue(boardCatalog, controller->currentText(), controllerUnit->value())); + return true; + } + + WinUaeQtConfig::OrderedSettings currentMountSettings() const + { + WinUaeQtConfig::OrderedSettings settings; + if (!mountedDrives) { + return settings; + } + QSet emitted; + for (int i = 0; i < mountedDrives->topLevelItemCount(); i++) { + const WinUaeQtMountEntry entry = mountEntryFromItem(mountedDrives->topLevelItem(i)); + const QString key = mountEntryConfigKey(entry, i); + const QString value = mountEntryConfigValue(entry); + if (key.isEmpty() || value.isEmpty()) { + continue; + } + const QString duplicateKey = mountEntryConfigKey(entry) + QLatin1Char('\n') + value; + if (emitted.contains(duplicateKey)) { + continue; + } + emitted.insert(duplicateKey); + settings.append({ key, value }); + } + return settings; + } + + void syncQuickDriveToFloppy(int drive) + { + if (drive < 0 || drive >= 2 || !dfPath[drive] || !quickDfPath[drive]) { + return; + } + setCheckBoxIfChanged(dfEnable[drive], quickDfEnable[drive]->isChecked()); + setComboTextIfChanged(dfType[drive], quickDfType[drive]->currentText()); + if (dfPath[drive]->currentText() != quickDfPath[drive]->currentText()) { + setPathComboText(dfPath[drive], quickDfPath[drive]->currentText()); + } + setCheckBoxIfChanged(dfWriteProtect[drive], quickDfWriteProtect[drive]->isChecked()); + } + + void syncFloppyDriveToQuick(int drive) + { + if (drive < 0 || drive >= 2 || !dfPath[drive] || !quickDfPath[drive]) { + return; + } + setCheckBoxIfChanged(quickDfEnable[drive], dfEnable[drive]->isChecked()); + setComboTextIfChanged(quickDfType[drive], dfType[drive]->currentText()); + if (quickDfPath[drive]->currentText() != dfPath[drive]->currentText()) { + setPathComboText(quickDfPath[drive], dfPath[drive]->currentText()); + } + setCheckBoxIfChanged(quickDfWriteProtect[drive], dfWriteProtect[drive]->isChecked()); + } + + int rtgModeMask() const + { + int mask = 0; + mask |= rtgColorDepthMask(rtg8Bit->currentText()); + mask |= rtgColorDepthMask(rtg16Bit->currentText()); + mask |= rtgColorDepthMask(rtg24Bit->currentText()); + mask |= rtgColorDepthMask(rtg32Bit->currentText()); + return mask ? mask : RtgDefaultModeMask; + } + + QString rtgOptionsValue() const + { + QStringList parts; + if (!rtgAutoswitch->isChecked()) { + parts.append(QStringLiteral("noautoswitch")); + } + return parts.join(QLatin1Char(',')); + } + + void applyRtgScaleValue(const QString &value) + { + const QString lower = value.toLower(); + rtgScale->setChecked(lower == QStringLiteral("scale")); + rtgCenter->setChecked(lower == QStringLiteral("center")); + rtgIntegerScale->setChecked(lower == QStringLiteral("integer")); + } + + void applyRtgOptionsValue(const QString &value) + { + bool autoswitch = true; + for (const QString &field : winUaeQtConfigFieldList(value)) { + const QString trimmed = field.trimmed(); + if (trimmed.compare(QStringLiteral("noautoswitch"), Qt::CaseInsensitive) == 0) { + autoswitch = false; + } else if (trimmed.compare(QStringLiteral("autoswitch"), Qt::CaseInsensitive) == 0) { + autoswitch = true; + } + } + rtgAutoswitch->setChecked(autoswitch); + rtgInitialMonitor->setChecked(false); + rtgMonitor->setCurrentText(QStringLiteral("1")); + } + + WinUaeQtConfig::Settings currentSettings() const + { + WinUaeQtConfig::Settings settings; + const int cpu = selectedCpuModel(); + const int fpu = fpuModelConfigValue(cpu); + const bool z3Rtg = rtgNeeds32BitAddressSpace(); + const bool cpu24BitAddressing = !z3Rtg && cpu24Bit && cpu24Bit->isChecked(); + const int requestedJitCacheSize = jitCache ? jitCacheSizeFromPosition(jitCache->value()) : 0; + const bool jitActive = jit && jit->isChecked() && unixJitBackendAvailable() + && cpu >= 68020 && !cpu24BitAddressing && requestedJitCacheSize > 0; + if (configDescription && !configDescription->text().trimmed().isEmpty()) { + settings.insert(QStringLiteral("config_description"), configDescription->text().trimmed()); + } + if (quickstartMode && quickstartMode->isChecked()) { + const QString quickstart = quickstartConfigValue(); + if (!quickstart.isEmpty()) { + settings.insert(QStringLiteral("quickstart"), quickstart); + } + } + if (romsPath && !romsPath->text().trimmed().isEmpty()) { + settings.insert(QStringLiteral("unix.rom_path"), romsPath->text().trimmed()); + } + insertLineEditSetting(settings, QStringLiteral("unix.config_path"), configsPath); + insertLineEditSetting(settings, QStringLiteral("unix.nvram_path"), nvramPath); + insertLineEditSetting(settings, QStringLiteral("unix.screenshot_path"), screenshotsPath); + insertLineEditSetting(settings, QStringLiteral("statefile_path"), stateFilesPath); + insertLineEditSetting(settings, QStringLiteral("unix.video_path"), videosPath); + insertLineEditSetting(settings, QStringLiteral("unix.saveimage_path"), saveImagesPath); + insertLineEditSetting(settings, QStringLiteral("unix.rip_path"), ripsPath); + insertLineEditSetting(settings, QStringLiteral("unix.data_path"), dataPath); + if (pathDefaultType) { + settings.insert(QStringLiteral("unix.ui.path_mode"), pathDefaultType->currentText()); + } + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.recursive_roms"), recursiveRoms); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.cache_configurations"), cacheConfigurations); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.cache_boxart"), cacheBoxArt); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.saveimage_original_path"), saveImageOriginalPath); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.relative_paths"), relativePaths); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.portable_mode"), portableMode); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.full_logging"), fullLogging); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.log_window"), logWindow); + if (miscGuiSize) { + settings.insert(QStringLiteral("unix.ui.gui_scale"), miscGuiSize->currentText()); + } + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.gui_resize"), miscGuiResize); + insertCheckBoxSetting(settings, QStringLiteral("unix.ui.gui_fullscreen"), miscGuiFullscreen); + settings.insert(QStringLiteral("unix.ui.gui_dark_mode"), guiDarkModeConfigValue()); + if (!miscGuiFontConfig.isEmpty()) { + settings.insert(QStringLiteral("unix.ui.gui_font"), miscGuiFontConfig); + } + insertLineEditSetting(settings, QStringLiteral("unix.output_file"), outputFile); + settings.insert(QStringLiteral("unix.output_audio_codec"), + outputAudioCodec && outputAudioCodec->currentIndex() == 1 ? QStringLiteral("wav") : QStringLiteral("pcm")); + settings.insert(QStringLiteral("unix.output_video_codec"), QStringLiteral("dib")); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_audio"), outputAudio); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_video"), outputVideo); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_enabled"), outputEnabled); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_frame_limiter_disabled"), outputFrameLimiter); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_original_size"), outputOriginalSize); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_no_sound"), outputNoSound); + insertCheckBoxSetting(settings, QStringLiteral("unix.output_no_sound_sync"), outputNoSoundSync); + insertCheckBoxSetting(settings, QStringLiteral("unix.screenshot_original_size"), screenshotOriginalSize); + insertCheckBoxSetting(settings, QStringLiteral("unix.screenshot_paletted"), screenshotPaletted); + insertCheckBoxSetting(settings, QStringLiteral("unix.screenshot_clip"), screenshotClip); + insertCheckBoxSetting(settings, QStringLiteral("unix.screenshot_auto"), screenshotAuto); + settings.insert(QStringLiteral("kickstart_rom_file"), romFile->currentText()); + if (!extendedRomFile->currentText().isEmpty()) { + settings.insert(QStringLiteral("kickstart_ext_rom_file"), extendedRomFile->currentText()); + } + if (!cartFile->currentText().isEmpty()) { + settings.insert(QStringLiteral("cart_file"), cartFile->currentText()); + } + if (!flashFile->text().isEmpty()) { + settings.insert(QStringLiteral("flash_file"), flashFile->text()); + } + if (!rtcFile->text().isEmpty()) { + settings.insert(QStringLiteral("rtc_file"), rtcFile->text()); + } + settings.insert(QStringLiteral("maprom"), mapRom->isChecked() ? QStringLiteral("0x0f000000") : QStringLiteral("0x0")); + settings.insert(QStringLiteral("kickshifter"), kickShifter->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + if (uaeBoardType->currentText() == QStringLiteral("ROM disabled")) { + settings.insert(QStringLiteral("boot_rom_uae"), QStringLiteral("disabled")); + settings.insert(QStringLiteral("uaeboard"), QStringLiteral("disabled")); + } else { + settings.insert(QStringLiteral("boot_rom_uae"), QStringLiteral("automatic")); + settings.insert(QStringLiteral("uaeboard"), uaeBoardConfigValue(uaeBoardType->currentText())); + } + for (int i = 0; i < MaxRomBoards; i++) { + WinUaeQtRomBoard board = customRomBoards.value(i); + if (i == currentCustomRomBoard && customRomStart && customRomEnd && customRomFile) { + board.start = customRomStart->text(); + board.end = customRomEnd->text(); + board.path = customRomFile->text(); + } + const QString value = romBoardConfigValue(board); + if (!value.isEmpty()) { + settings.insert(romBoardKey(i), value); + } + } + settings.insert(QStringLiteral("board_custom_order"), hardwareCustomBoardOrder->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + for (int i = 0; i < 4; i++) { + const int driveType = dfEnable[i]->isChecked() ? floppyTypeConfigValue(dfType[i]->currentText()) : -1; + settings.insert(QStringLiteral("floppy%1type").arg(i), QString::number(driveType)); + settings.insert(QStringLiteral("floppy%1wp").arg(i), dfWriteProtect[i]->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + if (driveType >= 0 && !dfPath[i]->currentText().isEmpty()) { + settings.insert(QStringLiteral("floppy%1").arg(i), dfPath[i]->currentText()); + } + } + settings.insert(QStringLiteral("nr_floppies"), QString::number(enabledFloppyCount())); + settings.insert(QStringLiteral("floppy_speed"), QString::number(floppySpeedConfigValue(floppySpeed->value()))); + for (int i = 0; i < MaxDiskSwapperSlots; i++) { + const QString path = diskSwapperPathAt(i); + if (!path.isEmpty()) { + settings.insert(QStringLiteral("diskimage%1").arg(i), path); + } + } + settings.insert(QStringLiteral("chipset"), chipsetConfigValue(chipset->currentText())); + settings.insert(QStringLiteral("chipset_compatible"), chipsetCompatible->currentText()); + settings.insert(QStringLiteral("ntsc"), chipsetNtsc->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("immediate_blits"), immediateBlits->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("waiting_blits"), waitingBlits->isChecked() ? QStringLiteral("automatic") : QStringLiteral("disabled")); + settings.insert(QStringLiteral("collision_level"), QStringList({ QStringLiteral("none"), QStringLiteral("sprites"), QStringLiteral("playfields"), QStringLiteral("full") }).value(collisionButtons->checkedId(), QStringLiteral("full"))); + settings.insert(QStringLiteral("display_optimizations"), displayOptimizationConfigValue(displayOptimization->currentText())); + settings.insert(QStringLiteral("hvcsync"), + configChoiceValue(hvSyncChoices, int(sizeof(hvSyncChoices) / sizeof(hvSyncChoices[0])), chipsetSyncMode->currentText())); + settings.insert(QStringLiteral("genlock"), genlockConnected->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("genlockmode"), + configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText())); + settings.insert(QStringLiteral("genlock_mix"), QString::number(genlockMixConfigValue())); + settings.insert(QStringLiteral("genlock_alpha"), genlockAlpha->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("genlock_aspect"), genlockKeepAspect->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + QString imagePath = genlockImagePath; + QString videoPath = genlockVideoPath; + const QString selectedGenlockMode = configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText()); + if (genlockModeUsesImageFile(selectedGenlockMode)) { + imagePath = genlockFile->currentText().trimmed(); + } else if (genlockModeUsesVideoFile(selectedGenlockMode)) { + videoPath = genlockFile->currentText().trimmed(); + } + if (!imagePath.isEmpty()) { + settings.insert(QStringLiteral("genlock_image"), imagePath); + } + if (!videoPath.isEmpty()) { + settings.insert(QStringLiteral("genlock_video"), videoPath); + } + const QString keyboardType = configChoiceValue(keyboardModeChoices, int(sizeof(keyboardModeChoices) / sizeof(keyboardModeChoices[0])), keyboardMode->currentText()); + settings.insert(QStringLiteral("keyboard_connected"), keyboardType == QStringLiteral("disconnected") ? QStringLiteral("false") : QStringLiteral("true")); + settings.insert(QStringLiteral("keyboard_type"), keyboardType); + settings.insert(QStringLiteral("keyboard_nkro"), keyboardNkro->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("ciaatod"), advancedRadioValue(advancedCiaTodButtons, ciaTodChoices, int(sizeof(ciaTodChoices) / sizeof(ciaTodChoices[0])))); + settings.insert(QStringLiteral("rtc"), advancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])))); + settings.insert(QStringLiteral("chipset_rtc_adjust"), advancedRtcAdjust->text().trimmed().isEmpty() ? QStringLiteral("0") : advancedRtcAdjust->text().trimmed()); + for (const AdvancedCheckChoice &choice : advancedCheckChoices) { + const QString key = QString::fromLatin1(choice.key); + settings.insert(key, advancedCheck(key) ? QStringLiteral("true") : QStringLiteral("false")); + } + settings.insert(QStringLiteral("ide"), advancedIdeA600A1200->isChecked() + ? QStringLiteral("a600/a1200") + : (advancedIdeA4000->isChecked() ? QStringLiteral("a4000") : QStringLiteral("none"))); + settings.insert(QStringLiteral("scsi_a3000"), advancedScsiA3000->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("scsi_a4000t"), advancedScsiA4000T->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("ciaa_type"), advancedCia391078->isChecked() ? QStringLiteral("391078-01") : QStringLiteral("default")); + settings.insert(QStringLiteral("ciab_type"), advancedCia391078->isChecked() ? QStringLiteral("391078-01") : QStringLiteral("default")); + settings.insert(QStringLiteral("unmapped_address_space"), + configChoiceValue(unmappedAddressChoices, int(sizeof(unmappedAddressChoices) / sizeof(unmappedAddressChoices[0])), advancedUnmappedAddress->currentText())); + settings.insert(QStringLiteral("eclocksync"), + configChoiceValue(ciaSyncChoices, int(sizeof(ciaSyncChoices) / sizeof(ciaSyncChoices[0])), advancedCiaSync->currentText())); + settings.insert(QStringLiteral("fatgary"), chipsetRevisionConfigValue(advancedFatGary, advancedFatGaryRevision, 0x00)); + settings.insert(QStringLiteral("ramsey"), chipsetRevisionConfigValue(advancedRamsey, advancedRamseyRevision, 0x0f)); + settings.insert(QStringLiteral("agnusmodel"), + configChoiceValue(agnusModelChoices, int(sizeof(agnusModelChoices) / sizeof(agnusModelChoices[0])), advancedAgnusModel->currentText())); + settings.insert(QStringLiteral("agnussize"), + configChoiceValue(agnusSizeChoices, int(sizeof(agnusSizeChoices) / sizeof(agnusSizeChoices[0])), advancedAgnusSize->currentText())); + settings.insert(QStringLiteral("denisemodel"), + configChoiceValue(deniseModelChoices, int(sizeof(deniseModelChoices) / sizeof(deniseModelChoices[0])), advancedDeniseModel->currentText())); + if (jitActive) { + settings.insert(QStringLiteral("cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("cpu_cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("cpu_memory_cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("blitter_cycle_exact"), QStringLiteral("false")); + } else if (chipsetCycleExact->isChecked()) { + settings.insert(QStringLiteral("cycle_exact"), QStringLiteral("true")); + settings.insert(QStringLiteral("cpu_cycle_exact"), QStringLiteral("true")); + settings.insert(QStringLiteral("cpu_memory_cycle_exact"), QStringLiteral("true")); + settings.insert(QStringLiteral("blitter_cycle_exact"), QStringLiteral("true")); + } else if (chipsetCycleExactMemory->isChecked()) { + settings.insert(QStringLiteral("cycle_exact"), QStringLiteral("memory")); + settings.insert(QStringLiteral("cpu_cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("cpu_memory_cycle_exact"), QStringLiteral("true")); + settings.insert(QStringLiteral("blitter_cycle_exact"), QStringLiteral("true")); + } else { + settings.insert(QStringLiteral("cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("cpu_cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("cpu_memory_cycle_exact"), QStringLiteral("false")); + settings.insert(QStringLiteral("blitter_cycle_exact"), QStringLiteral("false")); + } + settings.insert(QStringLiteral("cpu_model"), QString::number(cpu)); + settings.insert(QStringLiteral("cpu_speed"), cpuSpeedButtons->checkedId() == 1 ? QStringLiteral("max") : QStringLiteral("real")); + if (cpuSpeed->value() != 0) { + settings.insert(QStringLiteral("cpu_throttle"), QString::number(cpuSpeed->value() * 100.0, 'f', 1)); + } + settings.insert(QStringLiteral("cpu_compatible"), moreCompatible->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + if (fpu) { + settings.insert(QStringLiteral("fpu_model"), QString::number(fpu)); + } + settings.insert(QStringLiteral("cpu_24bit_addressing"), cpu24BitAddressing ? QStringLiteral("true") : QStringLiteral("false")); + if (mmuButtons->checkedId() == 1 && cpu >= 68030) { + settings.insert(QStringLiteral("mmu_model"), QString::number(cpu)); + } else if (mmuButtons->checkedId() == 2 && cpu >= 68030) { + settings.insert(QStringLiteral("mmu_model"), QStringLiteral("68ec0%1").arg(cpu % 100, 2, 10, QLatin1Char('0'))); + } + settings.insert(QStringLiteral("cpu_no_unimplemented"), cpuUnimplemented->isChecked() ? QStringLiteral("false") : QStringLiteral("true")); + settings.insert(QStringLiteral("fpu_no_unimplemented"), fpuUnimplemented->isChecked() ? QStringLiteral("false") : QStringLiteral("true")); + settings.insert(QStringLiteral("fpu_strict"), fpuStrict->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("fpu_softfloat"), fpuMode->currentText() == QStringLiteral("Softfloat (80-bit)") ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("fpu_msvc_long_double"), fpuMode->currentText() == QStringLiteral("Host (80-bit)") ? QStringLiteral("true") : QStringLiteral("false")); + if (cpuFrequency->currentText() == QStringLiteral("Custom")) { + bool ok = false; + const double mhz = cpuFrequencyCustom->text().toDouble(&ok); + if (ok && mhz >= 1.0 && mhz < 99.0) { + settings.insert(QStringLiteral("cpu_frequency"), QString::number(qRound64(mhz * 1000000.0))); + } + } else { + settings.insert(QStringLiteral("cpu_multiplier"), QString::number(cpuMultiplierValue(cpuFrequency->currentText()))); + } + settings.insert(QStringLiteral("chipmem_size"), QString::number(chipMemConfigValue())); + if (z2Fast->currentText() != QStringLiteral("None")) { + settings.insert(QStringLiteral("fastmem_size"), QString::number(megabytesFromText(z2Fast->currentText()))); + } + const int slow = slowMemConfigValue(); + if (slow) { + settings.insert(QStringLiteral("bogomem_size"), QString::number(slow)); + } + if (z3Fast->currentText() != QStringLiteral("None")) { + settings.insert(QStringLiteral("z3mem_size"), QString::number(megabytesFromText(z3Fast->currentText()))); + } + if (z3ChipMem->currentText() != QStringLiteral("None")) { + settings.insert(QStringLiteral("megachipmem_size"), QString::number(megabytesFromText(z3ChipMem->currentText()))); + } + if (processorSlotMem->currentText() != QStringLiteral("None")) { + settings.insert(QStringLiteral("mbresmem_size"), QString::number(megabytesFromText(processorSlotMem->currentText()))); + } + settings.insert(QStringLiteral("z3mapping"), + configChoiceValue(z3MappingChoices, int(sizeof(z3MappingChoices) / sizeof(z3MappingChoices[0])), z3Mapping->currentText())); + settings.insert(QStringLiteral("cachesize"), QString::number(jitActive ? requestedJitCacheSize : 0)); + settings.insert(QStringLiteral("compfpu"), jitActive && jitFpu->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("comp_constjump"), jitConstJump->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("comp_flushmode"), jitHardFlush->isChecked() ? QStringLiteral("hard") : QStringLiteral("soft")); + settings.insert(QStringLiteral("comp_nf"), jitNoFlags->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("comp_catchfault"), jitCatchFault->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const QString trust = jitTrust->checkedId() == 1 ? QStringLiteral("indirect") : QStringLiteral("direct"); + settings.insert(QStringLiteral("comp_trustbyte"), trust); + settings.insert(QStringLiteral("comp_trustword"), trust); + settings.insert(QStringLiteral("comp_trustlong"), trust); + settings.insert(QStringLiteral("comp_trustnaddr"), trust); + settings.insert(QStringLiteral("unix.soundcard"), QString::number(qMax(0, soundDevice->currentIndex()))); + const QString soundDeviceConfigName = soundDevice->currentData().toString(); + if (!soundDeviceConfigName.isEmpty()) { + settings.insert(QStringLiteral("unix.soundcardname"), soundDeviceConfigName); + } + settings.insert(QStringLiteral("sound_output"), soundOutputConfigValue(soundOutputButtons->checkedId())); + settings.insert(QStringLiteral("sound_auto"), soundAutomatic->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("sound_volume"), QString::number(100 - soundMasterVolume->value())); + settings.insert(QStringLiteral("sound_volume_paula"), QString::number(soundVolumeAttenuationValue(0))); + settings.insert(QStringLiteral("sound_volume_cd"), QString::number(soundVolumeAttenuationValue(1))); + settings.insert(QStringLiteral("sound_volume_ahi"), QString::number(soundVolumeAttenuationValue(2))); + settings.insert(QStringLiteral("sound_volume_midi"), QString::number(soundVolumeAttenuationValue(3))); + settings.insert(QStringLiteral("sound_volume_genlock"), QString::number(soundVolumeAttenuationValue(4))); + settings.insert(QStringLiteral("sound_max_buff"), QString::number(soundBufferSizeFromIndex(soundBufferSize->value()))); + settings.insert(QStringLiteral("sound_channels"), soundChannelConfigValue(soundChannels->currentText())); + if (soundChannels->currentText() == QStringLiteral("Mono")) { + settings.insert(QStringLiteral("sound_stereo_separation"), QStringLiteral("0")); + settings.insert(QStringLiteral("sound_stereo_mixing_delay"), QStringLiteral("0")); + } else { + settings.insert(QStringLiteral("sound_stereo_separation"), QString::number(qBound(0, 10 - soundStereoSeparation->currentIndex(), 10))); + settings.insert(QStringLiteral("sound_stereo_mixing_delay"), soundStereoDelay->currentText() == QStringLiteral("-") ? QStringLiteral("0") : soundStereoDelay->currentText()); + } + settings.insert(QStringLiteral("sound_frequency"), QString::number(qBound(8000, soundFrequency->currentText().toInt(), 768000))); + settings.insert(QStringLiteral("sound_interpol"), soundInterpolationConfigValue(soundInterpolation->currentText())); + settings.insert(QStringLiteral("sound_filter"), soundFilterConfigValue(soundFilter->currentText())); + settings.insert(QStringLiteral("sound_filter_type"), soundFilterTypeConfigValue(soundFilter->currentText())); + const int swapIndex = soundSwap->currentIndex(); + settings.insert(QStringLiteral("sound_stereo_swap_paula"), (swapIndex & 1) ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("sound_stereo_swap_ahi"), (swapIndex & 2) ? QStringLiteral("true") : QStringLiteral("false")); + for (int i = 0; i < FloppySoundDriveCount; i++) { + settings.insert(QStringLiteral("floppy%1sound").arg(i), QString::number(floppySoundTypeConfigValue(i))); + settings.insert(QStringLiteral("floppy%1soundvolume_empty").arg(i), QString::number(floppySoundEmptyAttenuationValue(i))); + settings.insert(QStringLiteral("floppy%1soundvolume_disk").arg(i), QString::number(floppySoundDiskAttenuationValue(i))); + } + const QString printerTypeValue = configChoiceValue(printerTypeChoices, int(sizeof(printerTypeChoices) / sizeof(printerTypeChoices[0])), printerType->currentText()); + settings.insert(QStringLiteral("parallel_matrix_emulation"), + printerTypeValue.startsWith(QStringLiteral("postscript_")) ? QStringLiteral("none") : printerTypeValue); + settings.insert(QStringLiteral("parallel_postscript_detection"), + printerTypeValue.startsWith(QStringLiteral("postscript_")) ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("parallel_postscript_emulation"), + printerTypeValue == QStringLiteral("postscript_emulation") ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("parallel_autoflush"), QString::number(printerAutoFlush->value())); + if (!ghostscriptParams->text().trimmed().isEmpty()) { + settings.insert(QStringLiteral("ghostscript_parameters"), ghostscriptParams->text().trimmed()); + } + const int samplerIndex = samplerDevice ? samplerDevice->currentIndex() - 1 : -1; + settings.insert(QStringLiteral("unix.samplersoundcard"), QString::number(samplerIndex)); + const QString samplerDeviceConfigName = samplerDevice ? samplerDevice->currentData().toString() : QString(); + if (samplerIndex >= 0 && !samplerDeviceConfigName.isEmpty()) { + settings.insert(QStringLiteral("unix.samplersoundcardname"), samplerDeviceConfigName); + } + settings.insert(QStringLiteral("sampler_stereo"), samplerIndex >= 0 && samplerStereo->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const int midiOutDevice = midiOut ? midiOut->currentData().toInt() : -2; + settings.insert(QStringLiteral("midiout_device"), QString::number(midiOutDevice)); + const int midiInDevice = midiIn ? midiIn->currentData().toInt() : -1; + settings.insert(QStringLiteral("midiin_device"), QString::number(midiInDevice)); + QString midiOutName = QStringLiteral("none"); + if (midiOutDevice == -1) { + midiOutName = QStringLiteral("default"); + } else if (midiOutDevice >= 0 && midiOut) { + midiOutName = midiOut->currentData(Qt::UserRole + 1).toString(); + if (midiOutName.isEmpty()) { + midiOutName = midiOut->currentText(); + } + } + settings.insert(QStringLiteral("midiout_device_name"), midiOutName); + QString midiInName = QStringLiteral("none"); + if (midiInDevice >= 0 && midiIn) { + midiInName = midiIn->currentData(Qt::UserRole + 1).toString(); + if (midiInName.isEmpty()) { + midiInName = midiIn->currentText(); + } + } + settings.insert(QStringLiteral("midiin_device_name"), midiInName); + const bool midiCanRoute = midiRouter && midiOutDevice >= -1 && midiInDevice >= 0; + settings.insert(QStringLiteral("midirouter"), midiCanRoute && midiRouter->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const QString serial = serialPort->currentText().trimmed(); + if (!serial.isEmpty() + && serial != QStringLiteral("") + && serial.compare(QStringLiteral("none"), Qt::CaseInsensitive) != 0) { + settings.insert(QStringLiteral("unix.serial_port"), serial); + } + settings.insert(QStringLiteral("serial_on_demand"), serialShared->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("serial_hardware_ctsrts"), serialCtsRts->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("serial_status"), serialStatus->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("serial_ri"), serialRingIndicator->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("serial_direct"), serialDirect->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("serial_translate"), serialCrlf->isChecked() ? QStringLiteral("crlf_cr") : QStringLiteral("disabled")); + settings.insert(QStringLiteral("uaeserial"), uaeSerial->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const QString dongle = configChoiceValue(dongleChoices, int(sizeof(dongleChoices) / sizeof(dongleChoices[0])), protectionDongle->currentText()); + if (!dongle.isEmpty() && dongle != QStringLiteral("none")) { + settings.insert(QStringLiteral("dongle"), dongle); + } + for (const MiscCheckChoice &choice : miscCheckChoices) { + const QString key = QString::fromLatin1(choice.key); + if (key == QStringLiteral("power_led_dim")) { + settings.insert(key, miscOptionChecked(key) ? QStringLiteral("128") : QStringLiteral("0")); + } else { + settings.insert(key, miscOptionChecked(key) ? QStringLiteral("true") : QStringLiteral("false")); + } + } + if (stateFileName && !stateFileClear->isChecked() && !stateFileName->currentText().trimmed().isEmpty()) { + settings.insert(QStringLiteral("statefile"), stateFileName->currentText().trimmed()); + } + QStringList ledValues; + bool anyKeyboardLed = false; + const QStringList ledNames { QStringLiteral("numlock"), QStringLiteral("capslock"), QStringLiteral("scrolllock") }; + for (int i = 0; i < 3; i++) { + const QString value = configChoiceValue(keyboardLedChoices, int(sizeof(keyboardLedChoices) / sizeof(keyboardLedChoices[0])), keyboardLed[i]->currentText()); + ledValues.append(QStringLiteral("%1:%2").arg(ledNames[i], value.isEmpty() ? QStringLiteral("none") : value)); + anyKeyboardLed = anyKeyboardLed || (!value.isEmpty() && value.compare(QStringLiteral("none"), Qt::CaseInsensitive) != 0); + } + if (anyKeyboardLed) { + settings.insert(QStringLiteral("keyboard_leds"), ledValues.join(QLatin1Char(','))); + } + settings.insert(QStringLiteral("active_priority"), QString::number(extensionPriorityValue(extensionActivePriority))); + settings.insert(QStringLiteral("active_not_captured_pause"), extensionActivePause->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("active_not_captured_nosound"), extensionActiveNoSound->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("active_input"), QString::number((extensionActiveNoJoy->isChecked() ? 0 : 4) | (extensionActiveNoKeyboard->isChecked() ? 0 : 1))); + settings.insert(QStringLiteral("inactive_priority"), QString::number(extensionPriorityValue(extensionInactivePriority))); + settings.insert(QStringLiteral("inactive_pause"), extensionInactivePause->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("inactive_nosound"), extensionInactiveNoSound->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("inactive_input"), QString::number(extensionInactiveNoJoy->isChecked() ? 0 : 4)); + settings.insert(QStringLiteral("iconified_priority"), QString::number(extensionPriorityValue(extensionMinimizedPriority))); + settings.insert(QStringLiteral("iconified_pause"), extensionMinimizedPause->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("iconified_nosound"), extensionMinimizedNoSound->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("iconified_input"), QString::number(extensionMinimizedNoJoy->isChecked() ? 0 : 4)); + if (rtgMem->currentText() != QStringLiteral("None")) { + settings.insert(QStringLiteral("gfxcard_size"), QString::number(megabytesFromText(rtgMem->currentText()))); + settings.insert(QStringLiteral("gfxcard_type"), rtgType->currentText()); + const QString options = rtgOptionsValue(); + if (!options.isEmpty()) { + settings.insert(QStringLiteral("gfxcard_options"), options); + } + } + settings.insert(QStringLiteral("gfx_filter_autoscale_rtg"), rtgScaleConfigValue(rtgScale->isChecked(), rtgCenter->isChecked(), rtgIntegerScale->isChecked())); + settings.insert(QStringLiteral("gfx_filter_aspect_ratio_rtg"), + configChoiceValue(rtgAspectRatioChoices, int(sizeof(rtgAspectRatioChoices) / sizeof(rtgAspectRatioChoices[0])), rtgAspectRatio->currentText())); + settings.insert(QStringLiteral("unix.rtg_vblank"), rtgVBlankConfigValue(rtgRefreshRate->currentText())); + settings.insert(QStringLiteral("gfx_backbuffers_rtg"), + rtgBuffers->currentText() == QStringLiteral("Triple") ? QStringLiteral("3") : QStringLiteral("2")); + settings.insert(QStringLiteral("rtg_modes"), QStringLiteral("0x%1").arg(rtgModeMask(), 0, 16)); + const QString cpuBoardConfig = selectedCpuBoardConfigValue(); + if (cpuBoardConfig.isEmpty()) { + settings.insert(QStringLiteral("cpuboard_type"), QStringLiteral("none")); + settings.insert(QStringLiteral("cpuboardmem1_size"), QStringLiteral("0")); + settings.insert(QStringLiteral("cpuboardmem2_size"), QStringLiteral("0")); + } else { + settings.insert(QStringLiteral("cpuboard_type"), cpuBoardConfig); + settings.insert(QStringLiteral("cpuboardmem1_size"), QString::number(megabytesFromText(cpuBoardMem->currentText()))); + settings.insert(QStringLiteral("cpuboardmem2_size"), QStringLiteral("0")); + const WinUaeQtCpuBoardCatalogItem *cpuBoardChoice = cpuBoardSubtypeChoiceByConfig(boardCatalog, cpuBoardConfig); + if (cpuBoardChoice && cpuBoardChoice->ppc) { + settings.insert(QStringLiteral("ppc_model"), QStringLiteral("manual")); + } + const QString cpuBoardRomPath = cpuBoardRom->currentText().trimmed(); + if (!cpuBoardRomPath.isEmpty()) { + settings.insert(QStringLiteral("cpuboard_rom_file"), cpuBoardRomPath); + } + if (!cpuBoardSettingsRaw.trimmed().isEmpty()) { + settings.insert(QStringLiteral("cpuboard_settings"), cpuBoardSettingsRaw.trimmed()); + } + } + settings.insert(QStringLiteral("bsdsocket_emu"), expansionBsdsocket->isEnabled() && expansionBsdsocket->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("scsi"), expansionScsiDevice->isEnabled() && expansionScsiDevice->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("uaescsimode"), configChoiceValue(scsiModeChoices, int(sizeof(scsiModeChoices) / sizeof(scsiModeChoices[0])), miscScsiMode->currentText())); + settings.insert(QStringLiteral("sana2"), expansionSana2->isEnabled() && expansionSana2->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const int displayIndex = qMax(0, hostDisplay->currentIndex()); + settings.insert(QStringLiteral("gfx_display"), QString::number(displayIndex)); + settings.insert(QStringLiteral("gfx_display_rtg"), QString::number(displayIndex)); + const QString displayName = hostDisplay->currentData().toString(); + const QString displayFriendly = hostDisplay->currentData(Qt::UserRole + 1).toString(); + if (!displayName.isEmpty()) { + settings.insert(QStringLiteral("gfx_display_name"), displayName); + settings.insert(QStringLiteral("gfx_display_name_rtg"), displayName); + } + if (!displayFriendly.isEmpty()) { + settings.insert(QStringLiteral("gfx_display_friendlyname"), displayFriendly); + settings.insert(QStringLiteral("gfx_display_friendlyname_rtg"), displayFriendly); + } + if (!windowWidth->text().isEmpty()) { + settings.insert(QStringLiteral("gfx_width_windowed"), windowWidth->text()); + } + if (!windowHeight->text().isEmpty()) { + settings.insert(QStringLiteral("gfx_height_windowed"), windowHeight->text()); + } + const QString fullscreenText = fullscreenResolution->currentText(); + if (fullscreenText == QStringLiteral("Native")) { + settings.insert(QStringLiteral("gfx_width_fullscreen"), QStringLiteral("native")); + settings.insert(QStringLiteral("gfx_height_fullscreen"), QStringLiteral("native")); + } else { + const QStringList parts = fullscreenText.split(QLatin1Char('x')); + if (parts.size() == 2) { + settings.insert(QStringLiteral("gfx_width_fullscreen"), parts.value(0)); + settings.insert(QStringLiteral("gfx_height_fullscreen"), parts.value(1)); + } + } + if (displayRefreshRate->currentText() == QStringLiteral("Default")) { + settings.insert(QStringLiteral("gfx_refreshrate"), QStringLiteral("0")); + } else { + settings.insert(QStringLiteral("gfx_refreshrate"), displayRefreshRate->currentText()); + } + settings.insert(QStringLiteral("gfx_backbuffers"), displayBufferCount->currentText() == QStringLiteral("Triple") ? QStringLiteral("3") : QStringLiteral("2")); + settings.insert(QStringLiteral("gfx_resize_windowed"), windowResize->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("gfx_fullscreen_amiga"), fullscreenModeConfigValue(nativeMode->currentText())); + settings.insert(QStringLiteral("gfx_fullscreen_picasso"), fullscreenModeConfigValue(rtgMode->currentText())); + settings.insert(QStringLiteral("gfx_vsync"), nativeVsyncConfigValue(nativeVsync->currentText())); + settings.insert(QStringLiteral("gfx_vsyncmode"), nativeVsyncModeConfigValue(nativeVsync->currentText())); + settings.insert(QStringLiteral("gfx_vsync_picasso"), rtgVsync->currentText() == QStringLiteral("-") ? QStringLiteral("false") : QStringLiteral("true")); + settings.insert(QStringLiteral("gfx_vsyncmode_picasso"), rtgVsync->currentText() == QStringLiteral("-") ? QStringLiteral("normal") : QStringLiteral("busywait")); + settings.insert(QStringLiteral("gfx_frame_slices"), nativeFrameSlices->currentText()); + settings.insert(QStringLiteral("gfx_resolution"), displayResolution->currentText()); + settings.insert(QStringLiteral("gfx_overscanmode"), overscanConfigValue(displayOverscan->currentText())); + settings.insert(QStringLiteral("gfx_autoresolution"), QString::number(autoResolutionValue(displayAutoResolution->currentText()))); + settings.insert(QStringLiteral("gfx_framerate"), QString::number(displayFrameRate->value())); + settings.insert(QStringLiteral("gfx_linemode"), lineModeConfigValue(displayLineModeButtons->checkedId(), displayInterlacedLineModeButtons->checkedId())); + settings.insert(QStringLiteral("gfx_center_horizontal"), displayCenterHorizontal->isChecked() ? QStringLiteral("simple") : QStringLiteral("none")); + settings.insert(QStringLiteral("gfx_center_vertical"), displayCenterVertical->isChecked() ? QStringLiteral("simple") : QStringLiteral("none")); + settings.insert(QStringLiteral("gfx_flickerfixer"), displayFlickerFixer->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("gfx_lores_mode"), displayLoresSmoothed->isChecked() ? QStringLiteral("filtered") : QStringLiteral("normal")); + settings.insert(QStringLiteral("gfx_blacker_than_black"), displayBlackerThanBlack->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("gfx_monochrome"), displayMonochrome->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("gfx_autoresolution_vga"), displayAutoResolutionVga->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("gfx_monitorblankdelay"), displayResyncBlank->isChecked() ? QStringLiteral("1000") : QStringLiteral("0")); + settings.insert(QStringLiteral("gfx_keep_aspect"), displayKeepAspect->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("gfx_ntscpixels"), filterNtscPixels->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const bool shaderPipeline = unixShaderPipelineAvailable(); + for (int i = 0; i < 3; i++) { + const WinUaeQtFilterState state = filterStateFromUi(i); + settings.insert(filterKey(QStringLiteral("gfx_filter_mode"), i), shaderPipeline ? state.modeH : QStringLiteral("1x")); + settings.insert(filterKey(QStringLiteral("gfx_filter_mode2"), i), shaderPipeline ? state.modeV : QStringLiteral("-")); + settings.insert(filterKey(QStringLiteral("gfx_filter_horiz_zoomf"), i), QString::number(state.horizZoom, 'f', 1)); + settings.insert(filterKey(QStringLiteral("gfx_filter_vert_zoomf"), i), QString::number(state.vertZoom, 'f', 1)); + settings.insert(filterKey(QStringLiteral("gfx_filter_horiz_zoom_multf"), i), QString::number(state.horizZoomMult, 'f', 2)); + settings.insert(filterKey(QStringLiteral("gfx_filter_vert_zoom_multf"), i), QString::number(state.vertZoomMult, 'f', 2)); + settings.insert(filterKey(QStringLiteral("gfx_filter_horiz_offsetf"), i), QString::number(state.horizOffset, 'f', 1)); + settings.insert(filterKey(QStringLiteral("gfx_filter_vert_offsetf"), i), QString::number(state.vertOffset, 'f', 1)); + settings.insert(filterKey(QStringLiteral("gfx_filter_keep_aspect"), i), state.keepAspect); + settings.insert(filterKey(QStringLiteral("gfx_filter_keep_autoscale_aspect"), i), state.keepAutoscaleAspect ? QStringLiteral("1") : QStringLiteral("0")); + settings.insert(filterKey(QStringLiteral("gfx_filter_autoscale"), i), i == 1 && !isRtgAutoscaleValue(state.autoscale) ? QStringLiteral("resize") : state.autoscale); + settings.insert(filterKey(QStringLiteral("gfx_filter_autoscale_limit"), i), state.integerLimit); + settings.insert(filterKey(QStringLiteral("gfx_filter_luminance"), i), QString::number(shaderPipeline ? state.luminance : 0)); + settings.insert(filterKey(QStringLiteral("gfx_filter_contrast"), i), QString::number(shaderPipeline ? state.contrast : 0)); + settings.insert(filterKey(QStringLiteral("gfx_filter_saturation"), i), QString::number(shaderPipeline ? state.saturation : 0)); + settings.insert(filterKey(QStringLiteral("gfx_filter_gamma"), i), QString::number(shaderPipeline ? state.gamma : 0)); + settings.insert(filterKey(QStringLiteral("gfx_filter_blur"), i), QString::number(shaderPipeline ? state.blur : 0)); + settings.insert(filterKey(QStringLiteral("gfx_filter_noise"), i), QString::number(shaderPipeline ? state.noise : 0)); + settings.insert(filterKey(QStringLiteral("gfx_filter_bilinear"), i), state.bilinear ? QStringLiteral("1") : QStringLiteral("0")); + settings.insert(filterKey(QStringLiteral("gfx_filter_scanlines"), i), QString::number(state.scanlines)); + settings.insert(filterKey(QStringLiteral("gfx_filter_scanlinelevel"), i), QString::number(state.scanlineLevel)); + settings.insert(filterKey(QStringLiteral("gfx_filter_scanlineoffset"), i), QString::number(state.scanlineOffset)); + } + settings.insert(QStringLiteral("gfx_filter_enable_lace"), filterStateFromUi(2).enable ? QStringLiteral("1") : QStringLiteral("0")); + for (int i = 0; i < MaxCdSlots; i++) { + const QString value = cdSlotConfigValue(cdSlotState(i)); + if (!value.isEmpty()) { + settings.insert(QStringLiteral("cdimage%1").arg(i), value); + } + } + settings.insert(QStringLiteral("cd_speed"), cdSpeedTurbo->isChecked() ? QStringLiteral("0") : QStringLiteral("100")); + settings.insert(QStringLiteral("filesys_no_fsdb"), filesysNoFsdb->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("filesys_max_size"), filesysLimitSize->isChecked() ? QString::number(950 * 1024) : QStringLiteral("0")); + const int replaySeconds = stateReplayRate->currentText().trimmed() == QStringLiteral("-") ? -1 : stateReplayRate->currentText().toInt(); + settings.insert(QStringLiteral("state_replay_rate"), QString::number(replaySeconds > 0 ? replaySeconds * 50 : -1)); + settings.insert(QStringLiteral("state_replay_buffers"), QString::number(qMax(1, stateReplayBuffers->currentText().toInt()))); + settings.insert(QStringLiteral("state_replay_autoplay"), stateReplayAutoplay->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + for (int i = 0; i < 4; i++) { + settings.insert(QStringLiteral("joyport%1").arg(i), joyportDeviceConfigValue(portDevice[i]->currentText())); + } + for (int i = 0; i < MaxJoyportCustomSlots; i++) { + if (!joyportCustom[i].trimmed().isEmpty()) { + settings.insert(QStringLiteral("joyportcustom%1").arg(i), joyportCustom[i].trimmed()); + } + } + for (int i = 0; i < 2; i++) { + settings.insert(QStringLiteral("joyport%1autofire").arg(i), autofireConfigValue(portAutofire[i]->currentText())); + const QString mode = joyportModeConfigValue(portMode[i]->currentText()); + if (!mode.isEmpty()) { + settings.insert(QStringLiteral("joyport%1mode").arg(i), mode); + } + } + const int inputConfig = inputType->currentText() == QStringLiteral("Game Ports") + ? 0 + : qBound(1, inputType->currentIndex() + 1, CustomInputConfigSlots); + settings.insert(QStringLiteral("input.config"), QString::number(inputConfig)); + settings.insert(QStringLiteral("input.joystick_deadzone"), QString::number(inputDeadzone->value())); + settings.insert(QStringLiteral("input.joymouse_deadzone"), QString::number(inputDeadzone->value())); + settings.insert(QStringLiteral("input.autofire_speed"), QString::number(inputAutofireRate->value())); + settings.insert(QStringLiteral("input.joymouse_speed_digital"), QString::number(inputJoyMouseDigital->value())); + settings.insert(QStringLiteral("input.joymouse_speed_analog"), QString::number(inputJoyMouseAnalog->value())); + settings.insert(QStringLiteral("input.mouse_speed"), QString::number(mouseSpeed->value())); + settings.insert(QStringLiteral("input.autoswitch"), portAutoswitch->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + const int untrapMode = mouseUntrapMode->currentIndex(); + settings.insert(QStringLiteral("middle_mouse"), (untrapMode == 1 || untrapMode == 3) ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("magic_mouse"), (untrapMode == 2 || untrapMode == 3) ? QStringLiteral("true") : QStringLiteral("false")); + settings.insert(QStringLiteral("magic_mousecursor"), magicMouseCursorConfigValue(magicMouseCursor->currentText())); + QString absoluteMouse = QStringLiteral("none"); + if (unixTabletBackendAvailable() && tabletMode->currentText() == QStringLiteral("Tablet emulation")) { + absoluteMouse = QStringLiteral("tablet"); + } else if (virtualMouseDriver->isChecked()) { + absoluteMouse = QStringLiteral("mousehack"); + } + settings.insert(QStringLiteral("absolute_mouse"), absoluteMouse); + settings.insert(QStringLiteral("tablet_library"), tabletLibrary->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); + for (const QString &key : inputOwnedMappingKeys) { + const QString value = inputMappingSettings.value(key).trimmed(); + if (!value.isEmpty()) { + settings.insert(key, value); + } + } + mergeHardwareOrderSettings(settings); + return settings; + } + + QStringList uiOwnedKeys() const + { + QStringList keys = { + QStringLiteral("config_description"), + QStringLiteral("quickstart"), + QStringLiteral("unix.rom_path"), + QStringLiteral("unix.config_path"), + QStringLiteral("unix.ui.config_path"), + QStringLiteral("unix.nvram_path"), + QStringLiteral("unix.ui.nvram_path"), + QStringLiteral("unix.screenshot_path"), + QStringLiteral("unix.ui.screenshot_path"), + QStringLiteral("statefile_path"), + QStringLiteral("unix.video_path"), + QStringLiteral("unix.ui.video_path"), + QStringLiteral("unix.saveimage_path"), + QStringLiteral("unix.ui.saveimage_path"), + QStringLiteral("unix.rip_path"), + QStringLiteral("unix.ripper_path"), + QStringLiteral("unix.ui.rip_path"), + QStringLiteral("unix.data_path"), + QStringLiteral("unix.ui.data_path"), + QStringLiteral("unix.ui.path_mode"), + QStringLiteral("unix.ui.recursive_roms"), + QStringLiteral("unix.ui.cache_configurations"), + QStringLiteral("unix.ui.cache_boxart"), + QStringLiteral("unix.ui.saveimage_original_path"), + QStringLiteral("unix.ui.relative_paths"), + QStringLiteral("unix.ui.portable_mode"), + QStringLiteral("unix.ui.full_logging"), + QStringLiteral("unix.ui.log_window"), + QStringLiteral("unix.ui.gui_scale"), + QStringLiteral("unix.ui.gui_resize"), + QStringLiteral("unix.ui.gui_fullscreen"), + QStringLiteral("unix.ui.gui_dark_mode"), + QStringLiteral("unix.ui.gui_font"), + QStringLiteral("unix.output_file"), + QStringLiteral("unix.output_audio_codec"), + QStringLiteral("unix.output_video_codec"), + QStringLiteral("unix.output_audio"), + QStringLiteral("unix.output_video"), + QStringLiteral("unix.output_enabled"), + QStringLiteral("unix.output_frame_limiter_disabled"), + QStringLiteral("unix.output_original_size"), + QStringLiteral("unix.output_no_sound"), + QStringLiteral("unix.output_no_sound_sync"), + QStringLiteral("unix.screenshot_original_size"), + QStringLiteral("unix.screenshot_paletted"), + QStringLiteral("unix.screenshot_clip"), + QStringLiteral("unix.screenshot_auto"), + QStringLiteral("unix.ui.output_file"), + QStringLiteral("unix.ui.output_audio_codec"), + QStringLiteral("unix.ui.output_video_codec"), + QStringLiteral("unix.ui.output_audio"), + QStringLiteral("unix.ui.output_video"), + QStringLiteral("unix.ui.output_enabled"), + QStringLiteral("unix.ui.output_frame_limiter_disabled"), + QStringLiteral("unix.ui.output_original_size"), + QStringLiteral("unix.ui.output_no_sound"), + QStringLiteral("unix.ui.output_no_sound_sync"), + QStringLiteral("unix.ui.screenshot_original_size"), + QStringLiteral("unix.ui.screenshot_paletted"), + QStringLiteral("unix.ui.screenshot_clip"), + QStringLiteral("unix.ui.screenshot_auto"), + QStringLiteral("kickstart_rom_file"), + QStringLiteral("kickstart_ext_rom_file"), + QStringLiteral("cart_file"), + QStringLiteral("flash_file"), + QStringLiteral("rtc_file"), + QStringLiteral("maprom"), + QStringLiteral("kickshifter"), + QStringLiteral("boot_rom_uae"), + QStringLiteral("uaeboard"), + QStringLiteral("romboard_options"), + QStringLiteral("romboard2_options"), + QStringLiteral("romboard3_options"), + QStringLiteral("romboard4_options"), + QStringLiteral("board_custom_order"), + QStringLiteral("floppy0"), + QStringLiteral("floppy1"), + QStringLiteral("floppy2"), + QStringLiteral("floppy3"), + QStringLiteral("floppy0type"), + QStringLiteral("floppy1type"), + QStringLiteral("floppy2type"), + QStringLiteral("floppy3type"), + QStringLiteral("floppy0wp"), + QStringLiteral("floppy1wp"), + QStringLiteral("floppy2wp"), + QStringLiteral("floppy3wp"), + QStringLiteral("nr_floppies"), + QStringLiteral("floppy_speed"), + QStringLiteral("diskimage0"), + QStringLiteral("diskimage1"), + QStringLiteral("diskimage2"), + QStringLiteral("diskimage3"), + QStringLiteral("diskimage4"), + QStringLiteral("diskimage5"), + QStringLiteral("diskimage6"), + QStringLiteral("diskimage7"), + QStringLiteral("diskimage8"), + QStringLiteral("diskimage9"), + QStringLiteral("diskimage10"), + QStringLiteral("diskimage11"), + QStringLiteral("diskimage12"), + QStringLiteral("diskimage13"), + QStringLiteral("diskimage14"), + QStringLiteral("diskimage15"), + QStringLiteral("diskimage16"), + QStringLiteral("diskimage17"), + QStringLiteral("diskimage18"), + QStringLiteral("diskimage19"), + QStringLiteral("chipset"), + QStringLiteral("chipset_compatible"), + QStringLiteral("ntsc"), + QStringLiteral("immediate_blits"), + QStringLiteral("waiting_blits"), + QStringLiteral("collision_level"), + QStringLiteral("display_optimizations"), + QStringLiteral("hvcsync"), + QStringLiteral("genlock"), + QStringLiteral("genlockmode"), + QStringLiteral("genlock_mix"), + QStringLiteral("genlock_alpha"), + QStringLiteral("genlock_aspect"), + QStringLiteral("genlock_image"), + QStringLiteral("genlock_video"), + QStringLiteral("keyboard_connected"), + QStringLiteral("keyboard_type"), + QStringLiteral("keyboard_nkro"), + QStringLiteral("ciaatod"), + QStringLiteral("rtc"), + QStringLiteral("chipset_rtc_adjust"), + QStringLiteral("cia_overlay"), + QStringLiteral("cd32cd"), + QStringLiteral("cdtvcd"), + QStringLiteral("ksmirror_e0"), + QStringLiteral("df0idhw"), + QStringLiteral("resetwarning"), + QStringLiteral("cia_todbug"), + QStringLiteral("1mchipjumper"), + QStringLiteral("toshiba_gary"), + QStringLiteral("a1000ram"), + QStringLiteral("cd32c2p"), + QStringLiteral("cdtvram"), + QStringLiteral("ksmirror_a8"), + QStringLiteral("z3_autoconfig"), + QStringLiteral("rom_is_slow"), + QStringLiteral("cd32nvram"), + QStringLiteral("cdtv-cr"), + QStringLiteral("pcmcia"), + QStringLiteral("color_burst"), + QStringLiteral("memory_pattern"), + QStringLiteral("ide"), + QStringLiteral("scsi_a3000"), + QStringLiteral("scsi_a4000t"), + QStringLiteral("ciaa_type"), + QStringLiteral("ciab_type"), + QStringLiteral("unmapped_address_space"), + QStringLiteral("eclocksync"), + QStringLiteral("fatgary"), + QStringLiteral("ramsey"), + QStringLiteral("agnusmodel"), + QStringLiteral("agnussize"), + QStringLiteral("denisemodel"), + QStringLiteral("cycle_exact"), + QStringLiteral("cpu_cycle_exact"), + QStringLiteral("cpu_memory_cycle_exact"), + QStringLiteral("blitter_cycle_exact"), + QStringLiteral("cpu_model"), + QStringLiteral("cpu_speed"), + QStringLiteral("cpu_throttle"), + QStringLiteral("cpu_compatible"), + QStringLiteral("fpu_model"), + QStringLiteral("cpu_24bit_addressing"), + QStringLiteral("mmu_model"), + QStringLiteral("cpu_no_unimplemented"), + QStringLiteral("fpu_no_unimplemented"), + QStringLiteral("fpu_strict"), + QStringLiteral("fpu_softfloat"), + QStringLiteral("fpu_msvc_long_double"), + QStringLiteral("cpu_multiplier"), + QStringLiteral("cpu_frequency"), + QStringLiteral("chipmem_size"), + QStringLiteral("fastmem_size"), + QStringLiteral("bogomem_size"), + QStringLiteral("z3mem_size"), + QStringLiteral("megachipmem_size"), + QStringLiteral("mbresmem_size"), + QStringLiteral("z3mapping"), + QStringLiteral("cachesize"), + QStringLiteral("compfpu"), + QStringLiteral("comp_constjump"), + QStringLiteral("comp_flushmode"), + QStringLiteral("comp_nf"), + QStringLiteral("comp_catchfault"), + QStringLiteral("comp_trustbyte"), + QStringLiteral("comp_trustword"), + QStringLiteral("comp_trustlong"), + QStringLiteral("comp_trustnaddr"), + QStringLiteral("soundcard"), + QStringLiteral("soundcardname"), + QStringLiteral("unix.soundcard"), + QStringLiteral("unix.soundcardname"), + QStringLiteral("sound_output"), + QStringLiteral("sound_auto"), + QStringLiteral("sound_volume"), + QStringLiteral("sound_volume_paula"), + QStringLiteral("sound_volume_cd"), + QStringLiteral("sound_volume_ahi"), + QStringLiteral("sound_volume_midi"), + QStringLiteral("sound_volume_genlock"), + QStringLiteral("sound_max_buff"), + QStringLiteral("sound_channels"), + QStringLiteral("sound_stereo_separation"), + QStringLiteral("sound_stereo_mixing_delay"), + QStringLiteral("sound_frequency"), + QStringLiteral("sound_interpol"), + QStringLiteral("sound_filter"), + QStringLiteral("sound_filter_type"), + QStringLiteral("sound_stereo_swap_paula"), + QStringLiteral("sound_stereo_swap_ahi"), + QStringLiteral("floppy0sound"), + QStringLiteral("floppy1sound"), + QStringLiteral("floppy2sound"), + QStringLiteral("floppy3sound"), + QStringLiteral("floppy0soundvolume_empty"), + QStringLiteral("floppy1soundvolume_empty"), + QStringLiteral("floppy2soundvolume_empty"), + QStringLiteral("floppy3soundvolume_empty"), + QStringLiteral("floppy0soundvolume_disk"), + QStringLiteral("floppy1soundvolume_disk"), + QStringLiteral("floppy2soundvolume_disk"), + QStringLiteral("floppy3soundvolume_disk"), + QStringLiteral("parallel_matrix_emulation"), + QStringLiteral("parallel_postscript_detection"), + QStringLiteral("parallel_postscript_emulation"), + QStringLiteral("parallel_autoflush"), + QStringLiteral("ghostscript_parameters"), + QStringLiteral("samplersoundcard"), + QStringLiteral("samplersoundcardname"), + QStringLiteral("sampler_soundcard"), + QStringLiteral("sampler_soundcardname"), + QStringLiteral("unix.samplersoundcard"), + QStringLiteral("unix.samplersoundcardname"), + QStringLiteral("unix.sampler_soundcard"), + QStringLiteral("unix.sampler_soundcardname"), + QStringLiteral("sampler_stereo"), + QStringLiteral("midi_device"), + QStringLiteral("midiout_device"), + QStringLiteral("midiin_device"), + QStringLiteral("midiout_device_name"), + QStringLiteral("midiin_device_name"), + QStringLiteral("midirouter"), + QStringLiteral("serial_port"), + QStringLiteral("unix.serial_port"), + QStringLiteral("serial_on_demand"), + QStringLiteral("serial_hardware_ctsrts"), + QStringLiteral("serial_status"), + QStringLiteral("serial_ri"), + QStringLiteral("serial_direct"), + QStringLiteral("serial_translate"), + QStringLiteral("uaeserial"), + QStringLiteral("dongle"), + QStringLiteral("use_gui"), + QStringLiteral("synchronize_clock"), + QStringLiteral("cpu_reset_pause"), + QStringLiteral("rtg_nocustom"), + QStringLiteral("clipboard_sharing"), + QStringLiteral("native_code"), + QStringLiteral("show_leds"), + QStringLiteral("show_leds_rtg"), + QStringLiteral("log_illegal_mem"), + QStringLiteral("floppy_write_protect"), + QStringLiteral("harddrive_write_protect"), + QStringLiteral("uae_hide_autoconfig"), + QStringLiteral("power_led_dim"), + QStringLiteral("debug_mem"), + QStringLiteral("cpu_halt_auto_reset"), + QStringLiteral("scsidevice_disable"), + QStringLiteral("warpboot"), + QStringLiteral("statefile"), + QStringLiteral("keyboard_leds"), + QStringLiteral("active_priority"), + QStringLiteral("activepriority"), + QStringLiteral("active_not_captured_pause"), + QStringLiteral("active_not_captured_nosound"), + QStringLiteral("active_input"), + QStringLiteral("inactive_priority"), + QStringLiteral("inactive_pause"), + QStringLiteral("inactive_nosound"), + QStringLiteral("inactive_input"), + QStringLiteral("iconified_priority"), + QStringLiteral("iconified_pause"), + QStringLiteral("iconified_nosound"), + QStringLiteral("iconified_input"), + QStringLiteral("gfxcard_size"), + QStringLiteral("gfxcard_type"), + QStringLiteral("gfxcard_options"), + QStringLiteral("gfx_filter_autoscale_rtg"), + QStringLiteral("gfx_backbuffers_rtg"), + QStringLiteral("gfx_refreshrate_rtg"), + QStringLiteral("unix.rtg_vblank"), + QStringLiteral("rtg_vblank"), + QStringLiteral("gfxcard_hardware_vblank"), + QStringLiteral("gfxcard_hardware_sprite"), + QStringLiteral("gfxcard_multithread"), + QStringLiteral("rtg_modes"), + QStringLiteral("gfx_filter_aspect_ratio_rtg"), + QStringLiteral("cpuboard_type"), + QStringLiteral("cpuboardmem1_size"), + QStringLiteral("cpuboardmem2_size"), + QStringLiteral("cpuboard_rom_file"), + QStringLiteral("cpuboard_settings"), + QStringLiteral("ppc_model"), + QStringLiteral("bsdsocket_emu"), + QStringLiteral("scsi"), + QStringLiteral("uaescsimode"), + QStringLiteral("unix.uaescsimode"), + QStringLiteral("sana2"), + QStringLiteral("gfx_display"), + QStringLiteral("gfx_display_rtg"), + QStringLiteral("gfx_display_name"), + QStringLiteral("gfx_display_name_rtg"), + QStringLiteral("gfx_display_friendlyname"), + QStringLiteral("gfx_display_friendlyname_rtg"), + QStringLiteral("gfx_width_windowed"), + QStringLiteral("gfx_height_windowed"), + QStringLiteral("gfx_width_fullscreen"), + QStringLiteral("gfx_height_fullscreen"), + QStringLiteral("gfx_refreshrate"), + QStringLiteral("gfx_backbuffers"), + QStringLiteral("gfx_resize_windowed"), + QStringLiteral("gfx_fullscreen_amiga"), + QStringLiteral("gfx_fullscreen_picasso"), + QStringLiteral("gfx_vsync"), + QStringLiteral("gfx_vsyncmode"), + QStringLiteral("gfx_vsync_picasso"), + QStringLiteral("gfx_vsyncmode_picasso"), + QStringLiteral("gfx_frame_slices"), + QStringLiteral("gfx_resolution"), + QStringLiteral("gfx_overscanmode"), + QStringLiteral("gfx_autoresolution"), + QStringLiteral("gfx_framerate"), + QStringLiteral("gfx_linemode"), + QStringLiteral("gfx_center_horizontal"), + QStringLiteral("gfx_center_vertical"), + QStringLiteral("gfx_flickerfixer"), + QStringLiteral("gfx_lores_mode"), + QStringLiteral("gfx_blacker_than_black"), + QStringLiteral("gfx_monochrome"), + QStringLiteral("gfx_autoresolution_vga"), + QStringLiteral("gfx_monitorblankdelay"), + QStringLiteral("gfx_keep_aspect"), + QStringLiteral("gfx_ntscpixels"), + QStringLiteral("gfx_filter_mode"), + QStringLiteral("gfx_filter_mode2"), + QStringLiteral("gfx_filter_horiz_zoomf"), + QStringLiteral("gfx_filter_vert_zoomf"), + QStringLiteral("gfx_filter_horiz_zoom_multf"), + QStringLiteral("gfx_filter_vert_zoom_multf"), + QStringLiteral("gfx_filter_horiz_offsetf"), + QStringLiteral("gfx_filter_vert_offsetf"), + QStringLiteral("gfx_filter_keep_aspect"), + QStringLiteral("gfx_filter_keep_autoscale_aspect"), + QStringLiteral("gfx_filter_autoscale"), + QStringLiteral("gfx_filter_autoscale_limit"), + QStringLiteral("gfx_filter_luminance"), + QStringLiteral("gfx_filter_contrast"), + QStringLiteral("gfx_filter_saturation"), + QStringLiteral("gfx_filter_gamma"), + QStringLiteral("gfx_filter_blur"), + QStringLiteral("gfx_filter_noise"), + QStringLiteral("gfx_filter_bilinear"), + QStringLiteral("gfx_filter_scanlines"), + QStringLiteral("gfx_filter_scanlinelevel"), + QStringLiteral("gfx_filter_scanlineoffset"), + QStringLiteral("gfx_filter_mode_rtg"), + QStringLiteral("gfx_filter_mode2_rtg"), + QStringLiteral("gfx_filter_horiz_zoomf_rtg"), + QStringLiteral("gfx_filter_vert_zoomf_rtg"), + QStringLiteral("gfx_filter_horiz_zoom_multf_rtg"), + QStringLiteral("gfx_filter_vert_zoom_multf_rtg"), + QStringLiteral("gfx_filter_horiz_offsetf_rtg"), + QStringLiteral("gfx_filter_vert_offsetf_rtg"), + QStringLiteral("gfx_filter_keep_aspect_rtg"), + QStringLiteral("gfx_filter_keep_autoscale_aspect_rtg"), + QStringLiteral("gfx_filter_autoscale_limit_rtg"), + QStringLiteral("gfx_filter_luminance_rtg"), + QStringLiteral("gfx_filter_contrast_rtg"), + QStringLiteral("gfx_filter_saturation_rtg"), + QStringLiteral("gfx_filter_gamma_rtg"), + QStringLiteral("gfx_filter_blur_rtg"), + QStringLiteral("gfx_filter_noise_rtg"), + QStringLiteral("gfx_filter_bilinear_rtg"), + QStringLiteral("gfx_filter_scanlines_rtg"), + QStringLiteral("gfx_filter_scanlinelevel_rtg"), + QStringLiteral("gfx_filter_scanlineoffset_rtg"), + QStringLiteral("gfx_filter_mode_lace"), + QStringLiteral("gfx_filter_mode2_lace"), + QStringLiteral("gfx_filter_horiz_zoomf_lace"), + QStringLiteral("gfx_filter_vert_zoomf_lace"), + QStringLiteral("gfx_filter_horiz_zoom_multf_lace"), + QStringLiteral("gfx_filter_vert_zoom_multf_lace"), + QStringLiteral("gfx_filter_horiz_offsetf_lace"), + QStringLiteral("gfx_filter_vert_offsetf_lace"), + QStringLiteral("gfx_filter_keep_aspect_lace"), + QStringLiteral("gfx_filter_keep_autoscale_aspect_lace"), + QStringLiteral("gfx_filter_autoscale_lace"), + QStringLiteral("gfx_filter_autoscale_limit_lace"), + QStringLiteral("gfx_filter_luminance_lace"), + QStringLiteral("gfx_filter_contrast_lace"), + QStringLiteral("gfx_filter_saturation_lace"), + QStringLiteral("gfx_filter_gamma_lace"), + QStringLiteral("gfx_filter_blur_lace"), + QStringLiteral("gfx_filter_noise_lace"), + QStringLiteral("gfx_filter_bilinear_lace"), + QStringLiteral("gfx_filter_scanlines_lace"), + QStringLiteral("gfx_filter_scanlinelevel_lace"), + QStringLiteral("gfx_filter_scanlineoffset_lace"), + QStringLiteral("gfx_filter_enable_lace"), + QStringLiteral("cdimage0"), + QStringLiteral("cdimage1"), + QStringLiteral("cdimage2"), + QStringLiteral("cdimage3"), + QStringLiteral("cdimage4"), + QStringLiteral("cdimage5"), + QStringLiteral("cdimage6"), + QStringLiteral("cdimage7"), + QStringLiteral("cd_speed"), + QStringLiteral("filesys_no_fsdb"), + QStringLiteral("filesys_max_size"), + QStringLiteral("state_replay_rate"), + QStringLiteral("state_replay_buffers"), + QStringLiteral("state_replay_autoplay"), + QStringLiteral("joyport0"), + QStringLiteral("joyport1"), + QStringLiteral("joyport2"), + QStringLiteral("joyport3"), + QStringLiteral("joyportcustom0"), + QStringLiteral("joyportcustom1"), + QStringLiteral("joyportcustom2"), + QStringLiteral("joyportcustom3"), + QStringLiteral("joyportcustom4"), + QStringLiteral("joyportcustom5"), + QStringLiteral("joyport0autofire"), + QStringLiteral("joyport1autofire"), + QStringLiteral("joyport0mode"), + QStringLiteral("joyport1mode"), + QStringLiteral("input.config"), + QStringLiteral("input.joystick_deadzone"), + QStringLiteral("input.joymouse_deadzone"), + QStringLiteral("input.autofire_speed"), + QStringLiteral("input.joymouse_speed_digital"), + QStringLiteral("input.joymouse_speed_analog"), + QStringLiteral("input.mouse_speed"), + QStringLiteral("input.autoswitch"), + QStringLiteral("middle_mouse"), + QStringLiteral("magic_mouse"), + QStringLiteral("magic_mousecursor"), + QStringLiteral("absolute_mouse"), + QStringLiteral("tablet_library") + }; + keys.append(hardwareOrderOwnedKeys); + keys.append(inputOwnedMappingKeys); + return keys; + } + + QStringList uiOwnedMountKeys() const + { + return { + QStringLiteral("filesystem2"), + QStringLiteral("hardfile2"), + QStringLiteral("uaehf0"), + QStringLiteral("uaehf1"), + QStringLiteral("uaehf2"), + QStringLiteral("uaehf3"), + QStringLiteral("uaehf4"), + QStringLiteral("uaehf5"), + QStringLiteral("uaehf6"), + QStringLiteral("uaehf7") + }; + } + + WinUaeQtConfig mergedConfig() const + { + WinUaeQtConfig config = loadedConfig; + const QStringList expansionKeys = expansionBoardOwnedKeys(); + const QStringList mountKeys = uiOwnedMountKeys(); + // Quickstart presets can clear board ROMs while parsing, so rewrite + // expansion boards after normal settings and before controller mounts. + config.applySettings(WinUaeQtConfig::Settings(), expansionKeys); + config.applyRepeatedSettings(WinUaeQtConfig::OrderedSettings(), mountKeys); + config.applySettings(currentSettings(), uiOwnedKeys()); + config.applySettings(currentExpansionBoardSettings(), expansionKeys); + config.applyRepeatedSettings(currentMountSettings(), mountKeys); + moveQuickstartBeforeOverrides(config, expansionKeys, mountKeys); + return config; + } + + void moveQuickstartBeforeOverrides(WinUaeQtConfig &config) const + { + moveQuickstartBeforeOverrides(config, expansionBoardOwnedKeys(), uiOwnedMountKeys()); + } + + void moveQuickstartBeforeOverrides(WinUaeQtConfig &config, const QStringList &expansionKeys, const QStringList &mountKeys) const + { + QStringList quickstartOverrideKeys = uiOwnedKeys(); + quickstartOverrideKeys.append(expansionKeys); + quickstartOverrideKeys.append(mountKeys); + quickstartOverrideKeys.removeAll(QStringLiteral("quickstart")); + config.moveSettingBefore(QStringLiteral("quickstart"), quickstartOverrideKeys); + } + + int enabledFloppyCount() const + { + int count = 0; + for (int i = 0; i < 4; i++) { + if (dfEnable[i]->isChecked() && floppyTypeConfigValue(dfType[i]->currentText()) >= 0) { + count = i + 1; + } + } + return qMax(1, count); + } + + QString configurationDirectory() const + { + if (configsPath && !configsPath->text().trimmed().isEmpty()) { + return expandedPathText(configsPath->text()); + } + return fileDialogInitialPath(QString()); + } + + QString normalizedConfigName() const + { + QString name = configName ? configName->currentText().trimmed() : QString(); + if (name.endsWith(QStringLiteral(".uae"), Qt::CaseInsensitive)) { + name.chop(4); + } + return name; + } + + QString namedConfigPath() const + { + const QString name = normalizedConfigName(); + if (name.isEmpty()) { + return QString(); + } + return QDir(configurationDirectory()).filePath(name + QStringLiteral(".uae")); + } + + QString selectedConfigPath() const + { + if (!configTree || !configTree->currentItem()) { + return QString(); + } + return configTree->currentItem()->data(0, Qt::UserRole).toString(); + } + + void refreshConfigList() + { + if (!configTree) { + return; + } + const QString selectedPath = configPath ? expandedPathText(configPath->text()) : QString(); + const QString search = configSearch ? configSearch->text().trimmed() : QString(); + const QString filter = configFilter ? configFilter->currentText() : QStringLiteral("All configurations"); + QSignalBlocker blocker(configTree); + configTree->clear(); + QTreeWidgetItem *root = new QTreeWidgetItem(configTree, QStringList(QStringLiteral("Configurations"))); + root->setIcon(0, resourceIcon(QStringLiteral("configfile.ico"))); + + QDir dir(configurationDirectory()); + const QFileInfoList files = dir.entryInfoList({ QStringLiteral("*.uae") }, QDir::Files, QDir::Name | QDir::IgnoreCase); + QTreeWidgetItem *selected = nullptr; + for (const QFileInfo &info : files) { + WinUaeQtConfig config; + config.load(info.absoluteFilePath()); + const QString description = config.value(QStringLiteral("config_description")); + const bool isHardware = configBoolValue(config.value(QStringLiteral("config_hardware"))); + const bool isHost = configBoolValue(config.value(QStringLiteral("config_host"))); + if (filter == QStringLiteral("Host") && !isHost) { + continue; + } + if (filter == QStringLiteral("Hardware") && !isHardware) { + continue; + } + if (!search.isEmpty() + && !info.completeBaseName().contains(search, Qt::CaseInsensitive) + && !description.contains(search, Qt::CaseInsensitive)) { + continue; + } + QString displayName = info.completeBaseName(); + if (!description.isEmpty()) { + displayName += QStringLiteral(" (%1)").arg(description); + } + QTreeWidgetItem *item = new QTreeWidgetItem(root, QStringList(displayName)); + item->setIcon(0, resourceIcon(QStringLiteral("configfile.ico"))); + item->setData(0, Qt::UserRole, info.absoluteFilePath()); + item->setData(0, Qt::UserRole + 1, description); + item->setToolTip(0, description.isEmpty() ? info.absoluteFilePath() : description); + if (info.absoluteFilePath() == selectedPath) { + selected = item; + } + } + root->setExpanded(true); + if (selected) { + configTree->setCurrentItem(selected); + } + } + + void selectConfigFromTree() + { + const QString path = selectedConfigPath(); + if (path.isEmpty()) { + return; + } + configPath->setText(path); + configName->setCurrentText(QFileInfo(path).completeBaseName()); + configDescription->setText(configTree->currentItem()->data(0, Qt::UserRole + 1).toString()); + } + + void loadSelectedConfig() + { + QString path = selectedConfigPath(); + if (path.isEmpty()) { + path = !configPath->text().trimmed().isEmpty() ? configPath->text().trimmed() : namedConfigPath(); + } + path = expandedPathText(path); + if (path.isEmpty() || !QFileInfo::exists(path)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Select a configuration to load.")); + return; + } + loadConfig(path); + } + + void saveNamedConfig() + { + QString path = !configPath->text().trimmed().isEmpty() ? configPath->text().trimmed() : namedConfigPath(); + path = expandedPathText(path); + if (path.isEmpty()) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Enter a configuration name before saving.")); + return; + } + QDir().mkpath(QFileInfo(path).absolutePath()); + saveConfig(path); + refreshConfigList(); + } + + void deleteSelectedConfig() + { + QString path = selectedConfigPath(); + if (path.isEmpty()) { + path = configPath->text().trimmed(); + } + path = expandedPathText(path); + if (path.isEmpty() || !QFileInfo::exists(path)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Select a configuration to delete.")); + return; + } + const QString name = QFileInfo(path).fileName(); + if (QMessageBox::question(this, windowTitle(), QStringLiteral("Delete configuration %1?").arg(name)) != QMessageBox::Yes) { + return; + } + if (!QFile::remove(path)) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("Could not delete %1").arg(path)); + return; + } + if (expandedPathText(configPath->text()) == path) { + configPath->clear(); + } + refreshConfigList(); + status->setText(QStringLiteral("Deleted %1").arg(path)); + } + + void loadConfigDialog() + { + const QString path = QFileDialog::getOpenFileName(this, QStringLiteral("Load configuration"), configurationDirectory(), QStringLiteral("WinUAE configuration (*.uae);;All files (*)")); + if (!path.isEmpty()) { + loadConfig(path); + } + } + + void saveConfigDialog() + { + const QString path = QFileDialog::getSaveFileName(this, QStringLiteral("Save configuration"), configurationDirectory(), QStringLiteral("WinUAE configuration (*.uae);;All files (*)")); + if (!path.isEmpty()) { + saveConfig(path); + } + } + + void loadConfig(const QString &path) + { + const QString expandedPath = expandedPathText(path); + WinUaeQtConfig config; + QString error; + if (!config.load(expandedPath, &error)) { + QMessageBox::warning(this, windowTitle(), error); + return; + } + if (mountedDrives) { + mountedDrives->clear(); + } + if (configDescription) { + configDescription->clear(); + } + for (int i = 0; i < MaxDiskSwapperSlots; i++) { + setDiskSwapperPath(i, QString()); + } + clearCdSlots(); + clearExpansionBoardStates(); + inputMappingSettings.clear(); + inputOwnedMappingKeys.clear(); + hardwareOrderOwnedKeys.clear(); + moveQuickstartBeforeOverrides(config); + for (const WinUaeQtConfig::Setting &setting : config.orderedSettings()) { + applySetting(setting.key, setting.value); + } + updateOutputControlState(); + updateMountButtons(); + refreshInputMappingList(); + loadedConfig = config; + refreshHardwareInfoPage(); + configPath->setText(expandedPath); + configName->setCurrentText(QFileInfo(expandedPath).completeBaseName()); + refreshConfigList(); + status->setText(QStringLiteral("Loaded %1").arg(expandedPath)); + } + + void applySetting(const QString &key, const QString &value) + { + trackHardwareOrderSetting(key, value); + if (winUaeQtIsInputDeviceConfigKey(key)) { + inputMappingSettings.insert(key, value); + } else if (key == QStringLiteral("config_description")) { + configDescription->setText(value); + } else if (key == QStringLiteral("quickstart")) { + const QStringList parts = value.split(QLatin1Char(',')); + const QuickstartModelChoice *choice = quickstartModelChoiceByConfigValue(parts.value(0).trimmed()); + if (choice) { + bool ok = false; + const int config = parts.value(1).toInt(&ok); + { + const QSignalBlocker modelBlocker(quickModel); + quickModel->setCurrentText(QString::fromLatin1(choice->display)); + refreshQuickstartConfigurationChoices(ok ? config : 0); + } + quickstartMode->setChecked(true); + quickstartSetConfig->setVisible(false); + applyQuickstartSelectionToUi(); + } + } else if (key == QStringLiteral("unix.rom_path") || key == QStringLiteral("rom_path")) { + romsPath->setText(value); + } else if (key == QStringLiteral("unix.config_path") || key == QStringLiteral("unix.ui.config_path")) { + configsPath->setText(value); + } else if (key == QStringLiteral("unix.nvram_path") || key == QStringLiteral("unix.ui.nvram_path")) { + nvramPath->setText(value); + } else if (key == QStringLiteral("unix.screenshot_path") || key == QStringLiteral("unix.ui.screenshot_path")) { + screenshotsPath->setText(value); + } else if (key == QStringLiteral("statefile_path")) { + stateFilesPath->setText(value); + } else if (key == QStringLiteral("unix.video_path") || key == QStringLiteral("unix.ui.video_path")) { + videosPath->setText(value); + } else if (key == QStringLiteral("unix.saveimage_path") || key == QStringLiteral("unix.ui.saveimage_path")) { + saveImagesPath->setText(value); + } else if (key == QStringLiteral("unix.rip_path") || key == QStringLiteral("unix.ripper_path") || key == QStringLiteral("unix.ui.rip_path")) { + ripsPath->setText(value); + } else if (key == QStringLiteral("unix.data_path") || key == QStringLiteral("unix.ui.data_path")) { + dataPath->setText(value); + } else if (key == QStringLiteral("unix.ui.path_mode")) { + pathDefaultType->setCurrentText(value); + } else if (key == QStringLiteral("unix.ui.recursive_roms")) { + recursiveRoms->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.cache_configurations")) { + cacheConfigurations->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.cache_boxart")) { + cacheBoxArt->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.saveimage_original_path")) { + saveImageOriginalPath->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.relative_paths")) { + relativePaths->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.portable_mode")) { + portableMode->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.full_logging")) { + fullLogging->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.log_window")) { + logWindow->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.ui.gui_scale")) { + miscGuiSize->setCurrentText(value); + applyGuiScaleSelection(false); + } else if (key == QStringLiteral("unix.ui.gui_resize")) { + miscGuiResize->setChecked(configBoolValue(value)); + applyGuiResizeMode(); + } else if (key == QStringLiteral("unix.ui.gui_fullscreen")) { + miscGuiFullscreen->setChecked(configBoolValue(value)); + applyGuiFullscreenMode(miscGuiFullscreen->isChecked()); + } else if (key == QStringLiteral("unix.ui.gui_dark_mode")) { + setGuiDarkModeFromConfig(value); + } else if (key == QStringLiteral("unix.ui.gui_font")) { + applyGuiFontConfig(value); + } else if (key == QStringLiteral("unix.output_file") || key == QStringLiteral("unix.ui.output_file")) { + outputFile->setText(value); + } else if (key == QStringLiteral("unix.output_audio_codec") || key == QStringLiteral("unix.ui.output_audio_codec")) { + outputAudioCodec->setCurrentIndex(value.compare(QStringLiteral("wav"), Qt::CaseInsensitive) == 0 ? 1 : 0); + updateOutputControlState(); + } else if (key == QStringLiteral("unix.output_video_codec") || key == QStringLiteral("unix.ui.output_video_codec")) { + outputVideoCodec->setCurrentIndex(0); + } else if (key == QStringLiteral("unix.output_audio") || key == QStringLiteral("unix.ui.output_audio")) { + outputAudio->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.output_video") || key == QStringLiteral("unix.ui.output_video")) { + outputVideo->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.output_enabled") || key == QStringLiteral("unix.ui.output_enabled")) { + outputEnabled->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.output_frame_limiter_disabled") || key == QStringLiteral("unix.ui.output_frame_limiter_disabled")) { + outputFrameLimiter->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.output_original_size") || key == QStringLiteral("unix.ui.output_original_size")) { + outputOriginalSize->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.output_no_sound") || key == QStringLiteral("unix.ui.output_no_sound")) { + outputNoSound->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.output_no_sound_sync") || key == QStringLiteral("unix.ui.output_no_sound_sync")) { + outputNoSoundSync->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.screenshot_original_size") || key == QStringLiteral("unix.ui.screenshot_original_size")) { + screenshotOriginalSize->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.screenshot_paletted") || key == QStringLiteral("unix.ui.screenshot_paletted")) { + screenshotPaletted->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.screenshot_clip") || key == QStringLiteral("unix.ui.screenshot_clip")) { + screenshotClip->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.screenshot_auto") || key == QStringLiteral("unix.ui.screenshot_auto")) { + screenshotAuto->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("kickstart_rom_file")) { + setPathComboText(romFile, value); + } else if (key == QStringLiteral("kickstart_ext_rom_file")) { + setPathComboText(extendedRomFile, value); + } else if (key == QStringLiteral("cart_file")) { + setPathComboText(cartFile, value); + } else if (key == QStringLiteral("flash_file")) { + flashFile->setText(value); + } else if (key == QStringLiteral("rtc_file")) { + rtcFile->setText(value); + } else if (key == QStringLiteral("maprom")) { + bool ok = false; + const uint mapValue = value.toUInt(&ok, 0); + mapRom->setChecked(ok && mapValue != 0); + } else if (key == QStringLiteral("kickshifter")) { + kickShifter->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("boot_rom_uae")) { + if (value.compare(QStringLiteral("disabled"), Qt::CaseInsensitive) == 0) { + uaeBoardType->setCurrentText(QStringLiteral("ROM disabled")); + } else if (uaeBoardType->currentText() == QStringLiteral("ROM disabled")) { + uaeBoardType->setCurrentText(QStringLiteral("Original UAE (FS + F0 ROM)")); + } + } else if (key == QStringLiteral("uaeboard")) { + if (value.compare(QStringLiteral("disabled"), Qt::CaseInsensitive) == 0 + || value.compare(QStringLiteral("disabled_off"), Qt::CaseInsensitive) == 0) { + if (uaeBoardType->currentText() != QStringLiteral("ROM disabled")) { + uaeBoardType->setCurrentText(QStringLiteral("Original UAE (FS + F0 ROM)")); + } + } else { + uaeBoardType->setCurrentText(uaeBoardText(value)); + } + } else if (romBoardIndexFromKey(key) >= 0) { + applyCustomRomBoard(romBoardIndexFromKey(key), value); + } else if (key == QStringLiteral("board_custom_order")) { + hardwareCustomBoardOrder->setChecked(configBoolValue(value)); + refreshHardwareInfoPage(); + } else if (key == QStringLiteral("floppy_speed")) { + floppySpeed->setValue(floppySpeedSliderPosition(value.toInt())); + updateFloppySpeedLabel(); + } else if (const int drive = floppyKeyDrive(key, QStringLiteral("type")); drive >= 0) { + const int driveType = value.toInt(); + dfEnable[drive]->setChecked(driveType >= 0); + dfType[drive]->setCurrentText(floppyTypeText(driveType)); + syncFloppyDriveToQuick(drive); + } else if (const int drive = floppyKeyDrive(key, QStringLiteral("wp")); drive >= 0) { + dfWriteProtect[drive]->setChecked(configBoolValue(value)); + syncFloppyDriveToQuick(drive); + } else if (const int drive = floppyKeyDrive(key); drive >= 0) { + dfEnable[drive]->setChecked(true); + setPathComboText(dfPath[drive], value); + syncFloppyDriveToQuick(drive); + } else if (key.startsWith(QStringLiteral("diskimage"))) { + bool ok = false; + const int slot = key.mid(9).toInt(&ok); + if (ok && slot >= 0 && slot < MaxDiskSwapperSlots) { + setDiskSwapperPath(slot, value); + if (slot == selectedDiskSwapperSlot()) { + setPathComboText(diskSwapperPath, value); + } + } + } else if (key.startsWith(QStringLiteral("uaehf"))) { + WinUaeQtMountEntry entry; + if (parseWinUaeQtUaehfMountValue(value, &entry)) { + addMountEntryIfUnique(entry); + } + } else if (key == QStringLiteral("filesystem2")) { + WinUaeQtMountEntry entry; + if (parseWinUaeQtFilesystem2MountValue(value, &entry)) { + addMountEntryIfUnique(entry); + } + } else if (key == QStringLiteral("hardfile2")) { + WinUaeQtMountEntry entry; + if (parseWinUaeQtHardfile2MountValue(value, &entry)) { + addMountEntryIfUnique(entry); + } + } else if (key.startsWith(QStringLiteral("cdimage"))) { + bool ok = false; + const int slot = key.mid(7).toInt(&ok); + if (ok && slot >= 0 && slot < MaxCdSlots) { + ensureCdSlots(); + cdSlots[slot] = cdSlotFromConfigValue(value); + if (slot == currentCdSlot) { + loadCdSlotToUi(slot); + } + } + } else if (key == QStringLiteral("cd_speed")) { + cdSpeedTurbo->setChecked(value == QStringLiteral("0")); + } else if (key == QStringLiteral("filesys_no_fsdb")) { + filesysNoFsdb->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("filesys_max_size")) { + bool ok = false; + filesysLimitSize->setChecked(configIntegerValue(value, &ok) != 0 && ok); + } else if (key == QStringLiteral("state_replay_rate")) { + const int seconds = value.toInt() > 0 ? value.toInt() / 50 : -1; + stateReplayRate->setCurrentText(seconds > 0 ? QString::number(seconds) : QStringLiteral("-")); + } else if (key == QStringLiteral("state_replay_buffers")) { + stateReplayBuffers->setCurrentText(value.toInt() > 0 ? value : QStringLiteral("100")); + } else if (key == QStringLiteral("state_replay_autoplay")) { + stateReplayAutoplay->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("chipset")) { + chipset->setCurrentText(chipsetText(value)); + } else if (key == QStringLiteral("chipset_compatible")) { + chipsetCompatible->setCurrentText(value); + quickModel->setCurrentText(value); + } else if (key == QStringLiteral("ntsc")) { + chipsetNtsc->setChecked(configBoolValue(value)); + ntsc->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("immediate_blits")) { + immediateBlits->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("waiting_blits")) { + waitingBlits->setChecked(value.compare(QStringLiteral("disabled"), Qt::CaseInsensitive) != 0 && value != QStringLiteral("0")); + } else if (key == QStringLiteral("collision_level")) { + const QString lower = value.toLower(); + int id = lower == QStringLiteral("none") ? 0 + : lower == QStringLiteral("sprites") ? 1 + : lower == QStringLiteral("playfields") ? 2 + : 3; + if (QAbstractButton *button = collisionButtons->button(id)) { + button->setChecked(true); + } + } else if (key == QStringLiteral("display_optimizations")) { + displayOptimization->setCurrentText(displayOptimizationText(value)); + } else if (key == QStringLiteral("hvcsync")) { + chipsetSyncMode->setCurrentText(configChoiceDisplay(hvSyncChoices, int(sizeof(hvSyncChoices) / sizeof(hvSyncChoices[0])), value)); + } else if (key == QStringLiteral("genlock")) { + genlockConnected->setChecked(configBoolValue(value)); + updateGenlockControlState(); + } else if (key == QStringLiteral("genlockmode")) { + genlockMode->setCurrentText(configChoiceDisplay(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), value)); + updateGenlockControlState(); + } else if (key == QStringLiteral("genlock_mix")) { + setGenlockMixConfigValue(value.toInt()); + } else if (key == QStringLiteral("genlock_alpha")) { + genlockAlpha->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("genlock_aspect")) { + genlockKeepAspect->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("genlock_image")) { + genlockImagePath = value; + const QString mode = configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText()); + if (genlockModeUsesImageFile(mode)) { + setPathComboText(genlockFile, value); + } + } else if (key == QStringLiteral("genlock_video")) { + genlockVideoPath = value; + const QString mode = configChoiceValue(genlockModeChoices, int(sizeof(genlockModeChoices) / sizeof(genlockModeChoices[0])), genlockMode->currentText()); + if (genlockModeUsesVideoFile(mode)) { + setPathComboText(genlockFile, value); + } + } else if (key == QStringLiteral("keyboard_connected")) { + if (!configBoolValue(value)) { + keyboardMode->setCurrentText(QStringLiteral("Keyboard disconnected")); + } else if (keyboardMode->currentText() == QStringLiteral("Keyboard disconnected")) { + keyboardMode->setCurrentText(QStringLiteral("UAE High level emulation")); + } + } else if (key == QStringLiteral("keyboard_type")) { + keyboardMode->setCurrentText(configChoiceDisplay(keyboardModeChoices, int(sizeof(keyboardModeChoices) / sizeof(keyboardModeChoices[0])), value)); + } else if (key == QStringLiteral("keyboard_nkro")) { + keyboardNkro->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("ciaatod")) { + setAdvancedRadioValue(advancedCiaTodButtons, ciaTodChoices, int(sizeof(ciaTodChoices) / sizeof(ciaTodChoices[0])), value); + } else if (key == QStringLiteral("rtc")) { + setAdvancedRadioValue(advancedRtcButtons, rtcChoices, int(sizeof(rtcChoices) / sizeof(rtcChoices[0])), value); + } else if (key == QStringLiteral("chipset_rtc_adjust")) { + advancedRtcAdjust->setText(value); + } else if (advancedCheckBoxes.contains(key)) { + setAdvancedCheck(key, configBoolValue(value)); + } else if (key == QStringLiteral("ide")) { + const QString lower = value.toLower(); + advancedIdeA600A1200->setChecked(lower == QStringLiteral("a600/a1200")); + advancedIdeA4000->setChecked(lower == QStringLiteral("a4000")); + } else if (key == QStringLiteral("scsi_a3000")) { + advancedScsiA3000->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("scsi_a4000t")) { + advancedScsiA4000T->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("ciaa_type")) { + advancedCia391078->setChecked(value.compare(QStringLiteral("391078-01"), Qt::CaseInsensitive) == 0); + } else if (key == QStringLiteral("ciab_type")) { + if (value.compare(QStringLiteral("391078-01"), Qt::CaseInsensitive) == 0) { + advancedCia391078->setChecked(true); + } + } else if (key == QStringLiteral("unmapped_address_space")) { + advancedUnmappedAddress->setCurrentText(configChoiceDisplay(unmappedAddressChoices, int(sizeof(unmappedAddressChoices) / sizeof(unmappedAddressChoices[0])), value)); + } else if (key == QStringLiteral("eclocksync")) { + advancedCiaSync->setCurrentText(configChoiceDisplay(ciaSyncChoices, int(sizeof(ciaSyncChoices) / sizeof(ciaSyncChoices[0])), value)); + } else if (key == QStringLiteral("fatgary") || key == QStringLiteral("ramsey")) { + bool ok = false; + const int revision = configIntegerValue(value, &ok); + if (key == QStringLiteral("fatgary")) { + setAdvancedRevision(advancedFatGary, advancedFatGaryRevision, ok ? revision : -1, 0x00); + } else { + setAdvancedRevision(advancedRamsey, advancedRamseyRevision, ok ? revision : -1, 0x0f); + } + } else if (key == QStringLiteral("agnusmodel")) { + advancedAgnusModel->setCurrentText(configChoiceDisplay(agnusModelChoices, int(sizeof(agnusModelChoices) / sizeof(agnusModelChoices[0])), value)); + } else if (key == QStringLiteral("agnussize")) { + advancedAgnusSize->setCurrentText(configChoiceDisplay(agnusSizeChoices, int(sizeof(agnusSizeChoices) / sizeof(agnusSizeChoices[0])), value)); + } else if (key == QStringLiteral("denisemodel")) { + advancedDeniseModel->setCurrentText(configChoiceDisplay(deniseModelChoices, int(sizeof(deniseModelChoices) / sizeof(deniseModelChoices[0])), value)); + } else if (key == QStringLiteral("cycle_exact")) { + const QString lower = value.toLower(); + chipsetCycleExact->setChecked(lower == QStringLiteral("true")); + chipsetCycleExactMemory->setChecked(lower == QStringLiteral("true") || lower == QStringLiteral("memory")); + } else if (key == QStringLiteral("cpu_cycle_exact")) { + chipsetCycleExact->setChecked(configBoolValue(value)); + if (configBoolValue(value)) { + chipsetCycleExactMemory->setChecked(true); + } + } else if (key == QStringLiteral("cpu_memory_cycle_exact")) { + chipsetCycleExactMemory->setChecked(configBoolValue(value)); + if (!configBoolValue(value)) { + chipsetCycleExact->setChecked(false); + } + } else if (key == QStringLiteral("blitter_cycle_exact")) { + if (!configBoolValue(value)) { + chipsetCycleExactMemory->setChecked(false); + } + } else if (key == QStringLiteral("cpu_model")) { + setCpuButton(value.toInt()); + } else if (key == QStringLiteral("cpu_speed")) { + const bool fastest = value.compare(QStringLiteral("max"), Qt::CaseInsensitive) == 0; + if (QAbstractButton *button = cpuSpeedButtons->button(fastest ? 1 : 0)) { + button->setChecked(true); + } + } else if (key == QStringLiteral("cpu_throttle")) { + cpuSpeed->setValue(qBound(cpuSpeed->minimum(), qRound(value.toDouble() / 100.0), cpuSpeed->maximum())); + updateCpuSpeedLabel(); + } else if (key == QStringLiteral("cpu_compatible")) { + moreCompatible->setChecked(configBoolValue(value)); + updateCpuControlState(); + } else if (key == QStringLiteral("fpu_model")) { + setFpuButton(value.toInt()); + } else if (key == QStringLiteral("cpu_24bit_addressing")) { + cpu24Bit->setChecked(value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0); + updateCpuControlState(); + } else if (key == QStringLiteral("mmu_model")) { + const QString lower = value.toLower(); + const int id = lower.startsWith(QStringLiteral("68ec")) ? 2 : (value.toInt() > 0 ? 1 : 0); + if (QAbstractButton *button = mmuButtons->button(id)) { + button->setChecked(true); + } + } else if (key == QStringLiteral("cpu_no_unimplemented")) { + cpuUnimplemented->setChecked(!configBoolValue(value)); + } else if (key == QStringLiteral("fpu_no_unimplemented")) { + fpuUnimplemented->setChecked(!configBoolValue(value)); + } else if (key == QStringLiteral("fpu_strict")) { + fpuStrict->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("fpu_softfloat")) { + if (configBoolValue(value)) { + fpuMode->setCurrentText(QStringLiteral("Softfloat (80-bit)")); + } else if (fpuMode->currentText() == QStringLiteral("Softfloat (80-bit)")) { + fpuMode->setCurrentText(QStringLiteral("Host (64-bit)")); + } + } else if (key == QStringLiteral("fpu_msvc_long_double")) { + if (configBoolValue(value)) { + fpuMode->setCurrentText(QStringLiteral("Host (80-bit)")); + } else if (fpuMode->currentText() == QStringLiteral("Host (80-bit)")) { + fpuMode->setCurrentText(QStringLiteral("Host (64-bit)")); + } + } else if (key == QStringLiteral("cpu_multiplier")) { + cpuFrequency->setCurrentText(cpuMultiplierText(value.toInt())); + } else if (key == QStringLiteral("cpu_frequency")) { + cpuFrequency->setCurrentText(QStringLiteral("Custom")); + cpuFrequencyCustom->setText(QString::number(value.toDouble() / 1000000.0, 'f', 6)); + } else if (key == QStringLiteral("cachesize")) { + const int size = value.toInt(); + jit->setChecked(unixJitBackendAvailable() && size > 0); + jitCache->setValue(jitCachePositionFromSize(size)); + updateCpuControlState(); + } else if (key == QStringLiteral("compfpu")) { + jitFpu->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("comp_constjump")) { + jitConstJump->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("comp_flushmode")) { + jitHardFlush->setChecked(value.compare(QStringLiteral("hard"), Qt::CaseInsensitive) == 0 || configBoolValue(value)); + } else if (key == QStringLiteral("comp_nf")) { + jitNoFlags->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("comp_catchfault")) { + jitCatchFault->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("comp_trustbyte") + || key == QStringLiteral("comp_trustword") + || key == QStringLiteral("comp_trustlong") + || key == QStringLiteral("comp_trustnaddr")) { + if (QAbstractButton *button = jitTrust->button(value.compare(QStringLiteral("indirect"), Qt::CaseInsensitive) == 0 ? 1 : 0)) { + button->setChecked(true); + } + } else if (key == QStringLiteral("chipmem_size")) { + const QMap map = { { 1, QStringLiteral("512 KB") }, { 2, QStringLiteral("1 MB") }, { 4, QStringLiteral("2 MB") }, { 8, QStringLiteral("4 MB") }, { 16, QStringLiteral("8 MB") } }; + chipMem->setCurrentText(map.value(value.toInt(), QStringLiteral("2 MB"))); + } else if (key == QStringLiteral("fastmem_size")) { + z2Fast->setCurrentText(value == QStringLiteral("0") ? QStringLiteral("None") : value + QStringLiteral(" MB")); + } else if (key == QStringLiteral("bogomem_size")) { + const QMap map = { + { 0, QStringLiteral("None") }, + { 2, QStringLiteral("512 KB") }, + { 4, QStringLiteral("1 MB") }, + { 6, QStringLiteral("1.5 MB") }, + { 7, QStringLiteral("1.8 MB") } + }; + slowMem->setCurrentText(map.value(value.toInt(), QStringLiteral("None"))); + } else if (key == QStringLiteral("z3mem_size")) { + z3Fast->setCurrentText(value == QStringLiteral("0") ? QStringLiteral("None") : value + QStringLiteral(" MB")); + } else if (key == QStringLiteral("megachipmem_size")) { + z3ChipMem->setCurrentText(value == QStringLiteral("0") ? QStringLiteral("None") : value + QStringLiteral(" MB")); + } else if (key == QStringLiteral("mbresmem_size")) { + processorSlotMem->setCurrentText(value == QStringLiteral("0") ? QStringLiteral("None") : value + QStringLiteral(" MB")); + } else if (key == QStringLiteral("z3mapping")) { + z3Mapping->setCurrentText(configChoiceDisplay(z3MappingChoices, int(sizeof(z3MappingChoices) / sizeof(z3MappingChoices[0])), value)); + } else if (key == QStringLiteral("gfxcard_size")) { + rtgMem->setCurrentText(value == QStringLiteral("0") ? QStringLiteral("None") : value + QStringLiteral(" MB")); + } else if (key == QStringLiteral("gfxcard_type")) { + rtgType->setCurrentText(value); + } else if (key == QStringLiteral("gfxcard_options")) { + applyRtgOptionsValue(value); + } else if (key == QStringLiteral("gfx_filter_autoscale_rtg")) { + applyRtgScaleValue(value); + filterStates[1].autoscale = isRtgAutoscaleValue(value) ? value : QStringLiteral("resize"); + if (currentFilterTarget == 1) { + loadFilterStateToUi(1); + } + } else if (key == QStringLiteral("gfx_filter_aspect_ratio_rtg")) { + rtgAspectRatio->setCurrentText(configChoiceDisplay(rtgAspectRatioChoices, int(sizeof(rtgAspectRatioChoices) / sizeof(rtgAspectRatioChoices[0])), value)); + } else if (key == QStringLiteral("gfx_backbuffers_rtg")) { + rtgBuffers->setCurrentText(rtgBufferText(value)); + } else if (key == QStringLiteral("gfx_refreshrate_rtg")) { + if (value == QStringLiteral("0")) { + rtgRefreshRate->setCurrentText(QStringLiteral("Chipset")); + } else { + rtgRefreshRate->setCurrentText(value); + } + } else if (key == QStringLiteral("unix.rtg_vblank") || key == QStringLiteral("rtg_vblank")) { + rtgRefreshRate->setCurrentText(rtgVBlankText(value)); + } else if (key == QStringLiteral("gfxcard_hardware_vblank")) { + rtgHardwareVBlank->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfxcard_hardware_sprite")) { + rtgHardwareSprite->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfxcard_multithread")) { + rtgMultithread->setChecked(rtgMultithread->isEnabled() && configBoolValue(value)); + } else if (key == QStringLiteral("rtg_modes")) { + bool ok = false; + int mask = value.toInt(&ok, 0); + if (!ok) { + mask = RtgDefaultModeMask; + } + rtg8Bit->setCurrentText(rtg8BitText(mask)); + rtg16Bit->setCurrentText(rtg16BitText(mask)); + rtg24Bit->setCurrentText(rtg24BitText(mask)); + rtg32Bit->setCurrentText(rtg32BitText(mask)); + } else if (key == QStringLiteral("cpuboard_type")) { + if (value.compare(QStringLiteral("none"), Qt::CaseInsensitive) == 0 || value == QStringLiteral("-")) { + selectCpuBoardConfigValue(QString()); + } else { + selectCpuBoardConfigValue(value); + } + } else if (key == QStringLiteral("cpuboardmem1_size")) { + setCpuBoardMemoryMb(value.toInt()); + } else if (key == QStringLiteral("cpuboard_rom_file")) { + setPathComboText(cpuBoardRom, value); + } else if (key == QStringLiteral("cpuboard_settings")) { + cpuBoardSettingsRaw = value; + if (acceleratorOption) { + acceleratorOption->setCurrentText(QStringLiteral("Settings preserved")); + } + } else if (applyExpansionBoardSetting(key, value)) { + } else if (key == QStringLiteral("bsdsocket_emu")) { + expansionBsdsocket->setChecked(expansionBsdsocket->isEnabled() && configBoolValue(value)); + } else if (key == QStringLiteral("scsi")) { + const QString lower = value.toLower(); + expansionScsiDevice->setChecked(expansionScsiDevice->isEnabled() && lower != QStringLiteral("false") && lower != QStringLiteral("0") && !lower.isEmpty()); + } else if (key == QStringLiteral("uaescsimode") || key == QStringLiteral("unix.uaescsimode")) { + miscScsiMode->setCurrentText(configChoiceDisplay(scsiModeChoices, int(sizeof(scsiModeChoices) / sizeof(scsiModeChoices[0])), value)); + } else if (key == QStringLiteral("sana2")) { + expansionSana2->setChecked(expansionSana2->isEnabled() && configBoolValue(value)); + } else if (key == QStringLiteral("unix.soundcard") || key == QStringLiteral("soundcard")) { + bool ok = false; + const int index = value.toInt(&ok); + if (ok && soundDevice && index >= 0 && index < soundDevice->count()) { + soundDevice->setCurrentIndex(index); + } + } else if (key == QStringLiteral("unix.soundcardname") || key == QStringLiteral("soundcardname")) { + selectSoundDeviceByConfigName(value); + } else if (key == QStringLiteral("sound_output")) { + if (QAbstractButton *button = soundOutputButtons->button(soundOutputId(value))) { + button->setChecked(true); + } + updateSoundControlState(); + } else if (key == QStringLiteral("sound_auto")) { + soundAutomatic->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("sound_volume")) { + soundMasterVolume->setValue(100 - qBound(0, value.toInt(), 100)); + } else if (key == QStringLiteral("sound_volume_paula") + || key == QStringLiteral("sound_volume_cd") + || key == QStringLiteral("sound_volume_ahi") + || key == QStringLiteral("sound_volume_midi") + || key == QStringLiteral("sound_volume_genlock")) { + const QMap volumeIndex = { + { QStringLiteral("sound_volume_paula"), 0 }, + { QStringLiteral("sound_volume_cd"), 1 }, + { QStringLiteral("sound_volume_ahi"), 2 }, + { QStringLiteral("sound_volume_midi"), 3 }, + { QStringLiteral("sound_volume_genlock"), 4 } + }; + const int index = volumeIndex.value(key, 0); + soundVolumeAttenuation[index] = qBound(0, value.toInt(), 100); + if (index == currentSoundVolume) { + loadSelectedSoundVolume(); + } + } else if (key == QStringLiteral("sound_max_buff")) { + soundBufferSize->setValue(soundBufferIndexFromSize(value.toInt())); + } else if (key == QStringLiteral("sound_channels")) { + soundChannels->setCurrentText(soundChannelText(value)); + updateSoundControlState(); + } else if (key == QStringLiteral("sound_stereo_separation")) { + soundStereoSeparation->setCurrentText(QStringLiteral("%1%").arg(qBound(0, value.toInt(), 10) * 10)); + } else if (key == QStringLiteral("sound_stereo_mixing_delay")) { + const int delay = qBound(0, value.toInt(), 10); + soundStereoDelay->setCurrentText(delay > 0 ? QString::number(delay) : QStringLiteral("-")); + } else if (key == QStringLiteral("sound_frequency")) { + soundFrequency->setCurrentText(QString::number(qBound(8000, value.toInt(), 768000))); + } else if (key == QStringLiteral("sound_interpol")) { + soundInterpolation->setCurrentText(soundInterpolationText(value)); + } else if (key == QStringLiteral("sound_filter")) { + soundFilter->setCurrentText(soundFilterText(value, soundFilterTypeConfigValue(soundFilter->currentText()))); + } else if (key == QStringLiteral("sound_filter_type")) { + soundFilter->setCurrentText(soundFilterText(soundFilterConfigValue(soundFilter->currentText()), value)); + } else if (key == QStringLiteral("sound_stereo_swap_paula")) { + setSoundSwapBit(true, configBoolValue(value)); + } else if (key == QStringLiteral("sound_stereo_swap_ahi")) { + setSoundSwapBit(false, configBoolValue(value)); + } else if (key == QStringLiteral("floppy_volume")) { + const int attenuation = qBound(0, value.toInt(), 100); + for (int i = 0; i < FloppySoundDriveCount; i++) { + floppySoundEmptyAttenuation[i] = attenuation; + floppySoundDiskAttenuation[i] = attenuation; + } + loadSelectedFloppySound(); + } else if (const int drive = floppyKeyDrive(key, QStringLiteral("sound")); drive >= 0) { + floppySoundTypeValue[drive] = qBound(0, value.toInt(), 1); + if (drive == currentFloppySoundDrive) { + loadSelectedFloppySound(); + } + } else if (const int drive = floppyKeyDrive(key, QStringLiteral("soundvolume_empty")); drive >= 0) { + floppySoundEmptyAttenuation[drive] = qBound(0, value.toInt(), 100); + if (drive == currentFloppySoundDrive) { + loadSelectedFloppySound(); + } + } else if (const int drive = floppyKeyDrive(key, QStringLiteral("soundvolume_disk")); drive >= 0) { + floppySoundDiskAttenuation[drive] = qBound(0, value.toInt(), 100); + if (drive == currentFloppySoundDrive) { + loadSelectedFloppySound(); + } + } else if (key == QStringLiteral("parallel_matrix_emulation")) { + printerType->setCurrentText(configChoiceDisplay(printerTypeChoices, int(sizeof(printerTypeChoices) / sizeof(printerTypeChoices[0])), value)); + } else if (key == QStringLiteral("parallel_postscript_detection")) { + if (configBoolValue(value)) { + printerType->setCurrentText(QStringLiteral("PostScript (Passthrough)")); + } else if (printerType->currentText().startsWith(QStringLiteral("PostScript"))) { + printerType->setCurrentText(QStringLiteral("Passthrough")); + } + } else if (key == QStringLiteral("parallel_postscript_emulation")) { + if (configBoolValue(value)) { + printerType->setCurrentText(QStringLiteral("PostScript (Emulation)")); + } else if (printerType->currentText() == QStringLiteral("PostScript (Emulation)")) { + printerType->setCurrentText(QStringLiteral("PostScript (Passthrough)")); + } + } else if (key == QStringLiteral("parallel_autoflush")) { + printerAutoFlush->setValue(qBound(0, value.toInt(), 3600)); + } else if (key == QStringLiteral("ghostscript_parameters")) { + ghostscriptParams->setText(value); + } else if (key == QStringLiteral("unix.samplersoundcard") + || key == QStringLiteral("samplersoundcard") + || key == QStringLiteral("unix.sampler_soundcard") + || key == QStringLiteral("sampler_soundcard")) { + bool ok = false; + const int index = value.toInt(&ok); + if (ok && samplerDevice && index >= -1 && index + 1 < samplerDevice->count()) { + samplerDevice->setCurrentIndex(index + 1); + } + updateIoPortsState(); + } else if (key == QStringLiteral("unix.samplersoundcardname") + || key == QStringLiteral("samplersoundcardname") + || key == QStringLiteral("unix.sampler_soundcardname") + || key == QStringLiteral("sampler_soundcardname")) { + selectSamplerDeviceByConfigName(value); + updateIoPortsState(); + } else if (key == QStringLiteral("sampler_stereo")) { + samplerStereo->setChecked(samplerDevice + && samplerDevice->currentText() != QStringLiteral("") + && configBoolValue(value)); + updateIoPortsState(); + } else if (key == QStringLiteral("midi_device") || key == QStringLiteral("midiout_device")) { + bool ok = false; + const int deviceId = value.toInt(&ok); + if (ok) { + selectMidiOutByDeviceId(deviceId); + } + } else if (key == QStringLiteral("midiout_device_name")) { + selectMidiOutByConfigName(value); + } else if (key == QStringLiteral("midiin_device")) { + bool ok = false; + const int deviceId = value.toInt(&ok); + if (ok) { + selectMidiInByDeviceId(deviceId); + } + } else if (key == QStringLiteral("midiin_device_name")) { + selectMidiInByConfigName(value); + } else if (key == QStringLiteral("midirouter")) { + midiRouter->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("unix.serial_port") || key == QStringLiteral("serial_port")) { + serialPort->setCurrentText(value.isEmpty() ? QStringLiteral("") : value); + updateIoPortsState(); + } else if (key == QStringLiteral("serial_on_demand")) { + serialShared->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("serial_hardware_ctsrts")) { + serialCtsRts->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("serial_status")) { + serialStatus->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("serial_ri")) { + serialRingIndicator->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("serial_direct")) { + serialDirect->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("serial_translate")) { + serialCrlf->setChecked(value.compare(QStringLiteral("crlf_cr"), Qt::CaseInsensitive) == 0 || configBoolValue(value)); + } else if (key == QStringLiteral("uaeserial")) { + uaeSerial->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("dongle")) { + bool ok = false; + const int index = value.toInt(&ok); + if (ok && index >= 0 && index < int(sizeof(dongleChoices) / sizeof(dongleChoices[0]))) { + protectionDongle->setCurrentText(QString::fromLatin1(dongleChoices[index].display)); + } else { + protectionDongle->setCurrentText(configChoiceDisplay(dongleChoices, int(sizeof(dongleChoices) / sizeof(dongleChoices[0])), value)); + } + } else if (miscOptionItems.contains(key)) { + if (key == QStringLiteral("power_led_dim")) { + setMiscOptionChecked(key, value.toInt() != 0); + } else { + setMiscOptionChecked(key, configBoolValue(value)); + } + } else if (key == QStringLiteral("statefile")) { + setPathComboText(stateFileName, value); + stateFileClear->setChecked(value.trimmed().isEmpty()); + } else if (key == QStringLiteral("keyboard_leds")) { + for (const QString &field : value.split(QLatin1Char(','))) { + const QStringList parts = field.split(QLatin1Char(':')); + if (parts.size() != 2) { + continue; + } + const QString name = parts[0].trimmed().toLower(); + const QString led = configChoiceDisplay(keyboardLedChoices, int(sizeof(keyboardLedChoices) / sizeof(keyboardLedChoices[0])), parts[1].trimmed()); + if (name == QStringLiteral("numlock")) { + keyboardLed[0]->setCurrentText(led); + } else if (name == QStringLiteral("capslock")) { + keyboardLed[1]->setCurrentText(led); + } else if (name == QStringLiteral("scrolllock")) { + keyboardLed[2]->setCurrentText(led); + } + } + } else if (key == QStringLiteral("active_priority") || key == QStringLiteral("activepriority")) { + setExtensionPriorityValue(extensionActivePriority, value.toInt()); + } else if (key == QStringLiteral("active_not_captured_pause")) { + extensionActivePause->setChecked(configBoolValue(value)); + updateExtensionActivityState(); + } else if (key == QStringLiteral("active_not_captured_nosound")) { + extensionActiveNoSound->setChecked(configBoolValue(value)); + updateExtensionActivityState(); + } else if (key == QStringLiteral("active_input")) { + const int mask = value.toInt(); + extensionActiveNoJoy->setChecked((mask & 4) == 0); + extensionActiveNoKeyboard->setChecked((mask & 1) == 0); + updateExtensionActivityState(); + } else if (key == QStringLiteral("inactive_priority")) { + setExtensionPriorityValue(extensionInactivePriority, value.toInt()); + } else if (key == QStringLiteral("inactive_pause")) { + extensionInactivePause->setChecked(configBoolValue(value)); + updateExtensionActivityState(); + } else if (key == QStringLiteral("inactive_nosound")) { + extensionInactiveNoSound->setChecked(configBoolValue(value)); + updateExtensionActivityState(); + } else if (key == QStringLiteral("inactive_input")) { + extensionInactiveNoJoy->setChecked((value.toInt() & 4) == 0); + updateExtensionActivityState(); + } else if (key == QStringLiteral("iconified_priority")) { + setExtensionPriorityValue(extensionMinimizedPriority, value.toInt()); + } else if (key == QStringLiteral("iconified_pause")) { + extensionMinimizedPause->setChecked(configBoolValue(value)); + updateExtensionActivityState(); + } else if (key == QStringLiteral("iconified_nosound")) { + extensionMinimizedNoSound->setChecked(configBoolValue(value)); + updateExtensionActivityState(); + } else if (key == QStringLiteral("iconified_input")) { + extensionMinimizedNoJoy->setChecked((value.toInt() & 4) == 0); + updateExtensionActivityState(); + } else if (key.startsWith(QStringLiteral("joyport")) && key.size() == 8) { + bool ok = false; + const int port = key.mid(7, 1).toInt(&ok); + if (ok && port >= 0 && port < 4) { + portDevice[port]->setCurrentText(joyportDeviceText(value, port < 2)); + } + } else if (key.startsWith(QStringLiteral("joyportcustom"))) { + bool ok = false; + const int slot = key.mid(13).toInt(&ok); + if (ok && slot >= 0 && slot < MaxJoyportCustomSlots) { + joyportCustom[slot] = value; + } + } else if (key.startsWith(QStringLiteral("joyport")) && key.endsWith(QStringLiteral("autofire"))) { + bool ok = false; + const int port = key.mid(7, 1).toInt(&ok); + if (ok && port >= 0 && port < 2) { + portAutofire[port]->setCurrentText(autofireText(value)); + } + } else if (key.startsWith(QStringLiteral("joyport")) && key.endsWith(QStringLiteral("mode"))) { + bool ok = false; + const int port = key.mid(7, 1).toInt(&ok); + if (ok && port >= 0 && port < 2) { + portMode[port]->setCurrentText(joyportModeText(value)); + } + } else if (key == QStringLiteral("input.config")) { + const int config = value.toInt(); + if (config <= 0) { + inputType->setCurrentText(QStringLiteral("Game Ports")); + } else { + inputType->setCurrentIndex(qBound(0, config - 1, CustomInputConfigSlots)); + } + } else if (key == QStringLiteral("input.joystick_deadzone") || key == QStringLiteral("input.joymouse_deadzone")) { + inputDeadzone->setValue(qBound(inputDeadzone->minimum(), value.toInt(), inputDeadzone->maximum())); + } else if (key == QStringLiteral("input.autofire_speed")) { + inputAutofireRate->setValue(qBound(inputAutofireRate->minimum(), value.toInt(), inputAutofireRate->maximum())); + } else if (key == QStringLiteral("input.joymouse_speed_digital")) { + inputJoyMouseDigital->setValue(qBound(inputJoyMouseDigital->minimum(), value.toInt(), inputJoyMouseDigital->maximum())); + } else if (key == QStringLiteral("input.joymouse_speed_analog")) { + inputJoyMouseAnalog->setValue(qBound(inputJoyMouseAnalog->minimum(), value.toInt(), inputJoyMouseAnalog->maximum())); + } else if (key == QStringLiteral("input.mouse_speed")) { + mouseSpeed->setValue(qBound(mouseSpeed->minimum(), value.toInt(), mouseSpeed->maximum())); + } else if (key == QStringLiteral("input.autoswitch")) { + portAutoswitch->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("middle_mouse")) { + setMouseUntrapBit(true, configBoolValue(value)); + } else if (key == QStringLiteral("magic_mouse")) { + setMouseUntrapBit(false, configBoolValue(value)); + } else if (key == QStringLiteral("magic_mousecursor")) { + magicMouseCursor->setCurrentText(magicMouseCursorText(value)); + } else if (key == QStringLiteral("absolute_mouse")) { + if (value.compare(QStringLiteral("tablet"), Qt::CaseInsensitive) == 0) { + virtualMouseDriver->setChecked(true); + tabletMode->setCurrentText(QStringLiteral("Tablet emulation")); + } else if (value.compare(QStringLiteral("mousehack"), Qt::CaseInsensitive) == 0) { + virtualMouseDriver->setChecked(true); + tabletMode->setCurrentText(QStringLiteral("-")); + } else { + virtualMouseDriver->setChecked(false); + tabletMode->setCurrentText(QStringLiteral("-")); + } + updateMouseExtraState(); + } else if (key == QStringLiteral("tablet_library")) { + tabletLibrary->setChecked(configBoolValue(value)); + updateMouseExtraState(); + } else if (key == QStringLiteral("gfx_display") || key == QStringLiteral("gfx_display_rtg")) { + bool ok = false; + const int index = value.toInt(&ok); + if (ok) { + selectHostDisplayByIndex(index); + } + } else if (key == QStringLiteral("gfx_display_name") + || key == QStringLiteral("gfx_display_name_rtg") + || key == QStringLiteral("gfx_display_friendlyname") + || key == QStringLiteral("gfx_display_friendlyname_rtg")) { + selectHostDisplayByName(value); + } else if (key == QStringLiteral("gfx_width_windowed")) { + windowWidth->setText(value); + } else if (key == QStringLiteral("gfx_height_windowed")) { + windowHeight->setText(value); + } else if (key == QStringLiteral("gfx_width_fullscreen")) { + fullscreenResolution->setProperty("winuae_width", value); + if (value == QStringLiteral("native")) { + fullscreenResolution->setCurrentText(QStringLiteral("Native")); + } else { + const QString currentHeight = fullscreenResolution->property("winuae_height").toString(); + if (!currentHeight.isEmpty() && currentHeight != QStringLiteral("native")) { + fullscreenResolution->setCurrentText(value + QStringLiteral("x") + currentHeight); + } + } + } else if (key == QStringLiteral("gfx_height_fullscreen")) { + fullscreenResolution->setProperty("winuae_height", value); + const QString currentWidth = fullscreenResolution->property("winuae_width").toString(); + if (value == QStringLiteral("native") || currentWidth == QStringLiteral("native")) { + fullscreenResolution->setCurrentText(QStringLiteral("Native")); + } else if (!currentWidth.isEmpty()) { + fullscreenResolution->setCurrentText(currentWidth + QStringLiteral("x") + value); + } + } else if (key == QStringLiteral("gfx_refreshrate")) { + displayRefreshRate->setCurrentText(value == QStringLiteral("0") ? QStringLiteral("Default") : value); + } else if (key == QStringLiteral("gfx_backbuffers")) { + displayBufferCount->setCurrentText(value.toInt() >= 3 ? QStringLiteral("Triple") : QStringLiteral("Double")); + } else if (key == QStringLiteral("gfx_resize_windowed")) { + windowResize->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfx_fullscreen_amiga")) { + nativeMode->setCurrentText(fullscreenModeText(value)); + } else if (key == QStringLiteral("gfx_fullscreen_picasso")) { + rtgMode->setCurrentText(fullscreenModeText(value)); + } else if (key == QStringLiteral("gfx_vsync")) { + nativeVsync->setProperty("winuae_vsync", value); + nativeVsync->setCurrentText(nativeVsyncText(value, nativeVsync->property("winuae_vsyncmode").toString())); + } else if (key == QStringLiteral("gfx_vsyncmode")) { + nativeVsync->setProperty("winuae_vsyncmode", value); + nativeVsync->setCurrentText(nativeVsyncText(nativeVsync->property("winuae_vsync").toString(), value)); + } else if (key == QStringLiteral("gfx_vsync_picasso")) { + rtgVsync->setProperty("winuae_vsync", value); + rtgVsync->setCurrentText(rtgVsyncText(value)); + } else if (key == QStringLiteral("gfx_vsyncmode_picasso")) { + rtgVsync->setProperty("winuae_vsyncmode", value); + if (rtgVsync->property("winuae_vsync").toString().compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0) { + rtgVsync->setCurrentText(QStringLiteral("VSync (Busy wait)")); + } + } else if (key == QStringLiteral("gfx_frame_slices")) { + nativeFrameSlices->setCurrentText(QString::number(qBound(1, value.toInt(), 4))); + } else if (key == QStringLiteral("gfx_resolution")) { + displayResolution->setCurrentText(value); + } else if (key == QStringLiteral("gfx_overscanmode")) { + displayOverscan->setCurrentText(overscanText(value)); + } else if (key == QStringLiteral("gfx_autoresolution")) { + displayAutoResolution->setCurrentText(autoResolutionText(value.toInt())); + } else if (key == QStringLiteral("gfx_framerate")) { + displayFrameRate->setValue(qBound(displayFrameRate->minimum(), value.toInt(), displayFrameRate->maximum())); + } else if (key == QStringLiteral("gfx_linemode")) { + if (QAbstractButton *button = displayLineModeButtons->button(progressiveLineModeId(value))) { + button->setChecked(true); + } + if (QAbstractButton *button = displayInterlacedLineModeButtons->button(interlacedLineModeId(value))) { + button->setChecked(true); + } + } else if (key == QStringLiteral("gfx_center_horizontal")) { + displayCenterHorizontal->setChecked(value.compare(QStringLiteral("none"), Qt::CaseInsensitive) != 0 && !value.isEmpty()); + } else if (key == QStringLiteral("gfx_center_vertical")) { + displayCenterVertical->setChecked(value.compare(QStringLiteral("none"), Qt::CaseInsensitive) != 0 && !value.isEmpty()); + } else if (key == QStringLiteral("gfx_flickerfixer")) { + displayFlickerFixer->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfx_lores_mode")) { + displayLoresSmoothed->setChecked(value.compare(QStringLiteral("filtered"), Qt::CaseInsensitive) == 0); + } else if (key == QStringLiteral("gfx_blacker_than_black")) { + displayBlackerThanBlack->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfx_monochrome")) { + displayMonochrome->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfx_autoresolution_vga")) { + displayAutoResolutionVga->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfx_monitorblankdelay")) { + displayResyncBlank->setChecked(value.toInt() > 0); + } else if (key == QStringLiteral("gfx_keep_aspect")) { + displayKeepAspect->setChecked(configBoolValue(value)); + } else if (key == QStringLiteral("gfx_ntscpixels")) { + filterNtscPixels->setChecked(configBoolValue(value)); + } else if (applyFilterSetting(key, value)) { + } + } + + void saveConfig(const QString &path) + { + const QString expandedPath = expandedPathText(path); + WinUaeQtConfig config = mergedConfig(); + QString error; + if (!config.save(expandedPath, &error)) { + QMessageBox::warning(this, windowTitle(), error); + return; + } + loadedConfig = config; + configPath->setText(expandedPath); + configName->setCurrentText(QFileInfo(expandedPath).completeBaseName()); + refreshConfigList(); + status->setText(QStringLiteral("Saved %1").arg(expandedPath)); + } + + void startEmulator() + { + const WinUaeQtConfig config = mergedConfig(); + const QStringList validationErrors = config.validateForLaunch(); + if (!validationErrors.isEmpty()) { + QMessageBox::warning(this, windowTitle(), validationErrors.join(QLatin1Char('\n'))); + navigation->setCurrentItem(navigation->topLevelItem(4)); + return; + } + + result.status = WinUaeQtLauncherStatus::StartRequested; + result.config = config; + accept(); + } +}; + +static bool systemPrefersDarkMode() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; +#else + return false; +#endif +} + +static void applyApplicationColors(QApplication &app, bool dark) +{ + QPalette palette; + if (dark) { + palette.setColor(QPalette::Window, QColor(0x20, 0x20, 0x20)); + palette.setColor(QPalette::WindowText, QColor(0xf0, 0xf0, 0xf0)); + palette.setColor(QPalette::Base, QColor(0x12, 0x12, 0x12)); + palette.setColor(QPalette::AlternateBase, QColor(0x1a, 0x1a, 0x1a)); + palette.setColor(QPalette::ToolTipBase, QColor(0x2b, 0x2b, 0x2b)); + palette.setColor(QPalette::ToolTipText, QColor(0xf0, 0xf0, 0xf0)); + palette.setColor(QPalette::Text, QColor(0xf0, 0xf0, 0xf0)); + palette.setColor(QPalette::Button, QColor(0x2b, 0x2b, 0x2b)); + palette.setColor(QPalette::ButtonText, QColor(0xf0, 0xf0, 0xf0)); + palette.setColor(QPalette::BrightText, Qt::white); + palette.setColor(QPalette::Highlight, QColor(0x33, 0x78, 0xd4)); + palette.setColor(QPalette::HighlightedText, Qt::white); + palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(0x88, 0x88, 0x88)); + palette.setColor(QPalette::Disabled, QPalette::Text, QColor(0x88, 0x88, 0x88)); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x88, 0x88, 0x88)); + app.setPalette(palette); + app.setStyleSheet(QStringLiteral( + "QDialog, QWidget#page, QStackedWidget#pageStack { background: #202020; color: #f0f0f0; }" + "QFrame#outerFrame { border: 1px solid #5a5a5a; background: #202020; }" + "QTreeWidget, QListWidget, QTableWidget, QPlainTextEdit { background: #121212; color: #f0f0f0; border: 1px solid #5a5a5a; alternate-background-color: #1a1a1a; }" + "QGroupBox { margin-top: 14px; padding: 9px 6px 6px 6px; color: #f0f0f0; }" + "QGroupBox::title { subcontrol-origin: margin; left: 8px; padding: 0 3px; font-size: 13px; }" + "QPushButton { min-height: 20px; padding: 1px 10px; background: #2b2b2b; color: #f0f0f0; border: 1px solid #6a6a6a; }" + "QPushButton:disabled { color: #888888; background: #242424; border-color: #4a4a4a; }" + "QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox { min-height: 22px; background: #121212; color: #f0f0f0; border: 1px solid #5a5a5a; }" + "QLabel#statusLine { padding-left: 6px; background: #202020; color: #f0f0f0; }" + "QWidget:disabled { color: #888888; }" + )); + return; + } + + palette.setColor(QPalette::Window, QColor(0xf0, 0xf0, 0xf0)); + palette.setColor(QPalette::WindowText, Qt::black); + palette.setColor(QPalette::Base, Qt::white); + palette.setColor(QPalette::AlternateBase, QColor(0xf7, 0xf7, 0xf7)); + palette.setColor(QPalette::ToolTipBase, QColor(0xff, 0xff, 0xdc)); + palette.setColor(QPalette::ToolTipText, Qt::black); + palette.setColor(QPalette::Text, Qt::black); + palette.setColor(QPalette::Button, QColor(0xf0, 0xf0, 0xf0)); + palette.setColor(QPalette::ButtonText, Qt::black); + palette.setColor(QPalette::BrightText, Qt::white); + palette.setColor(QPalette::Highlight, QColor(0x33, 0x99, 0xff)); + palette.setColor(QPalette::HighlightedText, Qt::white); + palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(0x80, 0x80, 0x80)); + palette.setColor(QPalette::Disabled, QPalette::Text, QColor(0x80, 0x80, 0x80)); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x80, 0x80, 0x80)); + app.setPalette(palette); + + app.setStyleSheet(QStringLiteral( + "QDialog, QWidget#page, QStackedWidget#pageStack { background: #f0f0f0; color: #000000; }" + "QFrame#outerFrame { border: 1px solid #808080; background: #f0f0f0; }" + "QTreeWidget, QListWidget, QTableWidget, QPlainTextEdit { background: #ffffff; color: #000000; border: 1px solid #7f9db9; alternate-background-color: #f7f7f7; }" + "QGroupBox { margin-top: 14px; padding: 9px 6px 6px 6px; }" + "QGroupBox::title { subcontrol-origin: margin; left: 8px; padding: 0 3px; font-size: 13px; }" + "QPushButton { min-height: 20px; padding: 1px 10px; }" + "QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox { min-height: 22px; background: #ffffff; color: #000000; }" + "QLabel#statusLine { padding-left: 6px; background: #f0f0f0; color: #000000; }" + "QWidget:disabled { color: #808080; }" + )); +} + +static void setupApplicationStyle(QApplication &app) +{ + if (QStyle *style = QStyleFactory::create(QStringLiteral("Windows"))) { + app.setStyle(style); + } else if (QStyle *style = QStyleFactory::create(QStringLiteral("Fusion"))) { + app.setStyle(style); + } + QString family = QStringLiteral("MS Sans Serif"); + const QStringList families = QFontDatabase::families(); + if (!families.contains(family)) { + const QStringList fallbacks = { + QStringLiteral("Microsoft Sans Serif"), + QStringLiteral("Tahoma"), + QStringLiteral("Arial") + }; + for (const QString &candidate : fallbacks) { + if (families.contains(candidate)) { + family = candidate; + break; + } + } + if (!families.contains(family)) { + family = app.font().family(); + } + } + QFont font(family); + font.setPixelSize(12); + app.setFont(font); + applyApplicationColors(app, systemPrefersDarkMode()); +} + +static void armQtSmokeExit(QDialog &dialog, QApplication *app = nullptr) +{ + if (!qEnvironmentVariableIsSet("WINUAE_MACOS_APP_SMOKE")) { + return; + } + QTimer::singleShot(750, &dialog, [&dialog, app]() { + std::fputs(dialog.isVisible() ? "WINUAE_QT_SMOKE_WINDOW_VISIBLE\n" : "WINUAE_QT_SMOKE_WINDOW_NOT_VISIBLE\n", stdout); + std::fflush(stdout); + dialog.reject(); + if (app) { + app->quit(); + } + }); +} + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(QApplication &app) +{ + return runWinUaeQtLauncherForConfig(app, QString()); +} + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(QApplication &app, const QString &initialConfigPath) +{ + return runWinUaeQtLauncherForConfig(app, initialConfigPath, WinUaeQtHardwareInfoProvider()); +} + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(QApplication &app, const QString &initialConfigPath, const WinUaeQtHardwareInfoProvider &hardwareProvider) +{ + setupApplicationStyle(app); + WinUaeQtDialog dialog( + nullptr, + initialConfigPath.isEmpty() ? initialConfigPathFromArguments(app.arguments()) : initialConfigPath, + hardwareProvider); + if (WinUaeQtApplication *qtApp = dynamic_cast(&app)) { + qtApp->setConfigOpenHandler([&dialog](const QString &path) { + dialog.openConfigFile(path); + }); + } + armQtSmokeExit(dialog); + if (dialog.exec() == QDialog::Accepted) { + return dialog.launcherResult(); + } + + WinUaeQtLauncherResult result; + result.status = WinUaeQtLauncherStatus::Canceled; + return result; +} + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(int argc, char **argv) +{ + return runWinUaeQtLauncherForConfig(argc, argv, QString()); +} + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(int argc, char **argv, const QString &initialConfigPath) +{ + return runWinUaeQtLauncherForConfig(argc, argv, initialConfigPath, WinUaeQtHardwareInfoProvider()); +} + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(int argc, char **argv, const QString &initialConfigPath, const WinUaeQtHardwareInfoProvider &hardwareProvider) +{ + WinUaeQtApplication app(argc, argv); + return runWinUaeQtLauncherForConfig(app, initialConfigPath, hardwareProvider); +} + +static QString runtimeDialogDirectory(const QString &initialPath) +{ + return fileDialogInitialDirectory(initialPath); +} + +static QString runtimeDialogSavePath(const QString &initialPath, const QString &fallbackName) +{ + return fileDialogInitialSavePath(initialPath, fallbackName); +} + +WinUaeQtRuntimeFileDialogResult runWinUaeQtRuntimeFileDialog(QApplication &app, int shortcut, const QString &initialPath) +{ + setupApplicationStyle(app); + + QString selected; + if (shortcut >= 0 && shortcut < 4) { + selected = QFileDialog::getOpenFileName( + nullptr, + QStringLiteral("Select disk image for DF%1:").arg(shortcut), + runtimeDialogDirectory(initialPath), + amigaDiskImageFilter()); + } else if (shortcut == 4) { + selected = QFileDialog::getOpenFileName( + nullptr, + QStringLiteral("Restore state"), + runtimeDialogDirectory(initialPath), + QStringLiteral("WinUAE state files (*.uss);;All files (*)")); + } else if (shortcut == 5) { + selected = QFileDialog::getSaveFileName( + nullptr, + QStringLiteral("Save state"), + runtimeDialogSavePath(initialPath, QStringLiteral("state.uss")), + QStringLiteral("WinUAE state files (*.uss);;All files (*)")); + if (!selected.isEmpty() && QFileInfo(selected).suffix().isEmpty()) { + selected += QStringLiteral(".uss"); + } + } else if (shortcut == 6) { + selected = QFileDialog::getOpenFileName( + nullptr, + QStringLiteral("Select CD image"), + runtimeDialogDirectory(initialPath), + cdImageFilter()); + } + + WinUaeQtRuntimeFileDialogResult result; + result.accepted = !selected.isEmpty(); + result.path = selected; + return result; +} + +WinUaeQtRuntimeFileDialogResult runWinUaeQtRuntimeFileDialog(int argc, char **argv, int shortcut, const QString &initialPath) +{ + WinUaeQtApplication app(argc, argv); + return runWinUaeQtRuntimeFileDialog(app, shortcut, initialPath); +} + +static int showMessageBox(int flags, const QString &message) +{ + QWidget *parent = QApplication::activeModalWidget(); + if (!parent) { + parent = QApplication::activeWindow(); + } + + QMessageBox box(QMessageBox::Warning, QStringLiteral("WinUAE"), message, QMessageBox::NoButton, parent); + box.setWindowModality(Qt::ApplicationModal); + + QPushButton *okButton = nullptr; + QPushButton *yesButton = nullptr; + QPushButton *noButton = nullptr; + QPushButton *cancelButton = nullptr; + if (flags == 0) { + okButton = box.addButton(QMessageBox::Ok); + box.setDefaultButton(okButton); + } else { + yesButton = box.addButton(QMessageBox::Yes); + noButton = box.addButton(QMessageBox::No); + box.setDefaultButton(yesButton); + if (flags == 2) { + cancelButton = box.addButton(QMessageBox::Cancel); + box.setEscapeButton(cancelButton); + } else { + box.setEscapeButton(noButton); + } + } + + box.exec(); + QAbstractButton *clicked = box.clickedButton(); + if (clicked == okButton) { + return 0; + } + if (clicked == yesButton) { + return 1; + } + if (clicked == noButton) { + return 2; + } + if (clicked == cancelButton) { + return -1; + } + return 0; +} + +int runWinUaeQtMessageBox(QApplication &app, int flags, const QString &message) +{ + setupApplicationStyle(app); + return showMessageBox(flags, message); +} + +int runWinUaeQtMessageBox(int argc, char **argv, int flags, const QString &message) +{ + if (QApplication *app = qobject_cast(QApplication::instance())) { + return runWinUaeQtMessageBox(*app, flags, message); + } + + WinUaeQtApplication app(argc, argv); + return runWinUaeQtMessageBox(app, flags, message); +} diff --git a/od-unix/qt/launcher.h b/od-unix/qt/launcher.h new file mode 100644 index 00000000..f4b829e0 --- /dev/null +++ b/od-unix/qt/launcher.h @@ -0,0 +1,146 @@ +#pragma once + +#include "config.h" + +#include + +class QApplication; + +enum class WinUaeQtLauncherStatus { + Canceled, + StartRequested, + Error +}; + +struct WinUaeQtLauncherResult { + WinUaeQtLauncherStatus status = WinUaeQtLauncherStatus::Canceled; + int exitCode = 0; + QString error; + WinUaeQtConfig config; +}; + +struct WinUaeQtRuntimeFileDialogResult { + bool accepted = false; + QString path; +}; + +struct WinUaeQtHardwareBoard { + int index = -1; + QString type; + QString name; + QString start; + QString end; + QString size; + QString id; + bool movable = false; +}; + +enum class WinUaeQtBoardSettingType { + CheckBox, + Multi, + String +}; + +enum WinUaeQtExpansionCategoryFlag { + WinUaeQtExpansionCategoryInternal = 1 << 0, + WinUaeQtExpansionCategoryScsi = 1 << 1, + WinUaeQtExpansionCategoryIde = 1 << 2, + WinUaeQtExpansionCategorySasi = 1 << 3, + WinUaeQtExpansionCategoryCustom = 1 << 4, + WinUaeQtExpansionCategoryPciBridge = 1 << 5, + WinUaeQtExpansionCategoryX86Bridge = 1 << 6, + WinUaeQtExpansionCategoryRtg = 1 << 7, + WinUaeQtExpansionCategorySound = 1 << 8, + WinUaeQtExpansionCategoryNet = 1 << 9, + WinUaeQtExpansionCategoryFloppy = 1 << 10, + WinUaeQtExpansionCategoryX86Expansion = 1 << 11 +}; + +struct WinUaeQtBoardSetting { + QString display; + QString configValue; + WinUaeQtBoardSettingType type = WinUaeQtBoardSettingType::CheckBox; + QStringList multiDisplays; + QStringList multiValues; +}; + +struct WinUaeQtBoardSubtype { + QString display; + QString configValue; + int deviceFlags = 0; + bool hasRomTypeOverride = false; + bool noRomFile = false; +}; + +struct WinUaeQtExpansionBoardCatalogItem { + QString key; + QString display; + int deviceFlags = 0; + int categoryMask = 0; + int zorro = 0; + bool singleOnly = false; + bool dma24Bit = false; + bool pcmcia = false; + bool autobootJumper = false; + bool idJumper = false; + bool noRomFile = false; + bool clockPort = false; + int extraHdPorts = 0; + QVector subtypes; + QVector settings; +}; + +struct WinUaeQtCpuBoardCatalogItem { + QString type; + QString display; + QString configValue; + int maxMemoryMb = 0; + bool ppc = false; + QVector settings; +}; + +struct WinUaeQtRtgBoardCatalogItem { + QString display; + QString configValue; + int configType = 0; +}; + +struct WinUaeQtBoardCatalog { + QVector expansionBoards; + QVector cpuBoards; + QVector rtgBoards; +}; + +struct WinUaeQtHardwareInfoProvider { + void *context = nullptr; + WinUaeQtBoardCatalog (*boardCatalog)(void *context) = nullptr; + bool (*applyConfig)(void *context, const WinUaeQtConfig &config) = nullptr; + QVector (*boards)(void *context) = nullptr; + bool (*customOrder)(void *context) = nullptr; + void (*setCustomOrder)(void *context, bool enabled) = nullptr; + bool (*canMove)(void *context, int index, int direction) = nullptr; + int (*move)(void *context, int index, int direction) = nullptr; + WinUaeQtConfig::Settings (*orderSettings)(void *context) = nullptr; + void (*pollHostWindowEvents)(void *context) = nullptr; + void (*saveScreenshot)(void *context) = nullptr; + bool (*sampleRipperEnabled)(void *context) = nullptr; + void (*setSampleRipperEnabled)(void *context, bool enabled) = nullptr; + bool (*statePlaybackEnabled)(void *context) = nullptr; + bool (*stateRecordingEnabled)(void *context) = nullptr; + bool (*canSaveStateRecording)(void *context) = nullptr; + bool (*setStatePlayback)(void *context, bool enabled, const char *path) = nullptr; + void (*toggleStateRecording)(void *context) = nullptr; + bool (*saveStateRecording)(void *context, const char *path) = nullptr; + void (*runProWizard)(void *context) = nullptr; +}; + +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(QApplication &app); +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(QApplication &app, const QString &initialConfigPath); +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(QApplication &app, const QString &initialConfigPath, const WinUaeQtHardwareInfoProvider &hardwareProvider); +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(int argc, char **argv); +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(int argc, char **argv, const QString &initialConfigPath); +WinUaeQtLauncherResult runWinUaeQtLauncherForConfig(int argc, char **argv, const QString &initialConfigPath, const WinUaeQtHardwareInfoProvider &hardwareProvider); +WinUaeQtRuntimeFileDialogResult runWinUaeQtRuntimeFileDialog(QApplication &app, int shortcut, const QString &initialPath); +WinUaeQtRuntimeFileDialogResult runWinUaeQtRuntimeFileDialog(int argc, char **argv, int shortcut, const QString &initialPath); +int runWinUaeQtMessageBox(QApplication &app, int flags, const QString &message); +int runWinUaeQtMessageBox(int argc, char **argv, int flags, const QString &message); diff --git a/od-unix/qt/launcher_bridge.cpp b/od-unix/qt/launcher_bridge.cpp new file mode 100644 index 00000000..9b860fbf --- /dev/null +++ b/od-unix/qt/launcher_bridge.cpp @@ -0,0 +1,595 @@ +#include "launcher_bridge.h" + +#include "launcher.h" +#include "prefs_adapter.h" + +#include +#include +#include +#include + +#include +#include + +// Qt defines Qt::HANDLE, and the Unix core compatibility header defines HANDLE. +#include "sysconfig.h" +#include "sysdeps.h" +#include "options.h" +#include "memory.h" +#include "autoconf.h" +#include "rommgr.h" +#include "xwin.h" +#include "host.h" +#include "uae.h" +#include "video.h" +#include "audio.h" +#include "inputrecord.h" +#include "savestate.h" +#include "newcpu.h" +#include "zfile.h" +#include "gfxboard.h" +#include "ethernet.h" +#ifdef PROWIZARD +#include "moduleripper.h" +#endif + +extern struct netdriverdata **target_ethernet_enumerate(void); + +static QString bridgeText(const TCHAR *text) +{ + return text && text[0] ? QString::fromLocal8Bit(text) : QString(); +} + +static QStringList bridgeTextList(const TCHAR *text) +{ + QStringList items; + if (!text) { + return items; + } + const TCHAR *p = text; + while (p[0]) { + items.append(bridgeText(p)); + while (p[0]) { + p++; + } + p++; + } + return items; +} + +static QString bridgeBoardDisplay(const TCHAR *name, const TCHAR *manufacturer) +{ + QString display = bridgeText(name); + const QString maker = bridgeText(manufacturer); + if (!maker.isEmpty()) { + display += QStringLiteral(" (%1)").arg(maker); + } + return display; +} + +static WinUaeQtBoardSetting bridgeBoardSetting(const struct expansionboardsettings *setting) +{ + WinUaeQtBoardSetting item; + if (!setting) { + return item; + } + + const QStringList names = bridgeTextList(setting->name); + const QStringList configs = bridgeTextList(setting->configname); + item.display = names.value(0); + item.configValue = configs.value(0); + if (setting->type == EXPANSIONBOARD_MULTI) { + item.type = WinUaeQtBoardSettingType::Multi; + item.multiDisplays = names.mid(1); + item.multiValues = configs.mid(1); + } else if (setting->type == EXPANSIONBOARD_STRING) { + item.type = WinUaeQtBoardSettingType::String; + } else { + item.type = WinUaeQtBoardSettingType::CheckBox; + } + return item; +} + +static QVector bridgeBoardSettings(const struct expansionboardsettings *settings) +{ + QVector items; + if (!settings) { + return items; + } + for (int i = 0; settings[i].name; i++) { + WinUaeQtBoardSetting item = bridgeBoardSetting(&settings[i]); + if (!item.display.isEmpty() && !item.configValue.isEmpty()) { + items.append(item); + } + } + return items; +} + +static int bridgeExpansionCategoryMask(int deviceFlags) +{ + int mask = 0; + if (deviceFlags & EXPANSIONTYPE_INTERNAL) { + mask |= WinUaeQtExpansionCategoryInternal; + } + if ((deviceFlags & EXPANSIONTYPE_SCSI) && !(deviceFlags & (EXPANSIONTYPE_SASI | EXPANSIONTYPE_CUSTOM))) { + mask |= WinUaeQtExpansionCategoryScsi; + } + if (deviceFlags & EXPANSIONTYPE_IDE) { + mask |= WinUaeQtExpansionCategoryIde; + } + if (deviceFlags & EXPANSIONTYPE_SASI) { + mask |= WinUaeQtExpansionCategorySasi; + } + if (deviceFlags & EXPANSIONTYPE_CUSTOM) { + mask |= WinUaeQtExpansionCategoryCustom; + } + if (deviceFlags & EXPANSIONTYPE_PCI_BRIDGE) { + mask |= WinUaeQtExpansionCategoryPciBridge; + } + if (deviceFlags & EXPANSIONTYPE_X86_BRIDGE) { + mask |= WinUaeQtExpansionCategoryX86Bridge; + } + if (deviceFlags & EXPANSIONTYPE_RTG) { + mask |= WinUaeQtExpansionCategoryRtg; + } + if (deviceFlags & EXPANSIONTYPE_SOUND) { + mask |= WinUaeQtExpansionCategorySound; + } + if (deviceFlags & EXPANSIONTYPE_NET) { + mask |= WinUaeQtExpansionCategoryNet; + } + if (deviceFlags & EXPANSIONTYPE_FLOPPY) { + mask |= WinUaeQtExpansionCategoryFloppy; + } + if (deviceFlags & EXPANSIONTYPE_X86_EXPANSION) { + mask |= WinUaeQtExpansionCategoryX86Expansion; + } + return mask; +} + +static WinUaeQtBoardCatalog bridgeBoardCatalog(void *) +{ + WinUaeQtBoardCatalog catalog; + + target_ethernet_enumerate(); + ethernet_updateselection(); + + for (int i = 0; expansionroms[i].name; i++) { + const struct expansionromtype *rom = &expansionroms[i]; + if (rom->romtype & ROMTYPE_CPUBOARD) { + continue; + } + WinUaeQtExpansionBoardCatalogItem item; + item.key = bridgeText(rom->name); + item.display = bridgeBoardDisplay(rom->friendlyname, rom->friendlymanufacturer); + item.deviceFlags = rom->deviceflags; + item.categoryMask = bridgeExpansionCategoryMask(rom->deviceflags); + item.zorro = rom->zorro; + item.singleOnly = rom->singleonly; + item.dma24Bit = rom->deviceflags & EXPANSIONTYPE_DMA24; + item.pcmcia = rom->deviceflags & EXPANSIONTYPE_PCMCIA; + item.autobootJumper = rom->autoboot_jumper; + item.idJumper = rom->id_jumper; + item.noRomFile = rom->romtype & ROMTYPE_NOT; + item.clockPort = rom->deviceflags & EXPANSIONTYPE_CLOCKPORT; + item.extraHdPorts = rom->extrahdports; + if (rom->subtypes) { + for (int j = 0; rom->subtypes[j].name; j++) { + WinUaeQtBoardSubtype subtype; + subtype.display = bridgeText(rom->subtypes[j].name); + subtype.configValue = bridgeText(rom->subtypes[j].configname); + subtype.deviceFlags = rom->subtypes[j].deviceflags; + subtype.hasRomTypeOverride = rom->subtypes[j].romtype != 0; + subtype.noRomFile = rom->subtypes[j].romtype & ROMTYPE_NOT; + item.subtypes.append(subtype); + } + } + item.settings = bridgeBoardSettings(rom->settings); + if (!item.key.isEmpty() && !item.display.isEmpty()) { + catalog.expansionBoards.append(item); + } + } + + for (int type = 0; cpuboards[type].name; type++) { + if (!cpuboards[type].subtypes || cpuboards[type].id < 0) { + continue; + } + const QString typeName = bridgeText(cpuboards[type].name); + for (int subtype = 0; cpuboards[type].subtypes[subtype].name; subtype++) { + const struct cpuboardsubtype *st = &cpuboards[type].subtypes[subtype]; + WinUaeQtCpuBoardCatalogItem item; + item.type = typeName; + item.display = bridgeText(st->name); + item.configValue = bridgeText(st->configname); + item.maxMemoryMb = st->maxmemory > 0 ? st->maxmemory / (1024 * 1024) : 0; + item.ppc = item.configValue.compare(QStringLiteral("BlizzardPPC"), Qt::CaseInsensitive) == 0 + || item.configValue.compare(QStringLiteral("CyberStormPPC"), Qt::CaseInsensitive) == 0; + item.settings = bridgeBoardSettings(st->settings); + if (!item.type.isEmpty() && !item.display.isEmpty() && !item.configValue.isEmpty()) { + catalog.cpuBoards.append(item); + } + } + } + + for (int index = 0;; index++) { + const int id = gfxboard_get_id_from_index(index); + if (id < 0) { + break; + } + WinUaeQtRtgBoardCatalogItem item; + item.display = bridgeBoardDisplay(gfxboard_get_name(id), gfxboard_get_manufacturername(id)); + item.configValue = bridgeText(gfxboard_get_configname(id)); + struct rtgboardconfig rbc = {}; + rbc.rtgmem_type = id; + rbc.rtgmem_size = 4 * 1024 * 1024; + item.configType = gfxboard_get_configtype(&rbc); + if (!item.display.isEmpty() && !item.configValue.isEmpty()) { + catalog.rtgBoards.append(item); + } + } + + return catalog; +} + +static QString bridgeHex(uae_u32 value) +{ + return QStringLiteral("0x%1").arg(value, 8, 16, QLatin1Char('0')); +} + +static QVector bridgeHardwareBoards(void *context) +{ + struct uae_prefs *prefs = static_cast(context); + QVector boards; + if (!prefs) { + return boards; + } + + expansion_generate_autoconfig_info(prefs); + for (int i = 0;; i++) { + struct autoconfig_info *aci = expansion_get_autoconfig_data(prefs, i); + if (!aci) { + break; + } + WinUaeQtHardwareBoard board; + board.index = i; + board.type = aci->zorro >= 1 && aci->zorro <= 3 ? QStringLiteral("Z%1").arg(aci->zorro) : QStringLiteral("-"); + if (aci->parent_of_previous) { + board.name += QStringLiteral(" - "); + } else if (aci->parent_address_space || aci->parent_romtype) { + board.name += QStringLiteral("? "); + } + board.name += bridgeText(aci->name); + if (board.name.isEmpty()) { + board.name = QStringLiteral(""); + } + board.start = aci->start != 0xffffffff ? bridgeHex(aci->start) : QStringLiteral("-"); + board.end = aci->size != 0 ? bridgeHex(aci->start + aci->size - 1) : QStringLiteral("-"); + board.size = aci->size != 0 ? bridgeHex(aci->size) : QStringLiteral("-"); + if (aci->autoconfig_bytes[0] != 0xff) { + board.id = QStringLiteral("0x%1/0x%2") + .arg((aci->autoconfig_bytes[4] << 8) | aci->autoconfig_bytes[5], 4, 16, QLatin1Char('0')) + .arg(aci->autoconfig_bytes[1], 2, 16, QLatin1Char('0')); + } else { + board.id = QStringLiteral("-"); + } + board.movable = expansion_can_move(prefs, i); + boards.append(board); + } + return boards; +} + +static bool bridgeApplyHardwareConfig(void *context, const WinUaeQtConfig &config) +{ + struct uae_prefs *prefs = static_cast(context); + return applyWinUaeQtConfigToPrefs(config, prefs); +} + +static bool bridgeHardwareCustomOrder(void *context) +{ + struct uae_prefs *prefs = static_cast(context); + return prefs && prefs->autoconfig_custom_sort; +} + +static void bridgeSetHardwareCustomOrder(void *context, bool enabled) +{ + struct uae_prefs *prefs = static_cast(context); + if (!prefs) { + return; + } + prefs->autoconfig_custom_sort = enabled; + expansion_set_autoconfig_sort(prefs); +} + +static bool bridgeHardwareCanMove(void *context, int index, int direction) +{ + struct uae_prefs *prefs = static_cast(context); + return prefs + && expansion_can_move(prefs, index) + && expansion_autoconfig_move(prefs, index, direction, true) >= 0; +} + +static int bridgeMoveHardwareBoard(void *context, int index, int direction) +{ + struct uae_prefs *prefs = static_cast(context); + if (!prefs) { + return -1; + } + return expansion_autoconfig_move(prefs, index, direction, false); +} + +static QString bridgeOrderOnlyValue(const QString &value) +{ + for (QString token : value.split(QLatin1Char(','), Qt::SkipEmptyParts)) { + token = token.trimmed(); + if (token.startsWith(QStringLiteral("order="), Qt::CaseInsensitive)) { + return token; + } + } + return QString(); +} + +static WinUaeQtConfig::Settings bridgeHardwareOrderSettings(void *context) +{ + struct uae_prefs *prefs = static_cast(context); + WinUaeQtConfig::Settings settings; + if (!prefs) { + return settings; + } + + struct zfile *file = zfile_fopen_empty(nullptr, _T("unix-qt-prefs.uae"), 0); + if (!file) { + return settings; + } + cfgfile_save_options(file, prefs, CONFIG_TYPE_ALL); + size_t len = 0; + const uae_u8 *data = zfile_get_data_pointer(file, &len); + const QString text = data && len ? QString::fromLocal8Bit((const char *)data, int(len)) : QString(); + zfile_fclose(file); + + for (QString line : text.split(QLatin1Char('\n'))) { + line = line.trimmed(); + const int eq = line.indexOf(QLatin1Char('=')); + if (eq <= 0) { + continue; + } + const QString key = line.left(eq).trimmed(); + const QString value = line.mid(eq + 1).trimmed(); + if (key == QStringLiteral("board_custom_order")) { + settings.insert(key, value); + continue; + } + const QString order = bridgeOrderOnlyValue(value); + if (!order.isEmpty()) { + settings.insert(key, order); + } + } + return settings; +} + +static void bridgeSaveScreenshot(void *) +{ + screenshot(-1, 1, 0); +} + +static bool bridgeSampleRipperEnabled(void *) +{ + return sampleripper_enabled != 0; +} + +static void bridgeSetSampleRipperEnabled(void *, bool enabled) +{ + if ((sampleripper_enabled != 0) == enabled) { + return; + } + sampleripper_enabled = enabled ? 1 : 0; + audio_sampleripper(-1); +} + +static void bridgeCopyPath(TCHAR *dst, size_t dstSize, const char *path) +{ + if (!dst || !dstSize) { + return; + } + if (!path) { + path = ""; + } + _tcsncpy(dst, path, dstSize - 1); + dst[dstSize - 1] = 0; +} + +static bool bridgeStatePlaybackEnabled(void *) +{ + return input_play != 0; +} + +static bool bridgeStateRecordingEnabled(void *) +{ + return input_record != 0; +} + +static bool bridgeCanSaveStateRecording(void *) +{ + return input_record > INPREC_RECORD_NORMAL; +} + +static bool bridgeSetStatePlayback(void *, bool enabled, const char *path) +{ + if (input_record) { + inprec_close(true); + } + if (!enabled) { + if (input_play) { + inprec_close(true); + } + return true; + } + if (!path || !path[0]) { + return false; + } + inprec_close(true); + input_play = INPREC_PLAY_NORMAL; + bridgeCopyPath(currprefs.inprecfile, sizeof currprefs.inprecfile / sizeof(TCHAR), path); + set_special(SPCFLAG_MODE_CHANGE); + return true; +} + +static void bridgeToggleStateRecording(void *) +{ + if (input_play) { + inprec_playtorecord(); + } else if (input_record) { + inprec_close(true); + } else { + input_record = INPREC_RECORD_START; + set_special(SPCFLAG_MODE_CHANGE); + } +} + +static bool bridgeSaveStateRecording(void *, const char *path) +{ + if (input_record <= INPREC_RECORD_NORMAL || !path || !path[0]) { + return false; + } + + TCHAR inputPath[MAX_DPATH]; + TCHAR statePath[MAX_DPATH]; + bridgeCopyPath(inputPath, sizeof inputPath / sizeof(TCHAR), path); + _sntprintf(statePath, sizeof statePath / sizeof(TCHAR), _T("%s.uss"), inputPath); + statePath[(sizeof statePath / sizeof(TCHAR)) - 1] = 0; + + inprec_save(inputPath, statePath); + statefile_save_recording(statePath); + return true; +} + +#ifdef PROWIZARD +static void bridgeRunProWizard(void *) +{ + moduleripper(); +} +#endif + +static void bridgePollHostWindowEvents(void *) +{ + unix_host_check_quit(); + bool quitRequested = false; + unix_video_poll_window_events(&quitRequested); + if (quitRequested) { + uae_quit(); + } + unix_host_check_quit(); +} + +static WinUaeQtHardwareInfoProvider bridgeHardwareProvider(struct uae_prefs *prefs, bool runtimeActions) +{ + WinUaeQtHardwareInfoProvider provider; + provider.context = prefs; + provider.boardCatalog = bridgeBoardCatalog; + provider.applyConfig = bridgeApplyHardwareConfig; + provider.boards = bridgeHardwareBoards; + provider.customOrder = bridgeHardwareCustomOrder; + provider.setCustomOrder = bridgeSetHardwareCustomOrder; + provider.canMove = bridgeHardwareCanMove; + provider.move = bridgeMoveHardwareBoard; + provider.orderSettings = bridgeHardwareOrderSettings; + provider.sampleRipperEnabled = bridgeSampleRipperEnabled; + provider.setSampleRipperEnabled = bridgeSetSampleRipperEnabled; + if (runtimeActions) { + provider.pollHostWindowEvents = bridgePollHostWindowEvents; + provider.saveScreenshot = bridgeSaveScreenshot; + provider.statePlaybackEnabled = bridgeStatePlaybackEnabled; + provider.stateRecordingEnabled = bridgeStateRecordingEnabled; + provider.canSaveStateRecording = bridgeCanSaveStateRecording; + provider.setStatePlayback = bridgeSetStatePlayback; + provider.toggleStateRecording = bridgeToggleStateRecording; + provider.saveStateRecording = bridgeSaveStateRecording; +#ifdef PROWIZARD + provider.runProWizard = bridgeRunProWizard; +#endif + } + return provider; +} + +int runWinUaeQtLauncherForPrefs(int argc, char **argv, struct uae_prefs *prefs, int *exitCode) +{ + return runWinUaeQtLauncherForPrefsWithConfig(argc, argv, prefs, nullptr, exitCode); +} + +int runWinUaeQtLauncherForPrefsWithConfig(int argc, char **argv, struct uae_prefs *prefs, const char *initialConfigPath, int *exitCode) +{ + const QString initialPath = initialConfigPath && initialConfigPath[0] + ? QString::fromLocal8Bit(initialConfigPath) + : QString(); + WinUaeQtLauncherResult result = runWinUaeQtLauncherForConfig(argc, argv, initialPath, bridgeHardwareProvider(prefs, !initialPath.isEmpty())); + if (result.status == WinUaeQtLauncherStatus::StartRequested) { + if (!applyWinUaeQtConfigToPrefs(result.config, prefs)) { + fprintf(stderr, "Unix Qt UI failed: no preferences target available\n"); + if (exitCode) { + *exitCode = 1; + } + return WINUAE_QT_LAUNCHER_ERROR; + } + return WINUAE_QT_LAUNCHER_START; + } + if (result.status == WinUaeQtLauncherStatus::Error) { + QByteArray error = result.error.toLocal8Bit(); + fprintf(stderr, "Unix Qt UI failed: %s\n", error.constData()); + if (exitCode) { + *exitCode = result.exitCode ? result.exitCode : 1; + } + return WINUAE_QT_LAUNCHER_ERROR; + } + if (exitCode) { + *exitCode = 0; + } + return WINUAE_QT_LAUNCHER_EXIT; +} + +int runWinUaeQtRuntimeFileDialog(int argc, char **argv, int shortcut, const char *initialPath, char *selectedPath, size_t selectedPathLen, int *exitCode) +{ + if (!selectedPath || selectedPathLen == 0) { + if (exitCode) { + *exitCode = 1; + } + return WINUAE_QT_LAUNCHER_ERROR; + } + selectedPath[0] = 0; + + const QString initial = initialPath && initialPath[0] + ? QString::fromLocal8Bit(initialPath) + : QString(); + const WinUaeQtRuntimeFileDialogResult result = runWinUaeQtRuntimeFileDialog(argc, argv, shortcut, initial); + if (!result.accepted) { + if (exitCode) { + *exitCode = 0; + } + return WINUAE_QT_LAUNCHER_EXIT; + } + + const QByteArray path = result.path.toLocal8Bit(); + if (size_t(path.size()) >= selectedPathLen) { + fprintf(stderr, "Unix Qt UI failed: selected path is too long\n"); + if (exitCode) { + *exitCode = 1; + } + return WINUAE_QT_LAUNCHER_ERROR; + } + memcpy(selectedPath, path.constData(), size_t(path.size()) + 1); + if (exitCode) { + *exitCode = 0; + } + return WINUAE_QT_LAUNCHER_START; +} + +int runWinUaeQtMessageBox(int argc, char **argv, int flags, const char *message, int *exitCode) +{ + const int result = runWinUaeQtMessageBox( + argc, + argv, + flags, + message && message[0] ? QString::fromLocal8Bit(message) : QString()); + if (exitCode) { + *exitCode = 0; + } + return result; +} diff --git a/od-unix/qt/launcher_bridge.h b/od-unix/qt/launcher_bridge.h new file mode 100644 index 00000000..8062510e --- /dev/null +++ b/od-unix/qt/launcher_bridge.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct uae_prefs; + +enum { + WINUAE_QT_LAUNCHER_EXIT = 1, + WINUAE_QT_LAUNCHER_START = 2, + WINUAE_QT_LAUNCHER_ERROR = 3 +}; + +int runWinUaeQtLauncherForPrefs(int argc, char **argv, struct uae_prefs *prefs, int *exit_code); +int runWinUaeQtLauncherForPrefsWithConfig(int argc, char **argv, struct uae_prefs *prefs, const char *initial_config_path, int *exit_code); +int runWinUaeQtRuntimeFileDialog(int argc, char **argv, int shortcut, const char *initial_path, char *selected_path, size_t selected_path_len, int *exit_code); +int runWinUaeQtMessageBox(int argc, char **argv, int flags, const char *message, int *exit_code); diff --git a/od-unix/qt/mount_config.cpp b/od-unix/qt/mount_config.cpp new file mode 100644 index 00000000..44bdaef7 --- /dev/null +++ b/od-unix/qt/mount_config.cpp @@ -0,0 +1,340 @@ +#include "mount_config.h" + +#include + +QString winUaeQtConfigAccessValue(bool readOnly) +{ + return readOnly ? QStringLiteral("ro") : QStringLiteral("rw"); +} + +QString winUaeQtConfigEscapeMin(QString value) +{ + const bool needsQuote = value.contains(QLatin1Char(',')) + || value.contains(QLatin1Char('"')) + || value.startsWith(QLatin1Char(' ')) + || value.endsWith(QLatin1Char(' ')); + if (!needsQuote) { + return value; + } + value.replace(QStringLiteral("\""), QStringLiteral("\\\"")); + return QStringLiteral("\"%1\"").arg(value); +} + +QString winUaeQtSanitizedAmigaName(QString value, const QString &fallback, bool uppercase) +{ + value = value.trimmed(); + if (value.isEmpty()) { + value = fallback; + } + value.replace(QLatin1Char(':'), QLatin1Char('_')); + value.replace(QLatin1Char(','), QLatin1Char('_')); + if (uppercase) { + value = value.toUpper(); + } + return value.left(30); +} + +QString winUaeQtDefaultVolumeName(const QString &path) +{ + const QString baseName = QFileInfo(path).completeBaseName(); + return winUaeQtSanitizedAmigaName(baseName, QStringLiteral("Hard Disk"), false); +} + +static bool takeConfigField(QString *input, QChar delimiter, QString *field, bool requireDelimiter) +{ + if (!input || !field) { + return false; + } + + if (input->startsWith(QLatin1Char('"'))) { + QString output; + bool escaped = false; + bool closed = false; + int index = 1; + for (; index < input->size(); index++) { + const QChar c = input->at(index); + if (escaped) { + output.append(c); + escaped = false; + } else if (c == QLatin1Char('\\')) { + escaped = true; + } else if (c == QLatin1Char('"')) { + index++; + closed = true; + break; + } else { + output.append(c); + } + } + if (!closed) { + return false; + } + if (index < input->size()) { + if (input->at(index) != delimiter) { + return false; + } + *input = input->mid(index + 1); + } else if (requireDelimiter) { + return false; + } else { + input->clear(); + } + *field = output; + return true; + } + + const int delimiterIndex = input->indexOf(delimiter); + if (delimiterIndex < 0) { + if (requireDelimiter) { + return false; + } + *field = *input; + input->clear(); + return true; + } + + *field = input->left(delimiterIndex); + *input = input->mid(delimiterIndex + 1); + return true; +} + +QStringList winUaeQtConfigFieldList(QString value) +{ + QStringList fields; + while (!value.isEmpty()) { + QString field; + if (!takeConfigField(&value, QLatin1Char(','), &field, false)) { + return fields; + } + fields.append(field); + } + return fields; +} + +QString winUaeQtConfigJoinFields(const QStringList &fields) +{ + QStringList escapedFields; + escapedFields.reserve(fields.size()); + for (const QString &field : fields) { + escapedFields.append(winUaeQtConfigEscapeMin(field)); + } + return escapedFields.join(QLatin1Char(',')); +} + +static bool parseAccessValue(const QString &value, bool *readOnly) +{ + if (value.compare(QStringLiteral("ro"), Qt::CaseInsensitive) == 0) { + *readOnly = true; + return true; + } + if (value.compare(QStringLiteral("rw"), Qt::CaseInsensitive) == 0) { + *readOnly = false; + return true; + } + return false; +} + +static bool parseDirectoryMountValue(QString value, WinUaeQtMountEntry *entry) +{ + QString access; + QString device; + QString volume; + QString path; + QString bootPri; + if (!takeConfigField(&value, QLatin1Char(','), &access, true) + || !takeConfigField(&value, QLatin1Char(':'), &device, true) + || !takeConfigField(&value, QLatin1Char(':'), &volume, true) + || !takeConfigField(&value, QLatin1Char(','), &path, true) + || !takeConfigField(&value, QLatin1Char(','), &bootPri, false) + || !parseAccessValue(access, &entry->readOnly)) { + return false; + } + entry->kind = QStringLiteral("dir"); + entry->device = device; + entry->volume = volume; + entry->path = path; + entry->bootPri = bootPri.toInt(); + return true; +} + +static bool parseHardfileMountValue(QString value, WinUaeQtMountEntry *entry) +{ + QString access; + QString device; + QString path; + if (!takeConfigField(&value, QLatin1Char(','), &access, true) + || !takeConfigField(&value, QLatin1Char(':'), &device, true) + || !takeConfigField(&value, QLatin1Char(','), &path, true) + || !parseAccessValue(access, &entry->readOnly)) { + return false; + } + QString sectors; + QString surfaces; + QString reserved; + QString blocksize; + QString bootPri; + if (takeConfigField(&value, QLatin1Char(','), §ors, true) + && takeConfigField(&value, QLatin1Char(','), &surfaces, true) + && takeConfigField(&value, QLatin1Char(','), &reserved, true) + && takeConfigField(&value, QLatin1Char(','), &blocksize, true) + && takeConfigField(&value, QLatin1Char(','), &bootPri, false)) { + entry->hardfileGeometry = QStringLiteral("%1,%2,%3,%4").arg(sectors, surfaces, reserved, blocksize); + entry->bootPri = bootPri.toInt(); + entry->hardfileTail = value; + } + entry->kind = QStringLiteral("hdf"); + entry->device = device; + entry->path = path; + return true; +} + +static bool parseUnitSuffix(const QString &kind, const QString &prefix, int *unit) +{ + if (!unit || !kind.startsWith(prefix, Qt::CaseInsensitive)) { + return false; + } + + const QString suffix = kind.mid(prefix.size()); + if (suffix.isEmpty()) { + *unit = 0; + return true; + } + if (suffix.size() != 1 || !suffix.at(0).isDigit()) { + return false; + } + *unit = suffix.toInt(); + return true; +} + +bool parseWinUaeQtUaehfMountValue(const QString &value, WinUaeQtMountEntry *entry) +{ + QString rest = value; + QString kind; + int unit = 0; + if (!takeConfigField(&rest, QLatin1Char(','), &kind, true)) { + return false; + } + if (kind.compare(QStringLiteral("dir"), Qt::CaseInsensitive) == 0) { + return parseDirectoryMountValue(rest, entry); + } + if (kind.compare(QStringLiteral("hdf"), Qt::CaseInsensitive) == 0) { + if (!parseHardfileMountValue(rest, entry)) { + return false; + } + entry->rawConfig = rest; + return true; + } + if (parseUnitSuffix(kind, QStringLiteral("cd"), &unit)) { + if (!parseHardfileMountValue(rest, entry)) { + return false; + } + entry->kind = QStringLiteral("cd"); + entry->emuUnit = unit; + entry->readOnly = true; + entry->rawConfig = rest; + if (entry->hardfileGeometry.isEmpty()) { + entry->hardfileGeometry = QStringLiteral("0,0,0,2048"); + } + return true; + } + if (parseUnitSuffix(kind, QStringLiteral("tape"), &unit)) { + if (!parseHardfileMountValue(rest, entry)) { + return false; + } + entry->kind = QStringLiteral("tape"); + entry->emuUnit = unit; + entry->rawConfig = rest; + if (entry->hardfileGeometry.isEmpty()) { + entry->hardfileGeometry = QStringLiteral("0,0,0,512"); + } + return true; + } + return false; +} + +bool parseWinUaeQtFilesystem2MountValue(const QString &value, WinUaeQtMountEntry *entry) +{ + return parseDirectoryMountValue(value, entry); +} + +bool parseWinUaeQtHardfile2MountValue(const QString &value, WinUaeQtMountEntry *entry) +{ + if (!parseHardfileMountValue(value, entry)) { + return false; + } + entry->rawConfig = value; + return true; +} + +QString serializeWinUaeQtFilesystem2MountValue(const WinUaeQtMountEntry &entry) +{ + return QStringLiteral("%1,%2:%3:%4,%5") + .arg(winUaeQtConfigAccessValue(entry.readOnly), + winUaeQtSanitizedAmigaName(entry.device, QStringLiteral("DH0"), true), + winUaeQtSanitizedAmigaName(entry.volume, QStringLiteral("Hard Disk"), false), + winUaeQtConfigEscapeMin(entry.path), + QString::number(entry.bootPri)); +} + +QString serializeWinUaeQtHardfile2MountValue(const WinUaeQtMountEntry &entry) +{ + const QString geometry = entry.hardfileGeometry.isEmpty() ? QStringLiteral("32,1,2,512") : entry.hardfileGeometry; + const QString tail = entry.hardfileGeometry.isEmpty() && entry.hardfileTail.isEmpty() ? QStringLiteral(",uae0") : entry.hardfileTail; + QString value = QStringLiteral("%1,%2:%3,%4,%5") + .arg(winUaeQtConfigAccessValue(entry.readOnly), + winUaeQtSanitizedAmigaName(entry.device, QStringLiteral("DH0"), true), + winUaeQtConfigEscapeMin(entry.path), + geometry, + QString::number(entry.bootPri)); + if (!tail.isEmpty()) { + value += QStringLiteral(",") + tail; + } + return value; +} + +static QString serializeWinUaeQtHardfileLikeMountValue( + const WinUaeQtMountEntry &entry, + const QString &fallbackDevice, + const QString &defaultGeometry, + const QString &defaultTail) +{ + const QString geometry = entry.hardfileGeometry.isEmpty() ? defaultGeometry : entry.hardfileGeometry; + const QString tail = entry.hardfileTail.isEmpty() ? defaultTail : entry.hardfileTail; + QString value = QStringLiteral("%1,%2:%3,%4,%5") + .arg(winUaeQtConfigAccessValue(entry.readOnly), + winUaeQtSanitizedAmigaName(entry.device, fallbackDevice, true), + winUaeQtConfigEscapeMin(entry.path), + geometry, + QString::number(entry.bootPri)); + if (!tail.isEmpty()) { + value += QStringLiteral(",") + tail; + } + return value; +} + +QString serializeWinUaeQtUaehfDirectoryMountValue(const WinUaeQtMountEntry &entry) +{ + return QStringLiteral("dir,%1").arg(serializeWinUaeQtFilesystem2MountValue(entry)); +} + +QString serializeWinUaeQtUaehfHardfileMountValue(const WinUaeQtMountEntry &entry) +{ + return QStringLiteral("hdf,%1").arg(serializeWinUaeQtHardfile2MountValue(entry)); +} + +QString serializeWinUaeQtUaehfCdMountValue(const WinUaeQtMountEntry &entry) +{ + WinUaeQtMountEntry cd = entry; + cd.readOnly = true; + cd.bootPri = 0; + return QStringLiteral("cd%1,%2") + .arg(qBound(0, cd.emuUnit, 9)) + .arg(serializeWinUaeQtHardfileLikeMountValue(cd, QString(), QStringLiteral("0,0,0,2048"), QStringLiteral(",ide0"))); +} + +QString serializeWinUaeQtUaehfTapeMountValue(const WinUaeQtMountEntry &entry) +{ + return QStringLiteral("tape%1,%2") + .arg(qBound(0, entry.emuUnit, 9)) + .arg(serializeWinUaeQtHardfileLikeMountValue(entry, QString(), QStringLiteral("0,0,0,512"), QStringLiteral(",uae0"))); +} diff --git a/od-unix/qt/mount_config.h b/od-unix/qt/mount_config.h new file mode 100644 index 00000000..c0c3f23d --- /dev/null +++ b/od-unix/qt/mount_config.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +struct WinUaeQtMountEntry { + QString kind; + QString device; + QString volume; + QString path; + QString rawConfig; + QString hardfileGeometry; + QString hardfileTail; + bool readOnly = false; + int bootPri = 0; + int emuUnit = 0; +}; + +QString winUaeQtConfigAccessValue(bool readOnly); +QString winUaeQtConfigEscapeMin(QString value); +QStringList winUaeQtConfigFieldList(QString value); +QString winUaeQtConfigJoinFields(const QStringList &fields); +QString winUaeQtSanitizedAmigaName(QString value, const QString &fallback, bool uppercase); +QString winUaeQtDefaultVolumeName(const QString &path); +bool parseWinUaeQtUaehfMountValue(const QString &value, WinUaeQtMountEntry *entry); +bool parseWinUaeQtFilesystem2MountValue(const QString &value, WinUaeQtMountEntry *entry); +bool parseWinUaeQtHardfile2MountValue(const QString &value, WinUaeQtMountEntry *entry); +QString serializeWinUaeQtFilesystem2MountValue(const WinUaeQtMountEntry &entry); +QString serializeWinUaeQtHardfile2MountValue(const WinUaeQtMountEntry &entry); +QString serializeWinUaeQtUaehfDirectoryMountValue(const WinUaeQtMountEntry &entry); +QString serializeWinUaeQtUaehfHardfileMountValue(const WinUaeQtMountEntry &entry); +QString serializeWinUaeQtUaehfCdMountValue(const WinUaeQtMountEntry &entry); +QString serializeWinUaeQtUaehfTapeMountValue(const WinUaeQtMountEntry &entry); diff --git a/od-unix/qt/mount_config_test.cpp b/od-unix/qt/mount_config_test.cpp new file mode 100644 index 00000000..504535e5 --- /dev/null +++ b/od-unix/qt/mount_config_test.cpp @@ -0,0 +1,139 @@ +#include "mount_config.h" + +#include + +static bool require(bool condition, const char *message) +{ + if (!condition) { + qWarning().noquote() << message; + } + return condition; +} + +static bool requireText(const QString &actual, const QString &expected, const char *field) +{ + if (actual == expected) { + return true; + } + qWarning().noquote() << field << "expected" << expected << "got" << actual; + return false; +} + +static bool requireInt(int actual, int expected, const char *field) +{ + if (actual == expected) { + return true; + } + qWarning().noquote() << field << "expected" << expected << "got" << actual; + return false; +} + +static bool testDirectoryMount() +{ + WinUaeQtMountEntry entry; + bool ok = parseWinUaeQtUaehfMountValue(QStringLiteral("dir,ro,DH1:Work:\"/tmp/Work,Disk\",5"), &entry); + ok = require(ok, "directory mount did not parse") && ok; + ok = requireText(entry.kind, QStringLiteral("dir"), "directory kind") && ok; + ok = requireText(entry.device, QStringLiteral("DH1"), "directory device") && ok; + ok = requireText(entry.volume, QStringLiteral("Work"), "directory volume") && ok; + ok = requireText(entry.path, QStringLiteral("/tmp/Work,Disk"), "directory path") && ok; + ok = require(entry.readOnly, "directory read-only") && ok; + ok = requireInt(entry.bootPri, 5, "directory boot priority") && ok; + ok = requireText(serializeWinUaeQtFilesystem2MountValue(entry), QStringLiteral("ro,DH1:Work:\"/tmp/Work,Disk\",5"), "filesystem2 serialized value") && ok; + ok = requireText(serializeWinUaeQtUaehfDirectoryMountValue(entry), QStringLiteral("dir,ro,DH1:Work:\"/tmp/Work,Disk\",5"), "uaehf directory serialized value") && ok; + WinUaeQtMountEntry filesystemEntry; + ok = require(parseWinUaeQtFilesystem2MountValue(QStringLiteral("ro,DH1:Work:\"/tmp/Work,Disk\",5"), &filesystemEntry), "filesystem2 mount did not parse") && ok; + ok = requireText(filesystemEntry.path, QStringLiteral("/tmp/Work,Disk"), "filesystem2 path") && ok; + return ok; +} + +static bool testHardfileMount() +{ + const QString config = QStringLiteral("rw,DH2:/tmp/disk.hdf,32,1,2,512,0,,uae0"); + WinUaeQtMountEntry entry; + bool ok = parseWinUaeQtHardfile2MountValue(config, &entry); + ok = require(ok, "hardfile mount did not parse") && ok; + ok = requireText(entry.kind, QStringLiteral("hdf"), "hardfile kind") && ok; + ok = requireText(entry.device, QStringLiteral("DH2"), "hardfile device") && ok; + ok = requireText(entry.path, QStringLiteral("/tmp/disk.hdf"), "hardfile path") && ok; + ok = require(!entry.readOnly, "hardfile read/write") && ok; + ok = requireInt(entry.bootPri, 0, "hardfile boot priority") && ok; + ok = requireText(serializeWinUaeQtHardfile2MountValue(entry), config, "hardfile2 serialized value") && ok; + ok = requireText(serializeWinUaeQtUaehfHardfileMountValue(entry), QStringLiteral("hdf,") + config, "uaehf hardfile serialized value") && ok; + entry.readOnly = true; + entry.device = QStringLiteral("DH3"); + entry.bootPri = 7; + ok = requireText(serializeWinUaeQtHardfile2MountValue(entry), QStringLiteral("ro,DH3:/tmp/disk.hdf,32,1,2,512,7,,uae0"), "edited hardfile2 serialized value") && ok; + WinUaeQtMountEntry uaehfEntry; + ok = require(parseWinUaeQtUaehfMountValue(QStringLiteral("hdf,") + config, &uaehfEntry), "uaehf hardfile did not parse") && ok; + ok = requireText(serializeWinUaeQtHardfile2MountValue(uaehfEntry), config, "uaehf hardfile direct serialized value") && ok; + WinUaeQtMountEntry minimalEntry; + ok = require(parseWinUaeQtHardfile2MountValue(QStringLiteral("rw,DH4:/tmp/min.hdf,16,2,1,512,3"), &minimalEntry), "minimal hardfile did not parse") && ok; + minimalEntry.readOnly = true; + minimalEntry.bootPri = 4; + ok = requireText(serializeWinUaeQtHardfile2MountValue(minimalEntry), QStringLiteral("ro,DH4:/tmp/min.hdf,16,2,1,512,4"), "minimal hardfile serialized value") && ok; + WinUaeQtMountEntry expansionEntry; + const QString expansionConfig = QStringLiteral("rw,DH5:/tmp/ripple.hdf,32,1,2,512,0,,ide0_ripple,ATA2+"); + ok = require(parseWinUaeQtHardfile2MountValue(expansionConfig, &expansionEntry), "expansion-controller hardfile did not parse") && ok; + ok = requireText(expansionEntry.hardfileTail, QStringLiteral(",ide0_ripple,ATA2+"), "expansion-controller hardfile tail") && ok; + ok = requireText(serializeWinUaeQtHardfile2MountValue(expansionEntry), expansionConfig, "expansion-controller hardfile serialized value") && ok; + return ok; +} + +static bool testCdMount() +{ + WinUaeQtMountEntry entry; + bool ok = parseWinUaeQtUaehfMountValue(QStringLiteral("cd1,ro,:,0,0,0,2048,0,,ide1"), &entry); + ok = require(ok, "uaehf cd did not parse") && ok; + ok = requireText(entry.kind, QStringLiteral("cd"), "cd kind") && ok; + ok = requireInt(entry.emuUnit, 1, "cd emulation unit") && ok; + ok = require(entry.readOnly, "cd should be read-only") && ok; + ok = requireText(entry.hardfileGeometry, QStringLiteral("0,0,0,2048"), "cd geometry") && ok; + ok = requireText(entry.hardfileTail, QStringLiteral(",ide1"), "cd tail") && ok; + ok = requireText(serializeWinUaeQtUaehfCdMountValue(entry), QStringLiteral("cd1,ro,:,0,0,0,2048,0,,ide1"), "uaehf cd serialized value") && ok; + WinUaeQtMountEntry expansionEntry; + ok = require(parseWinUaeQtUaehfMountValue(QStringLiteral("cd0,ro,:,0,0,0,2048,0,,scsi0_a4091"), &expansionEntry), "expansion-controller cd did not parse") && ok; + ok = requireText(expansionEntry.hardfileTail, QStringLiteral(",scsi0_a4091"), "expansion-controller cd tail") && ok; + ok = requireText(serializeWinUaeQtUaehfCdMountValue(expansionEntry), QStringLiteral("cd0,ro,:,0,0,0,2048,0,,scsi0_a4091"), "expansion-controller cd serialized value") && ok; + return ok; +} + +static bool testTapeMount() +{ + WinUaeQtMountEntry entry; + bool ok = parseWinUaeQtUaehfMountValue(QStringLiteral("tape0,rw,:\"/tmp/Tape,One\",0,0,0,512,0,,uae0"), &entry); + ok = require(ok, "uaehf tape did not parse") && ok; + ok = requireText(entry.kind, QStringLiteral("tape"), "tape kind") && ok; + ok = requireInt(entry.emuUnit, 0, "tape emulation unit") && ok; + ok = requireText(entry.path, QStringLiteral("/tmp/Tape,One"), "tape path") && ok; + ok = requireText(entry.hardfileGeometry, QStringLiteral("0,0,0,512"), "tape geometry") && ok; + ok = requireText(entry.hardfileTail, QStringLiteral(",uae0"), "tape tail") && ok; + ok = requireText(serializeWinUaeQtUaehfTapeMountValue(entry), QStringLiteral("tape0,rw,:\"/tmp/Tape,One\",0,0,0,512,0,,uae0"), "uaehf tape serialized value") && ok; + WinUaeQtMountEntry expansionEntry; + ok = require(parseWinUaeQtUaehfMountValue(QStringLiteral("tape1,rw,:,0,0,0,512,0,,ide1_alfapower"), &expansionEntry), "expansion-controller tape did not parse") && ok; + ok = requireText(expansionEntry.hardfileTail, QStringLiteral(",ide1_alfapower"), "expansion-controller tape tail") && ok; + ok = requireText(serializeWinUaeQtUaehfTapeMountValue(expansionEntry), QStringLiteral("tape1,rw,:,0,0,0,512,0,,ide1_alfapower"), "expansion-controller tape serialized value") && ok; + return ok; +} + +static bool testConfigFieldList() +{ + const QStringList fields = winUaeQtConfigFieldList(QStringLiteral(",ide0,\"/tmp/Geo,One\",CF,ATA2+S,flags=0x1")); + bool ok = requireInt(fields.size(), 6, "field list size"); + ok = requireText(fields.value(0), QString(), "empty filesys field") && ok; + ok = requireText(fields.value(2), QStringLiteral("/tmp/Geo,One"), "quoted field") && ok; + ok = requireText(winUaeQtConfigJoinFields(fields), QStringLiteral(",ide0,\"/tmp/Geo,One\",CF,ATA2+S,flags=0x1"), "joined fields") && ok; + return ok; +} + +int main() +{ + bool ok = true; + ok = testDirectoryMount() && ok; + ok = testHardfileMount() && ok; + ok = testCdMount() && ok; + ok = testTapeMount() && ok; + ok = testConfigFieldList() && ok; + ok = requireText(winUaeQtSanitizedAmigaName(QStringLiteral("dh:0, "), QStringLiteral("DH0"), true), QStringLiteral("DH_0_"), "sanitized name") && ok; + return ok ? 0 : 1; +} diff --git a/od-unix/qt/path_utils.cpp b/od-unix/qt/path_utils.cpp new file mode 100644 index 00000000..c6c0951a --- /dev/null +++ b/od-unix/qt/path_utils.cpp @@ -0,0 +1,111 @@ +#include "path_utils.h" + +#include +#include +#include + +QString winUaeQtEnvString(const char *name) +{ + return QString::fromLocal8Bit(qgetenv(name)); +} + +QString winUaeQtDefaultDataPath() +{ + return QStringLiteral("$HOME/Documents/WinUAE"); +} + +QString winUaeQtDefaultDataSubPath(const QString &name) +{ + return QDir(winUaeQtDefaultDataPath()).filePath(name); +} + +QString winUaeQtDefaultFileDialogPath() +{ + return winUaeQtExpandedPathText(winUaeQtDefaultDataPath()); +} + +QString winUaeQtExpandUnixPath(QString path) +{ + if (path.isEmpty()) { + return path; + } + + if (path.startsWith(QLatin1Char('~')) && (path.size() == 1 || path[1] == QLatin1Char('/'))) { + path = QDir::homePath() + path.mid(1); + } + + for (int i = 0; i < path.size(); i++) { + if (path[i] != QLatin1Char('$')) { + continue; + } + + int start = i + 1; + int end = start; + QString name; + if (start < path.size() && path[start] == QLatin1Char('{')) { + start++; + end = path.indexOf(QLatin1Char('}'), start); + if (end < 0) { + continue; + } + name = path.mid(start, end - start); + end++; + } else { + while (end < path.size()) { + const QChar c = path[end]; + if (!c.isLetterOrNumber() && c != QLatin1Char('_')) { + break; + } + end++; + } + name = path.mid(start, end - start); + } + if (name.isEmpty()) { + continue; + } + + const QByteArray value = qgetenv(name.toLocal8Bit().constData()); + if (value.isEmpty()) { + continue; + } + const QString replacement = QString::fromLocal8Bit(value); + path.replace(i, end - i, replacement); + i += replacement.size() - 1; + } + + return path; +} + +QString winUaeQtExpandedPathText(const QString &path) +{ + return winUaeQtExpandUnixPath(path.trimmed()); +} + +QString winUaeQtFileDialogInitialPath(const QString &path) +{ + const QString expandedPath = winUaeQtExpandedPathText(path); + return expandedPath.isEmpty() ? winUaeQtDefaultFileDialogPath() : expandedPath; +} + +QString winUaeQtFileDialogInitialDirectory(const QString &path) +{ + const QString expandedPath = winUaeQtExpandedPathText(path); + if (expandedPath.isEmpty()) { + return winUaeQtDefaultFileDialogPath(); + } + QFileInfo info(expandedPath); + if (info.isDir()) { + return info.absoluteFilePath(); + } + const QString parent = info.absolutePath(); + return parent.isEmpty() ? winUaeQtDefaultFileDialogPath() : parent; +} + +QString winUaeQtFileDialogInitialSavePath(const QString &path, const QString &fallbackName) +{ + const QString expandedPath = winUaeQtExpandedPathText(path); + if (!expandedPath.isEmpty()) { + return expandedPath; + } + return QDir(winUaeQtDefaultFileDialogPath()).filePath(fallbackName); +} diff --git a/od-unix/qt/path_utils.h b/od-unix/qt/path_utils.h new file mode 100644 index 00000000..a255022f --- /dev/null +++ b/od-unix/qt/path_utils.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +QString winUaeQtEnvString(const char *name); +QString winUaeQtDefaultDataPath(); +QString winUaeQtDefaultDataSubPath(const QString &name); +QString winUaeQtDefaultFileDialogPath(); +QString winUaeQtExpandUnixPath(QString path); +QString winUaeQtExpandedPathText(const QString &path); +QString winUaeQtFileDialogInitialPath(const QString &path); +QString winUaeQtFileDialogInitialDirectory(const QString &path); +QString winUaeQtFileDialogInitialSavePath(const QString &path, const QString &fallbackName); diff --git a/od-unix/qt/path_utils_test.cpp b/od-unix/qt/path_utils_test.cpp new file mode 100644 index 00000000..d1f9f985 --- /dev/null +++ b/od-unix/qt/path_utils_test.cpp @@ -0,0 +1,45 @@ +#include "path_utils.h" + +#include +#include + +static bool requireEqual(const char *label, const QString &actual, const QString &expected) +{ + if (actual == expected) { + return true; + } + qWarning().noquote() << label << "expected" << expected << "got" << actual; + return false; +} + +int main() +{ + bool ok = true; + qputenv("HOME", QByteArray("/tmp/winuae-qt-home")); + qputenv("WINUAE_QT_TEST_PATH", QByteArray("/tmp/winuae-qt-env")); + qputenv("WINUAE_QT_TEST_SUFFIX", QByteArray("-suffix")); + qunsetenv("WINUAE_QT_TEST_MISSING"); + + ok = requireEqual("home directory", winUaeQtExpandUnixPath(QStringLiteral("~")), QStringLiteral("/tmp/winuae-qt-home")) && ok; + ok = requireEqual("home subpath", winUaeQtExpandUnixPath(QStringLiteral("~/roms/kick.rom")), QStringLiteral("/tmp/winuae-qt-home/roms/kick.rom")) && ok; + ok = requireEqual("plain environment", winUaeQtExpandUnixPath(QStringLiteral("$WINUAE_QT_TEST_PATH/kick.rom")), QStringLiteral("/tmp/winuae-qt-env/kick.rom")) && ok; + ok = requireEqual("braced environment", winUaeQtExpandUnixPath(QStringLiteral("${WINUAE_QT_TEST_PATH}/kick.rom")), QStringLiteral("/tmp/winuae-qt-env/kick.rom")) && ok; + ok = requireEqual("adjacent environment", winUaeQtExpandUnixPath(QStringLiteral("$WINUAE_QT_TEST_PATH${WINUAE_QT_TEST_SUFFIX}")), QStringLiteral("/tmp/winuae-qt-env-suffix")) && ok; + ok = requireEqual("missing environment", winUaeQtExpandUnixPath(QStringLiteral("$WINUAE_QT_TEST_MISSING/kick.rom")), QStringLiteral("$WINUAE_QT_TEST_MISSING/kick.rom")) && ok; + ok = requireEqual("malformed brace", winUaeQtExpandUnixPath(QStringLiteral("${WINUAE_QT_TEST_PATH/kick.rom")), QStringLiteral("${WINUAE_QT_TEST_PATH/kick.rom")) && ok; + ok = requireEqual("literal dollar", winUaeQtExpandUnixPath(QStringLiteral("disk$")), QStringLiteral("disk$")) && ok; + ok = requireEqual("trimmed boundary", winUaeQtExpandedPathText(QStringLiteral(" $WINUAE_QT_TEST_PATH/config.uae ")), QStringLiteral("/tmp/winuae-qt-env/config.uae")) && ok; + ok = requireEqual("default data path", winUaeQtDefaultDataPath(), QStringLiteral("$HOME/Documents/WinUAE")) && ok; + ok = requireEqual("default data subpath", winUaeQtDefaultDataSubPath(QStringLiteral("Configuration")), QStringLiteral("$HOME/Documents/WinUAE/Configuration")) && ok; + ok = requireEqual("expanded default data path", winUaeQtExpandedPathText(winUaeQtDefaultDataSubPath(QStringLiteral("Configuration"))), QStringLiteral("/tmp/winuae-qt-home/Documents/WinUAE/Configuration")) && ok; + ok = requireEqual("default file dialog path", winUaeQtDefaultFileDialogPath(), QStringLiteral("/tmp/winuae-qt-home/Documents/WinUAE")) && ok; + ok = requireEqual("empty file dialog path", winUaeQtFileDialogInitialPath(QString()), QStringLiteral("/tmp/winuae-qt-home/Documents/WinUAE")) && ok; + ok = requireEqual("blank file dialog path", winUaeQtFileDialogInitialPath(QStringLiteral(" ")), QStringLiteral("/tmp/winuae-qt-home/Documents/WinUAE")) && ok; + ok = requireEqual("empty file dialog directory", winUaeQtFileDialogInitialDirectory(QString()), QStringLiteral("/tmp/winuae-qt-home/Documents/WinUAE")) && ok; + ok = requireEqual("configured file dialog path", winUaeQtFileDialogInitialPath(QStringLiteral("$WINUAE_QT_TEST_PATH/kick.rom")), QStringLiteral("/tmp/winuae-qt-env/kick.rom")) && ok; + ok = requireEqual("configured file dialog directory", winUaeQtFileDialogInitialDirectory(QStringLiteral("$WINUAE_QT_TEST_PATH/kick.rom")), QStringLiteral("/tmp/winuae-qt-env")) && ok; + ok = requireEqual("file dialog save fallback", winUaeQtFileDialogInitialSavePath(QString(), QStringLiteral("state.uss")), QStringLiteral("/tmp/winuae-qt-home/Documents/WinUAE/state.uss")) && ok; + ok = requireEqual("named home unsupported", winUaeQtExpandUnixPath(QStringLiteral("~other/roms")), QStringLiteral("~other/roms")) && ok; + + return ok ? 0 : 1; +} diff --git a/od-unix/qt/prefs_adapter.cpp b/od-unix/qt/prefs_adapter.cpp new file mode 100644 index 00000000..f0000626 --- /dev/null +++ b/od-unix/qt/prefs_adapter.cpp @@ -0,0 +1,931 @@ +#include "prefs_adapter.h" + +// Qt defines Qt::HANDLE, and the Unix core compatibility header defines HANDLE. +#include "config.h" + +#include +#include + +#include "sysconfig.h" +#include "sysdeps.h" +#include "audio.h" +#include "custom.h" +#include "gfxboard.h" +#include "options.h" + +enum class ApplySettingResult { + Fallback, + Handled, +}; + +struct Choice { + const char *name; + int value; +}; + +template +static bool parseChoice(const QString &text, const Choice (&choices)[N], int *out) +{ + for (const Choice &choice : choices) { + if (text.compare(QString::fromLatin1(choice.name), Qt::CaseInsensitive) == 0) { + *out = choice.value; + return true; + } + } + return false; +} + +static bool textToInt(const QString &text, int *out) +{ + bool ok = false; + const int value = text.toInt(&ok, 0); + if (ok && out) { + *out = value; + } + return ok; +} + +static bool settingToInt(const WinUaeQtConfig::Settings &settings, const QString &key, int *out) +{ + if (!settings.contains(key)) { + return false; + } + return textToInt(settings.value(key), out); +} + +static bool textToBool(const QString &text, bool *out) +{ + const QString normalized = text.trimmed().toLower(); + if (normalized == QStringLiteral("true") || normalized == QStringLiteral("t") || + normalized == QStringLiteral("yes") || normalized == QStringLiteral("y") || + normalized == QStringLiteral("1")) { + if (out) { + *out = true; + } + return true; + } + if (normalized == QStringLiteral("false") || normalized == QStringLiteral("f") || + normalized == QStringLiteral("no") || normalized == QStringLiteral("n") || + normalized == QStringLiteral("0")) { + if (out) { + *out = false; + } + return true; + } + return false; +} + +static bool settingToBool(const WinUaeQtConfig::Settings &settings, const QString &key, bool *out) +{ + if (!settings.contains(key)) { + return false; + } + return textToBool(settings.value(key), out); +} + +static bool rtgVBlankValueToInt(const QString &value, int *out) +{ + const QString normalized = value.trimmed().toLower(); + int parsed = 0; + bool ok = false; + if (normalized == QStringLiteral("real") || normalized == QStringLiteral("default")) { + parsed = -1; + ok = true; + } else if (normalized == QStringLiteral("disabled")) { + parsed = -2; + ok = true; + } else if (normalized == QStringLiteral("chipset")) { + parsed = 0; + ok = true; + } else { + parsed = normalized.toInt(&ok); + } + if (ok && out) { + *out = parsed; + } + return ok; +} + +static void copyTextSetting(TCHAR *dst, size_t dstSize, const QString &value) +{ + if (!dst || dstSize == 0) { + return; + } + QByteArray text = value.toLocal8Bit(); + _tcsncpy(dst, text.constData(), dstSize - 1); + dst[dstSize - 1] = 0; +} + +static bool parseSoundOutput(const QString &value, int *out) +{ + static const Choice choices[] = { + { "none", 0 }, + { "interrupts", 1 }, + { "normal", 2 }, + { "good", 2 }, + { "exact", 3 }, + { "best", 3 }, + }; + return parseChoice(value, choices, out); +} + +static bool parseChipsetCompatible(const QString &value, int *out) +{ + static const Choice choices[] = { + { "Generic", CP_GENERIC }, + { "CDTV", CP_CDTV }, + { "CDTV-CR", CP_CDTVCR }, + { "CD32", CP_CD32 }, + { "A500", CP_A500 }, + { "A500+", CP_A500P }, + { "A600", CP_A600 }, + { "A1000", CP_A1000 }, + { "A1200", CP_A1200 }, + { "A2000", CP_A2000 }, + { "A3000", CP_A3000 }, + { "A3000T", CP_A3000T }, + { "A4000", CP_A4000 }, + { "A4000T", CP_A4000T }, + { "Velvet", CP_VELVET }, + { "Casablanca", CP_CASABLANCA }, + { "DraCo", CP_DRACO }, + }; + return parseChoice(value, choices, out); +} + +static bool parseGfxCardType(const QString &value, int *out) +{ + for (int index = 0;; index++) { + const int id = gfxboard_get_id_from_index(index); + if (id < 0) { + break; + } + const TCHAR *name = gfxboard_get_configname(id); + if (name && value.compare(QString::fromLocal8Bit(name), Qt::CaseInsensitive) == 0) { + *out = id; + return true; + } + } + return false; +} + +static bool parseSoundChannels(const QString &value, int *out, struct uae_prefs *prefs) +{ + static const Choice choices[] = { + { "mono", SND_MONO }, + { "stereo", SND_STEREO }, + { "clonedstereo", SND_4CH_CLONEDSTEREO }, + { "4ch", SND_4CH }, + { "clonedstereo6ch", SND_6CH_CLONEDSTEREO }, + { "6ch", SND_6CH }, + { "clonedstereo8ch", SND_8CH_CLONEDSTEREO }, + { "8ch", SND_8CH }, + { "mixed", SND_NONE }, + }; + if (!parseChoice(value, choices, out)) { + return false; + } + if (*out == SND_NONE) { + *out = SND_STEREO; + prefs->sound_mixed_stereo_delay = 5; + prefs->sound_stereo_separation = 7; + } + return true; +} + +static bool parseLinemode(const QString &value, int *out) +{ + static const Choice choices[] = { + { "none", 0 }, + { "double", 1 }, + { "scanlines", 2 }, + { "scanlines2p", 3 }, + { "scanlines3p", 4 }, + { "double2", 5 }, + { "scanlines2", 6 }, + { "scanlines2p2", 7 }, + { "scanlines2p3", 8 }, + { "double3", 9 }, + { "scanlines3", 10 }, + { "scanlines3p2", 11 }, + { "scanlines3p3", 12 }, + }; + return parseChoice(value, choices, out); +} + +static bool parseFullscreenMode(const QString &value, int *out) +{ + static const Choice choices[] = { + { "false", GFX_WINDOW }, + { "true", GFX_FULLSCREEN }, + { "fullwindow", GFX_FULLWINDOW }, + }; + return parseChoice(value, choices, out); +} + +static bool parseVsyncMode(const QString &value, int *out) +{ + static const Choice choices[] = { + { "false", 0 }, + { "true", 1 }, + { "autoswitch", 2 }, + }; + if (parseChoice(value, choices, out)) { + return true; + } + bool enabled = false; + if (textToBool(value, &enabled)) { + *out = enabled ? 1 : 0; + return true; + } + return false; +} + +static void parseSettingOption(struct uae_prefs *prefs, const QString &key, const QString &value) +{ + if (value.isEmpty() || key.startsWith(QStringLiteral("unix.ui."))) { + return; + } + if (key == QStringLiteral("uaescsimode") || key == QStringLiteral("unix.uaescsimode")) { + return; + } + if (key == QStringLiteral("rtg_vblank") || key == QStringLiteral("unix.rtg_vblank")) { + return; + } + QByteArray option = key.toLocal8Bit(); + QByteArray optionValue = value.toLocal8Bit(); + cfgfile_parse_option(prefs, option.constData(), optionValue.data(), 0); +} + +static void applyCycleExactConstraint(struct uae_prefs *prefs) +{ + if (prefs->cpu_model >= 68020 && prefs->cachesize > 0) { + prefs->cpu_cycle_exact = false; + prefs->cpu_memory_cycle_exact = false; + prefs->blitter_cycle_exact = false; + } +} + +static ApplySettingResult applyTypedSetting(const WinUaeQtConfig::Settings &settings, + const WinUaeQtConfig::Setting &setting, struct uae_prefs *prefs) +{ + auto applyInt = [&](const char *key, int *field, int multiplier = 1) -> bool { + if (setting.key != QString::fromLatin1(key)) { + return false; + } + int parsed = 0; + if (textToInt(setting.value, &parsed)) { + *field = parsed * multiplier; + } + return true; + }; + auto applyBool = [&](const char *key, bool *field) -> bool { + if (setting.key != QString::fromLatin1(key)) { + return false; + } + bool parsed = false; + if (textToBool(setting.value, &parsed)) { + *field = parsed; + } + return true; + }; + auto applyText = [&](const char *key, TCHAR *field, size_t size) -> bool { + if (setting.key != QString::fromLatin1(key)) { + return false; + } + copyTextSetting(field, size, setting.value); + return true; + }; + + if (applyText("config_description", prefs->description, sizeof prefs->description / sizeof(TCHAR))) { + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("kickstart_rom_file")) { + copyTextSetting(prefs->romfile, sizeof prefs->romfile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("kickstart_ext_rom_file")) { + copyTextSetting(prefs->romextfile, sizeof prefs->romextfile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("kickstart_ext_rom_file2")) { + copyTextSetting(prefs->romextfile2, sizeof prefs->romextfile2 / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("flash_file")) { + copyTextSetting(prefs->flashfile, sizeof prefs->flashfile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cart_file")) { + copyTextSetting(prefs->cartfile, sizeof prefs->cartfile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("rtc_file")) { + copyTextSetting(prefs->rtcfile, sizeof prefs->rtcfile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("picassoiv_rom_file")) { + copyTextSetting(prefs->picassoivromfile, sizeof prefs->picassoivromfile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("statefile")) { + copyTextSetting(prefs->statefile, sizeof prefs->statefile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("statefile_quit")) { + copyTextSetting(prefs->quitstatefile, sizeof prefs->quitstatefile / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + + int value = 0; + bool boolValue = false; + for (int drive = 0; drive < 4; drive++) { + const QString key = QStringLiteral("floppy%1").arg(drive); + if (setting.key == key) { + copyTextSetting(prefs->floppyslots[drive].df, + sizeof prefs->floppyslots[drive].df / sizeof(TCHAR), setting.value); + return ApplySettingResult::Handled; + } + } + for (int drive = 0; drive < 4; drive++) { + const QString key = QStringLiteral("floppy%1type").arg(drive); + if (setting.key == key) { + int value = 0; + if (textToInt(setting.value, &value)) { + prefs->floppyslots[drive].dfxtype = value; + } + return ApplySettingResult::Handled; + } + } + for (int drive = 0; drive < 4; drive++) { + const QString key = QStringLiteral("floppy%1wp").arg(drive); + if (setting.key != key) { + continue; + } + bool value = false; + if (textToBool(setting.value, &value)) { + prefs->floppyslots[drive].forcedwriteprotect = value; + } + return ApplySettingResult::Handled; + } + + for (int drive = 0; drive < 4; drive++) { + const QString key = QStringLiteral("floppy%1sound").arg(drive); + if (setting.key == key) { + if (textToInt(setting.value, &value)) { + prefs->floppyslots[drive].dfxclick = value; + } + return ApplySettingResult::Handled; + } + } + for (int drive = 0; drive < 4; drive++) { + const QString key = QStringLiteral("floppy%1soundvolume_empty").arg(drive); + if (setting.key == key) { + if (textToInt(setting.value, &value)) { + prefs->dfxclickvolume_empty[drive] = value; + } + return ApplySettingResult::Handled; + } + } + for (int drive = 0; drive < 4; drive++) { + const QString key = QStringLiteral("floppy%1soundvolume_disk").arg(drive); + if (setting.key == key) { + if (textToInt(setting.value, &value)) { + prefs->dfxclickvolume_disk[drive] = value; + } + return ApplySettingResult::Handled; + } + } + + if (setting.key == QStringLiteral("nr_floppies")) { + if (textToInt(setting.value, &value)) { + prefs->nr_floppies = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("floppy_speed")) { + if (textToInt(setting.value, &value)) { + prefs->floppy_speed = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("maprom")) { + bool ok = false; + const uint parsed = setting.value.toUInt(&ok, 0); + if (ok) { + prefs->maprom = parsed; + } + return ApplySettingResult::Handled; + } + if (applyBool("kickshifter", &prefs->kickshifter) + || applyBool("ntsc", &prefs->ntscmode) + || applyBool("immediate_blits", &prefs->immediate_blits) + || applyBool("genlock", &prefs->genlock) + || applyBool("genlock_alpha", &prefs->genlock_alpha) + || applyBool("keyboard_nkro", &prefs->keyboard_nkro) + || applyBool("df0idhw", &prefs->cs_df0idhw) + || applyBool("sana2", &prefs->sana2) + || applyBool("bsdsocket_emu", &prefs->socket_emu) + || applyBool("uaeserial", &prefs->uaeserial) + || applyBool("sampler_stereo", &prefs->sampler_stereo) + || applyBool("sound_auto", &prefs->sound_auto) + || applyBool("sound_stereo_swap_paula", &prefs->sound_stereo_swap_paula) + || applyBool("sound_stereo_swap_ahi", &prefs->sound_stereo_swap_ahi) + || applyBool("serial_on_demand", &prefs->serial_demand) + || applyBool("serial_hardware_ctsrts", &prefs->serial_hwctsrts) + || applyBool("serial_status", &prefs->serial_rtsctsdtrdtecd) + || applyBool("serial_ri", &prefs->serial_ri) + || applyBool("serial_direct", &prefs->serial_direct) + || applyBool("parallel_postscript_detection", &prefs->parallel_postscript_detection) + || applyBool("parallel_postscript_emulation", &prefs->parallel_postscript_emulation) + || applyBool("state_replay_autoplay", &prefs->inprec_autoplay) + || applyBool("gfx_resize_windowed", &prefs->gfx_windowed_resize) + || applyBool("gfx_flickerfixer", &prefs->gfx_scandoubler) + || applyBool("gfx_blacker_than_black", &prefs->gfx_blackerthanblack) + || applyBool("gfx_monochrome", &prefs->gfx_grayscale) + || applyBool("gfx_autoresolution_vga", &prefs->gfx_autoresolution_vga) + || applyBool("gfx_keep_aspect", &prefs->gfx_keep_aspect) + || applyBool("gfx_ntscpixels", &prefs->gfx_ntscpixels) + || applyBool("tablet_library", &prefs->tablet_library)) { + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("genlock_aspect")) { + if (textToBool(setting.value, &boolValue)) { + prefs->genlock_aspect = boolValue ? 1 : 0; + } + return ApplySettingResult::Handled; + } + if (applyText("genlock_image", prefs->genlock_image_file, sizeof prefs->genlock_image_file / sizeof(TCHAR)) + || applyText("genlock_video", prefs->genlock_video_file, sizeof prefs->genlock_video_file / sizeof(TCHAR)) + || applyText("ghostscript_parameters", prefs->ghostscript_parameters, sizeof prefs->ghostscript_parameters / sizeof(TCHAR)) + || applyText("statefile_path", prefs->statefile_path, sizeof prefs->statefile_path / sizeof(TCHAR))) { + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("unix.serial_port")) { + const QString port = setting.value.trimmed(); + if (port.compare(QStringLiteral("none"), Qt::CaseInsensitive) == 0) { + prefs->sername[0] = 0; + } else { + copyTextSetting(prefs->sername, sizeof prefs->sername / sizeof(TCHAR), port); + } + prefs->use_serial = prefs->sername[0] != 0; + return ApplySettingResult::Handled; + } + if (applyInt("genlock_mix", &prefs->genlock_mix) + || applyInt("chipset_rtc_adjust", &prefs->cs_rtc_adjust) + || applyInt("fatgary", &prefs->cs_fatgaryrev) + || applyInt("ramsey", &prefs->cs_ramseyrev) + || applyInt("sound_max_buff", &prefs->sound_maxbsiz) + || applyInt("sound_frequency", &prefs->sound_freq) + || applyInt("sound_stereo_separation", &prefs->sound_stereo_separation) + || applyInt("sound_stereo_mixing_delay", &prefs->sound_mixed_stereo_delay) + || applyInt("parallel_autoflush", &prefs->parallel_autoflush_time) + || applyInt("midiout_device", &prefs->win32_midioutdev) + || applyInt("midi_device", &prefs->win32_midioutdev) + || applyInt("midiin_device", &prefs->win32_midiindev) + || applyInt("unix.soundcard", &prefs->win32_soundcard) + || applyInt("unix.samplersoundcard", &prefs->win32_samplersoundcard) + || applyInt("samplersoundcard", &prefs->win32_samplersoundcard) + || applyInt("state_replay_rate", &prefs->statecapturerate) + || applyInt("state_replay_buffers", &prefs->statecapturebuffersize) + || applyInt("gfx_display", &prefs->gfx_apmode[APMODE_NATIVE].gfx_display) + || applyInt("gfx_display_rtg", &prefs->gfx_apmode[APMODE_RTG].gfx_display) + || applyInt("gfx_refreshrate", &prefs->gfx_apmode[APMODE_NATIVE].gfx_refreshrate) + || applyInt("gfx_refreshrate_rtg", &prefs->gfx_apmode[APMODE_RTG].gfx_refreshrate) + || applyInt("gfx_backbuffers", &prefs->gfx_apmode[APMODE_NATIVE].gfx_backbuffers) + || applyInt("gfx_backbuffers_rtg", &prefs->gfx_apmode[APMODE_RTG].gfx_backbuffers) + || applyInt("gfx_frame_slices", &prefs->gfx_display_sections) + || applyInt("gfx_framerate", &prefs->gfx_framerate) + || applyInt("gfx_autoresolution", &prefs->gfx_autoresolution) + || applyInt("gfx_monitorblankdelay", &prefs->gfx_monitorblankdelay) + || applyInt("rtg_modes", &prefs->picasso96_modeflags) + || applyInt("cd_speed", &prefs->cd_speed) + || applyInt("filesys_max_size", &prefs->filesys_limit)) { + if (setting.key == QStringLiteral("gfx_display")) { + prefs->gfx_apmode[APMODE_RTG].gfx_display = prefs->gfx_apmode[APMODE_NATIVE].gfx_display; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cpuboardmem1_size") || + setting.key == QStringLiteral("cpuboardmem2_size")) { + if (textToInt(setting.value, &value)) { + if (setting.key == QStringLiteral("cpuboardmem1_size")) { + prefs->cpuboardmem1.size = value * 0x100000; + } else { + prefs->cpuboardmem2.size = value * 0x100000; + } + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cpu_model")) { + if (textToInt(setting.value, &value)) { + prefs->cpu_model = value; + prefs->fpu_model = 0; + applyCycleExactConstraint(prefs); + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("fpu_model")) { + if (textToInt(setting.value, &value)) { + prefs->fpu_model = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cpu_compatible")) { + if (textToBool(setting.value, &boolValue)) { + prefs->cpu_compatible = boolValue; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cpu_24bit_addressing")) { + if (textToBool(setting.value, &boolValue)) { + prefs->address_space_24 = boolValue; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cpu_cycle_exact")) { + if (textToBool(setting.value, &boolValue)) { + prefs->cpu_cycle_exact = boolValue; + prefs->cpu_memory_cycle_exact = boolValue; + applyCycleExactConstraint(prefs); + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cpu_memory_cycle_exact")) { + if (textToBool(setting.value, &boolValue)) { + prefs->cpu_memory_cycle_exact = boolValue; + if (!boolValue) { + prefs->cpu_cycle_exact = false; + prefs->blitter_cycle_exact = false; + } + applyCycleExactConstraint(prefs); + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("blitter_cycle_exact")) { + if (textToBool(setting.value, &boolValue)) { + prefs->blitter_cycle_exact = boolValue; + applyCycleExactConstraint(prefs); + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("chipset_compatible")) { + if (parseChipsetCompatible(setting.value, &value)) { + prefs->cs_compatible = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("chipmem_size")) { + if (textToInt(setting.value, &value)) { + if (value < 0) { + prefs->chipmem.size = 0x20000; + } else if (value == 0) { + prefs->chipmem.size = 0x40000; + } else { + prefs->chipmem.size = value * 0x80000; + } + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("fastmem_size")) { + if (textToInt(setting.value, &value)) { + prefs->fastmem[0].size = value * 0x100000; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("bogomem_size")) { + if (textToInt(setting.value, &value)) { + prefs->bogomem.size = value * 0x40000; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("z3mem_size")) { + if (textToInt(setting.value, &value)) { + prefs->z3fastmem[0].size = value * 0x100000; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("cachesize")) { + if (textToInt(setting.value, &value)) { + prefs->cachesize = value; + applyCycleExactConstraint(prefs); + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("compfpu")) { + if (textToBool(setting.value, &boolValue)) { + prefs->compfpu = boolValue; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("comp_constjump")) { + if (textToBool(setting.value, &boolValue)) { + prefs->comp_constjump = boolValue; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("comp_nf")) { + if (textToBool(setting.value, &boolValue)) { + prefs->compnf = boolValue; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("comp_catchfault")) { + if (textToBool(setting.value, &boolValue)) { + prefs->comp_catchfault = boolValue; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("comp_flushmode")) { + if (setting.value.compare(QStringLiteral("hard"), Qt::CaseInsensitive) == 0) { + prefs->comp_hardflush = true; + } else if (setting.value.compare(QStringLiteral("soft"), Qt::CaseInsensitive) == 0) { + prefs->comp_hardflush = false; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("comp_trustbyte") + || setting.key == QStringLiteral("comp_trustword") + || setting.key == QStringLiteral("comp_trustlong") + || setting.key == QStringLiteral("comp_trustnaddr")) { + static const Choice choices[] = { + { "direct", 0 }, + { "indirect", 1 }, + { "indirectKS", 2 }, + { "afterPic", 3 }, + }; + if (parseChoice(setting.value, choices, &value)) { + if (setting.key == QStringLiteral("comp_trustbyte")) { + prefs->comptrustbyte = value; + } else if (setting.key == QStringLiteral("comp_trustword")) { + prefs->comptrustword = value; + } else if (setting.key == QStringLiteral("comp_trustlong")) { + prefs->comptrustlong = value; + } else { + prefs->comptrustnaddr = value; + } + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_output")) { + if (parseSoundOutput(setting.value, &value)) { + prefs->produce_sound = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_channels")) { + if (parseSoundChannels(setting.value, &value, prefs)) { + prefs->sound_stereo = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_interpol")) { + static const Choice choices[] = { + { "none", 0 }, + { "anti", 1 }, + { "sinc", 2 }, + { "rh", 3 }, + { "crux", 4 }, + }; + if (parseChoice(setting.value, choices, &value)) { + prefs->sound_interpol = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_filter")) { + static const Choice choices[] = { + { "off", 0 }, + { "emulated", 1 }, + { "on", 2 }, + { "fixedonly", 3 }, + }; + if (parseChoice(setting.value, choices, &value)) { + prefs->sound_filter = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_filter_type")) { + static const Choice choices[] = { + { "standard", 0 }, + { "enhanced", 1 }, + }; + if (parseChoice(setting.value, choices, &value)) { + prefs->sound_filter_type = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_volume")) { + if (textToInt(setting.value, &value)) { + prefs->sound_volume_master = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_volume_paula")) { + if (textToInt(setting.value, &value)) { + prefs->sound_volume_paula = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_volume_cd")) { + if (textToInt(setting.value, &value)) { + prefs->sound_volume_cd = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_volume_ahi")) { + if (textToInt(setting.value, &value)) { + prefs->sound_volume_board = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_volume_midi")) { + if (textToInt(setting.value, &value)) { + prefs->sound_volume_midi = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("sound_volume_genlock")) { + if (textToInt(setting.value, &value)) { + prefs->sound_volume_genlock = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("midirouter")) { +#ifdef WITH_MIDI + if (textToBool(setting.value, &boolValue)) { + prefs->win32_midirouter = boolValue; + } +#else + prefs->win32_midirouter = false; +#endif + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("serial_translate")) { + static const Choice choices[] = { + { "disabled", 0 }, + { "crlf_cr", 1 }, + }; + if (parseChoice(setting.value, choices, &value)) { + prefs->serial_crlf = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("parallel_matrix_emulation")) { + static const Choice choices[] = { + { "none", 0 }, + { "ascii", 1 }, + { "epson_matrix_9pin", 2 }, + { "epson_matrix_24pin", 3 }, + { "epson_matrix_48pin", 4 }, + }; + if (parseChoice(setting.value, choices, &value)) { + prefs->parallel_matrix_emulation = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfxcard_size")) { + if (textToInt(setting.value, &value)) { + prefs->rtgboards[0].rtgmem_size = value * 0x100000; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfxcard_type")) { + if (parseGfxCardType(setting.value, &value)) { + prefs->rtgboards[0].rtgmem_type = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_width_windowed") || + setting.key == QStringLiteral("gfx_height_windowed")) { + const QString width = settings.value(QStringLiteral("gfx_width_windowed")); + const QString height = settings.value(QStringLiteral("gfx_height_windowed")); + if (width.compare(QStringLiteral("native"), Qt::CaseInsensitive) == 0 || + height.compare(QStringLiteral("native"), Qt::CaseInsensitive) == 0) { + prefs->gfx_monitor[0].gfx_size_win.width = 0; + prefs->gfx_monitor[0].gfx_size_win.height = 0; + } else if (setting.key == QStringLiteral("gfx_width_windowed") && textToInt(setting.value, &value)) { + prefs->gfx_monitor[0].gfx_size_win.width = value; + } else if (setting.key == QStringLiteral("gfx_height_windowed") && textToInt(setting.value, &value)) { + prefs->gfx_monitor[0].gfx_size_win.height = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_width_fullscreen") || + setting.key == QStringLiteral("gfx_height_fullscreen")) { + if (setting.value.compare(QStringLiteral("native"), Qt::CaseInsensitive) == 0) { + prefs->gfx_monitor[0].gfx_size_fs.width = 0; + prefs->gfx_monitor[0].gfx_size_fs.height = 0; + prefs->gfx_monitor[0].gfx_size_fs.special = WH_NATIVE; + } else if (setting.key == QStringLiteral("gfx_width_fullscreen") && textToInt(setting.value, &value)) { + prefs->gfx_monitor[0].gfx_size_fs.width = value; + prefs->gfx_monitor[0].gfx_size_fs.special = 0; + } else if (setting.key == QStringLiteral("gfx_height_fullscreen") && textToInt(setting.value, &value)) { + prefs->gfx_monitor[0].gfx_size_fs.height = value; + prefs->gfx_monitor[0].gfx_size_fs.special = 0; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_fullscreen_amiga")) { + if (parseFullscreenMode(setting.value, &value)) { + prefs->gfx_apmode[APMODE_NATIVE].gfx_fullscreen = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_fullscreen_picasso")) { + if (parseFullscreenMode(setting.value, &value)) { + prefs->gfx_apmode[APMODE_RTG].gfx_fullscreen = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_vsync")) { + if (parseVsyncMode(setting.value, &value)) { + prefs->gfx_apmode[APMODE_NATIVE].gfx_vsync = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_vsync_picasso")) { + if (parseVsyncMode(setting.value, &value)) { + prefs->gfx_apmode[APMODE_RTG].gfx_vsync = value; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_vsyncmode") || + setting.key == QStringLiteral("gfx_vsyncmode_picasso")) { + static const Choice choices[] = { + { "normal", 0 }, + { "busywait", 1 }, + }; + if (parseChoice(setting.value, choices, &value)) { + if (setting.key == QStringLiteral("gfx_vsyncmode")) { + prefs->gfx_apmode[APMODE_NATIVE].gfx_vsyncmode = value; + } else { + prefs->gfx_apmode[APMODE_RTG].gfx_vsyncmode = value; + } + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_linemode")) { + if (parseLinemode(setting.value, &value)) { + prefs->gfx_vresolution = VRES_NONDOUBLE; + prefs->gfx_pscanlines = 0; + prefs->gfx_iscanlines = 0; + if (value > 0) { + prefs->gfx_iscanlines = (value - 1) / 4; + prefs->gfx_pscanlines = (value - 1) % 4; + prefs->gfx_vresolution = VRES_DOUBLE; + } + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("gfx_center_horizontal") || + setting.key == QStringLiteral("gfx_center_vertical")) { + static const Choice choices[] = { + { "none", 0 }, + { "simple", 1 }, + { "smart", 2 }, + { "false", 0 }, + { "true", 1 }, + }; + if (parseChoice(setting.value, choices, &value)) { + if (setting.key == QStringLiteral("gfx_center_horizontal")) { + prefs->gfx_xcenter = value; + } else { + prefs->gfx_ycenter = value; + } + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("uaescsimode") || setting.key == QStringLiteral("unix.uaescsimode")) { + if (setting.value.compare(QStringLiteral("SPTI+SCSISCAN"), Qt::CaseInsensitive) == 0) { + prefs->win32_uaescsimode = UAESCSI_SPTISCAN; + } else if (setting.value.compare(QStringLiteral("SPTI"), Qt::CaseInsensitive) == 0) { + prefs->win32_uaescsimode = UAESCSI_SPTI; + } else { + prefs->win32_uaescsimode = UAESCSI_CDEMU; + } + return ApplySettingResult::Handled; + } + if (setting.key == QStringLiteral("rtg_vblank") || setting.key == QStringLiteral("unix.rtg_vblank")) { + if (rtgVBlankValueToInt(setting.value, &value)) { + prefs->win32_rtgvblankrate = value; + } + return ApplySettingResult::Handled; + } + return ApplySettingResult::Fallback; +} + +bool applyWinUaeQtConfigToPrefs(const WinUaeQtConfig &config, struct uae_prefs *prefs) +{ + if (!prefs) { + return false; + } + + const WinUaeQtConfig::Settings &settings = config.settings(); + for (const WinUaeQtConfig::Setting &setting : config.orderedSettings()) { + if (applyTypedSetting(settings, setting, prefs) == ApplySettingResult::Handled) { + continue; + } + parseSettingOption(prefs, setting.key, setting.value); + } + return true; +} diff --git a/od-unix/qt/prefs_adapter.h b/od-unix/qt/prefs_adapter.h new file mode 100644 index 00000000..64d9c78a --- /dev/null +++ b/od-unix/qt/prefs_adapter.h @@ -0,0 +1,6 @@ +#pragma once + +class WinUaeQtConfig; +struct uae_prefs; + +bool applyWinUaeQtConfigToPrefs(const WinUaeQtConfig &config, struct uae_prefs *prefs); diff --git a/od-unix/qt/prefs_adapter_test.cpp b/od-unix/qt/prefs_adapter_test.cpp new file mode 100644 index 00000000..6d332d3f --- /dev/null +++ b/od-unix/qt/prefs_adapter_test.cpp @@ -0,0 +1,422 @@ +#include "config.h" +#include "prefs_adapter.h" + +#include +#include + +#include +#include + +#include "sysconfig.h" +#include "sysdeps.h" +#include "audio.h" +#include "custom.h" +#include "disk.h" +#include "gfxboard.h" +#include "options.h" + +static QStringList parsedLines; + +int gfxboard_get_id_from_index(int index) +{ + static const int ids[] = { + GFXBOARD_UAE_Z2, + GFXBOARD_UAE_Z3, + GFXBOARD_ID_PICASSO4_Z3, + -1 + }; + return index >= 0 && index < int(sizeof(ids) / sizeof(ids[0])) ? ids[index] : -1; +} + +const TCHAR *gfxboard_get_configname(int id) +{ + switch (id) { + case GFXBOARD_UAE_Z2: + return _T("ZorroII"); + case GFXBOARD_UAE_Z3: + return _T("ZorroIII"); + case GFXBOARD_ID_PICASSO4_Z3: + return _T("PicassoIV_Z3"); + default: + return nullptr; + } +} + +static bool configBoolValue(const char *value) +{ + return !strcmp(value, "true") || !strcmp(value, "yes") || !strcmp(value, "1"); +} + +static void copyText(TCHAR *dst, size_t dstSize, const char *src) +{ + if (!dst || !dstSize) { + return; + } + snprintf(dst, dstSize, "%s", src ? src : ""); +} + +int cfgfile_parse_option(struct uae_prefs *prefs, const TCHAR *key, TCHAR *value, int) +{ + parsedLines.append(QStringLiteral("%1=%2") + .arg(QString::fromLocal8Bit(key), QString::fromLocal8Bit(value))); + + if (!strcmp(key, "kickstart_rom_file")) { + copyText(prefs->romfile, sizeof prefs->romfile, value); + } else if (!strcmp(key, "kickstart_ext_rom_file")) { + copyText(prefs->romextfile, sizeof prefs->romextfile, value); + } else if (!strcmp(key, "floppy0")) { + copyText(prefs->floppyslots[0].df, sizeof prefs->floppyslots[0].df, value); + } else if (!strcmp(key, "floppy1")) { + copyText(prefs->floppyslots[1].df, sizeof prefs->floppyslots[1].df, value); + } else if (!strcmp(key, "floppy0type")) { + prefs->floppyslots[0].dfxtype = atoi(value); + } else if (!strcmp(key, "floppy1type")) { + prefs->floppyslots[1].dfxtype = atoi(value); + } else if (!strcmp(key, "floppy2type")) { + prefs->floppyslots[2].dfxtype = atoi(value); + } else if (!strcmp(key, "floppy0wp")) { + prefs->floppyslots[0].forcedwriteprotect = configBoolValue(value); + } else if (!strcmp(key, "floppy1wp")) { + prefs->floppyslots[1].forcedwriteprotect = configBoolValue(value); + } else if (!strcmp(key, "floppy2wp")) { + prefs->floppyslots[2].forcedwriteprotect = configBoolValue(value); + } else if (!strcmp(key, "chipset")) { + prefs->chipset_mask = !strcmp(value, "aga") ? 0x0707 : 0x0101; + } else if (!strcmp(key, "chipset_compatible")) { + prefs->cs_compatible = !strcmp(value, "A1200") ? CP_A1200 : CP_GENERIC; + } else if (!strcmp(key, "cpu_model")) { + prefs->cpu_model = atoi(value); + prefs->fpu_model = 0; + } else if (!strcmp(key, "fpu_model")) { + prefs->fpu_model = atoi(value); + } else if (!strcmp(key, "sound_output")) { + prefs->produce_sound = !strcmp(value, "normal") ? 2 : 0; + } else if (!strcmp(key, "gfxcard_type")) { + prefs->rtgboards[0].rtg_index = 0; + prefs->rtgboards[0].rtgmem_type = !strcmp(value, "ZorroIII") ? 1 : 0; + } + return 1; +} + +static bool require(bool condition, const char *message) +{ + if (!condition) { + qWarning().noquote() << message; + } + return condition; +} + +static bool requireInt(int actual, int expected, const char *field) +{ + if (actual == expected) { + return true; + } + qWarning().noquote() << field << "expected" << expected << "got" << actual; + return false; +} + +static bool requireUnsigned(uae_u32 actual, uae_u32 expected, const char *field) +{ + if (actual == expected) { + return true; + } + qWarning().noquote() << field << "expected" << (qulonglong)expected << "got" << (qulonglong)actual; + return false; +} + +static bool requireText(const TCHAR *actual, const char *expected, const char *field) +{ + if (!strcmp(actual, expected)) { + return true; + } + qWarning().noquote() << field << "expected" << expected << "got" << actual; + return false; +} + +static struct uae_prefs *allocPrefs() +{ + return static_cast(calloc(1, sizeof(struct uae_prefs))); +} + +static bool testRepresentativeConfig() +{ + WinUaeQtConfig::Settings settings; + settings.insert(QStringLiteral("kickstart_rom_file"), QStringLiteral("/roms/A1200.rom")); + settings.insert(QStringLiteral("kickstart_ext_rom_file"), QStringLiteral("/roms/ext.rom")); + settings.insert(QStringLiteral("floppy0"), QStringLiteral("/disks/install.adf")); + settings.insert(QStringLiteral("floppy1"), QStringLiteral("/disks/extras.adf")); + settings.insert(QStringLiteral("floppy0type"), QStringLiteral("0")); + settings.insert(QStringLiteral("floppy1type"), QStringLiteral("1")); + settings.insert(QStringLiteral("floppy2type"), QStringLiteral("-1")); + settings.insert(QStringLiteral("floppy0wp"), QStringLiteral("true")); + settings.insert(QStringLiteral("floppy1wp"), QStringLiteral("false")); + settings.insert(QStringLiteral("floppy2wp"), QStringLiteral("yes")); + settings.insert(QStringLiteral("floppy3wp"), QStringLiteral("t")); + settings.insert(QStringLiteral("uaehf0"), QStringLiteral("dir,rw,DH0:System:/tmp/System,0")); + settings.insert(QStringLiteral("nr_floppies"), QStringLiteral("2")); + settings.insert(QStringLiteral("chipset"), QStringLiteral("aga")); + settings.insert(QStringLiteral("chipset_compatible"), QStringLiteral("A1200")); + settings.insert(QStringLiteral("cpu_model"), QStringLiteral("68020")); + settings.insert(QStringLiteral("fpu_model"), QStringLiteral("68882")); + settings.insert(QStringLiteral("cpu_24bit_addressing"), QStringLiteral("false")); + settings.insert(QStringLiteral("chipmem_size"), QStringLiteral("4")); + settings.insert(QStringLiteral("fastmem_size"), QStringLiteral("8")); + settings.insert(QStringLiteral("bogomem_size"), QStringLiteral("2")); + settings.insert(QStringLiteral("z3mem_size"), QStringLiteral("64")); + settings.insert(QStringLiteral("cachesize"), QStringLiteral("8")); + settings.insert(QStringLiteral("sound_output"), QStringLiteral("normal")); + settings.insert(QStringLiteral("sound_auto"), QStringLiteral("true")); + settings.insert(QStringLiteral("sound_channels"), QStringLiteral("mixed")); + settings.insert(QStringLiteral("sound_frequency"), QStringLiteral("44100")); + settings.insert(QStringLiteral("sound_max_buff"), QStringLiteral("4096")); + settings.insert(QStringLiteral("sound_interpol"), QStringLiteral("sinc")); + settings.insert(QStringLiteral("sound_filter"), QStringLiteral("emulated")); + settings.insert(QStringLiteral("sound_filter_type"), QStringLiteral("enhanced")); + settings.insert(QStringLiteral("sound_stereo_separation"), QStringLiteral("8")); + settings.insert(QStringLiteral("sound_stereo_mixing_delay"), QStringLiteral("2")); + settings.insert(QStringLiteral("sound_stereo_swap_paula"), QStringLiteral("true")); + settings.insert(QStringLiteral("floppy0sound"), QStringLiteral("1")); + settings.insert(QStringLiteral("floppy0soundvolume_empty"), QStringLiteral("34")); + settings.insert(QStringLiteral("floppy0soundvolume_disk"), QStringLiteral("22")); + settings.insert(QStringLiteral("df0idhw"), QStringLiteral("false")); + settings.insert(QStringLiteral("gfxcard_size"), QStringLiteral("16")); + settings.insert(QStringLiteral("gfxcard_type"), QStringLiteral("PicassoIV_Z3")); + settings.insert(QStringLiteral("gfx_width_windowed"), QStringLiteral("800")); + settings.insert(QStringLiteral("gfx_height_windowed"), QStringLiteral("600")); + settings.insert(QStringLiteral("gfx_width_fullscreen"), QStringLiteral("native")); + settings.insert(QStringLiteral("gfx_height_fullscreen"), QStringLiteral("native")); + settings.insert(QStringLiteral("gfx_fullscreen_amiga"), QStringLiteral("fullwindow")); + settings.insert(QStringLiteral("gfx_fullscreen_picasso"), QStringLiteral("true")); + settings.insert(QStringLiteral("gfx_vsync"), QStringLiteral("autoswitch")); + settings.insert(QStringLiteral("gfx_vsyncmode"), QStringLiteral("busywait")); + settings.insert(QStringLiteral("gfx_vsync_picasso"), QStringLiteral("true")); + settings.insert(QStringLiteral("gfx_vsyncmode_picasso"), QStringLiteral("normal")); + settings.insert(QStringLiteral("gfx_backbuffers"), QStringLiteral("3")); + settings.insert(QStringLiteral("gfx_resize_windowed"), QStringLiteral("true")); + settings.insert(QStringLiteral("gfx_framerate"), QStringLiteral("2")); + settings.insert(QStringLiteral("gfx_linemode"), QStringLiteral("scanlines2p")); + settings.insert(QStringLiteral("gfx_center_horizontal"), QStringLiteral("simple")); + settings.insert(QStringLiteral("gfx_center_vertical"), QStringLiteral("smart")); + settings.insert(QStringLiteral("gfx_keep_aspect"), QStringLiteral("true")); + settings.insert(QStringLiteral("rtg_modes"), QStringLiteral("0x10")); + settings.insert(QStringLiteral("bsdsocket_emu"), QStringLiteral("true")); + settings.insert(QStringLiteral("sana2"), QStringLiteral("true")); + settings.insert(QStringLiteral("uaescsimode"), QStringLiteral("SPTI")); + settings.insert(QStringLiteral("unix.rtg_vblank"), QStringLiteral("real")); + settings.insert(QStringLiteral("unix.serial_port"), QStringLiteral("TCP://0.0.0.0:1234")); + settings.insert(QStringLiteral("serial_on_demand"), QStringLiteral("true")); + settings.insert(QStringLiteral("serial_hardware_ctsrts"), QStringLiteral("true")); + settings.insert(QStringLiteral("serial_status"), QStringLiteral("true")); + settings.insert(QStringLiteral("serial_ri"), QStringLiteral("true")); + settings.insert(QStringLiteral("serial_direct"), QStringLiteral("true")); + settings.insert(QStringLiteral("serial_translate"), QStringLiteral("crlf_cr")); + settings.insert(QStringLiteral("uaeserial"), QStringLiteral("true")); + settings.insert(QStringLiteral("unix.samplersoundcard"), QStringLiteral("3")); + settings.insert(QStringLiteral("sampler_stereo"), QStringLiteral("true")); + settings.insert(QStringLiteral("midiout_device"), QStringLiteral("-1")); + settings.insert(QStringLiteral("midiin_device"), QStringLiteral("2")); + settings.insert(QStringLiteral("midirouter"), QStringLiteral("false")); + settings.insert(QStringLiteral("parallel_matrix_emulation"), QStringLiteral("ascii")); + settings.insert(QStringLiteral("parallel_postscript_detection"), QStringLiteral("true")); + settings.insert(QStringLiteral("parallel_postscript_emulation"), QStringLiteral("false")); + settings.insert(QStringLiteral("parallel_autoflush"), QStringLiteral("9")); + settings.insert(QStringLiteral("ghostscript_parameters"), QStringLiteral("-dNOPAUSE")); + settings.insert(QStringLiteral("unix.ui.config_path"), QStringLiteral("/configs")); + settings.insert(QStringLiteral("unix.screenshot_path"), QStringLiteral("/screenshots")); + + struct uae_prefs *prefs = allocPrefs(); + if (!prefs) { + qWarning().noquote() << "failed to allocate preferences"; + return false; + } + prefs->fpu_model = 68881; + prefs->address_space_24 = true; + + parsedLines.clear(); + bool ok = applyWinUaeQtConfigToPrefs(WinUaeQtConfig(settings), prefs); + ok = require(ok, "adapter rejected representative config") && ok; + ok = require(!parsedLines.contains(QStringLiteral("unix.ui.config_path=/configs")), "UI-only path was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("kickstart_rom_file=/roms/A1200.rom")), "kickstart_rom_file was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("kickstart_ext_rom_file=/roms/ext.rom")), "kickstart_ext_rom_file was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy0=/disks/install.adf")), "floppy0 was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy1=/disks/extras.adf")), "floppy1 was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy0type=0")), "floppy0type was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy1type=1")), "floppy1type was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy2type=-1")), "floppy2type was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy0wp=true")), "floppy0wp was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy1wp=false")), "floppy1wp was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy2wp=yes")), "floppy2wp was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy3wp=t")), "floppy3wp was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("chipset_compatible=A1200")), "chipset compatibility was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("cpu_model=68020")), "cpu model was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("fpu_model=68882")), "fpu model was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("cpu_24bit_addressing=false")), "cpu_24bit_addressing was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("nr_floppies=2")), "nr_floppies was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("chipmem_size=4")), "chipmem_size was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("fastmem_size=8")), "fastmem_size was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("bogomem_size=2")), "bogomem_size was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("z3mem_size=64")), "z3mem_size was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("cachesize=8")), "cachesize was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("sound_output=normal")), "sound output was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("sound_auto=true")), "sound_auto was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("sound_channels=mixed")), "sound_channels was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("floppy0sound=1")), "floppy0sound was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("df0idhw=false")), "df0idhw was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("gfxcard_size=16")), "gfxcard_size was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("gfxcard_type=PicassoIV_Z3")), "gfxcard_type was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("gfx_width_windowed=800")), "gfx_width_windowed was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("gfx_height_windowed=600")), "gfx_height_windowed was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("gfx_fullscreen_amiga=fullwindow")), "gfx_fullscreen_amiga was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("gfx_vsync=autoswitch")), "gfx_vsync was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("rtg_modes=0x10")), "rtg_modes was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("bsdsocket_emu=true")), "bsdsocket_emu was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("sana2=true")), "sana2 was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("uaescsimode=SPTI")), "uaescsimode was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("unix.rtg_vblank=real")), "unix.rtg_vblank was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("unix.serial_port=TCP://0.0.0.0:1234")), "unix.serial_port was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("serial_translate=crlf_cr")), "serial_translate was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("uaeserial=true")), "uaeserial was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("unix.samplersoundcard=3")), "unix.samplersoundcard was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("midiout_device=-1")), "midiout_device was delegated") && ok; + ok = require(!parsedLines.contains(QStringLiteral("parallel_matrix_emulation=ascii")), "parallel_matrix_emulation was delegated") && ok; + ok = require(parsedLines.contains(QStringLiteral("unix.screenshot_path=/screenshots")), "runtime path was not delegated") && ok; + ok = require(parsedLines.contains(QStringLiteral("chipset=aga")), "chipset was not delegated") && ok; + ok = require(parsedLines.contains(QStringLiteral("uaehf0=dir,rw,DH0:System:/tmp/System,0")), "hard drive mount was not delegated") && ok; + + ok = requireText(prefs->romfile, "/roms/A1200.rom", "romfile") && ok; + ok = requireText(prefs->romextfile, "/roms/ext.rom", "romextfile") && ok; + ok = requireText(prefs->floppyslots[0].df, "/disks/install.adf", "floppy0") && ok; + ok = requireText(prefs->floppyslots[1].df, "/disks/extras.adf", "floppy1") && ok; + ok = requireInt(prefs->floppyslots[0].dfxtype, DRV_35_DD, "floppy0type") && ok; + ok = requireInt(prefs->floppyslots[1].dfxtype, DRV_35_HD, "floppy1type") && ok; + ok = requireInt(prefs->floppyslots[2].dfxtype, DRV_NONE, "floppy2type") && ok; + ok = require(prefs->floppyslots[0].forcedwriteprotect, "floppy0wp") && ok; + ok = require(!prefs->floppyslots[1].forcedwriteprotect, "floppy1wp") && ok; + ok = require(prefs->floppyslots[2].forcedwriteprotect, "floppy2wp") && ok; + ok = require(prefs->floppyslots[3].forcedwriteprotect, "floppy3wp") && ok; + ok = requireInt(prefs->nr_floppies, 2, "nr_floppies") && ok; + ok = require(!prefs->cs_df0idhw, "df0idhw") && ok; + ok = requireInt(prefs->cs_compatible, CP_A1200, "cs_compatible") && ok; + ok = requireInt(prefs->cpu_model, 68020, "cpu_model") && ok; + ok = requireInt(prefs->fpu_model, 68882, "fpu_model") && ok; + ok = require(!prefs->address_space_24, "cpu_24bit_addressing") && ok; + ok = requireUnsigned(prefs->chipmem.size, 4 * 0x80000, "chipmem.size") && ok; + ok = requireUnsigned(prefs->fastmem[0].size, 8 * 0x100000, "fastmem[0].size") && ok; + ok = requireUnsigned(prefs->bogomem.size, 2 * 0x40000, "bogomem.size") && ok; + ok = requireUnsigned(prefs->z3fastmem[0].size, 64 * 0x100000, "z3fastmem[0].size") && ok; + ok = requireInt(prefs->cachesize, 8, "cachesize") && ok; + ok = requireInt(prefs->produce_sound, 2, "produce_sound") && ok; + ok = require(prefs->sound_auto, "sound_auto") && ok; + ok = requireInt(prefs->sound_stereo, SND_STEREO, "sound_stereo") && ok; + ok = requireInt(prefs->sound_freq, 44100, "sound_freq") && ok; + ok = requireInt(prefs->sound_maxbsiz, 4096, "sound_maxbsiz") && ok; + ok = requireInt(prefs->sound_interpol, 2, "sound_interpol") && ok; + ok = requireInt(prefs->sound_filter, 1, "sound_filter") && ok; + ok = requireInt(prefs->sound_filter_type, 1, "sound_filter_type") && ok; + ok = requireInt(prefs->sound_stereo_separation, 8, "sound_stereo_separation") && ok; + ok = requireInt(prefs->sound_mixed_stereo_delay, 2, "sound_mixed_stereo_delay") && ok; + ok = require(prefs->sound_stereo_swap_paula, "sound_stereo_swap_paula") && ok; + ok = requireInt(prefs->floppyslots[0].dfxclick, 1, "floppy0sound") && ok; + ok = requireInt(prefs->dfxclickvolume_empty[0], 34, "floppy0soundvolume_empty") && ok; + ok = requireInt(prefs->dfxclickvolume_disk[0], 22, "floppy0soundvolume_disk") && ok; + ok = requireInt(prefs->win32_uaescsimode, UAESCSI_SPTI, "win32_uaescsimode") && ok; + ok = requireInt(prefs->win32_rtgvblankrate, -1, "win32_rtgvblankrate") && ok; + ok = requireUnsigned(prefs->rtgboards[0].rtgmem_size, 16 * 0x100000, "rtgmem_size") && ok; + ok = requireInt(prefs->rtgboards[0].rtgmem_type, GFXBOARD_ID_PICASSO4_Z3, "rtgmem_type") && ok; + ok = requireInt(prefs->gfx_monitor[0].gfx_size_win.width, 800, "gfx width") && ok; + ok = requireInt(prefs->gfx_monitor[0].gfx_size_win.height, 600, "gfx height") && ok; + ok = requireInt(prefs->gfx_monitor[0].gfx_size_fs.special, WH_NATIVE, "gfx fullscreen native") && ok; + ok = requireInt(prefs->gfx_apmode[APMODE_NATIVE].gfx_fullscreen, GFX_FULLWINDOW, "gfx_fullscreen_amiga") && ok; + ok = requireInt(prefs->gfx_apmode[APMODE_RTG].gfx_fullscreen, GFX_FULLSCREEN, "gfx_fullscreen_picasso") && ok; + ok = requireInt(prefs->gfx_apmode[APMODE_NATIVE].gfx_vsync, 2, "gfx_vsync") && ok; + ok = requireInt(prefs->gfx_apmode[APMODE_NATIVE].gfx_vsyncmode, 1, "gfx_vsyncmode") && ok; + ok = requireInt(prefs->gfx_apmode[APMODE_NATIVE].gfx_backbuffers, 3, "gfx_backbuffers") && ok; + ok = require(prefs->gfx_windowed_resize, "gfx_resize_windowed") && ok; + ok = requireInt(prefs->gfx_framerate, 2, "gfx_framerate") && ok; + ok = requireInt(prefs->gfx_vresolution, VRES_DOUBLE, "gfx_vresolution") && ok; + ok = requireInt(prefs->gfx_pscanlines, 2, "gfx_pscanlines") && ok; + ok = requireInt(prefs->gfx_xcenter, 1, "gfx_center_horizontal") && ok; + ok = requireInt(prefs->gfx_ycenter, 2, "gfx_center_vertical") && ok; + ok = require(prefs->gfx_keep_aspect, "gfx_keep_aspect") && ok; + ok = requireInt(prefs->picasso96_modeflags, 0x10, "rtg_modes") && ok; + ok = require(prefs->socket_emu, "bsdsocket_emu") && ok; + ok = require(prefs->sana2, "sana2") && ok; + ok = requireText(prefs->sername, "TCP://0.0.0.0:1234", "unix.serial_port") && ok; + ok = require(prefs->use_serial, "use_serial") && ok; + ok = require(prefs->serial_demand, "serial_on_demand") && ok; + ok = require(prefs->serial_hwctsrts, "serial_hardware_ctsrts") && ok; + ok = require(prefs->serial_rtsctsdtrdtecd, "serial_status") && ok; + ok = require(prefs->serial_ri, "serial_ri") && ok; + ok = require(prefs->serial_direct, "serial_direct") && ok; + ok = requireInt(prefs->serial_crlf, 1, "serial_translate") && ok; + ok = require(prefs->uaeserial, "uaeserial") && ok; + ok = requireInt(prefs->win32_samplersoundcard, 3, "win32_samplersoundcard") && ok; + ok = require(prefs->sampler_stereo, "sampler_stereo") && ok; + ok = requireInt(prefs->win32_midioutdev, -1, "win32_midioutdev") && ok; + ok = requireInt(prefs->win32_midiindev, 2, "win32_midiindev") && ok; + ok = require(!prefs->win32_midirouter, "win32_midirouter") && ok; + ok = requireInt(prefs->parallel_matrix_emulation, 1, "parallel_matrix_emulation") && ok; + ok = require(prefs->parallel_postscript_detection, "parallel_postscript_detection") && ok; + ok = requireInt(prefs->parallel_autoflush_time, 9, "parallel_autoflush") && ok; + ok = requireText(prefs->ghostscript_parameters, "-dNOPAUSE", "ghostscript_parameters") && ok; + free(prefs); + return ok; +} + +static bool testNativeWindowSize() +{ + WinUaeQtConfig::Settings settings; + settings.insert(QStringLiteral("gfx_width_windowed"), QStringLiteral("native")); + settings.insert(QStringLiteral("gfx_height_windowed"), QStringLiteral("900")); + + struct uae_prefs *prefs = allocPrefs(); + if (!prefs) { + qWarning().noquote() << "failed to allocate preferences"; + return false; + } + prefs->gfx_monitor[0].gfx_size_win.width = 123; + prefs->gfx_monitor[0].gfx_size_win.height = 456; + + bool ok = applyWinUaeQtConfigToPrefs(WinUaeQtConfig(settings), prefs); + ok = require(ok, "adapter rejected native window size config") && ok; + ok = requireInt(prefs->gfx_monitor[0].gfx_size_win.width, 0, "native gfx width") && ok; + ok = requireInt(prefs->gfx_monitor[0].gfx_size_win.height, 0, "native gfx height") && ok; + free(prefs); + return ok; +} + +static bool testRepeatedMountDelegation() +{ + WinUaeQtConfig config; + config.applyRepeatedSettings({ + { QStringLiteral("filesystem2"), QStringLiteral("rw,DH0:System:/tmp/System,0") }, + { QStringLiteral("filesystem2"), QStringLiteral("ro,DH1:Work:/tmp/Work,5") } + }, { + QStringLiteral("filesystem2") + }); + + struct uae_prefs *prefs = allocPrefs(); + if (!prefs) { + qWarning().noquote() << "failed to allocate preferences"; + return false; + } + + parsedLines.clear(); + bool ok = applyWinUaeQtConfigToPrefs(config, prefs); + ok = require(ok, "adapter rejected repeated mount config") && ok; + ok = requireInt(parsedLines.size(), 2, "repeated mount parsed line count") && ok; + ok = require(parsedLines.contains(QStringLiteral("filesystem2=rw,DH0:System:/tmp/System,0")), "first repeated mount was not delegated") && ok; + ok = require(parsedLines.contains(QStringLiteral("filesystem2=ro,DH1:Work:/tmp/Work,5")), "second repeated mount was not delegated") && ok; + free(prefs); + return ok; +} + +int main() +{ + bool ok = true; + ok = require(!applyWinUaeQtConfigToPrefs(WinUaeQtConfig(), nullptr), "null prefs should fail") && ok; + ok = testRepresentativeConfig() && ok; + ok = testNativeWindowSize() && ok; + ok = testRepeatedMountDelegation() && ok; + return ok ? 0 : 1; +}