From bd39bcfdd297222050bdecac425d95ebd095e51a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 19 Nov 2024 14:52:47 -0500 Subject: [PATCH] Begin new layout dialog redesign --- forms/mainwindow.ui | 20 +++-- forms/newlayoutdialog.ui | 129 +++++++++++++++++++++++++++ forms/newlayoutform.ui | 8 -- forms/newmapdialog.ui | 33 ++++--- include/core/maplayout.h | 12 ++- include/mainwindow.h | 4 +- include/project.h | 16 +++- include/ui/newlayoutdialog.h | 51 +++++++++++ include/ui/newlayoutform.h | 16 +--- include/ui/newmapdialog.h | 16 +--- porymap.pro | 3 + src/mainwindow.cpp | 45 ++++++---- src/project.cpp | 129 +++++++++++++++++++-------- src/ui/newlayoutdialog.cpp | 160 +++++++++++++++++++++++++++++++++ src/ui/newlayoutform.cpp | 19 ++-- src/ui/newmapdialog.cpp | 165 +++++++++++++++++------------------ 16 files changed, 617 insertions(+), 209 deletions(-) create mode 100644 forms/newlayoutdialog.ui create mode 100644 include/ui/newlayoutdialog.h create mode 100644 src/ui/newlayoutdialog.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index eb6c7345..d7c4abe1 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1740,7 +1740,7 @@ 0 0 100 - 16 + 30 @@ -1834,7 +1834,7 @@ 0 0 100 - 16 + 30 @@ -1928,7 +1928,7 @@ 0 0 100 - 16 + 30 @@ -2028,7 +2028,7 @@ 0 0 100 - 16 + 30 @@ -2122,7 +2122,7 @@ 0 0 100 - 16 + 30 @@ -2700,8 +2700,8 @@ 0 0 - 204 - 16 + 100 + 30 @@ -2926,6 +2926,7 @@ + @@ -3282,6 +3283,11 @@ Grid Settings... + + + New Layout... + + diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui new file mode 100644 index 00000000..8e623ba0 --- /dev/null +++ b/forms/newlayoutdialog.ui @@ -0,0 +1,129 @@ + + + NewLayoutDialog + + + New Map Options + + + + + + true + + + + + 0 + 0 + 238 + 146 + + + + + 10 + + + + + Layout ID + + + + + + + <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 + + + + + + + Layout Name + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + 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> + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + + + + + + + NewLayoutForm + QWidget +
newlayoutform.h
+ 1 +
+
+ + +
diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui index 65183046..f51fb742 100644 --- a/forms/newlayoutform.ui +++ b/forms/newlayoutform.ui @@ -2,14 +2,6 @@ NewLayoutForm - - - 0 - 0 - 304 - 344 - - Form diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index ed52f445..ae3ba2d9 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -2,14 +2,6 @@ NewMapDialog - - - 0 - 0 - 453 - 588 - - New Map Options @@ -24,8 +16,8 @@ 0 0 - 427 - 522 + 229 + 254 @@ -69,7 +61,14 @@ - + + + true + + + QComboBox::InsertPolicy::NoInsert + + @@ -176,7 +175,7 @@ - + Layout ID @@ -206,17 +205,17 @@
- - NoScrollComboBox - QComboBox -
noscrollcombobox.h
-
NewLayoutForm QWidget
newlayoutform.h
1
+ + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 0cffcefa..caef753a 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -14,6 +14,8 @@ class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; +// TODO: Privatize members as appropriate + class Layout : public QObject { Q_OBJECT public: @@ -70,14 +72,16 @@ public: QUndoStack editHistory; // to simplify new layout settings transfer between functions - struct SimpleSettings { + // TODO: Make this the equivalent of struct MapHeader + struct Settings { QString id; QString name; int width; int height; - QString tileset_primary_label; - QString tileset_secondary_label; - QString from_id = QString(); + int borderWidth; + int borderHeight; + QString primaryTilesetLabel; + QString secondaryTilesetLabel; }; public: diff --git a/include/mainwindow.h b/include/mainwindow.h index e96c4cc3..a8dbca1f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -23,6 +23,7 @@ #include "filterchildrenproxymodel.h" #include "maplistmodels.h" #include "newmapdialog.h" +#include "newlayoutdialog.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" @@ -188,6 +189,7 @@ private slots: 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); @@ -306,6 +308,7 @@ private: QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; QPointer newMapDialog = nullptr; + QPointer newLayoutDialog = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; @@ -334,7 +337,6 @@ private: QMap lastSelectedEvent; bool isProgrammaticEventTabChange; - bool newMapDefaultsSet = false; bool tilesetNeedsRedraw = false; diff --git a/include/project.h b/include/project.h index 36c8e1e3..19b757db 100644 --- a/include/project.h +++ b/include/project.h @@ -81,6 +81,16 @@ public: bool wildEncountersLoaded; bool saveEmptyMapsec; + struct NewMapSettings { + QString mapName; + QString mapId; + QString group; + bool canFlyTo; + Layout::Settings layout; + MapHeader header; + }; + NewMapSettings newMapSettings; + void set_root(QString); void clearMapCache(); @@ -124,6 +134,8 @@ public: void addNewMapGroup(const QString &groupName); void addNewLayout(Layout* newLayout); QString getNewMapName(); + QString getNewLayoutName(); + bool isLayoutNameUnique(const QString &name); QString getProjectTitle(); bool readWildMonData(); @@ -147,7 +159,7 @@ public: bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); - Layout *createNewLayout(Layout::SimpleSettings &layoutSettings); + Layout *createNewLayout(const Layout::Settings &layoutSettings); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -222,6 +234,8 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + void initNewMapSettings(); + void initNewLayoutSettings(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h new file mode 100644 index 00000000..49047cc7 --- /dev/null +++ b/include/ui/newlayoutdialog.h @@ -0,0 +1,51 @@ +#ifndef NEWLAYOUTDIALOG_H +#define NEWLAYOUTDIALOG_H + +#include +#include +#include "editor.h" +#include "project.h" +#include "map.h" +#include "mapheaderform.h" +#include "newlayoutform.h" +#include "lib/collapsiblesection.h" + +namespace Ui { +class NewLayoutDialog; +} + +class NewLayoutDialog : public QDialog +{ + Q_OBJECT +public: + explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr); + ~NewLayoutDialog(); + void init(Layout *); + void accept() override; + +signals: + void applied(const QString &newLayoutId); + +private: + Ui::NewLayoutDialog *ui; + Project *project; + Layout *importedLayout = nullptr; + Layout::Settings *settings = nullptr; + + // 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 saveSettings(); + bool isExistingLayout() const; + void useLayoutSettings(Layout *mapLayout); + +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 &); +}; + +#endif // NEWLAYOUTDIALOG_H diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index d6230f6f..6f8d9905 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -3,6 +3,8 @@ #include +#include "maplayout.h" + class Project; namespace Ui { @@ -19,18 +21,8 @@ public: void initUi(Project *project); - struct Settings { - QString id; // TODO: Support in UI (toggleable line edit) - int width; - int height; - int borderWidth; - int borderHeight; - QString primaryTilesetLabel; - QString secondaryTilesetLabel; - }; - - void setSettings(const Settings &settings); - NewLayoutForm::Settings settings() const; + void setSettings(const Layout::Settings &settings); + Layout::Settings settings() const; void setDisabled(bool disabled); diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 2e860cba..8a769740 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -24,7 +24,6 @@ public: void init(int tabIndex, QString data); void init(Layout *); void accept() override; - static void setDefaultSettings(const Project *project); signals: void applied(const QString &newMapName); @@ -35,27 +34,20 @@ private: CollapsibleSection *headerSection; MapHeaderForm *headerForm; Layout *importedLayout = nullptr; + Project::NewMapSettings *settings = nullptr; // 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 validateID(bool allowEmpty = false); + bool validateMapID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); bool validateGroup(bool allowEmpty = false); void saveSettings(); bool isExistingLayout() const; - void useLayoutSettings(Layout *mapLayout); - - struct Settings { - QString group; - bool canFlyTo; - NewLayoutForm::Settings layout; - MapHeader header; - }; - static struct Settings settings; + void useLayoutSettings(const Layout *mapLayout); + void useLayoutIdSettings(const QString &layoutId); 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_MapID_textChanged(const QString &); diff --git a/porymap.pro b/porymap.pro index c4db35d0..1cc9497c 100644 --- a/porymap.pro +++ b/porymap.pro @@ -90,6 +90,7 @@ SOURCES += src/core/block.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ src/ui/neweventtoolbutton.cpp \ + src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ @@ -196,6 +197,7 @@ HEADERS += include/core/block.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ include/ui/neweventtoolbutton.h \ + include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ @@ -241,6 +243,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 59b6057f..8c483f1f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,6 +290,8 @@ void MainWindow::initExtraSignals() { label_MapRulerStatus->setAlignment(Qt::AlignCenter); label_MapRulerStatus->setTextFormat(Qt::PlainText); label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse); + + connect(ui->actionNew_Layout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -402,6 +404,7 @@ void MainWindow::initMapList() { layout->setContentsMargins(0, 0, 0, 0); // 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); layout->addWidget(buttonAdd); @@ -442,7 +445,7 @@ void MainWindow::initMapList() { // Connect the "add folder" button in each of the map lists connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddGroup); connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddArea); - connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddLayout); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); } @@ -608,8 +611,6 @@ bool MainWindow::openProject(QString dir, bool initial) { projectConfig.projectDir = dir; projectConfig.load(); - this->newMapDefaultsSet = false; - Scripting::init(this); // Create the project @@ -920,6 +921,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // setLayout, but with a visible error message in case of failure. // Use when the user is specifically requesting a layout to open. +// TODO: Update the various functions taking layout IDs to take layout names (to mirror the equivalent map functions, this discrepancy is confusing atm) bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { QMessageBox msgBox(this); @@ -934,11 +936,6 @@ bool MainWindow::userSetLayout(QString layoutId) { } bool MainWindow::setLayout(QString layoutId) { - if (this->editor->map) - logInfo("Switching to layout-only editing mode. Disabling map-related edits."); - - unsetMap(); - // Prefer logging the name of the layout as displayed in the map list. const Layout* layout = this->editor->project ? this->editor->project->mapLayouts.value(layoutId) : nullptr; logInfo(QString("Setting layout to '%1'").arg(layout ? layout->name : layoutId)); @@ -947,6 +944,11 @@ bool MainWindow::setLayout(QString layoutId) { return false; } + if (this->editor->map) + logInfo("Switching to layout-only editing mode. Disabling map-related edits."); + + unsetMap(); + layoutTreeModel->setLayout(layoutId); refreshMapScene(); @@ -1224,6 +1226,7 @@ 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); @@ -1289,7 +1292,9 @@ void MainWindow::mapListAddGroup() { // (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); @@ -1361,10 +1366,10 @@ void MainWindow::mapListAddLayout() { errorMessage = "Name cannot be empty"; } // unique layout name & id - /*else if (this->editor->project->layoutIds.contains(newId->text()) + 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"; - }*/ // TODO: Re-implement + } // from id is existing value else if (useExistingCheck->isChecked()) { if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) { @@ -1400,6 +1405,7 @@ void MainWindow::mapListAddLayout() { Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); setLayout(newLayout->id); } + */ } void MainWindow::mapListAddArea() { @@ -1408,6 +1414,7 @@ void MainWindow::mapListAddArea() { QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + // TODO: This would be a little more seamless with a single line edit that enforces the MAPSEC prefix, rather than a separate label for the actual name. const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); auto newNameEdit = new QLineEdit(&dialog); auto newNameDisplay = new QLabel(&dialog); @@ -1436,10 +1443,8 @@ void MainWindow::mapListAddArea() { QLabel *newNameEditLabel = new QLabel("New Area Name", &dialog); QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); - newNameDisplayLabel->setEnabled(false); QFormLayout form(&dialog); - form.addRow(newNameEditLabel, newNameEdit); form.addRow(newNameDisplayLabel, newNameDisplay); form.addRow("", errorMessageLabel); @@ -1499,11 +1504,10 @@ 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->newMapDefaultsSet) { - NewMapDialog::setDefaultSettings(this->editor->project); - this->newMapDefaultsSet = true; - } if (!this->newMapDialog) { this->newMapDialog = new NewMapDialog(this, this->editor->project); connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); @@ -1518,6 +1522,15 @@ void MainWindow::on_action_NewMap_triggered() { this->newMapDialog->init(); } +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); +} + // Insert label for newly-created tileset into sorted list of existing labels int MainWindow::insertTilesetLabel(QStringList * list, QString label) { int i = 0; diff --git a/src/project.cpp b/src/project.cpp index ccd6c8a2..825c3c48 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -107,6 +107,7 @@ bool Project::load() { && readSongNames() && readMapGroups(); applyParsedLimits(); + initNewMapSettings(); return success; } @@ -368,39 +369,53 @@ bool Project::loadMapData(Map* map) { return true; } -// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout -Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - Layout *layout; +/* +void Project::addNewLayout(Layout* newLayout) { - // Handle the case where we are copying from an existing layout first. - if (!layoutSettings.from_id.isEmpty()) { + 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[layoutSettings.from_id]); - - layout = mapLayouts[layoutSettings.from_id]->copy(); - layout->name = layoutSettings.name; - layout->id = layoutSettings.id; - layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + 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); } - else { - layout = new Layout; +} +*/ - layout->name = layoutSettings.name; - layout->id = layoutSettings.id; - layout->width = layoutSettings.width; - layout->height = layoutSettings.height; - layout->border_width = DEFAULT_BORDER_WIDTH; - layout->border_height = DEFAULT_BORDER_HEIGHT; - layout->tileset_primary_label = layoutSettings.tileset_primary_label; - layout->tileset_secondary_label = layoutSettings.tileset_secondary_label; - layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); +// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout +Layout *Project::createNewLayout(const Layout::Settings &settings) { + Layout *layout = new Layout; + layout->id = settings.id; + layout->name = settings.name; + layout->width = settings.width; + layout->height = settings.height; + layout->border_width = settings.borderWidth; + layout->border_height = settings.borderHeight; + layout->tileset_primary_label = settings.primaryTilesetLabel; + layout->tileset_secondary_label = settings.secondaryTilesetLabel; - setNewLayoutBlockdata(layout); - setNewLayoutBorder(layout); - } + 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); // Create a new directory for the layout QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); @@ -410,16 +425,8 @@ Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { return nullptr; } - // TODO: Redundancy here, some of this is already handled in saveLayout > updateLayout - this->mapLayouts.insert(layout->id, layout); - this->mapLayoutsMaster.insert(layout->id, layout->copy()); - this->layoutIds.append(layout->id); - this->layoutIdsMaster.append(layout->id); - - saveLayout(layout); - - loadLayout(layout); - emit layoutAdded(layout); + addNewLayout(layout); + saveLayout(layout); // TODO: Ideally we shouldn't automatically save new layouts return layout; } @@ -1987,6 +1994,26 @@ QString Project::getNewMapName() { return newMapName; } +QString Project::getNewLayoutName() { + // Ensure default name doesn't already exist. + int i = 0; + QString newLayoutName; + do { + newLayoutName = QString("NewLayout%1").arg(++i); + } while (!isLayoutNameUnique(newLayoutName)); + + return newLayoutName; +} + +bool Project::isLayoutNameUnique(const QString &name) { + for (const auto &layout : this->mapLayouts) { + if (layout->name == name) { + return false; + } + } + return true; +} + Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) { Project::DataQualifiers qualifiers; @@ -3028,6 +3055,32 @@ 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/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp new file mode 100644 index 00000000..0f67545e --- /dev/null +++ b/src/ui/newlayoutdialog.cpp @@ -0,0 +1,160 @@ +#include "newlayoutdialog.h" +#include "maplayout.h" +#include "ui_newlayoutdialog.h" +#include "config.h" + +#include +#include +#include + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewLayoutDialog::NewLayoutDialog(QWidget *parent, Project *project) : + QDialog(parent), + ui(new Ui::NewLayoutDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + ui->setupUi(this); + this->project = project; + this->settings = &project->newMapSettings.layout; + + ui->lineEdit_Name->setText(project->getNewLayoutName()); + + ui->newLayoutForm->initUi(project); + ui->newLayoutForm->setSettings(*this->settings); + + // Names and IDs 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); + adjustSize(); +} + +NewLayoutDialog::~NewLayoutDialog() +{ + saveSettings(); + delete this->importedLayout; + delete ui; +} + +// Creating new map from AdvanceMap import +// TODO: Re-use for a "Duplicate Layout" option? +void NewLayoutDialog::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); +} + +void NewLayoutDialog::saveSettings() { + *this->settings = ui->newLayoutForm->settings(); + this->settings->id = ui->lineEdit_LayoutID->text(); + this->settings->name = ui->lineEdit_Name->text(); +} + +void NewLayoutDialog::useLayoutSettings(Layout *layout) { + if (!layout) return; + this->settings->width = layout->width; + this->settings->height = layout->height; + this->settings->borderWidth = layout->border_width; + this->settings->borderHeight = layout->border_height; + this->settings->primaryTilesetLabel = layout->tileset_primary_label; + this->settings->secondaryTilesetLabel = layout->tileset_secondary_label; + ui->newLayoutForm->setSettings(*this->settings); + + // Don't allow changes to the layout settings + ui->newLayoutForm->setDisabled(true); +} + +bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { + QString id = ui->lineEdit_LayoutID->text(); + + 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); + } + + bool isValid = errorText.isEmpty(); + ui->label_LayoutIDError->setText(errorText); + ui->label_LayoutIDError->setVisible(!isValid); + ui->lineEdit_LayoutID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewLayoutDialog::on_lineEdit_LayoutID_textChanged(const QString &) { + validateLayoutID(true); +} + +bool NewLayoutDialog::validateName(bool allowEmpty) { + QString name = ui->lineEdit_Name->text(); + + 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); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewLayoutDialog::on_lineEdit_Name_textChanged(const QString &text) { + validateName(true); + ui->lineEdit_LayoutID->setText(Layout::layoutConstantFromName(text)); +} + +void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(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); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewLayoutDialog::accept() { + // Make sure to call each validation function so that all errors are shown at once. + bool success = true; + if (!ui->newLayoutForm->validate()) success = false; + if (!validateLayoutID()) success = false; + if (!validateName()) success = false; + if (!success) + return; + + // 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) + return; + + emit applied(layout->id); + QDialog::accept(); +} diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 2f972a52..3b77b5c7 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -10,6 +10,8 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) { ui->setupUi(this); + ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); + // TODO: Read from project? ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); @@ -36,8 +38,6 @@ void NewLayoutForm::initUi(Project *project) { ui->spinBox_MapWidth->setMaximum(m_project->getMaxMapWidth()); ui->spinBox_MapHeight->setMaximum(m_project->getMaxMapHeight()); } - - ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); } void NewLayoutForm::setDisabled(bool disabled) { @@ -46,7 +46,7 @@ void NewLayoutForm::setDisabled(bool disabled) { ui->groupBox_Tilesets->setDisabled(disabled); } -void NewLayoutForm::setSettings(const Settings &settings) { +void NewLayoutForm::setSettings(const Layout::Settings &settings) { ui->spinBox_MapWidth->setValue(settings.width); ui->spinBox_MapHeight->setValue(settings.height); ui->spinBox_BorderWidth->setValue(settings.borderWidth); @@ -55,12 +55,17 @@ void NewLayoutForm::setSettings(const Settings &settings) { ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); } -NewLayoutForm::Settings NewLayoutForm::settings() const { - NewLayoutForm::Settings settings; +Layout::Settings NewLayoutForm::settings() const { + Layout::Settings settings; settings.width = ui->spinBox_MapWidth->value(); settings.height = ui->spinBox_MapHeight->value(); - settings.borderWidth = ui->spinBox_BorderWidth->value(); - settings.borderHeight = ui->spinBox_BorderHeight->value(); + if (ui->groupBox_BorderDimensions->isVisible()) { + settings.borderWidth = ui->spinBox_BorderWidth->value(); + settings.borderHeight = ui->spinBox_BorderHeight->value(); + } else { + settings.borderWidth = DEFAULT_BORDER_WIDTH; + settings.borderHeight = DEFAULT_BORDER_HEIGHT; + } settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); return settings; diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 0a14e552..ef31a7b1 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -10,8 +10,6 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -struct NewMapDialog::Settings NewMapDialog::settings = {}; - NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : QDialog(parent), ui(new Ui::NewMapDialog) @@ -20,21 +18,26 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : setModal(true); ui->setupUi(this); this->project = project; + this->settings = &project->newMapSettings; + // Populate UI using data from project + this->settings->mapName = project->getNewMapName(); ui->newLayoutForm->initUi(project); - ui->comboBox_Group->addItems(project->groupNames); + ui->comboBox_LayoutID->addItems(project->layoutIds); - // Map names and IDs can only contain word characters, and cannot start with a digit. + // Names and IDs 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_MapID->setValidator(validator); ui->comboBox_Group->setValidator(validator); + ui->comboBox_LayoutID->setValidator(validator); // 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); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -44,6 +47,9 @@ 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); + + adjustSize(); // TODO: Save geometry? } NewMapDialog::~NewMapDialog() @@ -54,11 +60,13 @@ NewMapDialog::~NewMapDialog() } void NewMapDialog::init() { - ui->comboBox_Group->setTextItem(settings.group); - ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); - ui->newLayoutForm->setSettings(settings.layout); - this->headerForm->setHeader(&settings.header); - ui->lineEdit_Name->setText(project->getNewMapName()); + 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 @@ -66,16 +74,18 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { switch (tabIndex) { case MapListTab::Groups: - settings.group = fieldName; + this->settings->group = fieldName; ui->label_Group->setDisabled(true); ui->comboBox_Group->setDisabled(true); break; case MapListTab::Areas: - settings.header.setLocation(fieldName); + this->settings->header.setLocation(fieldName); this->headerForm->setLocationsDisabled(true); break; case MapListTab::Layouts: - useLayoutSettings(project->mapLayouts.value(fieldName)); + ui->label_LayoutID->setDisabled(true); + ui->comboBox_LayoutID->setDisabled(true); + useLayoutIdSettings(fieldName); break; } init(); @@ -96,57 +106,47 @@ void NewMapDialog::init(Layout *layoutToCopy) { init(); } -void NewMapDialog::setDefaultSettings(const Project *project) { - settings.group = project->groupNames.at(0); - settings.canFlyTo = false; - // TODO: Layout id - settings.layout.width = project->getDefaultMapDimension(); - settings.layout.height = project->getDefaultMapDimension(); - settings.layout.borderWidth = DEFAULT_BORDER_WIDTH; - settings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; - settings.layout.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); - settings.layout.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); - settings.header.setSong(project->defaultSong); - settings.header.setLocation(project->mapSectionIdNames.value(0, "0")); - settings.header.setRequiresFlash(false); - settings.header.setWeather(project->weatherNames.value(0, "0")); - settings.header.setType(project->mapTypes.value(0, "0")); - settings.header.setBattleScene(project->mapBattleScenes.value(0, "0")); - settings.header.setShowsLocationName(true); - settings.header.setAllowsRunning(false); - settings.header.setAllowsBiking(false); - settings.header.setAllowsEscaping(false); - settings.header.setFloorNumber(0); -} - void NewMapDialog::saveSettings() { - settings.group = ui->comboBox_Group->currentText(); - settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); - settings.layout = ui->newLayoutForm->settings(); - settings.header = this->headerForm->headerData(); + 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(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } -void NewMapDialog::useLayoutSettings(Layout *layout) { - if (!layout) return; - settings.layout.id = layout->id; - settings.layout.width = layout->width; - settings.layout.height = layout->height; - settings.layout.borderWidth = layout->border_width; - settings.layout.borderHeight = layout->border_height; - settings.layout.primaryTilesetLabel = layout->tileset_primary_label; - settings.layout.secondaryTilesetLabel = layout->tileset_secondary_label; +void NewMapDialog::useLayoutSettings(const Layout *layout) { + if (!layout) { + 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(settings.layout.id); + return this->project->mapLayouts.contains(this->settings->layout.id); } -bool NewMapDialog::validateID(bool allowEmpty) { +bool NewMapDialog::validateMapID(bool allowEmpty) { QString id = ui->lineEdit_MapID->text(); const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); @@ -172,7 +172,7 @@ bool NewMapDialog::validateID(bool allowEmpty) { } void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { - validateID(true); + validateMapID(true); } bool NewMapDialog::validateName(bool allowEmpty) { @@ -195,6 +195,9 @@ bool NewMapDialog::validateName(bool allowEmpty) { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); + if (ui->comboBox_LayoutID->isEnabled()) { + ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); + } } bool NewMapDialog::validateGroup(bool allowEmpty) { @@ -221,7 +224,7 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - setDefaultSettings(this->project); // TODO: Don't allow this to change locked settings + this->project->initNewMapSettings(); // TODO: Don't allow this to change locked settings init(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); @@ -229,56 +232,46 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { } void NewMapDialog::accept() { - saveSettings(); - // Make sure to call each validation function so that all errors are shown at once. bool success = true; if (!ui->newLayoutForm->validate()) success = false; - if (!validateID()) success = false; + if (!validateMapID()) success = false; if (!validateName()) success = false; if (!validateGroup()) success = false; if (!success) return; - Map *newMap = new Map; - newMap->setName(ui->lineEdit_Name->text()); - newMap->setConstantName(ui->lineEdit_MapID->text()); - newMap->setHeader(this->headerForm->headerData()); - newMap->setNeedsHealLocation(settings.canFlyTo); + // Update settings from UI + saveSettings(); - Layout *layout; + Map *newMap = new Map; + newMap->setName(this->settings->mapName); + newMap->setConstantName(this->settings->mapId); + newMap->setHeader(this->settings->header); + newMap->setNeedsHealLocation(this->settings->canFlyTo); + + Layout *layout = nullptr; const bool existingLayout = isExistingLayout(); if (existingLayout) { - layout = this->project->mapLayouts.value(settings.layout.id); - newMap->setNeedsLayoutDir(false); + layout = this->project->mapLayouts.value(this->settings->layout.id); + newMap->setNeedsLayoutDir(false); // TODO: Remove this member } else { - layout = new Layout; - layout->id = Layout::layoutConstantFromName(newMap->name()); - layout->name = QString("%1_Layout").arg(newMap->name()); - layout->width = settings.layout.width; - layout->height = settings.layout.height; - if (projectConfig.useCustomBorderSize) { - layout->border_width = settings.layout.borderWidth; - layout->border_height = settings.layout.borderHeight; - } else { - layout->border_width = DEFAULT_BORDER_WIDTH; - layout->border_height = DEFAULT_BORDER_HEIGHT; + /* 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->tileset_primary_label = settings.layout.primaryTilesetLabel; - layout->tileset_secondary_label = settings.layout.secondaryTilesetLabel; - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, newMap->name()); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMap->name()); - } - if (this->importedLayout) { // TODO: This seems at odds with existingLayout. Would it be possible to override an existing layout? - // 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); } + if (!layout) + return; + newMap->setLayout(layout); - this->project->addNewMap(newMap, settings.group); + this->project->addNewMap(newMap, this->settings->group); emit applied(newMap->name()); QDialog::accept(); }