Make map load failure non-destructive

This commit is contained in:
GriffinR
2025-05-07 13:45:16 -04:00
parent a15daf6c14
commit 9a3b45117b
16 changed files with 283 additions and 255 deletions

View File

@@ -48,6 +48,9 @@ public:
void setLayout(Layout *layout);
Layout* layout() const { return m_layout; }
void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; }
QString layoutId() const { return layout() ? layout()->id : m_layoutId; }
int getWidth() const;
int getHeight() const;
int getBorderWidth() const;
@@ -107,6 +110,7 @@ public:
private:
QString m_name;
QString m_constantName;
QString m_layoutId; // Only needed if layout fails to load.
QString m_sharedEventsMap = "";
QString m_sharedScriptsMap = "";

View File

@@ -342,11 +342,11 @@ private:
bool tilesetNeedsRedraw = false;
bool setLayout(QString layoutId);
bool setMap(QString);
bool setLayout(const QString &layoutId);
bool setMap(const QString &mapName);
void unsetMap();
bool userSetLayout(QString layoutId);
bool userSetMap(QString);
bool userSetLayout(const QString &layoutId);
bool userSetMap(const QString &mapName);
void redrawMapScene();
void refreshMapScene();
void refreshMetatileViews();
@@ -380,7 +380,6 @@ private:
bool closeProject();
void showRecentError(const QString &baseMessage);
void showProjectOpenFailure();
void showMapsExcludedAlert(const QStringList &excludedMapNames);
bool setInitialMap();
void saveGlobalConfigs();

View File

@@ -31,17 +31,11 @@ public:
public:
QString root;
QStringList mapNames;
QStringList groupNames;
QMap<QString, QStringList> groupNameToMapNames;
QStringList healLocationSaveOrder;
QMap<QString, QList<HealLocationEvent*>> healLocations;
QMap<QString, QString> mapConstantsToMapNames;
QString layoutsLabel;
QStringList layoutIds;
QStringList layoutIdsMaster;
QMap<QString, Layout*> mapLayouts;
QMap<QString, Layout*> mapLayoutsMaster;
QMap<QString, int> gfxDefines;
QString defaultSong;
QStringList songNames;
@@ -78,6 +72,27 @@ public:
void setRoot(const QString&);
const QStringList& mapNames() const { return this->alphabeticalMapNames; }
bool isKnownMap(const QString &mapName) const { return this->maps.contains(mapName); }
bool isErroredMap(const QString &mapName) const { return this->erroredMaps.contains(mapName); }
bool isLoadedMap(const QString &mapName) const { return this->loadedMapNames.contains(mapName); }
bool isUnsavedMap(const QString &mapName) const;
// Note: This does not guarantee the map is loaded.
Map* getMap(const QString &mapName) { return this->maps.value(mapName); }
Map* loadMap(const QString &mapName);
const QStringList& layoutIds() const { return this->alphabeticalLayoutIds; }
bool isKnownLayout(const QString &layoutId) const { return this->mapLayouts.contains(layoutId); }
bool isLoadedLayout(const QString &layoutId) const { return this->loadedLayoutIds.contains(layoutId); }
bool isUnsavedLayout(const QString &layoutId) const;
QString getLayoutName(const QString &layoutId) const;
QStringList getLayoutNames() const;
// Note: This does not guarantee the layout is loaded.
Layout* getLayout(const QString &layoutId) const { return this->mapLayouts.value(layoutId); }
Layout* loadLayout(const QString &layoutId);
void clearMaps();
void clearTilesetCache();
void clearMapLayouts();
@@ -88,16 +103,6 @@ public:
int getSupportedMajorVersion(QString *errorOut = nullptr);
bool load();
Map* loadMap(const QString &mapName);
// Note: This does not guarantee the map is loaded.
Map* getMap(const QString &mapName) { return this->maps.value(mapName); }
bool isMapLoaded(const Map *map) const { return map && isMapLoaded(map->name()); }
bool isMapLoaded(const QString &mapName) const { return this->loadedMapNames.contains(mapName); }
bool isLayoutLoaded(const Layout *layout) const { return layout && isLayoutLoaded(layout->id); }
bool isLayoutLoaded(const QString &layoutId) const { return this->loadedLayoutIds.contains(layoutId); }
QMap<QString, Tileset*> tilesetCache;
Tileset* loadTileset(QString, Tileset *tileset = nullptr);
Tileset* getTileset(QString, bool forceLoad = false);
@@ -158,13 +163,9 @@ public:
bool hasUnsavedChanges();
bool hasUnsavedDataChanges = false;
bool readMapJson(const QString &mapName, QJsonDocument * out);
bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None);
bool loadMapData(Map*);
bool readMapLayouts();
Layout *loadLayout(QString layoutId);
bool loadLayout(Layout *);
bool loadMapLayout(Map*);
bool loadLayoutTilesets(Layout *);
bool loadTilesetAssets(Tileset*);
void loadTilesetMetatileLabels(Tileset*);
@@ -265,6 +266,14 @@ private:
QMap<QString, QString> facingDirections;
QHash<QString, QString> speciesToIconPath;
QHash<QString, Map*> maps;
QHash<QString, QString> erroredMaps;
QStringList alphabeticalMapNames;
QString layoutsLabel;
QStringList alphabeticalLayoutIds;
QStringList orderedLayoutIds;
QStringList orderedLayoutIdsMaster;
QHash<QString, Layout*> mapLayouts;
QHash<QString, Layout*> mapLayoutsMaster;
// Fields for preserving top-level JSON data that Porymap isn't expecting.
QJsonObject customLayoutsData;
@@ -307,6 +316,8 @@ private:
};
QHash<QString, LocationData> locationData;
QJsonDocument readMapJson(const QString &mapName, QString *error = nullptr);
void setNewLayoutBlockdata(Layout *layout);
void setNewLayoutBorder(Layout *layout);
@@ -349,7 +360,6 @@ signals:
void mapSectionAdded(const QString &idName);
void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName);
void mapSectionIdNamesChanged(const QStringList &idNames);
void mapsExcluded(const QStringList &excludedMapNames);
void eventScriptLabelsRead();
};

View File

@@ -84,6 +84,7 @@ protected:
QIcon mapGrayIcon;
QIcon mapIcon;
QIcon mapEditedIcon;
QIcon mapErroredIcon;
QIcon mapOpenedIcon;
QIcon mapFolderIcon;
QIcon emptyMapFolderIcon;

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

View File

@@ -27,6 +27,7 @@
<file>icons/link.ico</file>
<file>icons/magnifier.ico</file>
<file>icons/map_edited.ico</file>
<file>icons/map_errored.ico</file>
<file>icons/map_opened.ico</file>
<file>icons/map.ico</file>
<file>icons/map_grayed.ico</file>

View File

@@ -56,6 +56,9 @@ void Map::setLayout(Layout *layout) {
if (layout == m_layout)
return;
m_layout = layout;
if (layout) {
m_layoutId = layout->id;
}
emit layoutChanged();
}
@@ -65,19 +68,19 @@ QString Map::mapConstantFromName(const QString &name) {
}
int Map::getWidth() const {
return m_layout->getWidth();
return m_layout ? m_layout->getWidth() : 0;
}
int Map::getHeight() const {
return m_layout->getHeight();
return m_layout ? m_layout->getHeight() : 0;
}
int Map::getBorderWidth() const {
return m_layout->getBorderWidth();
return m_layout ? m_layout->getBorderWidth() : 0;
}
int Map::getBorderHeight() const {
return m_layout->getBorderHeight();
return m_layout ? m_layout->getBorderHeight() : 0;
}
// Get the portion of the map that can be rendered when rendered as a map connection.
@@ -111,6 +114,9 @@ QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) cons
}
QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) {
if (!m_layout)
return QPixmap();
QRect bounds = getConnectionRect(direction, fromLayout);
if (!bounds.isValid())
return QPixmap();
@@ -365,7 +371,7 @@ void Map::setClean() {
}
bool Map::hasUnsavedChanges() const {
return !m_editHistory->isClean() || m_layout->hasUnsavedChanges() || m_hasUnsavedDataChanges || !m_isPersistedToFile;
return !m_editHistory->isClean() || (m_layout && m_layout->hasUnsavedChanges()) || m_hasUnsavedDataChanges || !m_isPersistedToFile;
}
void Map::pruneEditHistory() {

View File

@@ -779,7 +779,7 @@ void Editor::displayConnection(MapConnection *connection) {
connect(pixmapItem, &ConnectionPixmapItem::positionChanged, this, &Editor::maskNonVisibleConnectionTiles);
// Create item for the list panel
auto listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, pixmapItem->connection, project->mapNames);
auto listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, pixmapItem->connection, project->mapNames());
ui->layout_ConnectionsList->insertWidget(ui->layout_ConnectionsList->count() - 1, listItem); // Insert above the vertical spacer
// Double clicking the pixmap or clicking the list item's map button opens the connected map
@@ -939,7 +939,7 @@ void Editor::removeDivingMapPixmap(MapConnection *connection) {
}
bool Editor::setDivingMapName(const QString &mapName, const QString &direction) {
if (!mapName.isEmpty() && !this->project->mapNames.contains(mapName))
if (!mapName.isEmpty() && !this->project->isKnownMap(mapName))
return false;
if (!MapConnection::isDiving(direction))
return false;
@@ -977,7 +977,7 @@ void Editor::onDivingMapEditingFinished(NoScrollComboBox *combo, const QString &
}
void Editor::updateDivingMapButton(QToolButton* button, const QString &mapName) {
if (this->project) button->setDisabled(!this->project->mapNames.contains(mapName));
if (this->project) button->setDisabled(!this->project->isKnownMap(mapName));
}
void Editor::updateDivingMapsVisibility() {
@@ -1227,7 +1227,7 @@ bool Editor::setMap(QString map_name) {
unsetMap();
this->map = loadedMap;
setLayout(map->layout()->id);
setLayout(map->layoutId());
editGroup.addStack(map->editHistory());
editGroup.setActiveStack(map->editHistory());

View File

@@ -678,7 +678,6 @@ bool MainWindow::openProject(QString dir, bool initial) {
connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated);
connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated);
connect(project, &Project::mapSectionDisplayNameChanged, this, &MainWindow::onMapSectionDisplayNameChanged);
connect(project, &Project::mapsExcluded, this, &MainWindow::showMapsExcludedAlert);
this->editor->setProject(project);
// Make sure project looks reasonable before attempting to load it
@@ -798,19 +797,6 @@ void MainWindow::showProjectOpenFailure() {
RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this);
}
// Alert the user that one or more maps have been excluded while loading the project.
void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) {
auto msgBox = new RecentErrorMessage("", this);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
if (excludedMapNames.length() == 1) {
msgBox->setText(QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first()));
} else {
msgBox->setText(QStringLiteral("Failed to load the maps listed below. Saving will exclude these maps from your project."));
msgBox->setDetailedText(excludedMapNames.join("\n")); // Overwrites error details text, user will need to check the log.
}
msgBox->open();
}
bool MainWindow::isProjectOpen() {
return editor && editor->project;
}
@@ -819,22 +805,22 @@ bool MainWindow::setInitialMap() {
porysplash->showMessage("Opening initial map");
const QString recent = userConfig.recentMapOrLayout;
if (editor->project->mapNames.contains(recent)) {
if (editor->project->isKnownMap(recent)) {
// User recently had a map open that still exists.
if (setMap(recent))
return true;
} else if (editor->project->layoutIds.contains(recent)) {
} else if (editor->project->isKnownLayout(recent)) {
// User recently had a layout open that still exists.
if (setLayout(recent))
return true;
}
// Failed to open recent map/layout, or no recent map/layout. Try opening maps then layouts sequentially.
for (const auto &name : editor->project->mapNames) {
for (const auto &name : editor->project->mapNames()) {
if (name != recent && setMap(name))
return true;
}
for (const auto &id : editor->project->layoutIds) {
for (const auto &id : editor->project->layoutIds()) {
if (id != recent && setLayout(id))
return true;
}
@@ -966,39 +952,39 @@ void MainWindow::unsetMap() {
// setMap, but with a visible error message in case of failure.
// Use when the user is specifically requesting a map to open.
bool MainWindow::userSetMap(QString map_name) {
if (editor->map && editor->map->name() == map_name)
bool MainWindow::userSetMap(const QString &mapName) {
if (editor->map && editor->map->name() == mapName)
return true; // Already set
if (map_name.isEmpty()) {
if (mapName.isEmpty()) {
WarningMessage::show(QStringLiteral("Cannot open map with empty name."), this);
return false;
}
if (map_name == editor->project->getDynamicMapName()) {
auto msgBox = new WarningMessage(QString("Cannot open map '%1'.").arg(map_name), this);
if (mapName == editor->project->getDynamicMapName()) {
auto msgBox = new WarningMessage(QString("Cannot open map '%1'.").arg(mapName), this);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically."));
msgBox->open();
return false;
}
if (!setMap(map_name)) {
RecentErrorMessage::show(QString("There was an error opening map '%1'.").arg(map_name), this);
if (!setMap(mapName)) {
RecentErrorMessage::show(QString("There was an error opening map '%1'.").arg(mapName), this);
return false;
}
return true;
}
bool MainWindow::setMap(QString map_name) {
if (!editor || !editor->project || map_name.isEmpty() || map_name == editor->project->getDynamicMapName()) {
logWarn(QString("Ignored setting map to '%1'").arg(map_name));
bool MainWindow::setMap(const QString &mapName) {
if (!editor || !editor->project || mapName.isEmpty() || mapName == editor->project->getDynamicMapName()) {
logWarn(QString("Ignored setting map to '%1'").arg(mapName));
return false;
}
logInfo(QString("Setting map to '%1'").arg(map_name));
if (!editor->setMap(map_name)) {
logWarn(QString("Failed to set map to '%1'").arg(map_name));
logInfo(QString("Setting map to '%1'").arg(mapName));
if (!editor->setMap(mapName)) {
logWarn(QString("Failed to set map to '%1'").arg(mapName));
return false;
}
@@ -1018,9 +1004,9 @@ bool MainWindow::setMap(QString map_name) {
connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection);
userConfig.recentMapOrLayout = map_name;
userConfig.recentMapOrLayout = mapName;
Scripting::cb_MapOpened(map_name);
Scripting::cb_MapOpened(mapName);
prefab.updatePrefabUi(editor->layout);
updateTilesetEditor();
@@ -1043,7 +1029,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) {
// setLayout, but with a visible error message in case of failure.
// Use when the user is specifically requesting a layout to open.
bool MainWindow::userSetLayout(QString layoutId) {
bool MainWindow::userSetLayout(const QString &layoutId) {
if (!setLayout(layoutId)) {
RecentErrorMessage::show(QString("There was an error opening layout '%1'.").arg(layoutId), this);
return false;
@@ -1055,10 +1041,10 @@ bool MainWindow::userSetLayout(QString layoutId) {
return true;
}
bool MainWindow::setLayout(QString layoutId) {
bool MainWindow::setLayout(const QString &layoutId) {
// Prefer logging the name of the layout as displayed in the map list.
const Layout* layout = this->editor->project ? this->editor->project->mapLayouts.value(layoutId) : nullptr;
logInfo(QString("Setting layout to '%1'").arg(layout ? layout->name : layoutId));
QString layoutName = this->editor->project ? this->editor->project->getLayoutName(layoutId) : QString();
logInfo(QString("Setting layout to '%1'").arg(layoutName.isEmpty() ? layoutId : layoutName));
if (!this->editor->setLayout(layoutId)) {
return false;
@@ -1197,7 +1183,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te
if (!this->editor || !this->editor->project || !this->editor->map)
return;
if (!this->editor->project->mapLayouts.contains(text)) {
if (!this->editor->project->isKnownLayout(text)) {
// User may be in the middle of typing the name of a layout, don't bother trying to load it.
return;
}
@@ -1208,7 +1194,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te
// New layout failed to load, restore previous layout
const QSignalBlocker b(ui->comboBox_LayoutSelector);
ui->comboBox_LayoutSelector->setTextItem(this->editor->map->layout()->id);
ui->comboBox_LayoutSelector->setTextItem(this->editor->map->layoutId());
return;
}
this->editor->map->setLayout(layout);
@@ -1222,7 +1208,7 @@ void MainWindow::onLayoutSelectorEditingFinished() {
// If the user left the layout selector in an invalid state, restore it so that it displays the current layout.
const QString text = ui->comboBox_LayoutSelector->currentText();
if (!this->editor->project->mapLayouts.contains(text)) {
if (!this->editor->project->isKnownLayout(text)) {
const QSignalBlocker b(ui->comboBox_LayoutSelector);
ui->comboBox_LayoutSelector->setTextItem(this->editor->layout->id);
}
@@ -1247,17 +1233,17 @@ bool MainWindow::setProjectUI() {
const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector);
ui->comboBox_LayoutSelector->clear();
ui->comboBox_LayoutSelector->addItems(project->layoutIds);
ui->comboBox_LayoutSelector->addItems(project->layoutIds());
const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap);
ui->comboBox_DiveMap->clear();
ui->comboBox_DiveMap->addItems(project->mapNames);
ui->comboBox_DiveMap->addItems(project->mapNames());
ui->comboBox_DiveMap->setClearButtonEnabled(true);
ui->comboBox_DiveMap->setFocusedScrollingEnabled(false);
const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap);
ui->comboBox_EmergeMap->clear();
ui->comboBox_EmergeMap->addItems(project->mapNames);
ui->comboBox_EmergeMap->addItems(project->mapNames());
ui->comboBox_EmergeMap->setClearButtonEnabled(true);
ui->comboBox_EmergeMap->setFocusedScrollingEnabled(false);
@@ -1484,11 +1470,11 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) {
// Add new map to the map lists
this->mapGroupModel->insertMapItem(newMap->name(), groupName);
this->mapLocationModel->insertMapItem(newMap->name(), newMap->header()->location());
this->layoutTreeModel->insertMapItem(newMap->name(), newMap->layout()->id);
this->layoutTreeModel->insertMapItem(newMap->name(), newMap->layoutId());
// Refresh any combo box that displays map names and persists between maps
// (other combo boxes like for warp destinations are repopulated when the map changes).
int mapIndex = this->editor->project->mapNames.indexOf(newMap->name());
int mapIndex = this->editor->project->mapNames().indexOf(newMap->name());
if (mapIndex >= 0) {
ui->comboBox_DiveMap->insertItem(mapIndex, newMap->name());
ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name());
@@ -1502,7 +1488,7 @@ void MainWindow::onNewLayoutCreated(Layout *layout) {
logInfo(QString("Created a new layout named %1.").arg(layout->name));
// Refresh layout combo box
int layoutIndex = this->editor->project->layoutIds.indexOf(layout->id);
int layoutIndex = this->editor->project->layoutIds().indexOf(layout->id);
if (layoutIndex >= 0) {
const QSignalBlocker b(ui->comboBox_LayoutSelector);
ui->comboBox_LayoutSelector->insertItem(layoutIndex, layout->id);
@@ -2707,7 +2693,7 @@ void MainWindow::on_pushButton_AddConnection_clicked() {
if (!this->editor || !this->editor->map || !this->editor->project)
return;
auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames);
auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames());
connect(dialog, &NewMapConnectionDialog::newConnectionedAdded, this->editor, &Editor::addNewConnection);
connect(dialog, &NewMapConnectionDialog::connectionReplaced, this->editor, &Editor::replaceConnection);
dialog->open();

View File

@@ -280,24 +280,61 @@ Map* Project::loadMap(const QString &mapName) {
return nullptr;
}
// Some maps are ignored while opening the project because they have invalid or incomplete data.
// We already logged a warning about this, but now that we're trying to load the map it's an error.
auto it = this->erroredMaps.constFind(mapName);
if (it != this->erroredMaps.constEnd()) {
logError(it.value());
return nullptr;
}
Map* map = this->maps.value(mapName);
if (!map) {
logError(QString("Unknown map name '%1'.").arg(mapName));
return nullptr;
}
if (isMapLoaded(map))
if (isLoadedMap(mapName))
return map;
if (!(loadMapData(map) && loadMapLayout(map)))
if (!loadMapData(map))
return nullptr;
// Load map layout
if (map->isPersistedToFile() && !map->hasUnsavedChanges()) {
if (!loadLayout(map->layoutId()))
return nullptr;
}
this->loadedMapNames.insert(mapName);
emit mapLoaded(map);
return map;
}
Layout *Project::loadLayout(const QString &layoutId) {
Layout *layout = this->mapLayouts.value(layoutId);
if (!layout) {
logError(QString("Unknown layout ID '%1'.").arg(layoutId));
return nullptr;
}
if (isLoadedLayout(layoutId))
return layout;
// Force these to run even if one fails
bool loadedTilesets = loadLayoutTilesets(layout);
bool loadedBlockdata = loadBlockdata(layout);
bool loadedBorder = loadLayoutBorder(layout);
if (!loadedTilesets || !loadedBlockdata || !loadedBorder) {
// Error should already be logged.
return nullptr;
}
this->loadedLayoutIds.insert(layoutId);
return layout;
}
QSet<QString> Project::getTopLevelMapFields() const {
QSet<QString> fields = {
"id",
@@ -329,15 +366,17 @@ QSet<QString> Project::getTopLevelMapFields() const {
return fields;
}
bool Project::readMapJson(const QString &mapName, QJsonDocument * out) {
QJsonDocument Project::readMapJson(const QString &mapName, QString *error) {
const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName);
watchFile(mapFilepath);
QString error;
if (!parser.tryParseJsonFile(out, mapFilepath, &error)) {
logError(QString("Failed to read map data from '%1': %2").arg(mapFilepath).arg(error));
return false;
QJsonDocument doc;
if (!parser.tryParseJsonFile(&doc, mapFilepath, error)) {
if (error) {
error->prepend(QString("Failed to read map data from '%1': ").arg(mapFilepath));
}
}
return true;
return doc;
}
bool Project::loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType) {
@@ -360,9 +399,12 @@ bool Project::loadMapData(Map* map) {
return true;
}
QJsonDocument mapDoc;
if (!readMapJson(map->name(), &mapDoc))
QString error;
QJsonDocument mapDoc = readMapJson(map->name(), &error);
if (!error.isEmpty()) {
logError(error);
return false;
}
QJsonObject mapObj = mapDoc.object();
@@ -452,24 +494,15 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t
// Generate a unique MAP constant.
map->setConstantName(toUniqueIdentifier(map->expectedConstantName()));
// Make sure we keep the order of the map names the same as in the map group order.
int mapNamePos;
if (this->groupNames.contains(settings.group)) {
mapNamePos = 0;
for (const auto &name : this->groupNames) {
mapNamePos += this->groupNameToMapNames[name].length();
if (name == settings.group)
break;
}
} else if (isValidNewIdentifier(settings.group)) {
if (!this->groupNames.contains(settings.group)) {
// Adding map to a map group that doesn't exist yet.
// Create the group, and we already know the map will be last in the list.
addNewMapGroup(settings.group);
mapNamePos = this->mapNames.length();
} else {
logError(QString("Cannot create new map with invalid map group name '%1'.").arg(settings.group));
delete map;
return nullptr;
if (isValidNewIdentifier(settings.group)) {
addNewMapGroup(settings.group);
} else {
logError(QString("Cannot create new map with invalid map group name '%1'.").arg(settings.group));
delete map;
return nullptr;
}
}
Layout *layout = this->mapLayouts.value(settings.layout.id);
@@ -483,16 +516,20 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t
}
} else {
// This layout already exists. Make sure it's loaded.
loadLayout(layout);
if (!loadLayout(settings.layout.id)) {
// Layout failed to load. For now we can just record the ID.
map->setLayoutId(settings.layout.id);
}
}
map->setLayout(layout);
// Try to record the MAPSEC name in case this is a new name.
addNewMapsec(map->header()->location());
this->mapNames.insert(mapNamePos, map->name());
this->groupNameToMapNames[settings.group].append(map->name());
this->mapConstantsToMapNames.insert(map->constantName(), map->name());
this->alphabeticalMapNames.append(map->name());
Util::numericalModeSort(this->alphabeticalMapNames);
map->setIsPersistedToFile(false);
this->maps.insert(map->name(), map);
@@ -503,7 +540,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t
}
Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout *toDuplicate) {
if (this->layoutIds.contains(settings.id))
if (this->mapLayouts.contains(settings.id))
return nullptr;
Layout *layout = toDuplicate ? new Layout(*toDuplicate) : new Layout();
@@ -540,60 +577,24 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout
}
this->mapLayouts.insert(layout->id, layout);
this->layoutIds.append(layout->id);
this->orderedLayoutIds.append(layout->id);
this->loadedLayoutIds.insert(layout->id);
this->alphabeticalLayoutIds.append(layout->id);
Util::numericalModeSort(this->alphabeticalLayoutIds);
emit layoutCreated(layout);
return layout;
}
bool Project::loadLayout(Layout *layout) {
if (!isLayoutLoaded(layout)) {
// Force these to run even if one fails
bool loadedTilesets = loadLayoutTilesets(layout);
bool loadedBlockdata = loadBlockdata(layout);
bool loadedBorder = loadLayoutBorder(layout);
if (loadedTilesets && loadedBlockdata && loadedBorder) {
this->loadedLayoutIds.insert(layout->id);
return true;
} else {
return false;
}
}
return true;
}
Layout *Project::loadLayout(QString layoutId) {
Layout *layout = this->mapLayouts.value(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;
}
bool Project::loadMapLayout(Map* map) {
if (!map->isPersistedToFile() || map->hasUnsavedChanges()) {
return true;
} else {
return loadLayout(map->layout());
}
}
void Project::clearMapLayouts() {
qDeleteAll(this->mapLayouts);
this->mapLayouts.clear();
qDeleteAll(this->mapLayoutsMaster);
this->mapLayoutsMaster.clear();
this->layoutIds.clear();
this->layoutIdsMaster.clear();
this->alphabeticalLayoutIds.clear();
this->orderedLayoutIds.clear();
this->orderedLayoutIdsMaster.clear();
this->loadedLayoutIds.clear();
this->customLayoutsData = QJsonObject();
}
@@ -700,8 +701,8 @@ bool Project::readMapLayouts() {
this->mapLayouts.insert(layout->id, layout);
this->mapLayoutsMaster.insert(layout->id, layout->copy());
this->layoutIds.append(layout->id);
this->layoutIdsMaster.append(layout->id);
this->orderedLayoutIds.append(layout->id);
this->orderedLayoutIdsMaster.append(layout->id);
}
if (this->mapLayouts.isEmpty()) {
@@ -709,6 +710,9 @@ bool Project::readMapLayouts() {
return false;
}
this->alphabeticalLayoutIds = this->orderedLayoutIds;
Util::numericalModeSort(this->alphabeticalLayoutIds);
this->customLayoutsData = layoutsObj;
return true;
@@ -726,7 +730,7 @@ bool Project::saveMapLayouts() {
layoutsObj["layouts_table_label"] = this->layoutsLabel;
OrderedJson::array layoutsArr;
for (const QString &layoutId : this->layoutIdsMaster) {
for (const QString &layoutId : this->orderedLayoutIdsMaster) {
Layout *layout = this->mapLayoutsMaster.value(layoutId);
OrderedJson::object layoutObj;
layoutObj["id"] = layout->id;
@@ -1255,7 +1259,7 @@ bool Project::saveAll() {
}
bool Project::saveMap(Map *map, bool skipLayout) {
if (!map || !isMapLoaded(map)) return true;
if (!map || !isLoadedMap(map->name())) return true;
// Create/Modify a few collateral files for brand new maps.
const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name();
@@ -1295,7 +1299,7 @@ bool Project::saveMap(Map *map, bool skipLayout) {
// Header values.
mapObj["id"] = map->constantName();
mapObj["name"] = map->name();
mapObj["layout"] = map->layout()->id;
mapObj["layout"] = map->layoutId();
mapObj["music"] = map->header()->song();
mapObj["region_map_section"] = map->header()->location();
mapObj["requires_flash"] = map->header()->requiresFlash();
@@ -1396,15 +1400,15 @@ bool Project::saveMap(Map *map, bool skipLayout) {
}
bool Project::saveLayout(Layout *layout) {
if (!layout || !isLayoutLoaded(layout))
if (!layout || !isLoadedLayout(layout->id))
return true;
if (!layout->save(this->root))
return false;
// Update global data structures with current map data.
if (!this->layoutIdsMaster.contains(layout->id)) {
this->layoutIdsMaster.append(layout->id);
if (!this->orderedLayoutIdsMaster.contains(layout->id)) {
this->orderedLayoutIdsMaster.append(layout->id);
}
if (this->mapLayoutsMaster.contains(layout->id)) {
@@ -1902,7 +1906,7 @@ bool Project::readWildMonData() {
bool Project::readMapGroups() {
clearMaps();
this->mapConstantsToMapNames.clear();
this->mapNames.clear();
this->alphabeticalMapNames.clear();
this->groupNames.clear();
this->groupNameToMapNames.clear();
this->customMapGroupsData = QJsonObject();
@@ -1923,7 +1927,6 @@ bool Project::readMapGroups() {
const QString dynamicMapConstant = getDynamicMapDefineName();
// Process the map group lists
QStringList failedMapNames;
for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) {
const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex));
if (this->groupNames.contains(groupName)) {
@@ -1937,87 +1940,82 @@ bool Project::readMapGroups() {
// Process the names in this map group
for (int j = 0; j < mapNamesJson.size(); j++) {
const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j));
// This list should accept all maps we find, valid or not.
// It will only be used to populate the map list panel.
// Always keeping the name prevents us from deleting it from the list
// if we're unable to load the rest of the necessary map data.
this->groupNameToMapNames[groupName].append(mapName);
if (mapName == dynamicMapName) {
logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName));
failedMapNames.append(mapName);
logWarn(QString("Ignoring map %1 in map group '%2': Cannot use reserved map name '%3'.").arg(j).arg(groupName).arg(mapName));
continue;
}
if (this->mapNames.contains(mapName)) {
logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName));
failedMapNames.append(mapName);
if (this->maps.contains(mapName)) {
logWarn(QString("Ignoring map %1 in map group '%2': Repeated map name '%3'.").arg(j).arg(groupName).arg(mapName));
continue;
}
// Load the map's json file so we can get its ID constant (and two other constants we use for the map list).
QJsonDocument mapDoc;
if (!readMapJson(mapName, &mapDoc)) {
failedMapNames.append(mapName);
continue; // Error message has already been logged
QString mapJsonError;
QJsonDocument mapDoc = readMapJson(mapName, &mapJsonError);
if (!mapJsonError.isEmpty()) {
this->erroredMaps.insert(mapName, mapJsonError);
logWarn(mapJsonError);
continue;
}
// Read and validate the map's ID from its JSON data.
const QJsonObject mapObj = mapDoc.object();
const QString mapConstant = ParseUtil::jsonToQString(mapObj["id"]);
if (mapConstant.isEmpty()) {
logWarn(QString("Map '%1' is missing an \"id\" value and will be ignored.").arg(mapName));
failedMapNames.append(mapName);
QString message = QString("Map '%1' is invalid: Missing \"id\" value.").arg(mapName);
this->erroredMaps.insert(mapName, message);
logWarn(message);
continue;
}
if (mapConstant == dynamicMapConstant) {
logWarn(QString("Ignoring map with reserved \"id\" value '%1'.").arg(mapName));
failedMapNames.append(mapName);
QString message = QString("Map '%1' is invalid: Cannot use reserved name '%2' for \"id\" value.").arg(mapName).arg(mapConstant);
this->erroredMaps.insert(mapName, message);
logWarn(message);
continue;
}
auto it = this->mapConstantsToMapNames.constFind(mapConstant);
if (it != this->mapConstantsToMapNames.constEnd()) {
logWarn(QString("Map '%1' has the same \"id\" value '%2' as map '%3' and will be ignored.").arg(mapName).arg(it.key()).arg(it.value()));
failedMapNames.append(mapName);
QString message = QString("Map '%1' is invalid: Cannot use the same \"id\" value '%2' as map '%3'.")
.arg(mapName)
.arg(it.key())
.arg(it.value());
this->erroredMaps.insert(mapName, message);
logWarn(message);
continue;
}
// Read layout ID for map list
const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]);
if (!this->layoutIds.contains(layoutId)) {
// If a map has an unknown layout ID it won't be able to load it at all anyway, so skip it.
// Skipping these will let us assume all the map layout IDs are valid, which simplies some handling elsewhere.
logWarn(QString("Map '%1' has unknown \"layout\" value '%2' and will be ignored.").arg(mapName).arg(layoutId));
failedMapNames.append(mapName);
continue;
}
// Read MAPSEC name for map list
const QString mapSectionName = ParseUtil::jsonToQString(mapObj["region_map_section"]);
if (!this->mapSectionIdNames.contains(mapSectionName)) {
// An unknown location is OK. Aside from that name not appearing in the dropdowns this shouldn't cause problems.
// We'll log a warning, but allow this map to be displayed.
logWarn(QString("Map '%1' has unknown \"region_map_section\" value '%2'.").arg(mapName).arg(mapSectionName));
}
// Success, create the Map object
auto map = new Map;
map->setName(mapName);
map->setConstantName(mapConstant);
map->setLayout(this->mapLayouts.value(layoutId));
map->header()->setLocation(mapSectionName);
this->maps.insert(mapName, map);
this->mapNames.append(mapName);
this->groupNameToMapNames[groupName].append(mapName);
this->maps.insert(mapName, map);
this->alphabeticalMapNames.append(mapName);
this->mapConstantsToMapNames.insert(mapConstant, mapName);
// Read layout ID for map list
const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]);
map->setLayoutId(layoutId);
map->setLayout(this->mapLayouts.value(layoutId)); // This may set layout to nullptr. Don't report anything until user tries to load this map.
// Read MAPSEC name for map list
map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"]));
}
}
// Note: Not successfully reading any maps or map groups is ok. We only require at least 1 map layout.
if (!failedMapNames.isEmpty()) {
// At least 1 map was excluded due to an error.
// User should be alerted of this, rather than just silently logging the details.
emit mapsExcluded(failedMapNames);
}
// Save special "Dynamic" constant
this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName);
this->mapNames.append(dynamicMapName);
this->alphabeticalMapNames.append(dynamicMapName);
Util::numericalModeSort(this->alphabeticalMapNames);
// Chuck the "connections_include_order" field, this is only for matching.
if (!projectConfig.preserveMatchingOnlyData) {
@@ -2043,8 +2041,7 @@ void Project::addNewMapGroup(const QString &groupName) {
QString Project::mapNameToMapGroup(const QString &mapName) const {
for (auto it = this->groupNameToMapNames.constBegin(); it != this->groupNameToMapNames.constEnd(); it++) {
const QStringList mapNames = it.value();
if (mapNames.contains(mapName)) {
if (it.value().contains(mapName)) {
return it.key();
}
}
@@ -2060,7 +2057,7 @@ QString Project::getMapConstant(const QString &mapName, const QString &defaultVa
QString Project::getMapLayoutId(const QString &mapName, const QString &defaultValue) const {
Map* map = this->maps.value(mapName);
return (map && map->layout()) ? map->layout()->id : defaultValue;
return map ? map->layoutId() : defaultValue;
}
QString Project::getMapLocation(const QString &mapName, const QString &defaultValue) const {
@@ -2068,6 +2065,29 @@ QString Project::getMapLocation(const QString &mapName, const QString &defaultVa
return map ? map->header()->location() : defaultValue;
}
QString Project::getLayoutName(const QString &layoutId) const {
Layout* layout = this->mapLayouts.value(layoutId);
return layout ? layout->name : QString();
}
QStringList Project::getLayoutNames() const {
QStringList names;
for (const auto &layoutId : this->alphabeticalLayoutIds) {
names.append(getLayoutName(layoutId));
}
return names;
}
bool Project::isUnsavedMap(const QString &mapName) const {
Map* map = this->maps.value(mapName);
return map ? map->hasUnsavedChanges() : false;
}
bool Project::isUnsavedLayout(const QString &layoutId) const {
Layout* layout = this->mapLayouts.value(layoutId);
return layout ? layout->hasUnsavedChanges() : false;
}
// Determining which map a secret base ID refers to relies on assumptions about its name.
// The default format is for a secret base ID of 'SECRET_BASE_FOO_#' to refer to a map with the constant
// 'MAP_SECRET_BASE_FOO', so we strip the `_#` suffix and add the default map prefix 'MAP_'. If this fails,
@@ -2105,7 +2125,7 @@ QString Project::secretBaseIdToMapName(const QString &secretBaseId) const {
// In general this only matters to Porymap if the identifier will be added to the group it collides with,
// but name collisions are likely undesirable in the project.
bool Project::isIdentifierUnique(const QString &identifier) const {
if (this->mapNames.contains(identifier))
if (this->maps.contains(identifier) || this->erroredMaps.contains(identifier))
return false;
if (this->mapConstantsToMapNames.contains(identifier))
return false;
@@ -2115,7 +2135,7 @@ bool Project::isIdentifierUnique(const QString &identifier) const {
return false;
if (this->tilesetLabelsOrdered.contains(identifier))
return false;
if (this->layoutIds.contains(identifier))
if (this->mapLayouts.contains(identifier))
return false;
for (const auto &layout : this->mapLayouts) {
if (layout->name == identifier) {

View File

@@ -246,7 +246,7 @@ void ScriptUtility::setMetatileLayerOpacity(QList<float> order) {
QList<QString> ScriptUtility::getMapNames() {
if (!window || !window->editor || !window->editor->project)
return QList<QString>();
return window->editor->project->mapNames;
return window->editor->project->mapNames();
}
QList<QString> ScriptUtility::getMapConstants() {
@@ -256,18 +256,15 @@ QList<QString> ScriptUtility::getMapConstants() {
}
QList<QString> ScriptUtility::getLayoutNames() {
QList<QString> names;
if (!window || !window->editor || !window->editor->project)
return names;
for (const auto &layout : window->editor->project->mapLayouts)
names.append(layout->name);
return names;
return {};
return window->editor->project->getLayoutNames();
}
QList<QString> ScriptUtility::getLayoutConstants() {
if (!window || !window->editor || !window->editor->project)
return QList<QString>();
return window->editor->project->layoutIds;
return window->editor->project->layoutIds();
}
QList<QString> ScriptUtility::getTilesetNames() {

View File

@@ -227,14 +227,14 @@ void EventFrame::populateMapNameDropdown(NoScrollComboBox * combo, Project * pro
if (!project)
return;
populateDropdown(combo, project->mapNames);
populateDropdown(combo, project->mapNames());
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) {
if (!project || !project->mapNames.contains(mapName))
if (!project || !project->isKnownMap(mapName))
return;
Map *map = project->loadMap(mapName);

View File

@@ -100,11 +100,11 @@ void MapImageExporter::setModeSpecificUi() {
const QSignalBlocker b(ui->comboBox_MapSelection);
ui->comboBox_MapSelection->clear();
if (m_map) {
ui->comboBox_MapSelection->addItems(m_project->mapNames);
ui->comboBox_MapSelection->addItems(m_project->mapNames());
ui->comboBox_MapSelection->setTextItem(m_map->name());
ui->label_MapSelection->setText(m_mode == ImageExporterMode::Stitch ? QStringLiteral("Starting Map") : QStringLiteral("Map"));
} else if (m_layout) {
ui->comboBox_MapSelection->addItems(m_project->layoutIds);
ui->comboBox_MapSelection->addItems(m_project->layoutIds());
ui->comboBox_MapSelection->setTextItem(m_layout->id);
ui->label_MapSelection->setText(QStringLiteral("Layout"));
}
@@ -155,13 +155,13 @@ void MapImageExporter::updateMapSelection() {
auto oldLayout = m_layout;
const QString text = ui->comboBox_MapSelection->currentText();
if (m_project->mapNames.contains(text)) {
if (m_project->isKnownMap(text)) {
auto newMap = m_project->loadMap(text);
if (newMap) {
m_map = newMap;
m_layout = newMap->layout();
}
} else if (m_project->layoutIds.contains(text)) {
} else if (m_project->isKnownLayout(text)) {
auto newLayout = m_project->loadLayout(text);
if (newLayout) {
m_map = nullptr;

View File

@@ -47,6 +47,7 @@ MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemMod
this->mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico"));
this->mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
this->mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
this->mapErroredIcon = QIcon(QStringLiteral(":/icons/map_errored.ico"));
this->mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off);
@@ -121,7 +122,7 @@ QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QSta
}
QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) {
if (mapName.isEmpty() || mapName == this->project->getDynamicMapName()) // Disallow adding MAP_DYNAMIC to the map list.
if (mapName.isEmpty() || folderName.isEmpty() || mapName == this->project->getDynamicMapName()) // Disallow adding MAP_DYNAMIC to the map list.
return nullptr;
QStandardItem *map = createMapItem(mapName);
@@ -163,11 +164,13 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const {
// Decorating map in the map list
if (name == this->activeItemName)
return this->mapOpenedIcon;
const Map* map = this->project->getMap(name);
if (!this->project->isMapLoaded(map))
return this->mapGrayIcon;
return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon;
if (this->project->isErroredMap(name))
return this->mapErroredIcon;
if (this->project->isUnsavedMap(name))
return this->mapEditedIcon;
if (this->project->isLoadedMap(name))
return this->mapIcon;
return this->mapGrayIcon;
} else if (type == this->folderTypeName) {
// Decorating map folder in the map list
return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon;
@@ -372,7 +375,6 @@ void MapGroupModel::updateProject() {
if (!this->project) return;
// Temporary objects in case of failure, so we won't modify the project unless it succeeds.
QStringList mapNames;
QStringList groupNames;
QMap<QString, QStringList> groupNameToMapNames;
@@ -388,11 +390,9 @@ void MapGroupModel::updateProject() {
}
QString mapName = mapItem->data(MapListUserRoles::NameRole).toString();
groupNameToMapNames[groupName].append(mapName);
mapNames.append(mapName);
}
}
this->project->mapNames = mapNames;
this->project->groupNames = groupNames;
this->project->groupNameToMapNames = groupNameToMapNames;
this->project->hasUnsavedDataChanges = true;
@@ -445,7 +445,7 @@ MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListM
for (const auto &idName : this->project->mapSectionIdNames) {
insertMapFolderItem(idName);
}
for (const auto &mapName : this->project->mapNames) {
for (const auto &mapName : this->project->mapNames()) {
insertMapItem(mapName, this->project->getMapLocation(mapName));
}
}
@@ -466,10 +466,10 @@ QStandardItem *MapLocationModel::createMapFolderItem(const QString &folderName,
LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) {
this->folderTypeName = "map_layout";
for (const auto &layoutId : this->project->layoutIds) {
for (const auto &layoutId : this->project->layoutIds()) {
insertMapFolderItem(layoutId);
}
for (const auto &mapName : this->project->mapNames) {
for (const auto &mapName : this->project->mapNames()) {
insertMapItem(mapName, this->project->getMapLayoutId(mapName));
}
}
@@ -483,8 +483,8 @@ QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, Q
// Despite using layout IDs internally, the Layouts map list shows layouts using their file path name.
// We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name.
const Layout* layout = this->project->mapLayouts.value(folderName);
if (layout) folder->setText(layout->name);
QString layoutName = this->project->getLayoutName(folderName);
if (!layoutName.isEmpty()) folder->setText(layoutName);
// The layout ID will instead be shown as a tool tip.
folder->setToolTip(folderName);
@@ -501,18 +501,20 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const {
const QStandardItem *item = this->itemAt(index)->child(row, col);
const QString type = item->data(MapListUserRoles::TypeRole).toString();
const QString name = item->data(MapListUserRoles::NameRole).toString();
const QString layoutId = item->data(MapListUserRoles::NameRole).toString();
if (type == this->folderTypeName) {
if (role == Qt::DecorationRole) {
// Map layouts are used as folders, but we display them with the same icons as maps.
if (name == this->activeItemName)
if (layoutId == this->activeItemName)
return this->mapOpenedIcon;
const Layout* layout = this->project->mapLayouts.value(name);
if (!this->project->isLayoutLoaded(layout))
return this->mapGrayIcon;
return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon;
/*if (this->project->isErroredLayout(layoutId))
return this->mapErroredIcon;*/
if (this->project->isUnsavedLayout(layoutId))
return this->mapEditedIcon;
if (this->project->isLoadedLayout(layoutId))
return this->mapIcon;
return this->mapGrayIcon;
}
}
return MapListModel::data(index, role);

View File

@@ -43,7 +43,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare
ui->newLayoutForm->initUi(project);
ui->comboBox_Group->addItems(project->groupNames);
ui->comboBox_LayoutID->addItems(project->layoutIds);
ui->comboBox_LayoutID->addItems(project->layoutIds());
auto validator = new IdentifierValidator(this);
ui->lineEdit_Name->setValidator(validator);
@@ -128,7 +128,7 @@ void NewMapDialog::saveSettings() {
// (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for
// something the majority of users creating a map won't need. If they want to give a specific name to a layout
// they can create the layout first, then create a new map that uses that layout.)
const Layout *layout = this->project->mapLayouts.value(settings->layout.id);
const Layout *layout = this->project->getLayout(settings->layout.id);
if (!layout) {
const QString newLayoutName = settings->name + QStringLiteral("_Layout");
settings->layout.name = this->project->toUniqueIdentifier(newLayoutName);
@@ -198,7 +198,7 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) {
QString errorText;
if (layoutId.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text());
} else if (!this->project->layoutIds.contains(layoutId) && !this->project->isIdentifierUnique(layoutId)) {
} else if (!this->project->isKnownLayout(layoutId) && !this->project->isIdentifierUnique(layoutId)) {
errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text());
}
@@ -213,7 +213,7 @@ void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text)
validateLayoutID(true);
// Changing the layout ID to an existing layout updates the layout settings to match.
const Layout *layout = this->project->mapLayouts.value(text);
const Layout *layout = this->project->getLayout(text);
if (!layout && this->mapToCopy) {
// When duplicating a map, if a new layout ID is specified the settings will be updated
// to match the layout of the map we're duplicating.

View File

@@ -1145,12 +1145,13 @@ void TilesetEditor::countMetatileUsage() {
// do not double count
this->metatileSelector->usedMetatiles.fill(0);
for (auto layout : this->project->mapLayouts) {
for (const auto &layoutId : this->project->layoutIds()) {
Layout *layout = this->project->getLayout(layoutId);
bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name);
bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name);
if (usesPrimary || usesSecondary) {
if (!this->project->loadLayout(layout))
if (!this->project->loadLayout(layoutId))
continue;
// for each block in the layout, mark in the vector that it is used
@@ -1183,7 +1184,8 @@ void TilesetEditor::countTileUsage() {
QSet<Tileset*> primaryTilesets;
QSet<Tileset*> secondaryTilesets;
for (auto &layout : this->project->mapLayouts) {
for (const auto &layoutId : this->project->layoutIds()) {
Layout *layout = this->project->getLayout(layoutId);
if (layout->tileset_primary_label == this->primaryTileset->name
|| layout->tileset_secondary_label == this->secondaryTileset->name) {
// need to check metatiles