Merge pull request #717 from GriffinRichards/write-errors

Better error handling if saving fails
This commit is contained in:
GriffinR
2025-04-21 17:27:20 -04:00
committed by GitHub
16 changed files with 295 additions and 260 deletions

View File

@@ -26,7 +26,7 @@ static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_
class KeyValueConfigBase
{
public:
void save();
bool save();
void load();
virtual ~KeyValueConfigBase();
virtual void reset() = 0;

View File

@@ -116,9 +116,12 @@ public:
void clearBorderCache();
void cacheBorder();
void setClean();
bool hasUnsavedChanges() const;
bool save(const QString &root);
bool saveBorder(const QString &root);
bool saveBlockdata(const QString &root);
bool layoutBlockChanged(int i, const Blockdata &cache);
uint16_t getBorderMetatileId(int x, int y);
@@ -143,6 +146,7 @@ public:
private:
void setNewDimensionsBlockdata(int newWidth, int newHeight);
void setNewBorderDimensionsBlockdata(int newWidth, int newHeight);
bool writeBlockdata(const QString &path, const Blockdata &blockdata) const;
static int getBorderDrawDistance(int dimension, qreal minimum);

View File

@@ -7,7 +7,7 @@
namespace PaletteUtil {
QList<QRgb> parse(QString filepath, bool *error);
void writeJASC(QString filepath, QVector<QRgb> colors, int offset, int nColors);
bool writeJASC(const QString &filepath, const QVector<QRgb> &colors, int offset, int nColors);
}
#endif // PALETTEUTIL_H

View File

@@ -55,17 +55,17 @@ public:
static QString getExpectedDir(QString tilesetName, bool isSecondary);
QString getExpectedDir();
void load();
void loadMetatiles();
void loadMetatileAttributes();
void loadTilesImage(QImage *importedImage = nullptr);
void loadPalettes();
bool load();
bool loadMetatiles();
bool loadMetatileAttributes();
bool loadTilesImage(QImage *importedImage = nullptr);
bool loadPalettes();
void save();
void saveMetatileAttributes();
void saveMetatiles();
void saveTilesImage();
void savePalettes();
bool save();
bool saveMetatileAttributes();
bool saveMetatiles();
bool saveTilesImage();
bool savePalettes();
bool appendToHeaders(QString root, QString friendlyName, bool usingAsm);
bool appendToGraphics(QString root, QString friendlyName, bool usingAsm);

View File

@@ -57,8 +57,8 @@ public:
GridSettings gridSettings;
void setProject(Project * project);
void saveAll();
void saveCurrent();
bool saveAll();
bool saveCurrent();
void saveEncounterTabData();
void closeProject();
@@ -199,7 +199,7 @@ private:
EditMode editMode = EditMode::None;
void save(bool currentOnly);
bool save(bool currentOnly);
void clearMap();
void clearMetatileSelector();
void clearMovementPermissionSelector();

View File

@@ -175,7 +175,7 @@ private slots:
void on_action_Reload_Project_triggered();
void on_action_Close_Project_triggered();
void on_action_Save_Project_triggered();
void save(bool currentOnly = false);
bool save(bool currentOnly = false);
void openWarpMap(QString map_name, int event_id, Event::Group event_group);

View File

@@ -108,10 +108,6 @@ public:
bool loadBlockdata(Layout *);
bool loadLayoutBorder(Layout *);
void saveTextFile(QString path, QString text);
void appendTextFile(QString path, QString text);
void deleteFile(QString path);
bool readMapGroups();
void addNewMapGroup(const QString &groupName);
QString mapNameToMapGroup(const QString &mapName) const;
@@ -168,25 +164,20 @@ public:
bool loadLayout(Layout *);
bool loadMapLayout(Map*);
bool loadLayoutTilesets(Layout *);
void loadTilesetAssets(Tileset*);
bool loadTilesetAssets(Tileset*);
void loadTilesetMetatileLabels(Tileset*);
void readTilesetPaths(Tileset* tileset);
void saveAll();
void saveGlobalData();
void saveLayout(Layout *);
void saveLayoutBlockdata(Layout *);
void saveLayoutBorder(Layout *);
void writeBlockdata(QString, const Blockdata &);
void saveMap(Map *map, bool skipLayout = false);
void saveConfig();
void saveMapLayouts();
void saveMapGroups();
void saveRegionMapSections();
void saveWildMonData();
void saveHealLocations();
void saveTilesets(Tileset*, Tileset*);
void saveTilesetMetatileLabels(Tileset*, Tileset*);
bool saveAll();
bool saveGlobalData();
bool saveConfig();
bool saveLayout(Layout *layout);
bool saveMap(Map *map, bool skipLayout = false);
bool saveTextFile(const QString &path, const QString &text);
bool saveRegionMapSections();
bool saveTilesets(Tileset*, Tileset*);
bool saveTilesetMetatileLabels(Tileset*, Tileset*);
void appendTilesetLabel(const QString &label, const QString &isSecondaryStr);
bool readTilesetLabels();
bool readTilesetMetatileLabels();
@@ -309,8 +300,6 @@ private:
};
QHash<QString, LocationData> locationData;
void updateLayout(Layout *);
void setNewLayoutBlockdata(Layout *layout);
void setNewLayoutBorder(Layout *layout);
@@ -318,6 +307,12 @@ private:
void recordFileChange(const QString &filepath);
void resetFileCache();
bool saveMapLayouts();
bool saveMapGroups();
bool saveWildMonData();
bool saveHealLocations();
bool appendTextFile(const QString &path, const QString &text);
QString findSpeciesIconPath(const QStringList &names) const;
int maxEventsPerGroup;

View File

@@ -71,8 +71,6 @@ private slots:
void on_spinBox_paletteSelector_valueChanged(int arg1);
void on_actionSave_Tileset_triggered();
void on_actionImport_Primary_Tiles_triggered();
void on_actionImport_Secondary_Tiles_triggered();
@@ -173,6 +171,8 @@ private:
bool lockSelection = false;
QSet<uint16_t> metatileReloadQueue;
bool save();
signals:
void tilesetsSaved(QString, QString);
};

View File

@@ -233,7 +233,7 @@ void KeyValueConfigBase::load() {
file.close();
}
void KeyValueConfigBase::save() {
bool KeyValueConfigBase::save() {
QString text = "";
QMap<QString, QString> map = this->getKeyValueMap();
for (QMap<QString, QString>::iterator it = map.begin(); it != map.end(); it++) {
@@ -241,12 +241,14 @@ void KeyValueConfigBase::save() {
}
QFile file(this->getConfigFilepath());
if (file.open(QIODevice::WriteOnly)) {
file.write(text.toUtf8());
file.close();
} else {
if (!file.open(QIODevice::WriteOnly)) {
logError(QString("Could not open config file '%1' for writing: ").arg(this->getConfigFilepath()) + file.errorString());
return false;
}
file.write(text.toUtf8());
file.close();
return true;
}
bool KeyValueConfigBase::getConfigBool(QString key, QString value) {

View File

@@ -467,11 +467,50 @@ QPixmap Layout::getLayoutItemPixmap() {
return this->layoutItem ? this->layoutItem->pixmap() : QPixmap();
}
void Layout::setClean() {
this->editHistory.setClean();
this->hasUnsavedDataChanges = false;
}
bool Layout::hasUnsavedChanges() const {
return !this->editHistory.isClean() || this->hasUnsavedDataChanges || !this->newFolderPath.isEmpty();
}
bool Layout::save(const QString &root) {
if (!this->newFolderPath.isEmpty()) {
// Layout directory doesn't exist yet, create it now.
const QString fullPath = QString("%1/%2").arg(root).arg(this->newFolderPath);
if (!QDir::root().mkpath(fullPath)) {
logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath));
return false;
}
this->newFolderPath = QString();
}
bool success = true;
if (!saveBorder(root)) success = false;
if (!saveBlockdata(root)) success = false;
if (!success)
return false;
this->editHistory.setClean();
this->hasUnsavedDataChanges = false;
return true;
}
bool Layout::saveBorder(const QString &root) {
QString path = QString("%1/%2").arg(root).arg(this->border_path);
return writeBlockdata(path, this->border);
}
bool Layout::saveBlockdata(const QString &root) {
QString path = QString("%1/%2").arg(root).arg(this->blockdata_path);
return writeBlockdata(path, this->blockdata);
}
bool Layout::writeBlockdata(const QString &path, const Blockdata &blockdata) const {
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
logError(QString("Could not open '%1' for writing: %2").arg(path).arg(file.errorString()));
return false;
}
QByteArray data = blockdata.serialize();
file.write(data);
return true;
}

View File

@@ -38,14 +38,14 @@ QList<QRgb> PaletteUtil::parse(QString filepath, bool *error) {
return QList<QRgb>();
}
void PaletteUtil::writeJASC(QString filepath, QVector<QRgb> palette, int offset, int nColors) {
bool PaletteUtil::writeJASC(const QString &filepath, const QVector<QRgb> &palette, int offset, int nColors) {
if (!nColors) {
logWarn(QString("Cannot save a palette with no colors."));
return;
logError(QString("Cannot save a palette with no colors."));
return false;
}
if (offset > palette.size() || offset + nColors > palette.size()) {
logWarn("Palette offset out of range for color table.");
return;
logError("Palette offset out of range for color table.");
return false;
}
QString text = "JASC-PAL\r\n0100\r\n";
@@ -59,11 +59,13 @@ void PaletteUtil::writeJASC(QString filepath, QVector<QRgb> palette, int offset,
}
QFile file(filepath);
if (file.open(QIODevice::WriteOnly)) {
file.write(text.toUtf8());
} else {
logWarn(QString("Could not write to file '%1': ").arg(filepath) + file.errorString());
if (!file.open(QIODevice::WriteOnly)) {
logError(QString("Could not write to file '%1': ").arg(filepath) + file.errorString());
return false;
}
file.write(text.toUtf8());
return true;
}
QList<QRgb> parsePal(QString filepath, bool *error) {

View File

@@ -402,13 +402,13 @@ QHash<int, QString> Tileset::getHeaderMemberMap(bool usingAsm)
return map;
}
void Tileset::loadMetatiles() {
bool Tileset::loadMetatiles() {
clearMetatiles();
QFile metatiles_file(this->metatiles_path);
if (!metatiles_file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading.").arg(this->metatiles_path));
return;
logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(metatiles_file.errorString()));
return false;
}
QByteArray data = metatiles_file.readAll();
@@ -425,13 +425,14 @@ void Tileset::loadMetatiles() {
}
m_metatiles.append(metatile);
}
return true;
}
void Tileset::saveMetatiles() {
bool Tileset::saveMetatiles() {
QFile metatiles_file(this->metatiles_path);
if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing.").arg(this->metatiles_path));
return;
logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(metatiles_file.errorString()));
return false;
}
QByteArray data;
@@ -444,13 +445,14 @@ void Tileset::saveMetatiles() {
}
}
metatiles_file.write(data);
return true;
}
void Tileset::loadMetatileAttributes() {
bool Tileset::loadMetatileAttributes() {
QFile attrs_file(this->metatile_attrs_path);
if (!attrs_file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading.").arg(this->metatile_attrs_path));
return;
logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString()));
return false;
}
QByteArray data = attrs_file.readAll();
@@ -467,13 +469,14 @@ void Tileset::loadMetatileAttributes() {
attributes |= static_cast<unsigned char>(data.at(i * attrSize + j)) << (8 * j);
m_metatiles.at(i)->setAttributes(attributes);
}
return true;
}
void Tileset::saveMetatileAttributes() {
bool Tileset::saveMetatileAttributes() {
QFile attrs_file(this->metatile_attrs_path);
if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing.").arg(this->metatile_attrs_path));
return;
logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString()));
return false;
}
QByteArray data;
@@ -483,9 +486,10 @@ void Tileset::saveMetatileAttributes() {
data.append(static_cast<char>(attributes >> (8 * i)));
}
attrs_file.write(data);
return true;
}
void Tileset::loadTilesImage(QImage *importedImage) {
bool Tileset::loadTilesImage(QImage *importedImage) {
QImage image;
if (importedImage) {
image = *importedImage;
@@ -520,23 +524,25 @@ void Tileset::loadTilesImage(QImage *importedImage) {
}
this->tilesImage = image;
this->tiles = tiles;
return true;
}
void Tileset::saveTilesImage() {
bool Tileset::saveTilesImage() {
// Only write the tiles image if it was changed.
// Porymap will only ever change an existing tiles image by importing a new one.
if (!m_hasUnsavedTilesImage)
return;
return true;
if (!this->tilesImage.save(this->tilesImagePath, "PNG")) {
logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath));
return;
return false;
}
m_hasUnsavedTilesImage = false;
return true;
}
void Tileset::loadPalettes() {
bool Tileset::loadPalettes() {
this->palettes.clear();
this->palettePreviews.clear();
@@ -559,26 +565,34 @@ void Tileset::loadPalettes() {
this->palettes.append(palette);
this->palettePreviews.append(palette);
}
return true;
}
void Tileset::savePalettes() {
bool Tileset::savePalettes() {
bool success = true;
int numPalettes = qMin(this->palettePaths.length(), this->palettes.length());
for (int i = 0; i < numPalettes; i++) {
PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16);
if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16))
success = false;
}
return success;
}
void Tileset::load() {
loadMetatiles();
loadMetatileAttributes();
loadTilesImage();
loadPalettes();
bool Tileset::load() {
bool success = true;
if (!loadMetatiles()) success = false;
if (!loadMetatileAttributes()) success = false;
if (!loadTilesImage()) success = false;
if (!loadPalettes()) success = false;
return success;
}
// Because metatile labels are global (and handled by the project) we don't save them here.
void Tileset::save() {
saveMetatiles();
saveMetatileAttributes();
saveTilesImage();
savePalettes();
bool Tileset::save() {
bool success = true;
if (!saveMetatiles()) success = false;
if (!saveMetatileAttributes()) success = false;
if (!saveTilesImage()) success = false;
if (!savePalettes()) success = false;
return success;
}

View File

@@ -68,30 +68,33 @@ Editor::~Editor()
closeProject();
}
void Editor::saveCurrent() {
save(true);
bool Editor::saveCurrent() {
return save(true);
}
void Editor::saveAll() {
save(false);
bool Editor::saveAll() {
return save(false);
}
void Editor::save(bool currentOnly) {
bool Editor::save(bool currentOnly) {
if (!this->project)
return;
return true;
saveEncounterTabData();
bool success = true;
if (currentOnly) {
if (this->map) {
this->project->saveMap(this->map);
success = this->project->saveMap(this->map);
} else if (this->layout) {
this->project->saveLayout(this->layout);
success = this->project->saveLayout(this->layout);
}
this->project->saveGlobalData();
if (!this->project->saveGlobalData())
success = false;
} else {
this->project->saveAll();
success = this->project->saveAll();
}
return success;
}
void Editor::setProject(Project * project) {

View File

@@ -1634,16 +1634,15 @@ void MainWindow::on_action_Save_triggered() {
save(true);
}
void MainWindow::save(bool currentOnly) {
if (currentOnly) {
this->editor->saveCurrent();
} else {
this->editor->saveAll();
bool MainWindow::save(bool currentOnly) {
bool success = currentOnly ? this->editor->saveCurrent() : this->editor->saveAll();
if (!success) {
RecentErrorMessage::show(QStringLiteral("Failed to save some project changes."), this);
}
updateWindowTitle();
updateMapList();
if (!porymapConfig.shownInGameReloadMessage) {
if (success && !porymapConfig.shownInGameReloadMessage) {
// Show a one-time warning that the user may need to reload their map to see their new changes.
InfoMessage::show(QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, "
"the changes may not appear until you leave the map and return."),
@@ -1652,6 +1651,7 @@ void MainWindow::save(bool currentOnly) {
}
saveGlobalConfigs();
return success;
}
void MainWindow::duplicate() {
@@ -3048,7 +3048,8 @@ bool MainWindow::closeProject() {
auto reply = msgBox.exec();
if (reply == QMessageBox::Yes) {
save();
if (!save())
return false;
} else if (reply == QMessageBox::No) {
logWarn("Closing project with unsaved changes.");
} else if (reply == QMessageBox::Cancel) {

View File

@@ -162,8 +162,10 @@ void Project::clearTilesetCache() {
Map* Project::loadMap(const QString &mapName) {
Map* map = this->maps.value(mapName);
if (!map)
if (!map) {
logError(QString("Unknown map name '%1'.").arg(mapName));
return nullptr;
}
if (isMapLoaded(map))
return map;
@@ -445,8 +447,13 @@ bool Project::loadLayout(Layout *layout) {
Layout *Project::loadLayout(QString layoutId) {
Layout *layout = this->mapLayouts.value(layoutId);
if (!layout || !loadLayout(layout)) {
logError(QString("Failed to load layout '%1'").arg(layoutId));
if (!layout) {
logError(QString("Unknown layout ID '%1'.").arg(layoutId));
return nullptr;
}
if (!loadLayout(layout)) {
// Error should already be logged.
return nullptr;
}
return layout;
@@ -587,12 +594,12 @@ bool Project::readMapLayouts() {
return true;
}
void Project::saveMapLayouts() {
bool Project::saveMapLayouts() {
QString layoutsFilepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_layouts);
QFile layoutsFile(layoutsFilepath);
if (!layoutsFile.open(QIODevice::WriteOnly)) {
logError(QString("Error: Could not open %1 for writing").arg(layoutsFilepath));
return;
logError(QString("Could not open '%1' for writing: %2").arg(layoutsFilepath).arg(layoutsFile.errorString()));
return false;
}
OrderedJson::object layoutsObj;
@@ -626,6 +633,7 @@ void Project::saveMapLayouts() {
OrderedJsonDoc jsonDoc(&layoutJson);
jsonDoc.dump(&layoutsFile);
layoutsFile.close();
return true;
}
void Project::ignoreWatchedFileTemporarily(QString filepath) {
@@ -651,12 +659,12 @@ void Project::recordFileChange(const QString &filepath) {
emit fileChanged(filepath);
}
void Project::saveMapGroups() {
bool Project::saveMapGroups() {
QString mapGroupsFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups));
QFile mapGroupsFile(mapGroupsFilepath);
if (!mapGroupsFile.open(QIODevice::WriteOnly)) {
logError(QString("Error: Could not open %1 for writing").arg(mapGroupsFilepath));
return;
logError(QString("Could not open '%1' for writing: %2").arg(mapGroupsFilepath).arg(mapGroupsFile.errorString()));
return false;
}
OrderedJson::object mapGroupsObj;
@@ -686,14 +694,15 @@ void Project::saveMapGroups() {
OrderedJsonDoc jsonDoc(&mapGroupJson);
jsonDoc.dump(&mapGroupsFile);
mapGroupsFile.close();
return true;
}
void Project::saveRegionMapSections() {
bool Project::saveRegionMapSections() {
const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries));
QFile file(filepath);
if (!file.open(QIODevice::WriteOnly)) {
logError(QString("Could not open '%1' for writing").arg(filepath));
return;
logError(QString("Could not open '%1' for writing: %2").arg(filepath).arg(file.errorString()));
return false;
}
OrderedJson::array mapSectionArray;
@@ -727,16 +736,17 @@ void Project::saveRegionMapSections() {
OrderedJsonDoc jsonDoc(&json);
jsonDoc.dump(&file);
file.close();
return true;
}
void Project::saveWildMonData() {
if (!this->wildEncountersLoaded) return;
bool Project::saveWildMonData() {
if (!this->wildEncountersLoaded) return true;
QString wildEncountersJsonFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_wild_encounters));
QFile wildEncountersFile(wildEncountersJsonFilepath);
if (!wildEncountersFile.open(QIODevice::WriteOnly)) {
logError(QString("Error: Could not open %1 for writing").arg(wildEncountersJsonFilepath));
return;
logError(QString("Could not open '%1' for writing: %2").arg(wildEncountersJsonFilepath).arg(wildEncountersFile.errorString()));
return false;
}
OrderedJson::object wildEncountersObject;
@@ -822,6 +832,7 @@ void Project::saveWildMonData() {
OrderedJsonDoc jsonDoc(&encounterJson);
jsonDoc.dump(&wildEncountersFile);
wildEncountersFile.close();
return true;
}
// For a map with a constant of 'MAP_FOO', returns a unique 'HEAL_LOCATION_FOO'.
@@ -838,12 +849,12 @@ QString Project::getNewHealLocationName(const Map* map) const {
return toUniqueIdentifier(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + idName);
}
void Project::saveHealLocations() {
bool Project::saveHealLocations() {
const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_heal_locations));
QFile file(filepath);
if (!file.open(QIODevice::WriteOnly)) {
logError(QString("Could not open '%1' for writing").arg(filepath));
return;
logError(QString("Could not open '%1' for writing: %2").arg(filepath).arg(file.errorString()));
return false;
}
// Build the JSON data for output.
@@ -880,17 +891,21 @@ void Project::saveHealLocations() {
OrderedJsonDoc jsonDoc(&json);
jsonDoc.dump(&file);
file.close();
return true;
}
void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
saveTilesetMetatileLabels(primaryTileset, secondaryTileset);
if (primaryTileset)
primaryTileset->save();
if (secondaryTileset)
secondaryTileset->save();
bool Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
bool success = saveTilesetMetatileLabels(primaryTileset, secondaryTileset);
if (primaryTileset && !primaryTileset->save())
success = false;
if (secondaryTileset && !secondaryTileset->save())
success = false;
return success;
}
void Project::updateTilesetMetatileLabels(Tileset *tileset) {
if (!tileset) return;
// Erase old labels, then repopulate with new labels
const QString prefix = tileset->getMetatileLabelPrefix();
this->metatileLabelsMap[tileset->name].clear();
@@ -925,11 +940,11 @@ QString Project::buildMetatileLabelsText(const QMap<QString, uint16_t> defines)
return output;
}
void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *secondaryTileset) {
bool Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *secondaryTileset) {
// Skip writing the file if there are no labels in both the new and old sets
if (metatileLabelsMap[primaryTileset->name].size() == 0 && primaryTileset->metatileLabels.size() == 0
&& metatileLabelsMap[secondaryTileset->name].size() == 0 && secondaryTileset->metatileLabels.size() == 0)
return;
if ((!primaryTileset || (metatileLabelsMap[primaryTileset->name].size() == 0 && primaryTileset->metatileLabels.size() == 0))
&& (!secondaryTileset || (metatileLabelsMap[secondaryTileset->name].size() == 0 && secondaryTileset->metatileLabels.size() == 0)))
return true;
updateTilesetMetatileLabels(primaryTileset);
updateTilesetMetatileLabels(secondaryTileset);
@@ -956,42 +971,23 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels);
ignoreWatchedFileTemporarily(root + "/" + filename);
saveTextFile(root + "/" + filename, outputText);
return saveTextFile(root + "/" + filename, outputText);
}
bool Project::loadLayoutTilesets(Layout *layout) {
layout->tileset_primary = getTileset(layout->tileset_primary_label);
if (!layout->tileset_primary) {
QString defaultTileset = this->getDefaultPrimaryTilesetLabel();
layout->tileset_primary_label = defaultTileset;
layout->tileset_primary = getTileset(layout->tileset_primary_label);
if (!layout->tileset_primary) {
logError(QString("%1 has invalid primary tileset '%2'.").arg(layout->name).arg(layout->tileset_primary_label));
return false;
}
logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset));
}
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
if (!layout->tileset_secondary) {
QString defaultTileset = this->getDefaultSecondaryTilesetLabel();
layout->tileset_secondary_label = defaultTileset;
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
if (!layout->tileset_secondary) {
logError(QString("%1 has invalid secondary tileset '%2'.").arg(layout->name).arg(layout->tileset_secondary_label));
return false;
}
logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset));
}
return true;
return layout->tileset_primary && layout->tileset_secondary;
}
Tileset* Project::loadTileset(QString label, Tileset *tileset) {
auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets);
if (this->usingAsmTilesets) {
// Read asm tileset header. Backwards compatibility
const QStringList values = parser.getLabelValues(parser.parseAsm(projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm)), label);
const QString path = projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm);
const QStringList values = parser.getLabelValues(parser.parseAsm(path), label);
if (values.isEmpty()) {
logError(QString("Failed to find header data in '%1' for tileset '%2'.").arg(path).arg(label));
return nullptr;
}
if (tileset == nullptr) {
@@ -1005,8 +1001,10 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) {
tileset->metatile_attrs_label = values.value(memberMap.key("metatileAttributes"));
} else {
// Read C tileset header
auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap);
const QString path = projectConfig.getFilePath(ProjectFilePath::tilesets_headers);
auto structs = parser.readCStructs(path, label, memberMap);
if (!structs.contains(label)) {
logError(QString("Failed to find header data in '%1' for tileset '%2'.").arg(path).arg(label));
return nullptr;
}
if (tileset == nullptr) {
@@ -1021,7 +1019,11 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) {
tileset->metatile_attrs_label = tilesetAttributes.value("metatileAttributes");
}
loadTilesetAssets(tileset);
if (!loadTilesetAssets(tileset)) {
// Error should already be logged.
delete tileset;
return nullptr;
}
tilesetCache.insert(label, tileset);
return tileset;
@@ -1110,38 +1112,22 @@ void Project::setNewLayoutBorder(Layout *layout) {
layout->lastCommitBlocks.borderDimensions = QSize(width, height);
}
void Project::saveLayoutBorder(Layout *layout) {
QString path = QString("%1/%2").arg(root).arg(layout->border_path);
writeBlockdata(path, layout->border);
}
void Project::saveLayoutBlockdata(Layout *layout) {
QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path);
writeBlockdata(path, layout->blockdata);
}
void Project::writeBlockdata(QString path, const Blockdata &blockdata) {
QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
QByteArray data = blockdata.serialize();
file.write(data);
} else {
logError(QString("Failed to open blockdata file for writing: '%1'").arg(path));
}
}
void Project::saveAll() {
bool Project::saveAll() {
bool success = true;
for (auto map : this->maps) {
saveMap(map, true); // Avoid double-saving the layouts
if (!saveMap(map, true)) // Avoid double-saving the layouts
success = false;
}
for (auto layout : this->mapLayouts) {
saveLayout(layout);
if (!saveLayout(layout))
success = false;
}
saveGlobalData();
if (!saveGlobalData()) success = false;
return success;
}
void Project::saveMap(Map *map, bool skipLayout) {
if (!map || !isMapLoaded(map)) return;
bool Project::saveMap(Map *map, bool skipLayout) {
if (!map || !isMapLoaded(map)) return true;
// Create/Modify a few collateral files for brand new maps.
const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name();
@@ -1149,7 +1135,7 @@ void Project::saveMap(Map *map, bool skipLayout) {
if (!map->isPersistedToFile()) {
if (!QDir::root().mkpath(fullPath)) {
logError(QString("Failed to create directory for new map: '%1'").arg(fullPath));
return;
return false;
}
// Create file data/maps/<map_name>/scripts.inc
@@ -1173,8 +1159,8 @@ void Project::saveMap(Map *map, bool skipLayout) {
QString mapFilepath = fullPath + "/map.json";
QFile mapFile(mapFilepath);
if (!mapFile.open(QIODevice::WriteOnly)) {
logError(QString("Error: Could not open %1 for writing").arg(mapFilepath));
return;
logError(QString("Could not open '%1' for writing: %2").arg(mapFilepath).arg(mapFile.errorString()));
return false;
}
OrderedJson::object mapObj;
@@ -1270,72 +1256,61 @@ void Project::saveMap(Map *map, bool skipLayout) {
jsonDoc.dump(&mapFile);
mapFile.close();
if (!skipLayout) saveLayout(map->layout());
// Try to record the MAPSEC name in case this is a new name.
addNewMapsec(map->header()->location());
map->setClean();
if (!skipLayout && !saveLayout(map->layout()))
return false;
return true;
}
void Project::saveLayout(Layout *layout) {
bool Project::saveLayout(Layout *layout) {
if (!layout || !isLayoutLoaded(layout))
return;
return true;
if (!layout->newFolderPath.isEmpty()) {
// Layout directory doesn't exist yet, create it now.
const QString fullPath = QString("%1/%2").arg(this->root).arg(layout->newFolderPath);
if (!QDir::root().mkpath(fullPath)) {
logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath));
return;
}
layout->newFolderPath = QString();
}
saveLayoutBorder(layout);
saveLayoutBlockdata(layout);
if (!layout->save(this->root))
return false;
// Update global data structures with current map data.
updateLayout(layout);
layout->setClean();
}
void Project::updateLayout(Layout *layout) {
if (!this->layoutIdsMaster.contains(layout->id)) {
this->layoutIdsMaster.append(layout->id);
}
if (this->mapLayoutsMaster.contains(layout->id)) {
this->mapLayoutsMaster[layout->id]->copyFrom(layout);
}
else {
} else {
this->mapLayoutsMaster.insert(layout->id, layout->copy());
}
return true;
}
void Project::saveGlobalData() {
saveMapLayouts();
saveMapGroups();
saveRegionMapSections();
saveHealLocations();
saveWildMonData();
saveConfig();
bool Project::saveGlobalData() {
bool success = true;
if (!saveMapLayouts()) success = false;
if (!saveMapGroups()) success = false;
if (!saveRegionMapSections()) success = false;
if (!saveHealLocations()) success = false;
if (!saveWildMonData()) success = false;
if (!saveConfig()) success = false;
if (!success)
return false;
this->hasUnsavedDataChanges = false;
return true;
}
void Project::saveConfig() {
projectConfig.save();
userConfig.save();
bool Project::saveConfig() {
bool success = true;
if (!projectConfig.save()) success = false;
if (!userConfig.save()) success = false;
return success;
}
void Project::loadTilesetAssets(Tileset* tileset) {
if (tileset->name.isNull()) {
return;
}
bool Project::loadTilesetAssets(Tileset* tileset) {
readTilesetPaths(tileset);
loadTilesetMetatileLabels(tileset);
tileset->load();
return tileset->load();
}
void Project::readTilesetPaths(Tileset* tileset) {
@@ -1529,6 +1504,8 @@ bool Project::readTilesetMetatileLabels() {
}
void Project::loadTilesetMetatileLabels(Tileset* tileset) {
if (!tileset || tileset->name.isEmpty()) return;
QString metatileLabelPrefix = tileset->getMetatileLabelPrefix();
// Reverse map for faster lookup by metatile id
@@ -1570,29 +1547,24 @@ Tileset* Project::getTileset(QString label, bool forceLoad) {
}
}
void Project::saveTextFile(QString path, QString text) {
bool Project::saveTextFile(const QString &path, const QString &text) {
QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
file.write(text.toUtf8());
} else {
if (!file.open(QIODevice::WriteOnly)) {
logError(QString("Could not open '%1' for writing: ").arg(path) + file.errorString());
return false;
}
file.write(text.toUtf8());
return true;
}
void Project::appendTextFile(QString path, QString text) {
bool Project::appendTextFile(const QString &path, const QString &text) {
QFile file(path);
if (file.open(QIODevice::Append)) {
file.write(text.toUtf8());
} else {
if (!file.open(QIODevice::Append)) {
logError(QString("Could not open '%1' for appending: ").arg(path) + file.errorString());
return false;
}
}
void Project::deleteFile(QString path) {
QFile file(path);
if (file.exists() && !file.remove()) {
logError(QString("Could not delete file '%1': ").arg(path) + file.errorString());
}
file.write(text.toUtf8());
return true;
}
bool Project::readWildMonData() {

View File

@@ -33,6 +33,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save);
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
@@ -94,7 +96,7 @@ void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondar
QMessageBox::No | QMessageBox::Yes,
QMessageBox::Yes);
if (result == QMessageBox::Yes)
this->on_actionSave_Tileset_triggered();
this->save();
}
this->setTilesets(primaryTilesetLabel, secondaryTilesetLabel);
this->refresh();
@@ -688,19 +690,23 @@ void TilesetEditor::commitLayerType() {
this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear
}
void TilesetEditor::on_actionSave_Tileset_triggered()
{
bool TilesetEditor::save() {
// Need this temporary flag to stop selection resetting after saving.
// This is a workaround; redrawing the map's metatile selector shouldn't emit the same signal as when it's selected.
this->lockSelection = true;
this->project->saveTilesets(this->primaryTileset, this->secondaryTileset);
bool success = this->project->saveTilesets(this->primaryTileset, this->secondaryTileset);
emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name);
if (this->paletteEditor) {
this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
}
this->ui->statusbar->showMessage(QString("Saved primary and secondary Tilesets!"), 5000);
this->hasUnsavedChanges = false;
this->ui->statusbar->showMessage(success ? QStringLiteral("Saved primary and secondary Tilesets!")
: QStringLiteral("Failed to save tilesets! See log for details."), 5000);
if (success) {
this->hasUnsavedChanges = false;
}
this->lockSelection = false;
return success;
}
void TilesetEditor::on_actionImport_Primary_Tiles_triggered()
@@ -812,8 +818,11 @@ void TilesetEditor::closeEvent(QCloseEvent *event)
QMessageBox::Yes);
if (result == QMessageBox::Yes) {
this->on_actionSave_Tileset_triggered();
event->accept();
if (this->save()) {
event->accept();
} else {
event->ignore();
}
} else if (result == QMessageBox::No) {
this->reset();
event->accept();
@@ -1143,12 +1152,6 @@ void TilesetEditor::countMetatileUsage() {
this->metatileSelector->usedMetatiles.fill(0);
for (auto layout : this->project->mapLayouts) {
// It's possible for a layout's tileset labels to change if they are invalid,
// so we need to load all the tilesets even if they aren't the tileset we're looking for.
// Otherwise the metatile usage counts may change because the layouts with invalid tilesets
// were updated to use a tileset we were looking for.
this->project->loadLayoutTilesets(layout);
bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name);
bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name);
@@ -1187,10 +1190,10 @@ void TilesetEditor::countTileUsage() {
QSet<Tileset*> secondaryTilesets;
for (auto &layout : this->project->mapLayouts) {
this->project->loadLayoutTilesets(layout);
if (layout->tileset_primary_label == this->primaryTileset->name
|| layout->tileset_secondary_label == this->secondaryTileset->name) {
// need to check metatiles
this->project->loadLayoutTilesets(layout);
if (layout->tileset_primary && layout->tileset_secondary) {
primaryTilesets.insert(layout->tileset_primary);
secondaryTilesets.insert(layout->tileset_secondary);