diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui
index 50e03e88..e9a3c440 100644
--- a/forms/mainwindow.ui
+++ b/forms/mainwindow.ui
@@ -2926,7 +2926,7 @@
-
+
@@ -3283,7 +3283,7 @@
Grid Settings...
-
+
New Layout...
diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui
index 8e623ba0..53d950fe 100644
--- a/forms/newlayoutdialog.ui
+++ b/forms/newlayoutdialog.ui
@@ -2,8 +2,16 @@
NewLayoutDialog
+
+
+ 0
+ 0
+ 264
+ 173
+
+
- New Map Options
+ New Layout Options
-
@@ -17,13 +25,46 @@
0
0
238
- 146
+ 106
10
+
-
+
+
+ false
+
+
+ color: rgb(255, 0, 0)
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
+ <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html>
+
+
+
+ -
+
+
+ Layout Name
+
+
+
-
@@ -34,20 +75,13 @@
-
- <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html>
+ <html><head/><body><p>The name of the new layout. The name cannot be the same as any other existing layout.</p></body></html>
true
- -
-
-
- Layout Name
-
-
-
-
@@ -64,32 +98,6 @@
- -
-
-
- false
-
-
- color: rgb(255, 0, 0)
-
-
-
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html>
-
-
-
- -
-
-
-
@@ -107,6 +115,19 @@
+ -
+
+
+ color: rgb(255, 0, 0)
+
+
+
+
+
+ true
+
+
+
-
diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui
index ae3ba2d9..e6aece18 100644
--- a/forms/newmapdialog.ui
+++ b/forms/newmapdialog.ui
@@ -2,6 +2,14 @@
NewMapDialog
+
+
+ 0
+ 0
+ 255
+ 320
+
+
New Map Options
@@ -17,32 +25,51 @@
0
0
229
- 254
+ 306
10
-
-
-
+
-
+
+
+ false
+
+
+ color: rgb(255, 0, 0)
+
- Map ID
+
+
+
+ true
- -
-
-
- Qt::Orientation::Vertical
+
-
+
+
+ true
-
-
- 20
- 40
-
+
+ QComboBox::InsertPolicy::NoInsert
-
+
+
+ -
+
+
+ color: rgb(255, 0, 0)
+
+
+
+
+
+ true
+
+
-
@@ -60,17 +87,21 @@
- -
-
-
- true
-
-
- QComboBox::InsertPolicy::NoInsert
+
-
+
+
+ <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html>
- -
+
-
+
+
+ Map ID
+
+
+
+ -
@@ -88,39 +119,20 @@
- -
-
-
- false
-
-
- color: rgb(255, 0, 0)
-
-
-
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html>
-
-
- true
-
-
-
- -
+
-
Can Fly To
+ -
+
+
+ Map Name
+
+
+
-
@@ -134,10 +146,23 @@
- -
-
+
-
+
+
+ <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html>
+
+
+ true
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html>
+
- Map Group
+
@@ -157,20 +182,13 @@
- -
-
-
- Map Name
-
-
+
-
+
- -
-
-
- <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html>
-
+
-
+
-
+ Map Group
@@ -181,20 +199,36 @@
- -
-
-
- <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html>
+
-
+
+
+ Qt::Orientation::Vertical
-
-
- -
-
+
+
+ 20
+ 40
+
+
+
+ -
+
+
+ color: rgb(255, 0, 0)
+
+
+
+
+
+ true
+
+
+
-
diff --git a/include/core/maplayout.h b/include/core/maplayout.h
index caef753a..8dea0795 100644
--- a/include/core/maplayout.h
+++ b/include/core/maplayout.h
@@ -21,6 +21,7 @@ class Layout : public QObject {
public:
Layout() {}
+ static QString layoutNameFromMapName(const QString &mapName);
static QString layoutConstantFromName(QString mapName);
bool loaded = false;
@@ -83,10 +84,10 @@ public:
QString primaryTilesetLabel;
QString secondaryTilesetLabel;
};
+ Settings settings() const;
-public:
- Layout *copy();
- void copyFrom(Layout *other);
+ Layout *copy() const;
+ void copyFrom(const Layout *other);
int getWidth() const { return width; }
int getHeight() const { return height; }
diff --git a/include/mainwindow.h b/include/mainwindow.h
index 53889dff..775d364e 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -22,8 +22,6 @@
#include "mapimageexporter.h"
#include "filterchildrenproxymodel.h"
#include "maplistmodels.h"
-#include "newmapdialog.h"
-#include "newlayoutdialog.h"
#include "newtilesetdialog.h"
#include "shortcutseditor.h"
#include "preferenceeditor.h"
@@ -188,8 +186,6 @@ private slots:
void onLayoutChanged(Layout *layout);
void onOpenConnectedMap(MapConnection*);
void onTilesetsSaved(QString, QString);
- void openNewMapDialog();
- void openNewLayoutDialog();
void onNewMapCreated(Map *newMap, const QString &groupName);
void onNewMapGroupCreated(const QString &groupName);
void onNewLayoutCreated(Layout *layout);
@@ -199,7 +195,6 @@ private slots:
void markMapEdited();
void markSpecificMapEdited(Map*);
- void on_action_NewMap_triggered();
void on_actionNew_Tileset_triggered();
void on_action_Save_triggered();
void on_action_Exit_triggered();
@@ -306,8 +301,6 @@ private:
QPointer regionMapEditor = nullptr;
QPointer shortcutsEditor = nullptr;
QPointer mapImageExporter = nullptr;
- QPointer newMapDialog = nullptr;
- QPointer newLayoutDialog = nullptr;
QPointer preferenceEditor = nullptr;
QPointer projectSettingsEditor = nullptr;
QPointer gridSettingsDialog = nullptr;
@@ -353,6 +346,8 @@ private:
bool setProjectUI();
void clearProjectUI();
+ void openNewMapDialog();
+ void openNewLayoutDialog();
void openSubWindow(QWidget * window);
void scrollMapList(MapTree *list, QString itemName);
void scrollMapListToCurrentMap(MapTree *list);
@@ -371,7 +366,6 @@ private:
void updateMapList();
void mapListAddGroup();
- void mapListAddLayout();
void mapListAddArea();
void openMapListItem(const QModelIndex &index);
void saveMapListTab(int index);
diff --git a/include/project.h b/include/project.h
index c1a36a80..41593350 100644
--- a/include/project.h
+++ b/include/project.h
@@ -82,8 +82,8 @@ public:
bool saveEmptyMapsec;
struct NewMapSettings {
- QString mapName;
- QString mapId;
+ QString name;
+ QString id;
QString group;
bool canFlyTo;
Layout::Settings layout;
@@ -133,9 +133,9 @@ public:
void addNewMap(Map* newMap, const QString &groupName);
void addNewMapGroup(const QString &groupName);
void addNewLayout(Layout* newLayout);
- QString getNewMapName();
- QString getNewLayoutName();
- bool isLayoutNameUnique(const QString &name);
+ NewMapSettings getNewMapSettings() const;
+ Layout::Settings getNewLayoutSettings() const;
+ bool isIdentifierUnique(const QString &identifier) const;
QString getProjectTitle();
bool readWildMonData();
@@ -159,7 +159,7 @@ public:
bool loadMapData(Map*);
bool readMapLayouts();
Layout *loadLayout(QString layoutId);
- Layout *createNewLayout(const Layout::Settings &layoutSettings);
+ Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr);
bool loadLayout(Layout *);
bool loadMapLayout(Map*);
bool loadLayoutTilesets(Layout *);
@@ -234,8 +234,6 @@ public:
static QString getExistingFilepath(QString filepath);
void applyParsedLimits();
- void initNewMapSettings();
- void initNewLayoutSettings();
static QString getDynamicMapDefineName();
static QString getDynamicMapName();
diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h
index 897b8e9a..136499e5 100644
--- a/include/ui/mapheaderform.h
+++ b/include/ui/mapheaderform.h
@@ -32,11 +32,13 @@ public:
MapHeader headerData() const;
void setLocations(QStringList locations);
- void setLocationsDisabled(bool disabled);
+ void setLocationDisabled(bool disabled);
+ bool isLocationDisabled() const { return m_locationDisabled; }
private:
Ui::MapHeaderForm *ui;
QPointer m_header = nullptr;
+ bool m_locationDisabled = false;
void updateUi();
void updateSong();
diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h
index c90a408b..ba4396b9 100644
--- a/include/ui/newlayoutdialog.h
+++ b/include/ui/newlayoutdialog.h
@@ -18,10 +18,11 @@ class NewLayoutDialog : public QDialog
{
Q_OBJECT
public:
- explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr);
+ explicit NewLayoutDialog(Project *project, QWidget *parent = nullptr);
+ explicit NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent = nullptr);
~NewLayoutDialog();
- void copyFrom(const Layout &);
- void accept() override;
+
+ virtual void accept() override;
signals:
void applied(const QString &newLayoutId);
@@ -30,18 +31,21 @@ private:
Ui::NewLayoutDialog *ui;
Project *project;
Layout *importedLayout = nullptr;
- Layout::Settings *settings = nullptr;
+
+ static Layout::Settings settings;
+ static bool initializedSettings;
// Each of these validation functions will allow empty names up until `OK` is selected,
// because clearing the text during editing is common and we don't want to flash errors for this.
bool validateLayoutID(bool allowEmpty = false);
bool validateName(bool allowEmpty = false);
+ void refresh();
+
void saveSettings();
bool isExistingLayout() const;
private slots:
- //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO
void dialogButtonClicked(QAbstractButton *button);
void on_lineEdit_Name_textChanged(const QString &);
void on_lineEdit_LayoutID_textChanged(const QString &);
diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h
index 8a769740..cdc324e2 100644
--- a/include/ui/newmapdialog.h
+++ b/include/ui/newmapdialog.h
@@ -18,12 +18,12 @@ class NewMapDialog : public QDialog
{
Q_OBJECT
public:
- explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr);
+ explicit NewMapDialog(Project *project, QWidget *parent = nullptr);
+ explicit NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent = nullptr);
+ explicit NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent = nullptr);
~NewMapDialog();
- void init();
- void init(int tabIndex, QString data);
- void init(Layout *);
- void accept() override;
+
+ virtual void accept() override;
signals:
void applied(const QString &newMapName);
@@ -33,25 +33,29 @@ private:
Project *project;
CollapsibleSection *headerSection;
MapHeaderForm *headerForm;
- Layout *importedLayout = nullptr;
- Project::NewMapSettings *settings = nullptr;
+ Map *importedMap = nullptr;
+
+ static Project::NewMapSettings settings;
+ static bool initializedSettings;
// Each of these validation functions will allow empty names up until `OK` is selected,
// because clearing the text during editing is common and we don't want to flash errors for this.
bool validateMapID(bool allowEmpty = false);
bool validateName(bool allowEmpty = false);
bool validateGroup(bool allowEmpty = false);
+ bool validateLayoutID(bool allowEmpty = false);
+
+ void refresh();
void saveSettings();
- bool isExistingLayout() const;
void useLayoutSettings(const Layout *mapLayout);
- void useLayoutIdSettings(const QString &layoutId);
private slots:
void dialogButtonClicked(QAbstractButton *button);
void on_lineEdit_Name_textChanged(const QString &);
void on_lineEdit_MapID_textChanged(const QString &);
void on_comboBox_Group_currentTextChanged(const QString &text);
+ void on_comboBox_LayoutID_currentTextChanged(const QString &text);
};
#endif // NEWMAPDIALOG_H
diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp
index 11e9943d..921c0894 100644
--- a/src/core/maplayout.cpp
+++ b/src/core/maplayout.cpp
@@ -7,13 +7,13 @@
-Layout *Layout::copy() {
+Layout *Layout::copy() const {
Layout *layout = new Layout;
layout->copyFrom(this);
return layout;
}
-void Layout::copyFrom(Layout *other) {
+void Layout::copyFrom(const Layout *other) {
this->id = other->id;
this->name = other->name;
this->width = other->width;
@@ -30,19 +30,30 @@ void Layout::copyFrom(Layout *other) {
this->border = other->border;
}
+QString Layout::layoutNameFromMapName(const QString &mapName) {
+ return QString("%1_Layout").arg(mapName);
+}
+
QString Layout::layoutConstantFromName(QString mapName) {
// Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'.
static const QRegularExpression caseChange("([a-z])([A-Z])");
QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2");
QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper();
static const QRegularExpression underscores("_+");
- QString constantName = withMapAndUppercase.replace(underscores, "_");
+ return withMapAndUppercase.replace(underscores, "_");
+}
- // Handle special cases.
- // SSTidal should be SS_TIDAL, rather than SSTIDAL
- constantName = constantName.replace("SSTIDAL", "SS_TIDAL");
-
- return constantName;
+Layout::Settings Layout::settings() const {
+ Layout::Settings settings;
+ settings.id = this->id;
+ settings.name = this->name;
+ settings.width = this->width;
+ settings.height = this->height;
+ settings.borderWidth = this->border_width;
+ settings.borderHeight = this->border_height;
+ settings.primaryTilesetLabel = this->tileset_primary_label;
+ settings.secondaryTilesetLabel = this->tileset_secondary_label;
+ return settings;
}
bool Layout::isWithinBounds(int x, int y) {
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 7af3d100..41f85724 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -23,6 +23,8 @@
#include "newmapconnectiondialog.h"
#include "config.h"
#include "filedialog.h"
+#include "newmapdialog.h"
+#include "newlayoutdialog.h"
#include
#include
@@ -291,7 +293,8 @@ void MainWindow::initExtraSignals() {
label_MapRulerStatus->setTextFormat(Qt::PlainText);
label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
- connect(ui->actionNew_Layout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog);
+ connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog);
+ connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog);
}
void MainWindow::on_actionCheck_for_Updates_triggered() {
@@ -406,7 +409,7 @@ void MainWindow::initMapList() {
// Create add map/layout button
// TODO: Tool tip
QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), "");
- connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::on_action_NewMap_triggered);
+ connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::openNewMapDialog);
layout->addWidget(buttonAdd);
/* TODO: Remove button disabled, no current support for deleting maps/layouts
@@ -932,6 +935,10 @@ bool MainWindow::userSetLayout(QString layoutId) {
msgBox.critical(nullptr, "Error Opening Layout", errorMsg);
return false;
}
+
+ // Only the Layouts tab of the map list shows Layouts, so if we're not already on that tab we'll open it now.
+ ui->mapListContainer->setCurrentIndex(MapListTab::Layouts);
+
return true;
}
@@ -1228,8 +1235,9 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) {
if (addToFolderAction) {
// All folders only contain maps, so adding an item to any folder is adding a new map.
connect(addToFolderAction, &QAction::triggered, [this, itemName] {
- openNewMapDialog();
- this->newMapDialog->init(ui->mapListContainer->currentIndex(), itemName);
+ auto dialog = new NewMapDialog(this->editor->project, ui->mapListContainer->currentIndex(), itemName, this);
+ connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap);
+ dialog->open();
});
}
if (deleteFolderAction) {
@@ -1267,8 +1275,8 @@ void MainWindow::mapListAddGroup() {
connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){
const QString mapGroupName = newNameEdit->text();
- if (this->editor->project->groupNames.contains(mapGroupName)) {
- errorMessageLabel->setText(QString("A map group with the name '%1' already exists").arg(mapGroupName));
+ if (!this->editor->project->isIdentifierUnique(mapGroupName)) {
+ errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(mapGroupName));
errorMessageLabel->setVisible(true);
} else {
dialog.accept();
@@ -1288,126 +1296,6 @@ void MainWindow::mapListAddGroup() {
}
}
-// TODO: Pull this all out into a custom window. Connect that to an action in the main menu as well.
-// (or, re-use the new map dialog with some tweaks)
-// TODO: This needs to take the same default settings you would get for a new map (tilesets, dimensions, etc.)
-// and initialize it with the same fill settings (default metatile/collision/elevation, default border)
-// TODO: Remove
-void MainWindow::mapListAddLayout() {
- /*
- if (!editor || !editor->project) return;
-
- QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
- dialog.setWindowModality(Qt::ApplicationModal);
- QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
- connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
-
- QLineEdit *newNameEdit = new QLineEdit(&dialog);
- newNameEdit->setClearButtonEnabled(true);
-
- static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*");
- newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit));
-
- // TODO: Support arbitrary LAYOUT_ ID names (Note from GriffinR: This is already handled in an unopened PR)
- QLabel *newId = new QLabel("LAYOUT_", &dialog);
- connect(newNameEdit, &QLineEdit::textChanged, [&](QString text){
- newId->setText(Layout::layoutConstantFromName(text.remove("_Layout")));
- });
-
- NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog);
- useExistingCombo->addItems(this->editor->project->layoutIds);
- useExistingCombo->setEnabled(false);
-
- QCheckBox *useExistingCheck = new QCheckBox(&dialog);
-
- QLabel *errorMessageLabel = new QLabel(&dialog);
- errorMessageLabel->setVisible(false);
- errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }");
-
- QComboBox *primaryCombo = new QComboBox(&dialog);
- primaryCombo->addItems(this->editor->project->primaryTilesetLabels);
- QComboBox *secondaryCombo = new QComboBox(&dialog);
- secondaryCombo->addItems(this->editor->project->secondaryTilesetLabels);
-
- QSpinBox *widthSpin = new QSpinBox(&dialog);
- QSpinBox *heightSpin = new QSpinBox(&dialog);
-
- widthSpin->setMinimum(1);
- heightSpin->setMinimum(1);
- widthSpin->setMaximum(this->editor->project->getMaxMapWidth());
- heightSpin->setMaximum(this->editor->project->getMaxMapHeight());
-
- connect(useExistingCheck, &QCheckBox::stateChanged, [&](int state){
- bool useExisting = (state == Qt::Checked);
- useExistingCombo->setEnabled(useExisting);
- primaryCombo->setEnabled(!useExisting);
- secondaryCombo->setEnabled(!useExisting);
- widthSpin->setEnabled(!useExisting);
- heightSpin->setEnabled(!useExisting);
- });
-
- QFormLayout form(&dialog);
- form.addRow("New Layout Name", newNameEdit);
- form.addRow("New Layout ID", newId);
- form.addRow("Copy Existing Layout", useExistingCheck);
- form.addRow("", useExistingCombo);
- form.addRow("Primary Tileset", primaryCombo);
- form.addRow("Secondary Tileset", secondaryCombo);
- form.addRow("Layout Width", widthSpin);
- form.addRow("Layout Height", heightSpin);
- form.addRow("", errorMessageLabel);
-
- connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){
- // verify some things
- QString errorMessage;
- QString tryLayoutName = newNameEdit->text();
- // name not empty
- if (tryLayoutName.isEmpty()) {
- errorMessage = "Name cannot be empty";
- }
- // unique layout name & id
- else if (this->editor->project->layoutIds.contains(newId->text())
- || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) {
- errorMessage = "Layout Name / ID is not unique";
- }
- // from id is existing value
- else if (useExistingCheck->isChecked()) {
- if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) {
- errorMessage = "Existing layout ID is not valid";
- }
- }
-
- if (!errorMessage.isEmpty()) {
- // show error
- errorMessageLabel->setText(errorMessage);
- errorMessageLabel->setVisible(true);
- }
- else {
- dialog.accept();
- }
- });
-
- form.addRow(&newItemButtonBox);
-
- if (dialog.exec() == QDialog::Accepted) {
- Layout::SimpleSettings layoutSettings;
- QString layoutName = newNameEdit->text();
- layoutSettings.name = layoutName;
- layoutSettings.id = Layout::layoutConstantFromName(layoutName.remove("_Layout"));
- if (useExistingCheck->isChecked()) {
- layoutSettings.from_id = useExistingCombo->currentText();
- } else {
- layoutSettings.width = widthSpin->value();
- layoutSettings.height = heightSpin->value();
- layoutSettings.tileset_primary_label = primaryCombo->currentText();
- layoutSettings.tileset_secondary_label = secondaryCombo->currentText();
- }
- Layout *newLayout = this->editor->project->createNewLayout(layoutSettings);
- setLayout(newLayout->id);
- }
- */
-}
-
void MainWindow::mapListAddArea() {
QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::ApplicationModal);
@@ -1433,8 +1321,8 @@ void MainWindow::mapListAddArea() {
connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){
const QString newAreaName = newNameDisplay->text();
- if (this->editor->project->mapSectionIdNames.contains(newAreaName)){
- errorMessageLabel->setText(QString("An area with the name '%1' already exists").arg(newAreaName));
+ if (!this->editor->project->isIdentifierUnique(newAreaName)) {
+ errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(newAreaName));
errorMessageLabel->setVisible(true);
} else {
dialog.accept();
@@ -1485,6 +1373,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) {
}
}
+// Called any time a new layout is created (including as a byproduct of creating a new map)
void MainWindow::onNewLayoutCreated(Layout *layout) {
logInfo(QString("Created a new layout named %1.").arg(layout->name));
@@ -1504,31 +1393,16 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) {
this->mapGroupModel->insertGroupItem(groupName);
}
-// TODO: This and the new layout dialog are modal. We shouldn't need to reference their dialogs outside these open functions,
-// so we should be able to remove them as members of MainWindow.
-// (plus, the opening then init() call after showing for NewMapDialog is Bad)
void MainWindow::openNewMapDialog() {
- if (!this->newMapDialog) {
- this->newMapDialog = new NewMapDialog(this, this->editor->project);
- connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap);
- }
-
- openSubWindow(this->newMapDialog);
-}
-
-void MainWindow::on_action_NewMap_triggered() {
- openNewMapDialog();
- //this->newMapDialog->initUi();//TODO
- this->newMapDialog->init();
+ auto dialog = new NewMapDialog(this->editor->project, this);
+ connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap);
+ dialog->open();
}
void MainWindow::openNewLayoutDialog() {
- if (!this->newLayoutDialog) {
- this->newLayoutDialog = new NewLayoutDialog(this, this->editor->project);
- connect(this->newLayoutDialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout);
- }
-
- openSubWindow(this->newLayoutDialog);
+ auto dialog = new NewLayoutDialog(this->editor->project, this);
+ connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout);
+ dialog->open();
}
// Insert label for newly-created tileset into sorted list of existing labels
@@ -2732,8 +2606,9 @@ void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() {
return;
}
- openNewLayoutDialog();
- this->newLayoutDialog->copyFrom(*mapLayout);
+ auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this);
+ connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout);
+ dialog->open();
delete mapLayout;
}
@@ -2756,7 +2631,7 @@ void MainWindow::on_pushButton_AddConnection_clicked() {
auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames);
connect(dialog, &NewMapConnectionDialog::accepted, this->editor, &Editor::addConnection);
- dialog->exec();
+ dialog->open();
}
void MainWindow::on_pushButton_NewWildMonGroup_clicked() {
@@ -3244,10 +3119,6 @@ bool MainWindow::closeSupplementaryWindows() {
return false;
this->mapImageExporter = nullptr;
- if (this->newMapDialog && !this->newMapDialog->close())
- return false;
- this->newMapDialog = nullptr;
-
if (this->shortcutsEditor && !this->shortcutsEditor->close())
return false;
this->shortcutsEditor = nullptr;
diff --git a/src/project.cpp b/src/project.cpp
index 2f37e24a..9976c549 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -107,7 +107,6 @@ bool Project::load() {
&& readSongNames()
&& readMapGroups();
applyParsedLimits();
- initNewMapSettings();
return success;
}
@@ -348,12 +347,7 @@ bool Project::loadMapData(Map* map) {
const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]);
const int offset = ParseUtil::jsonToInt(connectionObj["offset"]);
const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]);
- if (this->mapConstantsToMapNames.contains(mapConstant)) {
- // Successully read map connection
- map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant), direction, offset));
- } else {
- logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant));
- }
+ map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset));
}
}
@@ -369,40 +363,7 @@ bool Project::loadMapData(Map* map) {
return true;
}
-/*
-void Project::addNewLayout(Layout* newLayout) {
-
- if (newLayout->blockdata.isEmpty()) {
- // Fill layout using default fill settings
- setNewLayoutBlockdata(newLayout);
- }
- if (newLayout->border.isEmpty()) {
- // Fill border using default fill settings
- setNewLayoutBorder(newLayout);
- }
-
- emit layoutAdded(newLayout);
-}
-*/
-
-// TODO: Fold back into createNewLayout?
-/*
-Layout *Project::duplicateLayout(const Layout *toDuplicate) {
- //TODO
- if (!settings.from_id.isEmpty()) {
- // load from layout
- loadLayout(mapLayouts[settings.from_id]);
- layout = mapLayouts[settings.from_id]->copy();
- layout->name = settings.name;
- layout->id = settings.id;
- layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name);
- layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name);
- }
-}
-*/
-
-// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout
-Layout *Project::createNewLayout(const Layout::Settings &settings) {
+Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout *toDuplicate) {
Layout *layout = new Layout;
layout->id = settings.id;
layout->name = settings.name;
@@ -413,6 +374,13 @@ Layout *Project::createNewLayout(const Layout::Settings &settings) {
layout->tileset_primary_label = settings.primaryTilesetLabel;
layout->tileset_secondary_label = settings.secondaryTilesetLabel;
+ if (toDuplicate) {
+ // If we're duplicating an existing layout we'll copy over the blockdata.
+ // Otherwise addNewLayout will fill our new layout using the default settings.
+ layout->blockdata = toDuplicate->blockdata;
+ layout->border = toDuplicate->border;
+ }
+
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);
@@ -1276,6 +1244,7 @@ void Project::saveMap(Map *map) {
}
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)) {
@@ -1314,19 +1283,15 @@ void Project::saveMap(Map *map) {
mapObj["battle_scene"] = map->header()->battleScene();
// Connections
- auto connections = map->getConnections();
+ const auto connections = map->getConnections();
if (connections.length() > 0) {
OrderedJson::array connectionsArr;
- for (auto connection : connections) {
- if (this->mapNamesToMapConstants.contains(connection->targetMapName())) {
- OrderedJson::object connectionObj;
- connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName());
- connectionObj["offset"] = connection->offset();
- connectionObj["direction"] = connection->direction();
- connectionsArr.append(connectionObj);
- } else {
- logError(QString("Failed to write map connection. '%1' is not a valid map name").arg(connection->targetMapName()));
- }
+ for (const auto &connection : connections) {
+ OrderedJson::object connectionObj;
+ connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName(), connection->targetMapName());
+ connectionObj["offset"] = connection->offset();
+ connectionObj["direction"] = connection->direction();
+ connectionsArr.append(connectionObj);
}
mapObj["connections"] = connectionsArr;
} else {
@@ -1986,31 +1951,81 @@ void Project::addNewMapGroup(const QString &groupName) {
emit mapGroupAdded(groupName);
}
-QString Project::getNewMapName() {
- // Ensure default name doesn't already exist.
+Project::NewMapSettings Project::getNewMapSettings() const {
+ // Ensure default name/ID doesn't already exist.
int i = 0;
QString newMapName;
+ QString newMapId;
do {
newMapName = QString("NewMap%1").arg(++i);
- } while (this->mapNames.contains(newMapName));
+ newMapId = Map::mapConstantFromName(newMapName);
+ } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(newMapId));
- return newMapName;
+ NewMapSettings settings;
+ settings.name = newMapName;
+ settings.id = newMapId;
+ settings.group = this->groupNames.at(0);
+ settings.canFlyTo = false;
+ settings.layout = getNewLayoutSettings();
+ settings.layout.id = Layout::layoutConstantFromName(newMapName);
+ settings.layout.name = Layout::layoutNameFromMapName(newMapName);
+ settings.header.setSong(this->defaultSong);
+ settings.header.setLocation(this->mapSectionIdNames.value(0, "0"));
+ settings.header.setRequiresFlash(false);
+ settings.header.setWeather(this->weatherNames.value(0, "0"));
+ settings.header.setType(this->mapTypes.value(0, "0"));
+ settings.header.setBattleScene(this->mapBattleScenes.value(0, "0"));
+ settings.header.setShowsLocationName(true);
+ settings.header.setAllowsRunning(false);
+ settings.header.setAllowsBiking(false);
+ settings.header.setAllowsEscaping(false);
+ settings.header.setFloorNumber(0);
+ return settings;
}
-QString Project::getNewLayoutName() {
- // Ensure default name doesn't already exist.
+Layout::Settings Project::getNewLayoutSettings() const {
+ // Ensure default name/ID doesn't already exist.
int i = 0;
QString newLayoutName;
+ QString newLayoutId;
do {
newLayoutName = QString("NewLayout%1").arg(++i);
- } while (!isLayoutNameUnique(newLayoutName));
+ newLayoutId = Layout::layoutConstantFromName(newLayoutName);
+ } while (!isIdentifierUnique(newLayoutId) || !isIdentifierUnique(newLayoutName));
- return newLayoutName;
+ Layout::Settings settings;
+ settings.name = newLayoutName;
+ settings.id = newLayoutId;
+ settings.width = getDefaultMapDimension();
+ settings.height = getDefaultMapDimension();
+ settings.borderWidth = DEFAULT_BORDER_WIDTH;
+ settings.borderHeight = DEFAULT_BORDER_HEIGHT;
+ settings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel();
+ settings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel();
+ return settings;
}
-bool Project::isLayoutNameUnique(const QString &name) {
+// When we ask the user to provide a new identifier for something (like a map/layout name or ID)
+// we use this to make sure that it doesn't collide with any known identifiers first.
+// Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap.
+// 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.
+// TODO: Use elsewhere
+bool Project::isIdentifierUnique(const QString &identifier) const {
+ if (this->mapNames.contains(identifier))
+ return false;
+ if (this->mapConstantsToMapNames.contains(identifier))
+ return false;
+ if (this->groupNames.contains(identifier))
+ return false;
+ if (this->mapSectionIdNames.contains(identifier))
+ return false;
+ if (this->tilesetLabelsOrdered.contains(identifier))
+ return false;
+ if (this->layoutIds.contains(identifier))
+ return false;
for (const auto &layout : this->mapLayouts) {
- if (layout->name == name) {
+ if (layout->name == identifier) {
return false;
}
}
@@ -3056,32 +3071,6 @@ void Project::applyParsedLimits() {
projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1);
}
-void Project::initNewMapSettings() {
- this->newMapSettings.group = this->groupNames.at(0);
- this->newMapSettings.canFlyTo = false;
- this->newMapSettings.header.setSong(this->defaultSong);
- this->newMapSettings.header.setLocation(this->mapSectionIdNames.value(0, "0"));
- this->newMapSettings.header.setRequiresFlash(false);
- this->newMapSettings.header.setWeather(this->weatherNames.value(0, "0"));
- this->newMapSettings.header.setType(this->mapTypes.value(0, "0"));
- this->newMapSettings.header.setBattleScene(this->mapBattleScenes.value(0, "0"));
- this->newMapSettings.header.setShowsLocationName(true);
- this->newMapSettings.header.setAllowsRunning(false);
- this->newMapSettings.header.setAllowsBiking(false);
- this->newMapSettings.header.setAllowsEscaping(false);
- this->newMapSettings.header.setFloorNumber(0);
- initNewLayoutSettings();
-}
-
-void Project::initNewLayoutSettings() {
- this->newMapSettings.layout.width = getDefaultMapDimension();
- this->newMapSettings.layout.height = getDefaultMapDimension();
- this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH;
- this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT;
- this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel();
- this->newMapSettings.layout.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel();
-}
-
bool Project::hasUnsavedChanges() {
if (this->hasUnsavedDataChanges)
return true;
diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp
index 1fd9084c..fa0e2c1c 100644
--- a/src/ui/mapheaderform.cpp
+++ b/src/ui/mapheaderform.cpp
@@ -154,9 +154,10 @@ MapHeader MapHeaderForm::headerData() const {
return header;
}
-void MapHeaderForm::setLocationsDisabled(bool disabled) {
- ui->label_Location->setDisabled(disabled);
- ui->comboBox_Location->setDisabled(disabled);
+void MapHeaderForm::setLocationDisabled(bool disabled) {
+ m_locationDisabled = disabled;
+ ui->label_Location->setDisabled(m_locationDisabled);
+ ui->comboBox_Location->setDisabled(m_locationDisabled);
}
void MapHeaderForm::updateSong() {
diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp
index 73822ba8..3cf7b9a9 100644
--- a/src/ui/newlayoutdialog.cpp
+++ b/src/ui/newlayoutdialog.cpp
@@ -9,31 +9,55 @@
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
-NewLayoutDialog::NewLayoutDialog(QWidget *parent, Project *project) :
+Layout::Settings NewLayoutDialog::settings = {};
+bool NewLayoutDialog::initializedSettings = false;
+
+NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewLayoutDialog)
{
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
ui->setupUi(this);
+ ui->label_GenericError->setVisible(false);
this->project = project;
- this->settings = &project->newMapSettings.layout;
-
- ui->lineEdit_Name->setText(project->getNewLayoutName());
+ Layout::Settings newSettings = project->getNewLayoutSettings();
+ if (!initializedSettings) {
+ // The first time this dialog is opened we initialize all the default settings.
+ settings = newSettings;
+ initializedSettings = true;
+ } else {
+ // On subsequent openings we only initialize the settings that should be unique,
+ // preserving all other settings from the last time the dialog was open.
+ settings.name = newSettings.name;
+ settings.id = newSettings.id;
+ }
ui->newLayoutForm->initUi(project);
- ui->newLayoutForm->setSettings(*this->settings);
- // Names and IDs can only contain word characters, and cannot start with a digit.
+ // Identifiers can only contain word characters, and cannot start with a digit.
static const QRegularExpression re("[A-Za-z_]+[\\w]*");
auto validator = new QRegularExpressionValidator(re, this);
ui->lineEdit_Name->setValidator(validator);
ui->lineEdit_LayoutID->setValidator(validator);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked);
+
+ refresh();
adjustSize();
}
+// Creating new layout from AdvanceMap import
+// TODO: Re-use for a "Duplicate Layout" option
+NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layout, QWidget *parent) :
+ NewLayoutDialog(project, parent)
+{
+ if (layout) {
+ this->importedLayout = layout->copy();
+ refresh();
+ }
+}
+
NewLayoutDialog::~NewLayoutDialog()
{
saveSettings();
@@ -41,33 +65,24 @@ NewLayoutDialog::~NewLayoutDialog()
delete ui;
}
-// Creating new layout from AdvanceMap import
-// TODO: Re-use for a "Duplicate Layout" option?
-void NewLayoutDialog::copyFrom(const Layout &layoutToCopy) {
- if (this->importedLayout)
- delete this->importedLayout;
+void NewLayoutDialog::refresh() {
+ if (this->importedLayout) {
+ // If we're importing a layout then some settings will be enforced.
+ ui->newLayoutForm->setSettings(this->importedLayout->settings());
+ ui->newLayoutForm->setDisabled(true);
+ } else {
+ ui->newLayoutForm->setSettings(settings);
+ ui->newLayoutForm->setDisabled(false);
+ }
- this->importedLayout = new Layout();
- this->importedLayout->blockdata = layoutToCopy.blockdata;
- if (!layoutToCopy.border.isEmpty())
- this->importedLayout->border = layoutToCopy.border;
-
- this->settings->width = layoutToCopy.width;
- this->settings->height = layoutToCopy.height;
- this->settings->borderWidth = layoutToCopy.border_width;
- this->settings->borderHeight = layoutToCopy.border_height;
- this->settings->primaryTilesetLabel = layoutToCopy.tileset_primary_label;
- this->settings->secondaryTilesetLabel = layoutToCopy.tileset_secondary_label;
-
- // Don't allow changes to the layout settings
- ui->newLayoutForm->setSettings(*this->settings);
- ui->newLayoutForm->setDisabled(true);
+ ui->lineEdit_Name->setText(settings.name);
+ ui->lineEdit_LayoutID->setText(settings.id);
}
void NewLayoutDialog::saveSettings() {
- *this->settings = ui->newLayoutForm->settings();
- this->settings->id = ui->lineEdit_LayoutID->text();
- this->settings->name = ui->lineEdit_Name->text();
+ settings = ui->newLayoutForm->settings();
+ settings.id = ui->lineEdit_LayoutID->text();
+ settings.name = ui->lineEdit_Name->text();
}
bool NewLayoutDialog::validateLayoutID(bool allowEmpty) {
@@ -76,8 +91,8 @@ bool NewLayoutDialog::validateLayoutID(bool allowEmpty) {
QString errorText;
if (id.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text());
- } else if (this->project->mapLayouts.contains(id)) {
- errorText = QString("%1 '%2' is already in use.").arg(ui->label_LayoutID->text()).arg(id);
+ } else if (!this->project->isIdentifierUnique(id)) {
+ errorText = QString("%1 '%2' is not unique.").arg(ui->label_LayoutID->text()).arg(id);
}
bool isValid = errorText.isEmpty();
@@ -97,8 +112,8 @@ bool NewLayoutDialog::validateName(bool allowEmpty) {
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text());
- } else if (!this->project->isLayoutNameUnique(name)) {
- errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name);
+ } else if (!this->project->isIdentifierUnique(name)) {
+ errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name);
}
bool isValid = errorText.isEmpty();
@@ -110,6 +125,8 @@ bool NewLayoutDialog::validateName(bool allowEmpty) {
void NewLayoutDialog::on_lineEdit_Name_textChanged(const QString &text) {
validateName(true);
+
+ // Changing the layout name updates the layout ID field to match.
ui->lineEdit_LayoutID->setText(Layout::layoutConstantFromName(text));
}
@@ -118,8 +135,8 @@ void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) {
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::ResetRole) {
- this->project->initNewLayoutSettings(); // TODO: Don't allow this to change locked settings
- ui->newLayoutForm->setSettings(*this->settings);
+ settings = this->project->getNewLayoutSettings();
+ refresh();
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
@@ -137,18 +154,13 @@ void NewLayoutDialog::accept() {
// Update settings from UI
saveSettings();
- /*
- if (this->importedLayout) {
- // Copy layout data from imported layout
- layout->blockdata = this->importedLayout->blockdata;
- if (!this->importedLayout->border.isEmpty())
- layout->border = this->importedLayout->border;
- }
- */
-
- Layout *layout = this->project->createNewLayout(*this->settings);
- if (!layout)
+ Layout *layout = this->project->createNewLayout(settings, this->importedLayout);
+ if (!layout) {
+ ui->label_GenericError->setText(QString("Failed to create layout. See %1 for details.").arg(getLogPath()));
+ ui->label_GenericError->setVisible(true);
return;
+ }
+ ui->label_GenericError->setVisible(false);
emit applied(layout->id);
QDialog::accept();
diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp
index ef31a7b1..a97fd2ab 100644
--- a/src/ui/newmapdialog.cpp
+++ b/src/ui/newmapdialog.cpp
@@ -10,23 +10,36 @@
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
-NewMapDialog::NewMapDialog(QWidget *parent, Project *project) :
+Project::NewMapSettings NewMapDialog::settings = {};
+bool NewMapDialog::initializedSettings = false;
+
+NewMapDialog::NewMapDialog(Project *project, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewMapDialog)
{
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
ui->setupUi(this);
+ ui->label_GenericError->setVisible(false);
this->project = project;
- this->settings = &project->newMapSettings;
- // Populate UI using data from project
- this->settings->mapName = project->getNewMapName();
+ Project::NewMapSettings newSettings = project->getNewMapSettings();
+ if (!initializedSettings) {
+ // The first time this dialog is opened we initialize all the default settings.
+ settings = newSettings;
+ initializedSettings = true;
+ } else {
+ // On subsequent openings we only initialize the settings that should be unique,
+ // preserving all other settings from the last time the dialog was open.
+ settings.name = newSettings.name;
+ settings.id = newSettings.id;
+ }
ui->newLayoutForm->initUi(project);
+
ui->comboBox_Group->addItems(project->groupNames);
ui->comboBox_LayoutID->addItems(project->layoutIds);
- // Names and IDs can only contain word characters, and cannot start with a digit.
+ // Identifiers can only contain word characters, and cannot start with a digit.
static const QRegularExpression re("[A-Za-z_]+[\\w]*");
auto validator = new QRegularExpressionValidator(re, this);
ui->lineEdit_Name->setValidator(validator);
@@ -37,7 +50,7 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) :
// Create a collapsible section that has all the map header data.
this->headerForm = new MapHeaderForm();
this->headerForm->init(project);
- this->headerForm->setHeader(&this->settings->header);
+ this->headerForm->setHeader(&settings.header);
auto sectionLayout = new QVBoxLayout();
sectionLayout->addWidget(this->headerForm);
@@ -47,103 +60,91 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) :
ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding));
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked);
- connect(ui->comboBox_LayoutID, &QComboBox::currentTextChanged, this, &NewMapDialog::useLayoutIdSettings);
+ refresh();
adjustSize(); // TODO: Save geometry?
}
+// Adding new map to existing map list folder.
+NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) :
+ NewMapDialog(project, parent)
+{
+ switch (mapListTab)
+ {
+ case MapListTab::Groups:
+ settings.group = mapListItem;
+ ui->label_Group->setDisabled(true);
+ ui->comboBox_Group->setDisabled(true);
+ ui->comboBox_Group->setTextItem(settings.group);
+ break;
+ case MapListTab::Areas:
+ settings.header.setLocation(mapListItem);
+ this->headerForm->setLocationDisabled(true);
+ // Header UI is kept in sync automatically by MapHeaderForm
+ break;
+ case MapListTab::Layouts:
+ settings.layout.id = mapListItem;
+ ui->label_LayoutID->setDisabled(true);
+ ui->comboBox_LayoutID->setDisabled(true);
+ ui->comboBox_LayoutID->setTextItem(settings.layout.id);
+ break;
+ }
+}
+
+// TODO: Use for a "Duplicate Map" option
+NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) :
+ NewMapDialog(project, parent)
+{
+ /*
+ if (this->importedMap)
+ delete this->importedMap;
+
+ this->importedMap = new Map(mapToCopy);
+ useLayoutSettings(this->importedMap->layout());
+ */
+}
+
NewMapDialog::~NewMapDialog()
{
saveSettings();
- delete this->importedLayout;
+ delete this->importedMap;
delete ui;
}
-void NewMapDialog::init() {
- const QSignalBlocker b_LayoutId(ui->comboBox_LayoutID);
- ui->comboBox_LayoutID->setCurrentText(this->settings->layout.id);
-
- ui->lineEdit_Name->setText(this->settings->mapName);
- ui->comboBox_Group->setTextItem(this->settings->group);
- ui->checkBox_CanFlyTo->setChecked(this->settings->canFlyTo);
- ui->newLayoutForm->setSettings(this->settings->layout);
-}
-
-// Creating new map by right-clicking in the map list
-void NewMapDialog::init(int tabIndex, QString fieldName) {
- switch (tabIndex)
- {
- case MapListTab::Groups:
- this->settings->group = fieldName;
- ui->label_Group->setDisabled(true);
- ui->comboBox_Group->setDisabled(true);
- break;
- case MapListTab::Areas:
- this->settings->header.setLocation(fieldName);
- this->headerForm->setLocationsDisabled(true);
- break;
- case MapListTab::Layouts:
- ui->label_LayoutID->setDisabled(true);
- ui->comboBox_LayoutID->setDisabled(true);
- useLayoutIdSettings(fieldName);
- break;
- }
- init();
-}
-
-// Creating new map from AdvanceMap import
-// TODO: Re-use for a "Duplicate Map/Layout" option?
-void NewMapDialog::init(Layout *layoutToCopy) {
- if (this->importedLayout)
- delete this->importedLayout;
-
- this->importedLayout = new Layout();
- this->importedLayout->blockdata = layoutToCopy->blockdata;
- if (!layoutToCopy->border.isEmpty())
- this->importedLayout->border = layoutToCopy->border;
-
- useLayoutSettings(this->importedLayout);
- init();
+// Sync UI with settings. If any UI elements are disabled (because their settings are being enforced)
+// then we don't update them using the settings here.
+void NewMapDialog::refresh() {
+ ui->lineEdit_Name->setText(settings.name);
+ ui->lineEdit_MapID->setText(settings.id);
+
+ ui->comboBox_Group->setTextItem(settings.group);
+ ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo);
+ ui->comboBox_LayoutID->setTextItem(settings.layout.id);
+ ui->newLayoutForm->setSettings(settings.layout);
+ // Header UI is kept in sync automatically by MapHeaderForm
}
void NewMapDialog::saveSettings() {
- this->settings->mapName = ui->lineEdit_Name->text();
- this->settings->mapId = ui->lineEdit_MapID->text();
- this->settings->group = ui->comboBox_Group->currentText();
- this->settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked();
- this->settings->layout = ui->newLayoutForm->settings();
- this->settings->layout.id = ui->comboBox_LayoutID->currentText();
- this->settings->layout.name = QString("%1_Layout").arg(this->settings->mapName);
- this->settings->header = this->headerForm->headerData();
+ settings.name = ui->lineEdit_Name->text();
+ settings.id = ui->lineEdit_MapID->text();
+ settings.group = ui->comboBox_Group->currentText();
+ settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked();
+ settings.layout = ui->newLayoutForm->settings();
+ settings.layout.id = ui->comboBox_LayoutID->currentText();
+ // We don't provide full control for naming new layouts here (just via the ID).
+ // If a user wants to explicitly name a layout they can create it individually before creating the map.
+ settings.layout.name = Layout::layoutNameFromMapName(settings.name);
+ settings.header = this->headerForm->headerData();
porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded();
}
void NewMapDialog::useLayoutSettings(const Layout *layout) {
- if (!layout) {
+ if (layout) {
+ ui->newLayoutForm->setSettings(layout->settings());
+ ui->newLayoutForm->setDisabled(true);
+ } else {
ui->newLayoutForm->setDisabled(false);
- return;
}
-
- this->settings->layout.width = layout->width;
- this->settings->layout.height = layout->height;
- this->settings->layout.borderWidth = layout->border_width;
- this->settings->layout.borderHeight = layout->border_height;
- this->settings->layout.primaryTilesetLabel = layout->tileset_primary_label;
- this->settings->layout.secondaryTilesetLabel = layout->tileset_secondary_label;
-
- // Don't allow changes to the layout settings
- ui->newLayoutForm->setSettings(this->settings->layout);
- ui->newLayoutForm->setDisabled(true);
-}
-
-void NewMapDialog::useLayoutIdSettings(const QString &layoutId) {
- this->settings->layout.id = layoutId;
- useLayoutSettings(this->project->mapLayouts.value(layoutId));
-}
-
-// Return true if the "layout ID" field is specifying a layout that already exists.
-bool NewMapDialog::isExistingLayout() const {
- return this->project->mapLayouts.contains(this->settings->layout.id);
}
bool NewMapDialog::validateMapID(bool allowEmpty) {
@@ -155,13 +156,8 @@ bool NewMapDialog::validateMapID(bool allowEmpty) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text());
} else if (!id.startsWith(expectedPrefix)) {
errorText = QString("%1 '%2' must start with '%3'.").arg(ui->label_MapID->text()).arg(id).arg(expectedPrefix);
- } else {
- for (auto i = this->project->mapNamesToMapConstants.constBegin(), end = this->project->mapNamesToMapConstants.constEnd(); i != end; i++) {
- if (id == i.value()) {
- errorText = QString("%1 '%2' is already in use.").arg(ui->label_MapID->text()).arg(id);
- break;
- }
- }
+ } else if (!this->project->isIdentifierUnique(id)) {
+ errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id);
}
bool isValid = errorText.isEmpty();
@@ -181,8 +177,8 @@ bool NewMapDialog::validateName(bool allowEmpty) {
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text());
- } else if (project->mapNames.contains(name)) {
- errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name);
+ } else if (!this->project->isIdentifierUnique(name)) {
+ errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name);
}
bool isValid = errorText.isEmpty();
@@ -206,6 +202,8 @@ bool NewMapDialog::validateGroup(bool allowEmpty) {
QString errorText;
if (groupName.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Group->text());
+ } else if (!this->project->groupNames.contains(groupName) && !this->project->isIdentifierUnique(groupName)) {
+ errorText = QString("%1 must either be the name of an existing map group, or a unique identifier for a new map group.").arg(ui->label_Group->text());
}
bool isValid = errorText.isEmpty();
@@ -219,13 +217,43 @@ void NewMapDialog::on_comboBox_Group_currentTextChanged(const QString &) {
validateGroup(true);
}
+bool NewMapDialog::validateLayoutID(bool allowEmpty) {
+ QString layoutId = ui->comboBox_LayoutID->currentText();
+
+ 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)) {
+ 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());
+ }
+
+ bool isValid = errorText.isEmpty();
+ ui->label_LayoutIDError->setText(errorText);
+ ui->label_LayoutIDError->setVisible(!isValid);
+ ui->comboBox_LayoutID->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
+ return isValid;
+}
+
+void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) {
+ validateLayoutID(true);
+ useLayoutSettings(this->project->mapLayouts.value(text));
+}
+
void NewMapDialog::dialogButtonClicked(QAbstractButton *button) {
auto role = ui->buttonBox->buttonRole(button);
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::ResetRole) {
- this->project->initNewMapSettings(); // TODO: Don't allow this to change locked settings
- init();
+ auto newSettings = this->project->getNewMapSettings();
+
+ // If the location setting is disabled we need to enforce that setting on the new header.
+ if (this->headerForm->isLocationDisabled())
+ newSettings.header.setLocation(settings.header.location());
+
+ settings = newSettings;
+ this->headerForm->setHeader(&settings.header); // TODO: Unnecessary?
+ refresh();
+
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
@@ -238,6 +266,7 @@ void NewMapDialog::accept() {
if (!validateMapID()) success = false;
if (!validateName()) success = false;
if (!validateGroup()) success = false;
+ if (!validateLayoutID()) success = false;
if (!success)
return;
@@ -245,33 +274,29 @@ void NewMapDialog::accept() {
saveSettings();
Map *newMap = new Map;
- newMap->setName(this->settings->mapName);
- newMap->setConstantName(this->settings->mapId);
- newMap->setHeader(this->settings->header);
- newMap->setNeedsHealLocation(this->settings->canFlyTo);
+ newMap->setName(settings.name);
+ newMap->setConstantName(settings.id);
+ newMap->setHeader(settings.header);
+ newMap->setNeedsHealLocation(settings.canFlyTo);
- Layout *layout = nullptr;
- const bool existingLayout = isExistingLayout();
- if (existingLayout) {
- layout = this->project->mapLayouts.value(this->settings->layout.id);
- newMap->setNeedsLayoutDir(false); // TODO: Remove this member
+ Layout *layout = this->project->mapLayouts.value(settings.layout.id);
+ if (layout) {
+ // Layout already exists
+ newMap->setNeedsLayoutDir(false); // TODO: Remove this member?
} else {
- /* TODO: Re-implement (make sure this won't ever override an existing layout)
- if (this->importedLayout) {
- // Copy layout data from imported layout
- layout->blockdata = this->importedLayout->blockdata;
- if (!this->importedLayout->border.isEmpty())
- layout->border = this->importedLayout->border;
- }
- */
- layout = this->project->createNewLayout(this->settings->layout);
+ layout = this->project->createNewLayout(settings.layout);
}
- if (!layout)
+ if (!layout) {
+ ui->label_GenericError->setText(QString("Failed to create layout for map. See %1 for details.").arg(getLogPath()));
+ ui->label_GenericError->setVisible(true);
+ delete newMap;
return;
+ }
+ ui->label_GenericError->setVisible(false);
newMap->setLayout(layout);
- this->project->addNewMap(newMap, this->settings->group);
+ this->project->addNewMap(newMap, settings.group);
emit applied(newMap->name());
QDialog::accept();
}
diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp
index e9eee946..8cb4c5f9 100644
--- a/src/ui/newtilesetdialog.cpp
+++ b/src/ui/newtilesetdialog.cpp
@@ -10,7 +10,7 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) :
this->setFixedSize(this->width(), this->height());
this->project = project;
//only allow characters valid for a symbol
- static const QRegularExpression expression("[_A-Za-z0-9]+$");
+ static const QRegularExpression expression("[_A-Za-z0-9]+$"); // TODO: Incorrect, allows digits at beginning
QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression);
this->ui->nameLineEdit->setValidator(validator);
@@ -35,6 +35,7 @@ void NewTilesetDialog::SecondaryChanged(){
NameOrSecondaryChanged();
}
+// TODO: No validation
void NewTilesetDialog::NameOrSecondaryChanged() {
this->friendlyName = this->ui->nameLineEdit->text();
this->fullSymbolName = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + this->friendlyName;