New JSON config format

This commit is contained in:
GriffinR
2026-02-06 23:47:06 -05:00
parent e3e42f39f0
commit 3571b4ea4b
56 changed files with 2005 additions and 1883 deletions

View File

@@ -81,6 +81,7 @@ MainWindow::MainWindow(QWidget *parent) :
void MainWindow::initialize() {
this->initWindow();
this->installEventFilter(new GeometrySaver(this));
if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) {
on_toolButton_Paint_clicked();
}
@@ -92,7 +93,7 @@ void MainWindow::initialize() {
if (porymapConfig.checkForUpdates)
this->checkForUpdates(false);
this->restoreWindowState();
this->resizeWithinScreen();
this->show();
}
@@ -109,13 +110,6 @@ MainWindow::~MainWindow()
}
void MainWindow::saveGlobalConfigs() {
porymapConfig.setMainGeometry(
this->saveGeometry(),
this->saveState(),
this->ui->splitter_map->saveState(),
this->ui->splitter_main->saveState(),
this->ui->splitter_Metatiles->saveState()
);
porymapConfig.save();
shortcutsConfig.save();
}
@@ -185,6 +179,7 @@ void MainWindow::setWindowDisabled(bool disabled) {
}
void MainWindow::initWindow() {
porymapConfig = PorymapConfig();
porymapConfig.load();
this->initLogStatusBar();
this->initCustomUI();
@@ -227,6 +222,7 @@ void MainWindow::initWindow() {
void MainWindow::initShortcuts() {
initExtraShortcuts();
shortcutsConfig = ShortcutsConfig();
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
@@ -303,9 +299,8 @@ void MainWindow::applyUserShortcuts() {
void MainWindow::initLogStatusBar() {
removeLogStatusBar(this->statusBar());
auto logTypes = QSet<LogType>(porymapConfig.statusBarLogTypes.begin(), porymapConfig.statusBarLogTypes.end());
if (!logTypes.isEmpty()) {
addLogStatusBar(this->statusBar(), logTypes);
if (!porymapConfig.statusBarLogTypes.isEmpty()) {
addLogStatusBar(this->statusBar(), porymapConfig.statusBarLogTypes.toQSet());
}
}
@@ -320,7 +315,7 @@ void MainWindow::initCustomUI() {
static const QMap<int, QIcon> mainTabIcons = {
{MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))},
{MainTab::Events, ProjectConfig::getPlayerIcon(BaseGameVersion::pokefirered, 0)}, // Arbitrary default
{MainTab::Events, BaseGame::getPlayerIcon(BaseGame::Version::pokefirered, 0)}, // Arbitrary default
{MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))},
{MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))},
{MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))},
@@ -591,9 +586,9 @@ void MainWindow::initMapList() {
// Initialize settings from config
ui->mapListToolBar_Groups->setEditsAllowed(porymapConfig.mapListEditGroupsEnabled);
for (auto i = porymapConfig.mapListHideEmptyEnabled.constBegin(); i != porymapConfig.mapListHideEmptyEnabled.constEnd(); i++) {
auto toolbar = getMapListToolBar(i.key());
if (toolbar) toolbar->setEmptyFoldersVisible(!i.value());
for (const auto &tab : porymapConfig.mapListTabsHidingEmptyFolders) {
auto toolbar = getMapListToolBar(tab);
if (toolbar) toolbar->setEmptyFoldersVisible(false);
}
// Update config if map list settings change
@@ -601,13 +596,16 @@ void MainWindow::initMapList() {
porymapConfig.mapListEditGroupsEnabled = allowed;
});
connect(ui->mapListToolBar_Groups, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups] = !visible;
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Groups);
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Groups);
});
connect(ui->mapListToolBar_Locations, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations] = !visible;
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Locations);
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Locations);
});
connect(ui->mapListToolBar_Layouts, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts] = !visible;
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Layouts);
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Layouts);
});
// When map list search filter is cleared we want the current map/layout in the editor to be visible in the list.
@@ -729,19 +727,8 @@ void MainWindow::loadUserSettings() {
refreshRecentProjectsMenu();
}
void MainWindow::restoreWindowState() {
QMap<QString, QByteArray> geometry = porymapConfig.getMainGeometry();
const QByteArray mainWindowGeometry = geometry.value("main_window_geometry");
if (!mainWindowGeometry.isEmpty()) {
logInfo("Restoring main window geometry from previous session.");
restoreGeometry(mainWindowGeometry);
restoreState(geometry.value("main_window_state"));
ui->splitter_map->restoreState(geometry.value("map_splitter_state"));
ui->splitter_main->restoreState(geometry.value("main_splitter_state"));
ui->splitter_Metatiles->restoreState(geometry.value("metatiles_splitter_state"));
}
// Resize the window if it exceeds the available screen size.
// Resize the window if it exceeds the available screen size.
void MainWindow::resizeWithinScreen() {
auto screen = windowHandle() ? windowHandle()->screen() : QGuiApplication::primaryScreen();
if (!screen) return;
const QRect screenGeometry = screen->availableGeometry();
@@ -800,14 +787,14 @@ bool MainWindow::openProject(QString dir, bool initial) {
logInfo("Aborted project open.");
return false;
}
const QString openMessage = QString("Opening %1").arg(projectString);
logInfo(openMessage);
logInfo(QString("Opening %1").arg(projectString));
if (porymapConfig.showProjectLoadingScreen) porysplash->start();
porysplash->showLoadingMessage("config");
if (!projectConfig.load(dir) || !userConfig.load(dir)) {
projectConfig = ProjectConfig(dir);
userConfig = UserConfig(dir);
if (!projectConfig.load() || !userConfig.load()) {
showProjectOpenFailure();
porysplash->stop();
return false;
@@ -845,9 +832,6 @@ bool MainWindow::openProject(QString dir, bool initial) {
porysplash->stop();
return false;
}
// Only create the config files once the project has opened successfully in case the user selected an invalid directory
this->editor->project->saveConfig();
updateWindowTitle();
@@ -899,24 +883,24 @@ bool MainWindow::checkProjectSanity(Project *project) {
bool MainWindow::checkProjectVersion(Project *project) {
QString error;
int projectVersion = project->getSupportedMajorVersion(&error);
if (projectVersion < 0) {
// Failed to identify a supported major version.
QVersionNumber minimumVersion = project->getMinimumVersion(&error);
if (!error.isEmpty()) {
// Failed to identify a supported version.
// We can't draw any conclusions from this, so we don't consider the project to be invalid.
QString msg = QStringLiteral("Failed to check project version");
logWarn(error.isEmpty() ? msg : QString("%1: '%2'").arg(msg).arg(error));
logWarn(QString("Failed to check project version: '%1'").arg(error));
} else {
QString msg = QStringLiteral("Successfully checked project version. ");
logInfo(msg + ((projectVersion != 0) ? QString("Supports at least Porymap v%1").arg(projectVersion)
: QStringLiteral("Too old for any Porymap version")));
if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) {
if (minimumVersion.isNull()) {
logInfo(QStringLiteral("Successfully checked project version. Too old for any Porymap version"));
} else {
logInfo(QString("Successfully checked project version. Supports at least Porymap v%1").arg(minimumVersion.toString()));
}
if (minimumVersion > porymapVersion || minimumVersion.majorVersion() != porymapVersion.majorVersion()) {
// We were unable to find the necessary changes for Porymap's current major version.
// Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes.
// Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting.
// Note: Do not report 'minimumVersion' to the user in this message. We've already logged it for troubleshooting.
// It is very plausible that the user may have reproduced the required changes in an
// unknown commit, rather than merging the required changes directly from the base repo.
// In this case the 'projectVersion' may actually be too old to use for their repo.
// In this case the 'minimumVersion' may actually be too old to use for their repo.
ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), porysplash);
msgBox.setTextFormat(Qt::RichText);
msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2.<br>"
@@ -929,7 +913,7 @@ bool MainWindow::checkProjectVersion(Project *project) {
return false;
}
// User opted to try with this version anyway. Don't warn them about this version again.
projectConfig.forcedMajorVersion = porymapVersion.majorVersion();
projectConfig.minimumVersion = QVersionNumber(porymapVersion.majorVersion());
}
}
return true;
@@ -939,7 +923,7 @@ void MainWindow::showProjectOpenFailure() {
if (!this->isVisible()){
// The main window is not visible during the initial project open; the splash screen is busy providing visual feedback.
// If project opening fails we can immediately display the empty main window (which we need anyway to parent messages to).
restoreWindowState();
resizeWithinScreen();
show();
}
RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this);
@@ -1491,7 +1475,7 @@ bool MainWindow::setProjectUI() {
this->mapGroupModel = new MapGroupModel(editor->project);
this->groupListProxyModel = new FilterChildrenProxyModel();
this->groupListProxyModel->setSourceModel(this->mapGroupModel);
this->groupListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups]);
this->groupListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Groups));
ui->mapList->setModel(groupListProxyModel);
this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this));
@@ -1500,14 +1484,14 @@ bool MainWindow::setProjectUI() {
this->mapLocationModel = new MapLocationModel(editor->project);
this->locationListProxyModel = new FilterChildrenProxyModel();
this->locationListProxyModel->setSourceModel(this->mapLocationModel);
this->locationListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations]);
this->locationListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Locations));
ui->locationList->setModel(locationListProxyModel);
setMapListSorted(ui->locationList, porymapConfig.mapListLocationsSorted);
this->layoutTreeModel = new LayoutTreeModel(editor->project);
this->layoutListProxyModel = new FilterChildrenProxyModel();
this->layoutListProxyModel->setSourceModel(this->layoutTreeModel);
this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts]);
this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Layouts));
ui->layoutList->setModel(layoutListProxyModel);
setMapListSorted(ui->layoutList, porymapConfig.mapListLayoutsSorted);
@@ -1524,7 +1508,7 @@ bool MainWindow::setProjectUI() {
if (eventTabIcon.isNull()) {
// We randomly choose between the available characters for ~flavor~.
// For now, this correctly assumes all versions have 2 icons.
eventTabIcon = ProjectConfig::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
eventTabIcon = BaseGame::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
}
ui->mainTabBar->setTabIcon(MainTab::Events, eventTabIcon);
@@ -2259,7 +2243,7 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index)
} else if (index == MapViewTab::Collision) {
refreshCollisionSelector();
} else if (index == MapViewTab::Prefabs) {
if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) {
if (userConfig.prefabsFilepath.isEmpty() && !userConfig.prefabsImportPrompted) {
// User hasn't set up prefabs and hasn't been prompted before.
// Ask if they'd like to import the default prefabs file.
if (prefab.tryImportDefaultPrefabs(this, projectConfig.baseGameVersion))
@@ -3299,7 +3283,8 @@ bool MainWindow::closeProject() {
return false;
}
}
editor->closeProject();
logInfo(QString("Closing project '%1'").arg(this->editor->project->root));
this->editor->closeProject();
clearProjectUI();
refreshRecentProjectsMenu();
setWindowDisabled(true);