Add sanity check to project opening
This commit is contained in:
parent
29ed696d9e
commit
9efe67a72f
5 changed files with 143 additions and 88 deletions
|
@ -344,9 +344,9 @@ private:
|
||||||
bool setMap(QString, bool scrollTreeView = false);
|
bool setMap(QString, bool scrollTreeView = false);
|
||||||
void redrawMapScene();
|
void redrawMapScene();
|
||||||
void refreshMapScene();
|
void refreshMapScene();
|
||||||
bool loadDataStructures();
|
bool checkProjectSanity();
|
||||||
bool loadProjectCombos();
|
bool loadProjectData();
|
||||||
bool populateMapList();
|
bool setProjectUI();
|
||||||
void sortMapList();
|
void sortMapList();
|
||||||
void openSubWindow(QWidget * window);
|
void openSubWindow(QWidget * window);
|
||||||
QString getExistingDirectory(QString);
|
QString getExistingDirectory(QString);
|
||||||
|
@ -376,7 +376,6 @@ private:
|
||||||
void initMapSortOrder();
|
void initMapSortOrder();
|
||||||
void initShortcuts();
|
void initShortcuts();
|
||||||
void initExtraShortcuts();
|
void initExtraShortcuts();
|
||||||
void setProjectSpecificUI();
|
|
||||||
void loadUserSettings();
|
void loadUserSettings();
|
||||||
void applyMapListFilter(QString filterText);
|
void applyMapListFilter(QString filterText);
|
||||||
void restoreWindowState();
|
void restoreWindowState();
|
||||||
|
|
|
@ -97,6 +97,9 @@ public:
|
||||||
DataQualifiers healLocationDataQualifiers;
|
DataQualifiers healLocationDataQualifiers;
|
||||||
QString healLocationsTableName;
|
QString healLocationsTableName;
|
||||||
|
|
||||||
|
bool sanityCheck();
|
||||||
|
bool load();
|
||||||
|
|
||||||
QMap<QString, Map*> mapCache;
|
QMap<QString, Map*> mapCache;
|
||||||
Map* loadMap(QString);
|
Map* loadMap(QString);
|
||||||
Map* getMap(QString);
|
Map* getMap(QString);
|
||||||
|
|
|
@ -890,6 +890,8 @@ void ProjectConfig::init() {
|
||||||
|
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
this->baseGameVersion = static_cast<BaseGameVersion>(baseGameVersionComboBox->currentData().toInt());
|
this->baseGameVersion = static_cast<BaseGameVersion>(baseGameVersionComboBox->currentData().toInt());
|
||||||
|
} else {
|
||||||
|
// TODO: If user closes window Porymap assumes pokeemerald; it should instead abort project opening
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->setUnreadKeys(); // Initialize version-specific options
|
this->setUnreadKeys(); // Initialize version-specific options
|
||||||
|
|
|
@ -413,35 +413,6 @@ void MainWindow::markMapEdited() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the UI using information we've read from the user's project files.
|
|
||||||
void MainWindow::setProjectSpecificUI()
|
|
||||||
{
|
|
||||||
// Wild Encounters tab
|
|
||||||
// TODO: This index should come from an enum
|
|
||||||
ui->mainTabBar->setTabEnabled(4, editor->project->wildEncountersLoaded);
|
|
||||||
|
|
||||||
bool hasFlags = projectConfig.mapAllowFlagsEnabled;
|
|
||||||
ui->checkBox_AllowRunning->setVisible(hasFlags);
|
|
||||||
ui->checkBox_AllowBiking->setVisible(hasFlags);
|
|
||||||
ui->checkBox_AllowEscaping->setVisible(hasFlags);
|
|
||||||
ui->label_AllowRunning->setVisible(hasFlags);
|
|
||||||
ui->label_AllowBiking->setVisible(hasFlags);
|
|
||||||
ui->label_AllowEscaping->setVisible(hasFlags);
|
|
||||||
|
|
||||||
ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.eventWeatherTriggerEnabled);
|
|
||||||
ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled);
|
|
||||||
ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled);
|
|
||||||
|
|
||||||
bool floorNumEnabled = projectConfig.floorNumberEnabled;
|
|
||||||
ui->spinBox_FloorNumber->setVisible(floorNumEnabled);
|
|
||||||
ui->label_FloorNumber->setVisible(floorNumEnabled);
|
|
||||||
|
|
||||||
Event::setIcons();
|
|
||||||
editor->setCollisionGraphics();
|
|
||||||
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
|
|
||||||
ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision());
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::mapSortOrder_changed(QAction *action)
|
void MainWindow::mapSortOrder_changed(QAction *action)
|
||||||
{
|
{
|
||||||
QList<QAction*> items = ui->toolButton_MapSortOrder->menu()->actions();
|
QList<QAction*> items = ui->toolButton_MapSortOrder->menu()->actions();
|
||||||
|
@ -528,13 +499,13 @@ void MainWindow::setTheme(QString theme) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openProject(const QString &dir, bool initial) {
|
bool MainWindow::openProject(const QString &dir, bool initial) {
|
||||||
if (!this->closeProject()) {
|
|
||||||
logInfo("Aborted project open.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir.isNull() || dir.length() <= 0) {
|
if (dir.isNull() || dir.length() <= 0) {
|
||||||
if (!initial) setWindowDisabled(true);
|
// If this happened on startup it's because the user has no recent projects, which is fine.
|
||||||
|
// This shouldn't happen otherwise, but if it does then display an error.
|
||||||
|
if (!initial) {
|
||||||
|
logError("Failed to open project: Directory name cannot be empty");
|
||||||
|
showProjectOpenFailure();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,6 +524,13 @@ bool MainWindow::openProject(const QString &dir, bool initial) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The above checks can fail and the user will be allowed to continue with their currently-opened project (if there is one).
|
||||||
|
// We close the current project below, after which either the new project will open successfully or the window will be disabled.
|
||||||
|
if (!this->closeProject()) {
|
||||||
|
logInfo("Aborted project open.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const QString openMessage = QString("Opening %1").arg(projectString);
|
const QString openMessage = QString("Opening %1").arg(projectString);
|
||||||
this->statusBar()->showMessage(openMessage);
|
this->statusBar()->showMessage(openMessage);
|
||||||
logInfo(openMessage);
|
logInfo(openMessage);
|
||||||
|
@ -577,8 +555,14 @@ bool MainWindow::openProject(const QString &dir, bool initial) {
|
||||||
});
|
});
|
||||||
this->editor->project->set_root(dir);
|
this->editor->project->set_root(dir);
|
||||||
|
|
||||||
|
// Make sure project looks reasonable before attempting to load it
|
||||||
|
if (!checkProjectSanity()) {
|
||||||
|
delete this->editor->project;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Load the project
|
// Load the project
|
||||||
if (!(loadDataStructures() && populateMapList() && setInitialMap())) {
|
if (!(loadProjectData() && setProjectUI() && setInitialMap())) {
|
||||||
this->statusBar()->showMessage(QString("Failed to open %1").arg(projectString));
|
this->statusBar()->showMessage(QString("Failed to open %1").arg(projectString));
|
||||||
showProjectOpenFailure();
|
showProjectOpenFailure();
|
||||||
delete this->editor->project;
|
delete this->editor->project;
|
||||||
|
@ -606,12 +590,39 @@ bool MainWindow::openProject(const QString &dir, bool initial) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MainWindow::loadProjectData() {
|
||||||
|
bool success = editor->project->load();
|
||||||
|
Scripting::populateGlobalObject(this);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::checkProjectSanity() {
|
||||||
|
if (editor->project->sanityCheck())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
QMessageBox msgBox;
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setText(QString("The selected directory appears to be invalid."));
|
||||||
|
msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n"
|
||||||
|
"Make sure you selected the correct project directory "
|
||||||
|
"(the one used to make your <b>.gba</b> file, e.g. 'pokeemerald').").arg(editor->project->root));
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setDefaultButton(QMessageBox::Ok);
|
||||||
|
auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole);
|
||||||
|
msgBox.exec();
|
||||||
|
if (msgBox.clickedButton() == tryAnyway) {
|
||||||
|
// The user has chosen to try to load this project anyway.
|
||||||
|
// This will almost certainly fail, but they'll get a more specific error message.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::showProjectOpenFailure() {
|
void MainWindow::showProjectOpenFailure() {
|
||||||
QString errorMsg = QString("There was an error opening the project. Please see %1 for full error details.").arg(getLogPath());
|
QString errorMsg = QString("There was an error opening the project. Please see %1 for full error details.").arg(getLogPath());
|
||||||
QMessageBox error(QMessageBox::Critical, "porymap", errorMsg, QMessageBox::Ok, this);
|
QMessageBox error(QMessageBox::Critical, "porymap", errorMsg, QMessageBox::Ok, this);
|
||||||
error.setDetailedText(getMostRecentError());
|
error.setDetailedText(getMostRecentError());
|
||||||
error.exec();
|
error.exec();
|
||||||
setWindowDisabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::isProjectOpen() {
|
bool MainWindow::isProjectOpen() {
|
||||||
|
@ -975,45 +986,8 @@ void MainWindow::on_spinBox_FloorNumber_valueChanged(int offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::loadDataStructures() {
|
// Update the UI using information we've read from the user's project files.
|
||||||
Project *project = editor->project;
|
bool MainWindow::setProjectUI() {
|
||||||
bool success = project->readMapLayouts()
|
|
||||||
&& project->readRegionMapSections()
|
|
||||||
&& project->readItemNames()
|
|
||||||
&& project->readFlagNames()
|
|
||||||
&& project->readVarNames()
|
|
||||||
&& project->readMovementTypes()
|
|
||||||
&& project->readInitialFacingDirections()
|
|
||||||
&& project->readMapTypes()
|
|
||||||
&& project->readMapBattleScenes()
|
|
||||||
&& project->readWeatherNames()
|
|
||||||
&& project->readCoordEventWeatherNames()
|
|
||||||
&& project->readSecretBaseIds()
|
|
||||||
&& project->readBgEventFacingDirections()
|
|
||||||
&& project->readTrainerTypes()
|
|
||||||
&& project->readMetatileBehaviors()
|
|
||||||
&& project->readFieldmapProperties()
|
|
||||||
&& project->readFieldmapMasks()
|
|
||||||
&& project->readTilesetLabels()
|
|
||||||
&& project->readTilesetMetatileLabels()
|
|
||||||
&& project->readHealLocations()
|
|
||||||
&& project->readMiscellaneousConstants()
|
|
||||||
&& project->readSpeciesIconPaths()
|
|
||||||
&& project->readWildMonData()
|
|
||||||
&& project->readEventScriptLabels()
|
|
||||||
&& project->readObjEventGfxConstants()
|
|
||||||
&& project->readEventGraphics()
|
|
||||||
&& project->readSongNames();
|
|
||||||
|
|
||||||
project->applyParsedLimits();
|
|
||||||
setProjectSpecificUI();
|
|
||||||
Scripting::populateGlobalObject(this);
|
|
||||||
|
|
||||||
return success && loadProjectCombos();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MainWindow::loadProjectCombos() {
|
|
||||||
// set up project ui comboboxes
|
|
||||||
Project *project = editor->project;
|
Project *project = editor->project;
|
||||||
|
|
||||||
// Block signals to the comboboxes while they are being modified
|
// Block signals to the comboboxes while they are being modified
|
||||||
|
@ -1025,6 +999,7 @@ bool MainWindow::loadProjectCombos() {
|
||||||
const QSignalBlocker blocker6(ui->comboBox_BattleScene);
|
const QSignalBlocker blocker6(ui->comboBox_BattleScene);
|
||||||
const QSignalBlocker blocker7(ui->comboBox_Type);
|
const QSignalBlocker blocker7(ui->comboBox_Type);
|
||||||
|
|
||||||
|
// Set up project comboboxes
|
||||||
ui->comboBox_Song->clear();
|
ui->comboBox_Song->clear();
|
||||||
ui->comboBox_Song->addItems(project->songNames);
|
ui->comboBox_Song->addItems(project->songNames);
|
||||||
ui->comboBox_Location->clear();
|
ui->comboBox_Location->clear();
|
||||||
|
@ -1040,15 +1015,36 @@ bool MainWindow::loadProjectCombos() {
|
||||||
ui->comboBox_Type->clear();
|
ui->comboBox_Type->clear();
|
||||||
ui->comboBox_Type->addItems(project->mapTypes);
|
ui->comboBox_Type->addItems(project->mapTypes);
|
||||||
|
|
||||||
return true;
|
sortMapList();
|
||||||
}
|
|
||||||
|
|
||||||
bool MainWindow::populateMapList() {
|
// Show/hide parts of the UI that are dependent on the user's project settings
|
||||||
bool success = editor->project->readMapGroups();
|
|
||||||
if (success) {
|
// Wild Encounters tab
|
||||||
sortMapList();
|
// TODO: This index should come from an enum
|
||||||
}
|
ui->mainTabBar->setTabEnabled(4, editor->project->wildEncountersLoaded);
|
||||||
return success;
|
|
||||||
|
bool hasFlags = projectConfig.mapAllowFlagsEnabled;
|
||||||
|
ui->checkBox_AllowRunning->setVisible(hasFlags);
|
||||||
|
ui->checkBox_AllowBiking->setVisible(hasFlags);
|
||||||
|
ui->checkBox_AllowEscaping->setVisible(hasFlags);
|
||||||
|
ui->label_AllowRunning->setVisible(hasFlags);
|
||||||
|
ui->label_AllowBiking->setVisible(hasFlags);
|
||||||
|
ui->label_AllowEscaping->setVisible(hasFlags);
|
||||||
|
|
||||||
|
ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.eventWeatherTriggerEnabled);
|
||||||
|
ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled);
|
||||||
|
ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled);
|
||||||
|
|
||||||
|
bool floorNumEnabled = projectConfig.floorNumberEnabled;
|
||||||
|
ui->spinBox_FloorNumber->setVisible(floorNumEnabled);
|
||||||
|
ui->label_FloorNumber->setVisible(floorNumEnabled);
|
||||||
|
|
||||||
|
Event::setIcons();
|
||||||
|
editor->setCollisionGraphics();
|
||||||
|
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
|
||||||
|
ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision());
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::sortMapList() {
|
void MainWindow::sortMapList() {
|
||||||
|
@ -3019,6 +3015,7 @@ bool MainWindow::closeProject() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editor->closeProject();
|
editor->closeProject();
|
||||||
|
setWindowDisabled(true);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,60 @@ void Project::set_root(QString dir) {
|
||||||
this->parser.set_root(dir);
|
this->parser.set_root(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Before attempting the initial project load we should check for a few notable files.
|
||||||
|
// If all are missing then we can warn the user, they may have accidentally selected the wrong folder.
|
||||||
|
bool Project::sanityCheck() {
|
||||||
|
// The goal with the file selection is to pick files that are important enough that any reasonable project would have
|
||||||
|
// at least 1 in the expected location, but unique enough that they're unlikely to overlap with a completely unrelated
|
||||||
|
// directory (e.g. checking for 'data/maps/' is a bad choice because it's too generic, pokeyellow would pass for instance)
|
||||||
|
static const QSet<ProjectFilePath> pathsToCheck = {
|
||||||
|
ProjectFilePath::json_map_groups,
|
||||||
|
ProjectFilePath::json_layouts,
|
||||||
|
ProjectFilePath::tilesets_headers,
|
||||||
|
ProjectFilePath::global_fieldmap,
|
||||||
|
};
|
||||||
|
for (auto pathId : pathsToCheck) {
|
||||||
|
const QString path = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(pathId));
|
||||||
|
QFileInfo fileInfo(path);
|
||||||
|
if (fileInfo.exists() && fileInfo.isFile())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Project::load() {
|
||||||
|
bool success = readMapLayouts()
|
||||||
|
&& readRegionMapSections()
|
||||||
|
&& readItemNames()
|
||||||
|
&& readFlagNames()
|
||||||
|
&& readVarNames()
|
||||||
|
&& readMovementTypes()
|
||||||
|
&& readInitialFacingDirections()
|
||||||
|
&& readMapTypes()
|
||||||
|
&& readMapBattleScenes()
|
||||||
|
&& readWeatherNames()
|
||||||
|
&& readCoordEventWeatherNames()
|
||||||
|
&& readSecretBaseIds()
|
||||||
|
&& readBgEventFacingDirections()
|
||||||
|
&& readTrainerTypes()
|
||||||
|
&& readMetatileBehaviors()
|
||||||
|
&& readFieldmapProperties()
|
||||||
|
&& readFieldmapMasks()
|
||||||
|
&& readTilesetLabels()
|
||||||
|
&& readTilesetMetatileLabels()
|
||||||
|
&& readHealLocations()
|
||||||
|
&& readMiscellaneousConstants()
|
||||||
|
&& readSpeciesIconPaths()
|
||||||
|
&& readWildMonData()
|
||||||
|
&& readEventScriptLabels()
|
||||||
|
&& readObjEventGfxConstants()
|
||||||
|
&& readEventGraphics()
|
||||||
|
&& readSongNames()
|
||||||
|
&& readMapGroups();
|
||||||
|
applyParsedLimits();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
QString Project::getProjectTitle() {
|
QString Project::getProjectTitle() {
|
||||||
if (!root.isNull()) {
|
if (!root.isNull()) {
|
||||||
return root.section('/', -1);
|
return root.section('/', -1);
|
||||||
|
|
Loading…
Reference in a new issue