Finish new layout dialog redesign

This commit is contained in:
GriffinR 2024-11-21 15:04:42 -05:00
parent e7df829843
commit d0101d807e
16 changed files with 529 additions and 561 deletions

View File

@ -2926,7 +2926,7 @@
<addaction name="actionMap_Shift"/>
<addaction name="separator"/>
<addaction name="action_NewMap"/>
<addaction name="actionNew_Layout"/>
<addaction name="action_NewLayout"/>
<addaction name="actionNew_Tileset"/>
<addaction name="actionTileset_Editor"/>
<addaction name="actionRegion_Map_Editor"/>
@ -3283,7 +3283,7 @@
<string>Grid Settings...</string>
</property>
</action>
<action name="actionNew_Layout">
<action name="action_NewLayout">
<property name="text">
<string>New Layout...</string>
</property>

View File

@ -2,8 +2,16 @@
<ui version="4.0">
<class>NewLayoutDialog</class>
<widget class="QDialog" name="NewLayoutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>264</width>
<height>173</height>
</rect>
</property>
<property name="windowTitle">
<string>New Map Options</string>
<string>New Layout Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@ -17,13 +25,46 @@
<x>0</x>
<y>0</y>
<width>238</width>
<height>146</height>
<height>106</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="verticalSpacing">
<number>10</number>
</property>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="NewLayoutForm" name="newLayoutForm" native="true"/>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_LayoutID">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Layout Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_LayoutID">
<property name="text">
@ -34,20 +75,13 @@
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new map. The name cannot be the same as any other existing map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new layout. The name cannot be the same as any other existing layout.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Layout Name</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_LayoutIDError">
<property name="visible">
@ -64,32 +98,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_LayoutID">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="NewLayoutForm" name="newLayoutForm" native="true"/>
</item>
<item row="8" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
@ -107,6 +115,19 @@
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="label_GenericError">
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">

View File

@ -2,6 +2,14 @@
<ui version="4.0">
<class>NewMapDialog</class>
<widget class="QDialog" name="NewMapDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>255</width>
<height>320</height>
</rect>
</property>
<property name="windowTitle">
<string>New Map Options</string>
</property>
@ -17,32 +25,51 @@
<x>0</x>
<y>0</y>
<width>229</width>
<height>254</height>
<height>306</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="verticalSpacing">
<number>10</number>
</property>
<item row="2" column="0">
<widget class="QLabel" name="label_MapID">
<item row="3" column="1">
<widget class="QLabel" name="label_MapIDError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string>Map ID</string>
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="13" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
<item row="6" column="1">
<widget class="NoScrollComboBox" name="comboBox_LayoutID">
<property name="editable">
<bool>true</bool>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</spacer>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_LayoutIDError">
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
@ -60,17 +87,21 @@
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="comboBox_LayoutID">
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_MapID">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="12" column="0" colspan="2">
<item row="2" column="0">
<widget class="QLabel" name="label_MapID">
<property name="text">
<string>Map ID</string>
</property>
</widget>
</item>
<item row="13" column="0" colspan="2">
<widget class="QWidget" name="widget_HeaderData" native="true">
<layout class="QVBoxLayout" name="layout_HeaderData">
<property name="leftMargin">
@ -88,39 +119,20 @@
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_MapIDError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new map. The name cannot be the same as any other existing map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_CanFlyTo">
<property name="text">
<string>Can Fly To</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Map Name</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="NoScrollComboBox" name="comboBox_Group">
<property name="toolTip">
@ -134,10 +146,23 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_Group">
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new map. The name cannot be the same as any other existing map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="checkBox_CanFlyTo">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, a Heal Location will be added to this map automatically.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Map Group</string>
<string/>
</property>
</widget>
</item>
@ -157,20 +182,13 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Map Name</string>
</property>
</widget>
<item row="9" column="0" colspan="2">
<widget class="NewLayoutForm" name="newLayoutForm" native="true"/>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="checkBox_CanFlyTo">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, a Heal Location will be added to this map automatically.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item row="4" column="0">
<widget class="QLabel" name="label_Group">
<property name="text">
<string/>
<string>Map Group</string>
</property>
</widget>
</item>
@ -181,20 +199,36 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_MapID">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item row="14" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="NewLayoutForm" name="newLayoutForm" native="true"/>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="label_GenericError">
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">

View File

@ -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; }

View File

@ -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> regionMapEditor = nullptr;
QPointer<ShortcutsEditor> shortcutsEditor = nullptr;
QPointer<MapImageExporter> mapImageExporter = nullptr;
QPointer<NewMapDialog> newMapDialog = nullptr;
QPointer<NewLayoutDialog> newLayoutDialog = nullptr;
QPointer<PreferenceEditor> preferenceEditor = nullptr;
QPointer<ProjectSettingsEditor> projectSettingsEditor = nullptr;
QPointer<GridSettingsDialog> 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);

View File

@ -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();

View File

@ -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<MapHeader> m_header = nullptr;
bool m_locationDisabled = false;
void updateUi();
void updateSong();

View File

@ -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 &);

View File

@ -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

View File

@ -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) {

View File

@ -23,6 +23,8 @@
#include "newmapconnectiondialog.h"
#include "config.h"
#include "filedialog.h"
#include "newmapdialog.h"
#include "newlayoutdialog.h"
#include <QClipboard>
#include <QDirIterator>
@ -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;

View File

@ -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;

View File

@ -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() {

View File

@ -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();

View File

@ -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();
}

View File

@ -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;