diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 2414918f..9cf4dc18 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -6,491 +6,566 @@ 0 0 - 410 - 687 + 453 + 563 New Map Options - - - - - - false + + + + + true + + + + + 0 + 0 + 427 + 841 + - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - QLayout::SetDefaultConstraint - + - 12 + 10 - + Name - + - <html><head/><body><p>The name of the new map. If the name is invalid (red), it will be replaced with the default name of a new map.</p></body></html> + <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 - - - - Group + + + + Border Dimensions + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + - - + + + + Map Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + 0 + 0 + + + + Height + + + + + + + + - <html><head/><body><p>New map group.</p></body></html> + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> true - - - - - - Map Width - - - - - - - <html><head/><body><p>Width (in blocks) of the new map.</p></body></html> - - - 200 - - - - - - - Map Height + + QComboBox::InsertPolicy::NoInsert - - - <html><head/><body><p>Height (in blocks) of the new map.</p></body></html> - - - 200 - - - - - - - Border Width - - - - - - - <html><head/><body><p>Width (in blocks) of the new map's border.</p></body></html> - - - 255 - - - - - - - Border Height - - - - - - - <html><head/><body><p>Height (in blocks) of the new map's border.</p></body></html> - - - 255 - - - - - - - Primary Tileset - - - - - - - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true - - - - - - - Secondary Tileset - - - - - - - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true - - - - - - - Type - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> - - - true - - - - - - - Location - - - - - - - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> - - - true - - - - - - - Song - - - - - - - <html><head/><body><p>The default background music for this map.</p></body></html> - - - true - - - - - - - Can Fly To - - - - - - - <html><head/><body><p>Whether to add a heal location to the new map.</p></body></html> - - - - - - - - - - Show Location Name - - - - - - - <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> - - - - - - - - - - Allow Running - - - - - - - <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> - - - - - - - - - - Allow Biking - - - - - - - <html><head/><body><p>Allows the player to use a Bike</p></body></html> - - - - - - - - - - Allow Dig & Escape Rope - - - - - - - <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> - - - - - - - - - - Floor Number - - - - - - - <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> - - - 127 - - - - - - + + false - - - - - - Layout - - - - - - - - - Use Existing Layout - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Accept - - - - - - - true - - - - 0 - 0 - - - - false - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - false - - - ! - - - - - - - - 0 - 0 - - 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> + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Header Data + + + + + + Song + + + + + + + <html><head/><body><p>The default background music for this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Location + + + + + + + Requires Flash + + + + + + + Weather + + + + + + + Type + + + + + + + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Battle Scene + + + + + + + Show Location + + + + + + + Allow Running + + + + + + + Allow Biking + + + + + + + Allow Escaping + + + + + + + Floor Number + + + + + + + Can Fly To + + + + + + + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + 127 + + + + + + + <html><head/><body><p>This field is used to help determine what graphics to use in the background of battles on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>The default weather on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + + + + + + + + + + + + + Group + + + + + + + ID + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Tilesets + + + + + + Primary + + + + + + + <html><head/><body><p>The primary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Secondary + + + + + + + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + - - - - - - - 0 - 0 - 410 - 22 - - - - + + + + + + + + Accept + + + + + + @@ -498,6 +573,11 @@ QComboBox
noscrollcombobox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
diff --git a/include/project.h b/include/project.h index a0105bda..1632d38f 100644 --- a/include/project.h +++ b/include/project.h @@ -125,7 +125,7 @@ public: void deleteFile(QString path); bool readMapGroups(); - Map* addNewMapToGroup(QString, int, Map*, bool, bool); + Map* addNewMapToGroup(Map*, int, bool, bool); QString getNewMapName(); QString getProjectTitle(); @@ -234,7 +234,7 @@ public: static int getNumPalettesPrimary(); static int getNumPalettesTotal(); static int getMaxMapDataSize(); - static int getDefaultMapSize(); + static int getDefaultMapDimension(); static int getMaxMapWidth(); static int getMaxMapHeight(); static int getMapDataSize(int width, int height); @@ -260,7 +260,7 @@ private: static int num_pals_primary; static int num_pals_total; static int max_map_data_size; - static int default_map_size; + static int default_map_dimension; static int max_object_events; signals: diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index b87f785e..4b17f32b 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -23,7 +23,7 @@ public: bool importedMap; QString layoutId; void init(); - void initUi(); + //void initUi(); void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -34,8 +34,13 @@ signals: private: Ui::NewMapDialog *ui; Project *project; - bool checkNewMapDimensions(); - bool checkNewMapGroup(); + + bool validateMapDimensions(); + bool validateMapGroup(); + bool validateTilesets(); + bool validateID(); + bool validateName(); + void saveSettings(); void useLayout(QString layoutId); void useLayoutSettings(Layout *mapLayout); @@ -48,23 +53,27 @@ private: int borderHeight; QString primaryTilesetLabel; QString secondaryTilesetLabel; - QString type; - QString location; QString song; - bool canFlyTo; + QString location; + bool requiresFlash; + QString weather; + QString type; + QString battleScene; bool showLocationName; bool allowRunning; bool allowBiking; bool allowEscaping; int floorNumber; + bool canFlyTo; }; static struct Settings settings; private slots: - void on_checkBox_UseExistingLayout_stateChanged(int state); - void on_comboBox_Layout_currentTextChanged(const QString &text); - void on_pushButton_NewMap_Accept_clicked(); - void on_lineEdit_NewMap_Name_textChanged(const QString &); + //void on_checkBox_UseExistingLayout_stateChanged(int state); + //void on_comboBox_Layout_currentTextChanged(const QString &text); + void on_pushButton_Accept_clicked(); + void on_lineEdit_Name_textChanged(const QString &); + void on_lineEdit_ID_textChanged(const QString &); }; #endif // NEWMAPDIALOG_H diff --git a/src/core/map.cpp b/src/core/map.cpp index 33a4a13d..942a4e95 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -39,13 +39,7 @@ QString Map::mapConstantFromName(QString mapName, bool includePrefix) { const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : ""; QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper(); static const QRegularExpression underscores("_+"); - QString constantName = withMapAndUppercase.replace(underscores, "_"); - - // Handle special cases. - // SSTidal needs to be SS_TIDAL, rather than SSTIDAL - constantName = constantName.replace("SSTIDAL", "SS_TIDAL"); - - return constantName; + return withMapAndUppercase.replace(underscores, "_"); } int Map::getWidth() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5f42e9c7..6105a3b4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1598,7 +1598,7 @@ void MainWindow::onNewMapCreated() { bool existingLayout = this->newMapDialog->existingLayout; bool importedMap = this->newMapDialog->importedMap; - newMap = editor->project->addNewMapToGroup(newMapName, newMapGroup, newMap, existingLayout, importedMap); + newMap = editor->project->addNewMapToGroup(newMap, newMapGroup, existingLayout, importedMap); logInfo(QString("Created a new map named %1.").arg(newMapName)); @@ -1657,7 +1657,7 @@ void MainWindow::openNewMapDialog() { void MainWindow::on_action_NewMap_triggered() { openNewMapDialog(); - this->newMapDialog->initUi(); + //this->newMapDialog->initUi();//TODO this->newMapDialog->init(); } diff --git a/src/project.cpp b/src/project.cpp index 4d4c37a8..f996ff1c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -32,7 +32,7 @@ int Project::num_metatiles_primary = 512; int Project::num_pals_primary = 6; int Project::num_pals_total = 13; int Project::max_map_data_size = 10240; // 0x2800 -int Project::default_map_size = 20; +int Project::default_map_dimension = 20; int Project::max_object_events = 64; Project::Project(QObject *parent) : @@ -821,16 +821,16 @@ void Project::saveHealLocations(Map *map) { } // Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h -void Project::saveHealLocationsData(Map *map) { +void Project::saveHealLocationsData(Map *) { +/* TODO: Will be re-implemented as part of changes to reading heal locations from map.json // Update heal locations from map -/* TODO: Re-enable if (map->events[Event::Group::Heal].length() > 0) { for (Event *healEvent : map->events[Event::Group::Heal]) { HealLocation hl = HealLocation::fromEvent(healEvent); this->healLocations[hl.index - 1] = hl; } } -*/ + // Find any duplicate constant names QMap healLocationsDupes; @@ -898,6 +898,7 @@ void Project::saveHealLocationsData(Map *map) { QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::data_heal_locations); ignoreWatchedFileTemporarily(filepath); saveTextFile(filepath, text); + */ } // Saves heal location defines in root + /include/constants/heal_locations.h @@ -1876,6 +1877,7 @@ bool Project::readMapGroups() { this->mapGroups.insert(mapName, groupIndex); this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); + // TODO: Keep these updated this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); } @@ -1898,20 +1900,19 @@ bool Project::readMapGroups() { return true; } -Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool existingLayout, bool importedMap) { +Map* Project::addNewMapToGroup(Map *newMap, int groupNum, bool existingLayout, bool importedMap) { int mapNamePos = 0; for (int i = 0; i <= groupNum; i++) mapNamePos += this->groupedMapNames.value(i).length(); - this->mapNames.insert(mapNamePos, mapName); - this->mapGroups.insert(mapName, groupNum); - this->groupedMapNames[groupNum].append(mapName); - - newMap->setIsPersistedToFile(false); - newMap->setName(mapName); // TODO: Set map name and map constant before calling this function - + this->mapNames.insert(mapNamePos, newMap->name()); + this->mapGroups.insert(newMap->name(), groupNum); + this->groupedMapNames[groupNum].append(newMap->name()); this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); + + newMap->setIsPersistedToFile(false); + if (!existingLayout) { this->mapLayouts.insert(newMap->layoutId(), newMap->layout()); this->mapLayoutsTable.append(newMap->layoutId()); @@ -2891,9 +2892,9 @@ int Project::getMapDataSize(int width, int height) return (width + 15) * (height + 14); } -int Project::getDefaultMapSize() +int Project::getDefaultMapDimension() { - return Project::default_map_size; + return Project::default_map_dimension; } int Project::getMaxMapWidth() @@ -2915,11 +2916,11 @@ bool Project::calculateDefaultMapSize(){ int max = getMaxMapDataSize(); if (max >= getMapDataSize(20, 20)) { - default_map_size = 20; + default_map_dimension = 20; } else if (max >= getMapDataSize(1, 1)) { // Below equation derived from max >= (x + 15) * (x + 14) // x^2 + 29x + (210 - max), then complete the square and simplify - default_map_size = qFloor((qSqrt(4 * getMaxMapDataSize() + 1) - 29) / 2); + default_map_dimension = qFloor((qSqrt(4 * getMaxMapDataSize() + 1) - 29) / 2); } else { logError(QString("'%1' of %2 is too small to support a 1x1 map. Must be at least %3.") .arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_size)) diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index ce6737a8..0d6bdd77 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -8,7 +8,9 @@ #include #include -// TODO: Convert to modal dialog (among other things, this means we wouldn't need to worry about changes to the map list while this is open) +// TODO: Make ui->groupBox_HeaderData collapsible + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; struct NewMapDialog::Settings NewMapDialog::settings = {}; @@ -21,6 +23,16 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : this->project = project; this->existingLayout = false; this->importedMap = false; + + // Map names and IDs can only contain word characters, and cannot start with a digit. + // TODO: Also validate this when we read ProjectIdentifier::define_map_prefix from the config + static const QRegularExpression re("[A-Za-z_]+[\\w]*"); + auto validator = new QRegularExpressionValidator(re, this); + ui->lineEdit_Name->setValidator(validator); + ui->lineEdit_ID->setValidator(validator); + + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); + connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); } NewMapDialog::~NewMapDialog() @@ -29,93 +41,77 @@ NewMapDialog::~NewMapDialog() delete ui; } -void NewMapDialog::initUi() { +void NewMapDialog::init() { // Populate combo boxes - ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels); - ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_NewMap_Group->addItems(project->groupNames); - ui->comboBox_NewMap_Song->addItems(project->songNames); - ui->comboBox_NewMap_Type->addItems(project->mapTypes); - ui->comboBox_NewMap_Location->addItems(project->mapSectionIdNames); - - const QSignalBlocker b(ui->comboBox_Layout); - ui->comboBox_Layout->addItems(project->mapLayoutsTable); - this->layoutId = project->mapLayoutsTable.first(); + ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); + ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); + ui->comboBox_Group->addItems(project->groupNames); + ui->comboBox_Song->addItems(project->songNames); + ui->comboBox_Location->addItems(project->mapSectionIdNames); + ui->comboBox_Weather->addItems(project->weatherNames); + ui->comboBox_Type->addItems(project->mapTypes); + ui->comboBox_BattleScene->addItems(project->mapBattleScenes); // Set spin box limits - ui->spinBox_NewMap_Width->setMinimum(1); - ui->spinBox_NewMap_Height->setMinimum(1); - ui->spinBox_NewMap_Width->setMaximum(project->getMaxMapWidth()); - ui->spinBox_NewMap_Height->setMaximum(project->getMaxMapHeight()); - ui->spinBox_NewMap_BorderWidth->setMinimum(1); - ui->spinBox_NewMap_BorderHeight->setMinimum(1); - ui->spinBox_NewMap_BorderWidth->setMaximum(MAX_BORDER_WIDTH); - ui->spinBox_NewMap_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); - ui->spinBox_NewMap_Floor_Number->setMinimum(-128); - ui->spinBox_NewMap_Floor_Number->setMaximum(127); + ui->spinBox_MapWidth->setMaximum(project->getMaxMapWidth()); + ui->spinBox_MapHeight->setMaximum(project->getMaxMapHeight()); + ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); + ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); // Hide config specific ui elements bool hasFlags = projectConfig.mapAllowFlagsEnabled; - ui->checkBox_NewMap_Allow_Running->setVisible(hasFlags); - ui->checkBox_NewMap_Allow_Biking->setVisible(hasFlags); - ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(hasFlags); - ui->label_NewMap_Allow_Running->setVisible(hasFlags); - ui->label_NewMap_Allow_Biking->setVisible(hasFlags); - ui->label_NewMap_Allow_Escape_Rope->setVisible(hasFlags); + ui->checkBox_AllowRunning->setVisible(hasFlags); + ui->checkBox_AllowBiking->setVisible(hasFlags); + ui->checkBox_AllowEscaping->setVisible(hasFlags); + ui->label_AllowRunning->setVisible(hasFlags); + ui->label_AllowBiking->setVisible(hasFlags); + ui->label_AllowEscaping->setVisible(hasFlags); - bool hasCustomBorders = projectConfig.useCustomBorderSize; - ui->spinBox_NewMap_BorderWidth->setVisible(hasCustomBorders); - ui->spinBox_NewMap_BorderHeight->setVisible(hasCustomBorders); - ui->label_NewMap_BorderWidth->setVisible(hasCustomBorders); - ui->label_NewMap_BorderHeight->setVisible(hasCustomBorders); + ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); bool hasFloorNumber = projectConfig.floorNumberEnabled; - ui->spinBox_NewMap_Floor_Number->setVisible(hasFloorNumber); - ui->label_NewMap_Floor_Number->setVisible(hasFloorNumber); + ui->spinBox_FloorNumber->setVisible(hasFloorNumber); + ui->label_FloorNumber->setVisible(hasFloorNumber); - this->updateGeometry(); -} - -void NewMapDialog::init() { // Restore previous settings - ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); - ui->comboBox_NewMap_Group->setTextItem(settings.group); - ui->spinBox_NewMap_Width->setValue(settings.width); - ui->spinBox_NewMap_Height->setValue(settings.height); - ui->spinBox_NewMap_BorderWidth->setValue(settings.borderWidth); - ui->spinBox_NewMap_BorderHeight->setValue(settings.borderHeight); - ui->comboBox_NewMap_Primary_Tileset->setTextItem(settings.primaryTilesetLabel); - ui->comboBox_NewMap_Secondary_Tileset->setTextItem(settings.secondaryTilesetLabel); - ui->comboBox_NewMap_Type->setTextItem(settings.type); - ui->comboBox_NewMap_Location->setTextItem(settings.location); - ui->comboBox_NewMap_Song->setTextItem(settings.song); - ui->checkBox_NewMap_Flyable->setChecked(settings.canFlyTo); - ui->checkBox_NewMap_Show_Location->setChecked(settings.showLocationName); - ui->checkBox_NewMap_Allow_Running->setChecked(settings.allowRunning); - ui->checkBox_NewMap_Allow_Biking->setChecked(settings.allowBiking); - ui->checkBox_NewMap_Allow_Escape_Rope->setChecked(settings.allowEscaping); - ui->spinBox_NewMap_Floor_Number->setValue(settings.floorNumber); - - // Connect signals - connect(ui->spinBox_NewMap_Width, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); - connect(ui->spinBox_NewMap_Height, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); - - ui->frame_NewMap_Options->setEnabled(true); + ui->lineEdit_Name->setText(project->getNewMapName()); + ui->comboBox_Group->setTextItem(settings.group); + ui->spinBox_MapWidth->setValue(settings.width); + ui->spinBox_MapHeight->setValue(settings.height); + ui->spinBox_BorderWidth->setValue(settings.borderWidth); + ui->spinBox_BorderHeight->setValue(settings.borderHeight); + ui->comboBox_PrimaryTileset->setTextItem(settings.primaryTilesetLabel); + ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); + ui->comboBox_Song->setTextItem(settings.song); + ui->comboBox_Location->setTextItem(settings.location); + ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); + ui->comboBox_Weather->setTextItem(settings.weather); + ui->comboBox_Type->setTextItem(settings.type); + ui->comboBox_BattleScene->setTextItem(settings.battleScene); + ui->checkBox_ShowLocation->setChecked(settings.showLocationName); + ui->checkBox_AllowRunning->setChecked(settings.allowRunning); + ui->checkBox_AllowBiking->setChecked(settings.allowBiking); + ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); + ui->spinBox_FloorNumber->setValue(settings.floorNumber); + ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); } // Creating new map by right-clicking in the map list void NewMapDialog::init(int tabIndex, QString fieldName) { - initUi(); + //initUi(); switch (tabIndex) { case MapListTab::Groups: settings.group = fieldName; + //ui->label_Group->setDisabled(true); + //ui->comboBox_Group->setDisabled(true); break; case MapListTab::Areas: settings.location = fieldName; + //ui->label_Location->setDisabled(true); + //ui->comboBox_Location->setDisabled(true); break; case MapListTab::Layouts: - this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked); useLayout(fieldName); break; } @@ -123,234 +119,268 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { } // Creating new map from AdvanceMap import -void NewMapDialog::init(Layout *mapLayout) { - initUi(); +void NewMapDialog::init(Layout *layout) { this->importedMap = true; - useLayoutSettings(mapLayout); + useLayoutSettings(layout); + // TODO: These are probably leaking this->map = new Map(); this->map->setLayout(new Layout()); - this->map->layout()->blockdata = mapLayout->blockdata; + this->map->layout()->blockdata = layout->blockdata; - if (!mapLayout->border.isEmpty()) { - this->map->layout()->border = mapLayout->border; + if (!layout->border.isEmpty()) { + this->map->layout()->border = layout->border; } init(); } -bool NewMapDialog::checkNewMapDimensions() { - int numMetatiles = project->getMapDataSize(ui->spinBox_NewMap_Width->value(), ui->spinBox_NewMap_Height->value()); - int maxMetatiles = project->getMaxMapDataSize(); - - if (numMetatiles > maxMetatiles) { - ui->frame_NewMap_Warning->setVisible(true); - QString errorText = QString("Error: The specified width and height are too large.\n" - "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) - .arg(ui->spinBox_NewMap_Width->value()) - .arg(ui->spinBox_NewMap_Height->value()) - .arg(numMetatiles); - ui->label_NewMap_WarningMessage->setText(errorText); - ui->label_NewMap_WarningMessage->setWordWrap(true); - return false; - } - else { - ui->frame_NewMap_Warning->setVisible(false); - ui->label_NewMap_WarningMessage->clear(); - return true; - } -} - -bool NewMapDialog::checkNewMapGroup() { - group = project->groupNames.indexOf(this->ui->comboBox_NewMap_Group->currentText()); - - if (group < 0) { - ui->frame_NewMap_Warning->setVisible(true); - QString errorText = QString("Error: The specified map group '%1' does not exist.") - .arg(ui->comboBox_NewMap_Group->currentText()); - ui->label_NewMap_WarningMessage->setText(errorText); - ui->label_NewMap_WarningMessage->setWordWrap(true); - return false; - } else { - ui->frame_NewMap_Warning->setVisible(false); - ui->label_NewMap_WarningMessage->clear(); - return true; - } -} - void NewMapDialog::setDefaultSettings(Project *project) { settings.group = project->groupNames.at(0); - settings.width = project->getDefaultMapSize(); - settings.height = project->getDefaultMapSize(); + settings.width = project->getDefaultMapDimension(); + settings.height = project->getDefaultMapDimension(); settings.borderWidth = DEFAULT_BORDER_WIDTH; settings.borderHeight = DEFAULT_BORDER_HEIGHT; settings.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); settings.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); - settings.type = project->mapTypes.value(0, "0"); - settings.location = project->mapSectionIdNames.value(0, "0"); settings.song = project->defaultSong; - settings.canFlyTo = false; + settings.location = project->mapSectionIdNames.value(0, "0"); + settings.requiresFlash = false; + settings.weather = project->weatherNames.value(0, "0"); + settings.type = project->mapTypes.value(0, "0"); + settings.battleScene = project->mapBattleScenes.value(0, "0"); settings.showLocationName = true; settings.allowRunning = false; settings.allowBiking = false; settings.allowEscaping = false; settings.floorNumber = 0; + settings.canFlyTo = false; } void NewMapDialog::saveSettings() { - settings.group = ui->comboBox_NewMap_Group->currentText(); - settings.width = ui->spinBox_NewMap_Width->value(); - settings.height = ui->spinBox_NewMap_Height->value(); - settings.borderWidth = ui->spinBox_NewMap_BorderWidth->value(); - settings.borderHeight = ui->spinBox_NewMap_BorderHeight->value(); - settings.primaryTilesetLabel = ui->comboBox_NewMap_Primary_Tileset->currentText(); - settings.secondaryTilesetLabel = ui->comboBox_NewMap_Secondary_Tileset->currentText(); - settings.type = ui->comboBox_NewMap_Type->currentText(); - settings.location = ui->comboBox_NewMap_Location->currentText(); - settings.song = ui->comboBox_NewMap_Song->currentText(); - settings.canFlyTo = ui->checkBox_NewMap_Flyable->isChecked(); - settings.showLocationName = ui->checkBox_NewMap_Show_Location->isChecked(); - settings.allowRunning = ui->checkBox_NewMap_Allow_Running->isChecked(); - settings.allowBiking = ui->checkBox_NewMap_Allow_Biking->isChecked(); - settings.allowEscaping = ui->checkBox_NewMap_Allow_Escape_Rope->isChecked(); - settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value(); + settings.group = ui->comboBox_Group->currentText(); + settings.width = ui->spinBox_MapWidth->value(); + settings.height = ui->spinBox_MapHeight->value(); + settings.borderWidth = ui->spinBox_BorderWidth->value(); + settings.borderHeight = ui->spinBox_BorderHeight->value(); + settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); + settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); + settings.song = ui->comboBox_Song->currentText(); + settings.location = ui->comboBox_Location->currentText(); + settings.requiresFlash = ui->checkBox_RequiresFlash->isChecked(); + settings.weather = ui->comboBox_Weather->currentText(); + settings.type = ui->comboBox_Type->currentText(); + settings.battleScene = ui->comboBox_BattleScene->currentText(); + settings.showLocationName = ui->checkBox_ShowLocation->isChecked(); + settings.allowRunning = ui->checkBox_AllowRunning->isChecked(); + settings.allowBiking = ui->checkBox_AllowBiking->isChecked(); + settings.allowEscaping = ui->checkBox_AllowEscaping->isChecked(); + settings.floorNumber = ui->spinBox_FloorNumber->value(); + settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); } void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; - settings.width = layout->width; - ui->spinBox_NewMap_Width->setValue(layout->width); - settings.height = layout->height; - ui->spinBox_NewMap_Height->setValue(layout->height); - settings.borderWidth = layout->border_width; - ui->spinBox_NewMap_BorderWidth->setValue(layout->border_width); - settings.borderHeight = layout->border_height; - ui->spinBox_NewMap_BorderWidth->setValue(layout->border_height); - settings.primaryTilesetLabel = layout->tileset_primary_label; - ui->comboBox_NewMap_Primary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Primary_Tileset->findText(layout->tileset_primary_label)); - settings.secondaryTilesetLabel = layout->tileset_secondary_label; - ui->comboBox_NewMap_Secondary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Secondary_Tileset->findText(layout->tileset_secondary_label)); } void NewMapDialog::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; - - this->ui->comboBox_Layout->setCurrentIndex(this->ui->comboBox_Layout->findText(layoutId)); - useLayoutSettings(project->mapLayouts.value(this->layoutId)); + + // Dimensions and tilesets can't be changed for new maps using an existing layout + ui->groupBox_MapDimensions->setDisabled(true); + ui->groupBox_BorderDimensions->setDisabled(true); + ui->groupBox_Tilesets->setDisabled(true); } -void NewMapDialog::on_checkBox_UseExistingLayout_stateChanged(int state) { - bool layoutEditsEnabled = (state == Qt::Unchecked); - - this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled); +bool NewMapDialog::validateMapDimensions() { + int size = project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); + int maxSize = project->getMaxMapDataSize(); - this->ui->spinBox_NewMap_Width->setEnabled(layoutEditsEnabled); - this->ui->spinBox_NewMap_Height->setEnabled(layoutEditsEnabled); - this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled); - this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled); - this->ui->comboBox_NewMap_Primary_Tileset->setEnabled(layoutEditsEnabled); - this->ui->comboBox_NewMap_Secondary_Tileset->setEnabled(layoutEditsEnabled); + QString errorText; + if (size > maxSize) { + errorText = QString("The specified width and height are too large.\n" + "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" + "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") + .arg(maxSize) + .arg(ui->spinBox_MapWidth->value()) + .arg(ui->spinBox_MapHeight->value()) + .arg(size); + } - if (!layoutEditsEnabled) { - useLayout(this->layoutId);//this->ui->comboBox_Layout->currentText()); + bool isValid = errorText.isEmpty(); + ui->label_MapDimensionsError->setText(errorText); + ui->label_MapDimensionsError->setVisible(!isValid); + return isValid; +} + +bool NewMapDialog::validateMapGroup() { + this->group = project->groupNames.indexOf(ui->comboBox_Group->currentText()); + + QString errorText; + if (this->group < 0) { + errorText = QString("The specified map group '%1' does not exist.") + .arg(ui->comboBox_Group->currentText()); + } + + bool isValid = errorText.isEmpty(); + ui->label_GroupError->setText(errorText); + ui->label_GroupError->setVisible(!isValid); + return isValid; +} + +bool NewMapDialog::validateTilesets() { + QString primaryTileset = ui->comboBox_PrimaryTileset->currentText(); + QString secondaryTileset = ui->comboBox_SecondaryTileset->currentText(); + + QString primaryErrorText; + if (primaryTileset.isEmpty()) { + primaryErrorText = QString("The primary tileset cannot be empty."); + } else if (ui->comboBox_PrimaryTileset->findText(primaryTileset) < 0) { + primaryErrorText = QString("The specified primary tileset '%1' does not exist.").arg(primaryTileset); + } + + QString secondaryErrorText; + if (secondaryTileset.isEmpty()) { + secondaryErrorText = QString("The secondary tileset cannot be empty."); + } else if (ui->comboBox_SecondaryTileset->findText(secondaryTileset) < 0) { + secondaryErrorText = QString("The specified secondary tileset '%2' does not exist.").arg(secondaryTileset); + } + + QString errorText = QString("%1%2%3") + .arg(primaryErrorText) + .arg(!primaryErrorText.isEmpty() ? "\n" : "") + .arg(secondaryErrorText); + + bool isValid = errorText.isEmpty(); + ui->label_TilesetsError->setText(errorText); + ui->label_TilesetsError->setVisible(!isValid); + return isValid; +} + +bool NewMapDialog::validateID() { + QString id = ui->lineEdit_ID->text(); + + QString errorText; + QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + if (!id.startsWith(expectedPrefix)) { + errorText = QString("The specified ID name '%1' must start with '%2'.").arg(id).arg(expectedPrefix); } else { - this->existingLayout = false; + for (auto i = project->mapNamesToMapConstants.constBegin(), end = project->mapNamesToMapConstants.constEnd(); i != end; i++) { + if (id == i.value()) { + errorText = QString("The specified ID name '%1' is already in use.").arg(id); + break; + } + } } + + bool isValid = errorText.isEmpty(); + ui->label_IDError->setText(errorText); + ui->label_IDError->setVisible(!isValid); + ui->lineEdit_ID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; } -void NewMapDialog::on_comboBox_Layout_currentTextChanged(const QString &text) { - if (this->project->mapLayoutsTable.contains(text)) { - useLayout(text); - } +void NewMapDialog::on_lineEdit_ID_textChanged(const QString &) { + validateID(); } -void NewMapDialog::on_lineEdit_NewMap_Name_textChanged(const QString &text) { - if (project->mapNames.contains(text)) { - this->ui->lineEdit_NewMap_Name->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); - } else { - this->ui->lineEdit_NewMap_Name->setStyleSheet(""); +bool NewMapDialog::validateName() { + QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (project->mapNames.contains(name)) { + errorText = QString("The specified map name '%1' is already in use.").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 NewMapDialog::on_pushButton_NewMap_Accept_clicked() { - if (!checkNewMapDimensions() || !checkNewMapGroup()) { - // ignore when map dimensions or map group are invalid +void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { + validateName(); + ui->lineEdit_ID->setText(Map::mapConstantFromName(text)); +} + +void NewMapDialog::on_pushButton_Accept_clicked() { + // Make sure to call each validation function so that all errors are shown at once. + bool success = true; + if (!validateMapDimensions()) success = false; + if (!validateMapGroup()) success = false; + if (!validateTilesets()) success = false; + if (!validateID()) success = false; + if (!validateName()) success = false; + if (!success) + return; + + // We check if the map name is empty separately from the validation above because it's likely + // that users will clear the name text box while editing, and we don't want to flash errors at them for this. + if (ui->lineEdit_Name->text().isEmpty()) { + ui->label_NameError->setText("The specified map name cannot be empty."); + ui->label_NameError->setVisible(true); + ui->lineEdit_Name->setStyleSheet(lineEdit_ErrorStylesheet); return; } + Map *newMap = new Map; - Layout *layout; - - // If map name is not unique, use default value. Also use only valid characters. - // After stripping invalid characters, strip any leading digits. - static const QRegularExpression re_invalidChars("[^a-zA-Z0-9_]+"); - QString newMapName = this->ui->lineEdit_NewMap_Name->text().remove(re_invalidChars); - static const QRegularExpression re_NaN("^[0-9]*"); - newMapName.remove(re_NaN); - if (project->mapNames.contains(newMapName) || newMapName.isEmpty()) { - newMapName = project->getNewMapName(); + newMap->setName(ui->lineEdit_Name->text()); + newMap->setConstantName(ui->lineEdit_ID->text()); + newMap->setSong(ui->comboBox_Song->currentText()); + newMap->setLocation(ui->comboBox_Location->currentText()); + newMap->setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); + newMap->setWeather(ui->comboBox_Weather->currentText()); + newMap->setType(ui->comboBox_Type->currentText()); + newMap->setBattleScene(ui->comboBox_BattleScene->currentText()); + newMap->setShowsLocation(ui->checkBox_ShowLocation->isChecked()); + if (projectConfig.mapAllowFlagsEnabled) { + newMap->setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); + newMap->setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); + newMap->setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); } + if (projectConfig.floorNumberEnabled) { + newMap->setFloorNumber(ui->spinBox_FloorNumber->value()); + } + newMap->setNeedsHealLocation(ui->checkBox_CanFlyTo->isChecked()); - newMap->setName(newMapName); - newMap->setType(this->ui->comboBox_NewMap_Type->currentText()); - newMap->setLocation(this->ui->comboBox_NewMap_Location->currentText()); - newMap->setSong(this->ui->comboBox_NewMap_Song->currentText()); - newMap->setRequiresFlash(false); - newMap->setWeather(this->project->weatherNames.value(0, "0")); - newMap->setShowsLocation(this->ui->checkBox_NewMap_Show_Location->isChecked()); - newMap->setBattleScene(this->project->mapBattleScenes.value(0, "0")); - + Layout *layout; if (this->existingLayout) { layout = this->project->mapLayouts.value(this->layoutId); newMap->setNeedsLayoutDir(false); } else { layout = new Layout; - layout->id = Layout::layoutConstantFromName(newMapName); + layout->id = Layout::layoutConstantFromName(newMap->name()); layout->name = QString("%1_Layout").arg(newMap->name()); - layout->width = this->ui->spinBox_NewMap_Width->value(); - layout->height = this->ui->spinBox_NewMap_Height->value(); + layout->width = ui->spinBox_MapWidth->value(); + layout->height = ui->spinBox_MapHeight->value(); if (projectConfig.useCustomBorderSize) { - layout->border_width = this->ui->spinBox_NewMap_BorderWidth->value(); - layout->border_height = this->ui->spinBox_NewMap_BorderHeight->value(); + layout->border_width = ui->spinBox_BorderWidth->value(); + layout->border_height = ui->spinBox_BorderHeight->value(); } else { layout->border_width = DEFAULT_BORDER_WIDTH; layout->border_height = DEFAULT_BORDER_HEIGHT; } - layout->tileset_primary_label = this->ui->comboBox_NewMap_Primary_Tileset->currentText(); - layout->tileset_secondary_label = this->ui->comboBox_NewMap_Secondary_Tileset->currentText(); + layout->tileset_primary_label = ui->comboBox_PrimaryTileset->currentText(); + layout->tileset_secondary_label = ui->comboBox_SecondaryTileset->currentText(); QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, newMapName); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMapName); + 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->importedMap) { layout->blockdata = map->layout()->blockdata; if (!map->layout()->border.isEmpty()) layout->border = map->layout()->border; } - - if (this->ui->checkBox_NewMap_Flyable->isChecked()) { - newMap->setNeedsHealLocation(true); - } - - if (projectConfig.mapAllowFlagsEnabled) { - newMap->setAllowsRunning(this->ui->checkBox_NewMap_Allow_Running->isChecked()); - newMap->setAllowsBiking(this->ui->checkBox_NewMap_Allow_Biking->isChecked()); - newMap->setAllowsEscaping(this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked()); - } - if (projectConfig.floorNumberEnabled) { - newMap->setFloorNumber(this->ui->spinBox_NewMap_Floor_Number->value()); - } - newMap->setLayout(layout); + if (this->existingLayout) { project->loadMapLayout(newMap); }