diff --git a/include/config.h b/include/config.h index 6746a0f4..0f1e8706 100644 --- a/include/config.h +++ b/include/config.h @@ -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; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 40a3a035..3822b5cf 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -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); diff --git a/include/core/paletteutil.h b/include/core/paletteutil.h index ce221026..34e9ae3f 100644 --- a/include/core/paletteutil.h +++ b/include/core/paletteutil.h @@ -7,7 +7,7 @@ namespace PaletteUtil { QList parse(QString filepath, bool *error); - void writeJASC(QString filepath, QVector colors, int offset, int nColors); + bool writeJASC(const QString &filepath, const QVector &colors, int offset, int nColors); } #endif // PALETTEUTIL_H diff --git a/include/core/tileset.h b/include/core/tileset.h index 32d18858..a05afdc3 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -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); diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..4d579093 100644 --- a/include/editor.h +++ b/include/editor.h @@ -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(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 410bd6e3..de790d63 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -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); diff --git a/include/project.h b/include/project.h index c5aed0a3..d8ef4c36 100644 --- a/include/project.h +++ b/include/project.h @@ -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 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; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index fdd4751c..b6a60a61 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -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 metatileReloadQueue; + bool save(); + signals: void tilesetsSaved(QString, QString); }; diff --git a/src/config.cpp b/src/config.cpp index 5c5feb54..e6ac885b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -233,7 +233,7 @@ void KeyValueConfigBase::load() { file.close(); } -void KeyValueConfigBase::save() { +bool KeyValueConfigBase::save() { QString text = ""; QMap map = this->getKeyValueMap(); for (QMap::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) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 45b35f91..e3d47422 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -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; +} diff --git a/src/core/paletteutil.cpp b/src/core/paletteutil.cpp index 929336b2..76a3d0c4 100644 --- a/src/core/paletteutil.cpp +++ b/src/core/paletteutil.cpp @@ -38,14 +38,14 @@ QList PaletteUtil::parse(QString filepath, bool *error) { return QList(); } -void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, int nColors) { +bool PaletteUtil::writeJASC(const QString &filepath, const QVector &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 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 parsePal(QString filepath, bool *error) { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index f6ce6a2e..0ad43d1a 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -402,13 +402,13 @@ QHash 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(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(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; } diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..d1347cf1 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -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) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..f0ba072b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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) { diff --git a/src/project.cpp b/src/project.cpp index c9d13840..edae7b9c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -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 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//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() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 3d610fb9..f9a09ce8 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -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 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);