diff --git a/include/core/map.h b/include/core/map.h index 00e7e63c..6e953877 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -67,12 +67,10 @@ public: QString sharedEventsMap() const { return m_sharedEventsMap; } QString sharedScriptsMap() const { return m_sharedScriptsMap; } - void setNeedsLayoutDir(bool needsLayoutDir) { m_needsLayoutDir = needsLayoutDir; } void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } - bool needsLayoutDir() const { return m_needsLayoutDir; } bool needsHealLocation() const { return m_needsHealLocation; } bool isPersistedToFile() const { return m_isPersistedToFile; } bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } @@ -121,7 +119,6 @@ private: bool m_isPersistedToFile = true; bool m_hasUnsavedDataChanges = false; - bool m_needsLayoutDir = true; bool m_needsHealLocation = false; bool m_scriptsLoaded = false; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 5d717a43..7761d7fc 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -22,8 +22,9 @@ public: Layout() {} Layout(const Layout &other); - static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); + static QString defaultSuffix(); + bool loaded = false; @@ -77,6 +78,9 @@ public: struct Settings { QString id; QString name; + // The name of a new layout's folder in `data/layouts/` is not always the same as the layout's name + // (e.g. the majority of the default layouts use the name of their associated map). + QString folderName; int width; int height; int borderWidth; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 1d0f316f..4b560bf1 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -40,7 +40,6 @@ private: void refresh(); void saveSettings(); - void setLayout(const Layout *mapLayout); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index c30ae798..26d4cb89 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,8 +32,8 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -QString Layout::layoutNameFromMapName(const QString &mapName) { - return QString("%1_Layout").arg(mapName); +QString Layout::defaultSuffix() { + return "_Layout"; } QString Layout::layoutConstantFromName(QString mapName) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bb01d5c5..9623963a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1382,7 +1382,9 @@ void MainWindow::mapListAddArea() { void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); - // TODO: Creating a new map shouldn't be automatically saved + // TODO: Creating a new map shouldn't be automatically saved. + // For one, it takes away the option to discard the new map. + // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); diff --git a/src/project.cpp b/src/project.cpp index f6cddfec..7772ac8c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -382,15 +382,17 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setConstantName(mapConstant); Layout *layout = this->mapLayouts.value(settings.layout.id); - if (layout) { - // Layout already exists - map->setNeedsLayoutDir(false); // TODO: Remove this member? - } else { - layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); - } if (!layout) { - delete map; - return nullptr; + // Layout doesn't already exist, create it. + layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); + if (!layout) { + // Layout creation failed. + delete map; + return nullptr; + } + } else { + // This layout already exists. Make sure it's loaded. + loadLayout(layout); } map->setLayout(layout); @@ -444,14 +446,17 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout layout->tileset_primary_label = settings.primaryTilesetLabel; layout->tileset_secondary_label = settings.secondaryTilesetLabel; - const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); + // If a special folder name was specified (as in the case when we're creating a layout for a new map) then use that name. + // Otherwise the new layout's folder name will just be the layout's name. + const QString folderName = !settings.folderName.isEmpty() ? settings.folderName : layout->name; + const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders) + folderName; + layout->border_path = folderPath + "/border.bin"; + layout->blockdata_path = folderPath + "/map.bin"; - // Create a new directory for the layout - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); - if (!QDir::root().mkdir(newLayoutDir)) { - logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); + // Create a new directory for the layout, if it doesn't already exist. + const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); delete layout; return nullptr; } @@ -493,14 +498,14 @@ bool Project::loadLayout(Layout *layout) { } Layout *Project::loadLayout(QString layoutId) { - if (mapLayouts.contains(layoutId)) { - Layout *layout = mapLayouts[layoutId]; + if (this->mapLayouts.contains(layoutId)) { + Layout *layout = this->mapLayouts[layoutId]; if (loadLayout(layout)) { return layout; } } - logError(QString("Error: Failed to load layout '%1'").arg(layoutId)); + logError(QString("Failed to load layout '%1'").arg(layoutId)); return nullptr; } @@ -509,10 +514,10 @@ bool Project::loadMapLayout(Map* map) { return true; } - if (mapLayouts.contains(map->layoutId())) { - map->setLayout(mapLayouts[map->layoutId()]); + if (this->mapLayouts.contains(map->layoutId())) { + map->setLayout(this->mapLayouts[map->layoutId()]); } else { - logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); + logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } @@ -535,8 +540,8 @@ void Project::clearMapLayouts() { bool Project::readMapLayouts() { clearMapLayouts(); - QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); - QString fullFilepath = QString("%1/%2").arg(root).arg(layoutsFilepath); + const QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); + const QString fullFilepath = QString("%1/%2").arg(this->root).arg(layoutsFilepath); fileWatcher.addPath(fullFilepath); QJsonDocument layoutsDoc; if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath)) { @@ -1295,40 +1300,32 @@ void Project::saveAllMaps() { void Project::saveMap(Map *map) { // Create/Modify a few collateral files for brand new maps. - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_map_folders); - QString mapDataDir = root + "/" + basePath + map->name(); + const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); + const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); if (!map->isPersistedToFile()) { - if (!QDir::root().mkdir(mapDataDir)) { - logError(QString("Error: failed to create directory for new map: '%1'").arg(mapDataDir)); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new map: '%1'").arg(fullPath)); } // Create file data/maps//scripts.inc QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name()); - saveTextFile(mapDataDir + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); + saveTextFile(fullPath + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); if (projectConfig.createMapTextFileEnabled) { // Create file data/maps//text.inc - saveTextFile(mapDataDir + "/text" + this->getScriptFileExtension(projectConfig.usePoryScript), "\n"); + saveTextFile(fullPath + "/text" + this->getScriptFileExtension(projectConfig.usePoryScript), "\n"); } // Simply append to data/event_scripts.s. - text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name()); + text = QString("\n\t.include \"%1/scripts.inc\"\n").arg(folderPath); if (projectConfig.createMapTextFileEnabled) { - text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name()); + text += QString("\t.include \"%1/text.inc\"\n").arg(folderPath); } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); - - // TODO: Either simplify this redundancy or explain why we need it (to create folders without the _Layout suffix) - if (map->needsLayoutDir()) { - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); - if (!QDir::root().mkdir(newLayoutDir)) { - logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); - } - } } // Create map.json for map data. - QString mapFilepath = QString("%1/map.json").arg(mapDataDir); + QString mapFilepath = fullPath + "/map.json"; QFile mapFile(mapFilepath); if (!mapFile.open(QIODevice::WriteOnly)) { logError(QString("Error: Could not open %1 for writing").arg(mapFilepath)); @@ -1428,7 +1425,6 @@ void Project::saveMap(Map *map) { } void Project::saveLayout(Layout *layout) { - // saveLayoutBorder(layout); saveLayoutBlockdata(layout); @@ -2022,7 +2018,8 @@ void Project::initNewMapSettings() { this->newMapSettings.group = this->groupNames.at(0); this->newMapSettings.canFlyTo = false; - this->newMapSettings.layout.name = Layout::layoutNameFromMapName(this->newMapSettings.name); + this->newMapSettings.layout.folderName = this->newMapSettings.name; + this->newMapSettings.layout.name = QString("%1%2").arg(this->newMapSettings.name).arg(Layout::defaultSuffix()); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); this->newMapSettings.layout.width = getDefaultMapDimension(); this->newMapSettings.layout.height = getDefaultMapDimension(); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 7b9d347f..79af74e5 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -28,26 +28,12 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { // Duplicating a layout, the initial name will be the base layout's name // with a numbered suffix to make it unique. - // Note: Layouts imported with AdvanceMap have no name, so they'll use the default new layout name instead. - - // If the layout name ends with the default '_Layout' suffix we'll ignore it. - // This is because (normally) the ID for these layouts will not have this suffix, - // so you can end up in a situation where you might have Map_Layout and Map_2_Layout, - // and if you try to duplicate Map_Layout the next available name (because of ID collisions) - // would be Map_Layout_3 instead of Map_3_Layout. - QString baseName = this->layoutToCopy->name; - QString suffix = "_Layout"; - if (baseName.length() > suffix.length() && baseName.endsWith(suffix)) { - baseName.truncate(baseName.length() - suffix.length()); - } else { - suffix = ""; - } - + // Note: If 'layoutToCopy' is an imported AdvanceMap layout it won't have + // a name, so it uses the default new layout name instead. int i = 2; do { - newName = QString("%1_%2%3").arg(baseName).arg(i).arg(suffix); - newId = QString("%1_%2").arg(this->layoutToCopy->id).arg(i); - i++; + newName = QString("%1_%2").arg(this->layoutToCopy->name).arg(i++); + newId = Layout::layoutConstantFromName(newName); } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); } else { newName = project->getNewLayoutName(); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 3b77b5c7..64c2fdfb 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -71,6 +71,7 @@ Layout::Settings NewLayoutForm::settings() const { return settings; } +// TODO: Validate while typing bool NewLayoutForm::validate() { // Make sure to call each validation function so that all errors are shown at once. bool valid = true; diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index e6d3101b..d362d2dc 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -71,12 +71,12 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); - adjustSize(); // TODO: Save geometry? + adjustSize(); } -// Adding new map to existing map list folder. Initialize settings accordingly. +// Adding new map to an existing map list folder. Initialize settings accordingly. // Even if we initialize settings like this we'll allow users to change them afterwards, -// because nothing is expecting them to stay at these values. +// because nothing is expecting them to stay at these values (with exception to layouts). NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) : NewMapDialog(project, parent) { @@ -132,21 +132,18 @@ void NewMapDialog::saveSettings() { settings->group = ui->comboBox_Group->currentText(); settings->layout = ui->newLayoutForm->settings(); settings->layout.id = ui->comboBox_LayoutID->currentText(); - settings->layout.name = Layout::layoutNameFromMapName(settings->name); // TODO: Verify uniqueness settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); -} + // TODO: Verify uniqueness. If the layout ID belongs to an existing layout we don't need to do this at all. + settings->layout.name = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); -void NewMapDialog::setLayout(const Layout *layout) { - if (layout) { - ui->comboBox_LayoutID->setTextItem(layout->id); - ui->newLayoutForm->setSettings(layout->settings()); - ui->newLayoutForm->setDisabled(true); - } else { - ui->newLayoutForm->setDisabled(false); - } + // Folders for new layouts created for new maps use the map name, rather than the layout name. + // There's no real reason for this, aside from maintaining consistency with the default layout + // folder names that do this (which would otherwise all have a '_Layout' suffix in the name). + settings->layout.folderName = settings->name; + + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } bool NewMapDialog::validateName(bool allowEmpty) { @@ -168,6 +165,8 @@ bool NewMapDialog::validateName(bool allowEmpty) { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); + + // Changing the map name updates the layout ID field to match. if (ui->comboBox_LayoutID->isEnabled()) { ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); } @@ -219,7 +218,15 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { validateLayoutID(true); - setLayout(this->project->mapLayouts.value(text)); + + // Changing the layout ID to an existing layout updates the layout settings to match. + const Layout *layout = this->project->mapLayouts.value(text); + if (layout) { + ui->newLayoutForm->setSettings(layout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { + ui->newLayoutForm->setDisabled(false); + } } void NewMapDialog::dialogButtonClicked(QAbstractButton *button) {