From 61256d39ca5f519c4d4f9b8b1fcb3951a22a14ec Mon Sep 17 00:00:00 2001 From: garak Date: Mon, 23 Jan 2023 12:19:21 -0500 Subject: [PATCH 001/364] reorganize some class data --- include/core/map.h | 35 +++++++++++++++++++++++++++++------ include/core/maplayout.h | 15 +++++++++++++++ include/editor.h | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 779afefe..d0617c43 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -39,6 +39,7 @@ public: public: QString name; QString constantName; + QString song; QString layoutId; QString location; @@ -51,14 +52,20 @@ public: bool allowEscaping; int floorNumber = 0; QString battle_scene; + QString sharedEventsMap = ""; QString sharedScriptsMap = ""; + QMap customHeaders; + MapLayout *layout; + bool isPersistedToFile = true; bool hasUnsavedDataChanges = false; + bool needsLayoutDir = true; bool needsHealLocation = false; + QImage collision_image; QPixmap collision_pixmap; QImage image; @@ -68,42 +75,62 @@ public: QList ownedEvents; // for memory management QList connections; + QList metatileLayerOrder; QList metatileLayerOpacity; + void setName(QString mapName); static QString mapConstantFromName(QString mapName); + + /// !HERE /* layout related stuff */ int getWidth(); int getHeight(); int getBorderWidth(); int getBorderHeight(); + + QUndoStack editHistory; + void modify(); + void clean(); + QPixmap render(bool ignoreCache = false, MapLayout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); QPixmap renderCollision(bool ignoreCache); + QPixmap renderConnection(MapConnection, MapLayout *); + QPixmap renderBorder(bool ignoreCache = false); + bool mapBlockChanged(int i, const Blockdata &cache); bool borderBlockChanged(int i, const Blockdata &cache); + void cacheBlockdata(); void cacheCollision(); + bool getBlock(int x, int y, Block *out); void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); + uint16_t getBorderMetatileId(int x, int y); void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); + void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + QList getAllEvents() const; QStringList eventScriptLabels(Event::Group group = Event::Group::None) const; void removeEvent(Event *); void addEvent(Event *); - QPixmap renderConnection(MapConnection, MapLayout *); - QPixmap renderBorder(bool ignoreCache = false); + void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); + void clearBorderCache(); void cacheBorder(); + bool hasUnsavedChanges(); + bool isWithinBounds(int x, int y); bool isWithinBorderBounds(int x, int y); + void openScript(QString label); MapPixmapItem *mapItem = nullptr; @@ -115,10 +142,6 @@ public: BorderMetatilesPixmapItem *borderItem = nullptr; void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } - QUndoStack editHistory; - void modify(); - void clean(); - private: void setNewDimensionsBlockdata(int newWidth, int newHeight); void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 13215fff..41fa6946 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -8,25 +8,40 @@ #include #include +class Map; + class MapLayout { public: MapLayout() {} + static QString layoutConstantFromName(QString mapName); + + /// !TODO + /* NEW */ + QList maps; + QString id; QString name; + int width; int height; int border_width; int border_height; + QString border_path; QString blockdata_path; + QString tileset_primary_label; QString tileset_secondary_label; + Tileset *tileset_primary = nullptr; Tileset *tileset_secondary = nullptr; + Blockdata blockdata; + QImage border_image; QPixmap border_pixmap; + Blockdata border; Blockdata cached_blockdata; Blockdata cached_collision; diff --git a/include/editor.h b/include/editor.h index 2282fba7..0b0bc591 100644 --- a/include/editor.h +++ b/include/editor.h @@ -43,15 +43,26 @@ public: public: Ui::MainWindow* ui; QObject *parent = nullptr; + Project *project = nullptr; Map *map = nullptr; + MapLayout *layout = nullptr; /* NEW */ + + QUndoGroup editGroup; // Manages the undo history for each map + Settings *settings; - void saveProject(); + void save(); - void closeProject(); - bool setMap(QString map_name); + void saveProject(); void saveUiFields(); void saveEncounterTabData(); + + void closeProject(); + + bool setMap(QString map_name); + + Tileset *getCurrentMapPrimaryTileset(); + bool displayMap(); void displayMetatileSelector(); void displayMapMetatiles(); @@ -74,6 +85,7 @@ public: void setEditingObjects(); void setEditingConnections(); void setMapEditingButtonsEnabled(bool enabled); + void setCurrentConnectionDirection(QString curDirection); void updateCurrentConnectionDirection(QString curDirection); void setConnectionsVisibility(bool visible); @@ -81,19 +93,21 @@ public: void setConnectionMap(QString mapName); void addNewConnection(); void removeCurrentConnection(); - void addNewWildMonGroup(QWidget *window); - void deleteWildMonGroup(); void updateDiveMap(QString mapName); void updateEmergeMap(QString mapName); void setSelectedConnectionFromMap(QString mapName); + + void addNewWildMonGroup(QWidget *window); + void deleteWildMonGroup(); + void configureEncounterJSON(QWidget *); + void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false); void updateSecondaryTileset(QString tilesetLabel, bool forceLoad = false); void toggleBorderVisibility(bool visible, bool enableScriptCallback = true); void updateCustomMapHeaderValues(QTableWidget *); - void configureEncounterJSON(QWidget *); - Tileset *getCurrentMapPrimaryTileset(); DraggablePixmapItem *addMapEvent(Event *event); + bool eventLimitReached(Map *, Event::Type); void selectMapEvent(DraggablePixmapItem *object); void selectMapEvent(DraggablePixmapItem *object, bool toggle); DraggablePixmapItem *addNewEvent(Event::Type type); @@ -101,10 +115,11 @@ public: void duplicateSelectedEvents(); void redrawObject(DraggablePixmapItem *item); QList getObjects(); + void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); - bool eventLimitReached(Map *, Event::Type); + QGraphicsScene *scene = nullptr; QGraphicsPixmapItem *current_view = nullptr; @@ -114,17 +129,20 @@ public: QGraphicsPathItem *connection_mask = nullptr; CollisionPixmapItem *collision_item = nullptr; QGraphicsItemGroup *events_group = nullptr; + QList borderItems; QList gridLines; + MapRuler *map_ruler = nullptr; + MovableRect *playerViewRect = nullptr; CursorTileRect *cursorMapTileRect = nullptr; - MapRuler *map_ruler = nullptr; QGraphicsScene *scene_metatiles = nullptr; QGraphicsScene *scene_current_metatile_selection = nullptr; QGraphicsScene *scene_selected_border_metatiles = nullptr; QGraphicsScene *scene_collision_metatiles = nullptr; QGraphicsScene *scene_elevation_metatiles = nullptr; + MetatileSelector *metatile_selector_item = nullptr; BorderMetatilesPixmapItem *selected_border_metatiles_item = nullptr; @@ -133,6 +151,7 @@ public: QList *selected_events = nullptr; + /// !TODO this QString map_edit_mode = "paint"; QString obj_edit_mode = "select"; @@ -143,8 +162,6 @@ public: int getBorderDrawDistance(int dimension); - QUndoGroup editGroup; // Manages the undo history for each map - bool selectingEvent = false; void shouldReselectEvents(); From 917e61b98a68c8cd50a9130a34b3d9487a130a0e Mon Sep 17 00:00:00 2001 From: garak Date: Mon, 30 Jan 2023 18:47:37 -0500 Subject: [PATCH 002/364] add different tabs for map list views --- forms/mainwindow.ui | 614 ++++++++++++++++++++++++++----------- include/mainwindow.h | 35 ++- include/ui/maplistmodels.h | 58 ++++ porymap.pro | 2 + src/mainwindow.cpp | 535 +++++++++++++++++--------------- src/ui/maplistmodels.cpp | 219 +++++++++++++ 6 files changed, 1008 insertions(+), 455 deletions(-) create mode 100644 include/ui/maplistmodels.h create mode 100644 src/ui/maplistmodels.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 7b8aa8b4..fb340222 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -32,7 +32,7 @@ Qt::Horizontal - + true @@ -42,168 +42,414 @@ 0 - - - 0 - - - 0 - - - 0 - - - 3 - - - 0 - - - - - 0 - - - 3 - - - 3 - - - 3 - - - - - true - - - <html><head/><body><p>Sort map list</p></body></html> - - - - :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - - - - 16 - 16 - - - - QToolButton::InstantPopup - - - Qt::ToolButtonIconOnly - - - true - - - Qt::NoArrow - - - - - - - <html><head/><body><p>Expand all map folders</p></body></html> - - - - - - - :/icons/expand_all.ico:/icons/expand_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Collapse all map list folders</p></body></html> - - - - - - - :/icons/collapse_all.ico:/icons/collapse_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 12 - 20 - - - - - - - - true - - - - - - Filter maps... - - - true - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectItems - - - false - - - - + + 0 + + + + Groups + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + 0 + + + 3 + + + 3 + + + 3 + + + + + <html><head/><body><p>Expand all map folders</p></body></html> + + + + + + + :/icons/expand_all.ico:/icons/expand_all.ico + + + QToolButton::InstantPopup + + + true + + + + + + + <html><head/><body><p>Collapse all map list folders</p></body></html> + + + + + + + :/icons/collapse_all.ico:/icons/collapse_all.ico + + + QToolButton::InstantPopup + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 12 + 20 + + + + + + + + true + + + + + + Filter... + + + true + + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectItems + + + false + + + + + + + + Areas + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + 0 + + + 3 + + + 3 + + + 3 + + + + + <html><head/><body><p>Expand all map folders</p></body></html> + + + + + + + :/icons/expand_all.ico:/icons/expand_all.ico + + + QToolButton::InstantPopup + + + true + + + + + + + <html><head/><body><p>Collapse all map list folders</p></body></html> + + + + + + + :/icons/collapse_all.ico:/icons/collapse_all.ico + + + QToolButton::InstantPopup + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 12 + 20 + + + + + + + + true + + + + + + Filter... + + + true + + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectItems + + + false + + + + + + + + Layouts + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + 0 + + + 3 + + + 3 + + + 3 + + + + + <html><head/><body><p>Expand all layout folders</p></body></html> + + + + + + + :/icons/expand_all.ico:/icons/expand_all.ico + + + QToolButton::InstantPopup + + + true + + + + + + + <html><head/><body><p>Collapse all layout folders</p></body></html> + + + + + + + :/icons/collapse_all.ico:/icons/collapse_all.ico + + + QToolButton::InstantPopup + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 12 + 20 + + + + + + + + true + + + + + + Filter... + + + true + + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectItems + + + false + + + + + @@ -260,7 +506,7 @@ - 0 + 3 false @@ -813,7 +1059,7 @@ 0 0 - 423 + 256 74 @@ -1001,10 +1247,10 @@ - 8 + 0 0 - 411 - 413 + 91 + 74 @@ -1154,8 +1400,8 @@ 0 0 - 428 - 696 + 92 + 550 @@ -1314,8 +1560,8 @@ 0 0 - 398 - 631 + 91 + 460 @@ -1615,8 +1861,8 @@ 0 0 - 434 - 581 + 100 + 16 @@ -1709,8 +1955,8 @@ 0 0 - 434 - 581 + 100 + 16 @@ -1803,8 +2049,8 @@ 0 0 - 434 - 581 + 100 + 16 @@ -1903,8 +2149,8 @@ 0 0 - 434 - 581 + 100 + 16 @@ -1997,8 +2243,8 @@ 0 0 - 434 - 581 + 100 + 16 @@ -2051,8 +2297,8 @@ 0 0 - 434 - 625 + 100 + 30 diff --git a/include/mainwindow.h b/include/mainwindow.h index 0f1eeac7..09d39f3a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -21,6 +21,7 @@ #include "regionmapeditor.h" #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" +#include "maplistmodels.h" #include "newmappopup.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" @@ -257,8 +258,6 @@ private slots: void on_actionTileset_Editor_triggered(); - void mapSortOrder_changed(QAction *action); - void on_lineEdit_filterBox_textChanged(const QString &arg1); void moveEvent(QMoveEvent *event); @@ -267,8 +266,14 @@ private slots: void eventTabChanged(int index); void on_horizontalSlider_CollisionTransparency_valueChanged(int value); - void on_toolButton_ExpandAll_clicked(); - void on_toolButton_CollapseAll_clicked(); + + void on_toolButton_ExpandAll_Groups_clicked(); + void on_toolButton_CollapseAll_Groups_clicked(); + void on_toolButton_ExpandAll_Areas_clicked(); + void on_toolButton_CollapseAll_Areas_clicked(); + void on_toolButton_ExpandAll_Layouts_clicked(); + void on_toolButton_CollapseAll_Layouts_clicked(); + void on_actionAbout_Porymap_triggered(); void on_actionOpen_Log_File_triggered(); void on_actionOpen_Config_Folder_triggered(); @@ -302,13 +307,15 @@ private: QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer customScriptsEditor = nullptr; - FilterChildrenProxyModel *mapListProxyModel; - QStandardItemModel *mapListModel; - QList *mapGroupItemsList; - QMap mapListIndexes; - QIcon* mapIcon; - QIcon* mapEditedIcon; - QIcon* mapOpenedIcon; + + FilterChildrenProxyModel *groupListProxyModel; + MapGroupModel *mapGroupModel; + // QStandardItemModel *mapListModel; + // QList *mapGroupItemsList; + // QMap mapListIndexes; + // QIcon* mapIcon; + // QIcon* mapEditedIcon; + // QIcon* mapOpenedIcon; QAction *undoAction = nullptr; QAction *redoAction = nullptr; @@ -397,10 +404,4 @@ private: int insertTilesetLabel(QStringList * list, QString label); }; -enum MapListUserRoles { - GroupRole = Qt::UserRole + 1, // Used to hold the map group number. - TypeRole, // Used to differentiate between the different layers of the map list tree view. - TypeRole2, // Used for various extra data needed. -}; - #endif // MAINWINDOW_H diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h new file mode 100644 index 00000000..972c8668 --- /dev/null +++ b/include/ui/maplistmodels.h @@ -0,0 +1,58 @@ +#pragma once +#ifndef MAPLISTMODELS_H +#define MAPLISTMODELS_H + +#include +#include + + + +class Project; + +enum MapListRoles { + GroupRole = Qt::UserRole + 1, // Used to hold the map group number. + TypeRole, // Used to differentiate between the different layers of the map list tree view. + TypeRole2, // Used for various extra data needed. +}; + +// or QStandardItemModel?? +class MapGroupModel : public QStandardItemModel { + Q_OBJECT + +public: + MapGroupModel(Project *project, QObject *parent = nullptr); + ~MapGroupModel() {} + + QVariant data(const QModelIndex &index, int role) const override; + +public: + void setMap(QString mapName) { this->openMap = mapName; } + + QStandardItem *createGroupItem(QString groupName, int groupIndex); + QStandardItem *createMapItem(QString mapName, int groupIndex, int mapIndex); + + QStandardItem *getItem(const QModelIndex &index) const; + QModelIndex indexOfMap(QString mapName); + + void initialize(); + +private: + Project *project; + QStandardItem *root = nullptr; + + QMap groupItems; + QMap mapItems; + // TODO: if reordering, will the item be the same? + + QString openMap; + + // QIcon *mapIcon = nullptr; + // QIcon *mapEditedIcon = nullptr; + // QIcon *mapOpenedIcon = nullptr; + // QIcon *mapFolderIcon = nullptr; + +signals: + void edited(); +}; + +#endif // MAPLISTMODELS_H diff --git a/porymap.pro b/porymap.pro index 63b97c1a..7e39f0f8 100644 --- a/porymap.pro +++ b/porymap.pro @@ -58,6 +58,7 @@ SOURCES += src/core/block.cpp \ src/ui/customattributestable.cpp \ src/ui/eventframes.cpp \ src/ui/filterchildrenproxymodel.cpp \ + src/ui/maplistmodels.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ src/ui/mappixmapitem.cpp \ @@ -147,6 +148,7 @@ HEADERS += include/core/block.h \ include/ui/customattributestable.h \ include/ui/eventframes.h \ include/ui/filterchildrenproxymodel.h \ + include/ui/maplistmodels.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ include/ui/mappixmapitem.h \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2a3b2735..5c4aaf34 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -19,6 +19,7 @@ #include "prefab.h" #include "montabwidget.h" #include "imageexport.h" +#include "maplistmodels.h" #include #include @@ -151,13 +152,14 @@ void MainWindow::initExtraShortcuts() { shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths"); shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths"); - auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked())); - shortcutExpand_All->setObjectName("shortcutExpand_All"); - shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); + /// !TODO + // auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked())); + // shortcutExpand_All->setObjectName("shortcutExpand_All"); + // shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); - auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked())); - shortcutCollapse_All->setObjectName("shortcutCollapse_All"); - shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); + // auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked())); + // shortcutCollapse_All->setObjectName("shortcutCollapse_All"); + // shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); auto *shortcut_Open_Scripts = new Shortcut(QKeySequence(), ui->toolButton_Open_Scripts, SLOT(click())); shortcut_Open_Scripts->setObjectName("shortcut_Open_Scripts"); @@ -210,6 +212,7 @@ void MainWindow::initCustomUI() { } void MainWindow::initExtraSignals() { + /// !TODO // Right-clicking on items in the map list tree view brings up a context menu. ui->mapList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->mapList, &QTreeView::customContextMenuRequested, @@ -311,16 +314,17 @@ void MainWindow::initEditor() { } void MainWindow::initMiscHeapObjects() { - mapIcon = new QIcon(QStringLiteral(":/icons/map.ico")); - mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico")); - mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico")); + // mapIcon = new QIcon(QStringLiteral(":/icons/map.ico")); + // mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico")); + // mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico")); - mapListModel = new QStandardItemModel; - mapGroupItemsList = new QList; - mapListProxyModel = new FilterChildrenProxyModel; + /// !TODO + // mapListModel = new QStandardItemModel; + // mapGroupItemsList = new QList; + // mapListProxyModel = new FilterChildrenProxyModel; - mapListProxyModel->setSourceModel(mapListModel); - ui->mapList->setModel(mapListProxyModel); + // mapListProxyModel->setSourceModel(mapListModel); + // ui->mapList->setModel(mapListProxyModel); eventTabObjectWidget = ui->tab_Objects; eventTabWarpWidget = ui->tab_Warps; @@ -332,23 +336,23 @@ void MainWindow::initMiscHeapObjects() { } void MainWindow::initMapSortOrder() { - QMenu *mapSortOrderMenu = new QMenu(this); - QActionGroup *mapSortOrderActionGroup = new QActionGroup(ui->toolButton_MapSortOrder); + // QMenu *mapSortOrderMenu = new QMenu(this); + // QActionGroup *mapSortOrderActionGroup = new QActionGroup(ui->toolButton_MapSortOrder); - mapSortOrderMenu->addAction(ui->actionSort_by_Group); - mapSortOrderMenu->addAction(ui->actionSort_by_Area); - mapSortOrderMenu->addAction(ui->actionSort_by_Layout); - ui->toolButton_MapSortOrder->setMenu(mapSortOrderMenu); + // mapSortOrderMenu->addAction(ui->actionSort_by_Group); + // mapSortOrderMenu->addAction(ui->actionSort_by_Area); + // mapSortOrderMenu->addAction(ui->actionSort_by_Layout); + // ui->toolButton_MapSortOrder->setMenu(mapSortOrderMenu); - mapSortOrderActionGroup->addAction(ui->actionSort_by_Group); - mapSortOrderActionGroup->addAction(ui->actionSort_by_Area); - mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout); + // mapSortOrderActionGroup->addAction(ui->actionSort_by_Group); + // mapSortOrderActionGroup->addAction(ui->actionSort_by_Area); + // mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout); - connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed); + // connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed); - QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[mapSortOrder]; - ui->toolButton_MapSortOrder->setIcon(sortOrder->icon()); - sortOrder->setChecked(true); + // QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[mapSortOrder]; + // ui->toolButton_MapSortOrder->setIcon(sortOrder->icon()); + // sortOrder->setChecked(true); } void MainWindow::showWindowTitle() { @@ -393,46 +397,20 @@ void MainWindow::setProjectSpecificUIVisibility() ui->label_FloorNumber->setVisible(floorNumEnabled); } -void MainWindow::mapSortOrder_changed(QAction *action) -{ - QList items = ui->toolButton_MapSortOrder->menu()->actions(); - int i = 0; - for (; i < items.count(); i++) - { - if (items[i] == action) - { - break; - } - } - - if (i != mapSortOrder) - { - ui->toolButton_MapSortOrder->setIcon(action->icon()); - mapSortOrder = static_cast(i); - porymapConfig.setMapSortOrder(mapSortOrder); - if (isProjectOpen()) - { - sortMapList(); - applyMapListFilter(ui->lineEdit_filterBox->text()); - } - } +void MainWindow::on_lineEdit_filterBox_textChanged(const QString &text) { + this->applyMapListFilter(text); } -void MainWindow::on_lineEdit_filterBox_textChanged(const QString &arg1) -{ - this->applyMapListFilter(arg1); -} - -void MainWindow::applyMapListFilter(QString filterText) -{ - mapListProxyModel->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); +void MainWindow::applyMapListFilter(QString filterText) { + /// !TODO + groupListProxyModel->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); if (filterText.isEmpty()) { ui->mapList->collapseAll(); } else { ui->mapList->expandToDepth(0); } - ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), true); - ui->mapList->scrollTo(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), QAbstractItemView::PositionAtCenter); + // ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), true); + // ui->mapList->scrollTo(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), QAbstractItemView::PositionAtCenter); } void MainWindow::loadUserSettings() { @@ -650,8 +628,9 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { return false; } - if (editor->map != nullptr && !editor->map->name.isNull()) { - ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), false); + if (editor->map && !editor->map->name.isNull()) { + // !TODO: function to act on current view? or that does all the views + ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); } refreshMapScene(); @@ -659,13 +638,12 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { if (scrollTreeView) { // Make sure we clear the filter first so we actually have a scroll target - mapListProxyModel->setFilterRegularExpression(QString()); - ui->mapList->setCurrentIndex(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name))); + /// !TODO: make this onto a function that scrolls the current view taking a map name or layout name + groupListProxyModel->setFilterRegularExpression(QString()); + ui->mapList->setCurrentIndex(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name))); ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); } - ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name)), true); - showWindowTitle(); connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged); @@ -973,169 +951,187 @@ bool MainWindow::loadProjectCombos() { return true; } +/// !TODO bool MainWindow::populateMapList() { + // bool success = editor->project->readMapGroups(); + // if (success) { + // sortMapList(); + // } + // return success; bool success = editor->project->readMapGroups(); - if (success) { - sortMapList(); - } + + this->mapGroupModel = new MapGroupModel(editor->project); + this->groupListProxyModel = new FilterChildrenProxyModel(); + groupListProxyModel->setSourceModel(this->mapGroupModel); + ui->mapList->setModel(groupListProxyModel); + + // ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); + // ui->mapList->setDragEnabled(true); + // ui->mapList->setAcceptDrops(true); + // ui->mapList->setDropIndicatorShown(true); + return success; + + //MapGroupModel } void MainWindow::sortMapList() { - Project *project = editor->project; + // Project *project = editor->project; - QIcon mapFolderIcon; - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + // QIcon mapFolderIcon; + // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - QIcon folderIcon; - folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - //folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); + // QIcon folderIcon; + // folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + // //folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - ui->mapList->setUpdatesEnabled(false); - mapListModel->clear(); - mapGroupItemsList->clear(); - QStandardItem *root = mapListModel->invisibleRootItem(); + // ui->mapList->setUpdatesEnabled(false); + // mapListModel->clear(); + // mapGroupItemsList->clear(); + // QStandardItem *root = mapListModel->invisibleRootItem(); - switch (mapSortOrder) - { - case MapSortOrder::Group: - for (int i = 0; i < project->groupNames.length(); i++) { - QString group_name = project->groupNames.value(i); - QStandardItem *group = new QStandardItem; - group->setText(group_name); - group->setIcon(mapFolderIcon); - group->setEditable(false); - group->setData(group_name, Qt::UserRole); - group->setData("map_group", MapListUserRoles::TypeRole); - group->setData(i, MapListUserRoles::GroupRole); - root->appendRow(group); - mapGroupItemsList->append(group); - QStringList names = project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); - group->appendRow(map); - mapListIndexes.insert(map_name, map->index()); - } - } - break; - case MapSortOrder::Area: - { - QMap mapsecToGroupNum; - for (int i = 0; i < project->mapSectionNameToValue.size(); i++) { - QString mapsec_name = project->mapSectionValueToName.value(i); - QStandardItem *mapsec = new QStandardItem; - mapsec->setText(mapsec_name); - mapsec->setIcon(folderIcon); - mapsec->setEditable(false); - mapsec->setData(mapsec_name, Qt::UserRole); - mapsec->setData("map_sec", MapListUserRoles::TypeRole); - mapsec->setData(i, MapListUserRoles::GroupRole); - root->appendRow(mapsec); - mapGroupItemsList->append(mapsec); - mapsecToGroupNum.insert(mapsec_name, i); - } - for (int i = 0; i < project->groupNames.length(); i++) { - QStringList names = project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); - QString location = project->readMapLocation(map_name); - QStandardItem *mapsecItem = mapGroupItemsList->at(mapsecToGroupNum[location]); - mapsecItem->setIcon(mapFolderIcon); - mapsecItem->appendRow(map); - mapListIndexes.insert(map_name, map->index()); - } - } - break; - } - case MapSortOrder::Layout: - { - QMap layoutIndices; - for (int i = 0; i < project->mapLayoutsTable.length(); i++) { - QString layoutId = project->mapLayoutsTable.value(i); - MapLayout *layout = project->mapLayouts.value(layoutId); - QStandardItem *layoutItem = new QStandardItem; - layoutItem->setText(layout->name); - layoutItem->setIcon(folderIcon); - layoutItem->setEditable(false); - layoutItem->setData(layout->name, Qt::UserRole); - layoutItem->setData("map_layout", MapListUserRoles::TypeRole); - layoutItem->setData(layout->id, MapListUserRoles::TypeRole2); - layoutItem->setData(i, MapListUserRoles::GroupRole); - root->appendRow(layoutItem); - mapGroupItemsList->append(layoutItem); - layoutIndices[layoutId] = i; - } - for (int i = 0; i < project->groupNames.length(); i++) { - QStringList names = project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); - QString layoutId = project->readMapLayoutId(map_name); - QStandardItem *layoutItem = mapGroupItemsList->at(layoutIndices.value(layoutId)); - layoutItem->setIcon(mapFolderIcon); - layoutItem->appendRow(map); - mapListIndexes.insert(map_name, map->index()); - } - } - break; - } - } + // switch (mapSortOrder) + // { + // case MapSortOrder::Group: + // for (int i = 0; i < project->groupNames.length(); i++) { + // QString group_name = project->groupNames.value(i); + // QStandardItem *group = new QStandardItem; + // group->setText(group_name); + // group->setIcon(mapFolderIcon); + // group->setEditable(false); + // group->setData(group_name, Qt::UserRole); + // group->setData("map_group", MapListUserRoles::TypeRole); + // group->setData(i, MapListUserRoles::GroupRole); + // root->appendRow(group); + // mapGroupItemsList->append(group); + // QStringList names = project->groupedMapNames.value(i); + // for (int j = 0; j < names.length(); j++) { + // QString map_name = names.value(j); + // QStandardItem *map = createMapItem(map_name, i, j); + // group->appendRow(map); + // mapListIndexes.insert(map_name, map->index()); + // } + // } + // break; + // case MapSortOrder::Area: + // { + // QMap mapsecToGroupNum; + // for (int i = 0; i < project->mapSectionNameToValue.size(); i++) { + // QString mapsec_name = project->mapSectionValueToName.value(i); + // QStandardItem *mapsec = new QStandardItem; + // mapsec->setText(mapsec_name); + // mapsec->setIcon(folderIcon); + // mapsec->setEditable(false); + // mapsec->setData(mapsec_name, Qt::UserRole); + // mapsec->setData("map_sec", MapListUserRoles::TypeRole); + // mapsec->setData(i, MapListUserRoles::GroupRole); + // root->appendRow(mapsec); + // mapGroupItemsList->append(mapsec); + // mapsecToGroupNum.insert(mapsec_name, i); + // } + // for (int i = 0; i < project->groupNames.length(); i++) { + // QStringList names = project->groupedMapNames.value(i); + // for (int j = 0; j < names.length(); j++) { + // QString map_name = names.value(j); + // QStandardItem *map = createMapItem(map_name, i, j); + // QString location = project->readMapLocation(map_name); + // QStandardItem *mapsecItem = mapGroupItemsList->at(mapsecToGroupNum[location]); + // mapsecItem->setIcon(mapFolderIcon); + // mapsecItem->appendRow(map); + // mapListIndexes.insert(map_name, map->index()); + // } + // } + // break; + // } + // case MapSortOrder::Layout: + // { + // QMap layoutIndices; + // for (int i = 0; i < project->mapLayoutsTable.length(); i++) { + // QString layoutId = project->mapLayoutsTable.value(i); + // MapLayout *layout = project->mapLayouts.value(layoutId); + // QStandardItem *layoutItem = new QStandardItem; + // layoutItem->setText(layout->name); + // layoutItem->setIcon(folderIcon); + // layoutItem->setEditable(false); + // layoutItem->setData(layout->name, Qt::UserRole); + // layoutItem->setData("map_layout", MapListUserRoles::TypeRole); + // layoutItem->setData(layout->id, MapListUserRoles::TypeRole2); + // layoutItem->setData(i, MapListUserRoles::GroupRole); + // root->appendRow(layoutItem); + // mapGroupItemsList->append(layoutItem); + // layoutIndices[layoutId] = i; + // } + // for (int i = 0; i < project->groupNames.length(); i++) { + // QStringList names = project->groupedMapNames.value(i); + // for (int j = 0; j < names.length(); j++) { + // QString map_name = names.value(j); + // QStandardItem *map = createMapItem(map_name, i, j); + // QString layoutId = project->readMapLayoutId(map_name); + // QStandardItem *layoutItem = mapGroupItemsList->at(layoutIndices.value(layoutId)); + // layoutItem->setIcon(mapFolderIcon); + // layoutItem->appendRow(map); + // mapListIndexes.insert(map_name, map->index()); + // } + // } + // break; + // } + // } - ui->mapList->setUpdatesEnabled(true); - ui->mapList->repaint(); - updateMapList(); + // ui->mapList->setUpdatesEnabled(true); + // ui->mapList->repaint(); + // updateMapList(); } +/// !TODO QStandardItem* MainWindow::createMapItem(QString mapName, int groupNum, int inGroupNum) { - QStandardItem *map = new QStandardItem; - map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName); - map->setIcon(*mapIcon); - map->setEditable(false); - map->setData(mapName, Qt::UserRole); - map->setData("map_name", MapListUserRoles::TypeRole); - return map; + // QStandardItem *map = new QStandardItem; + // map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName); + // map->setIcon(*mapIcon); + // map->setEditable(false); + // map->setData(mapName, Qt::UserRole); + // map->setData("map_name", MapListUserRoles::TypeRole); + // return map; } void MainWindow::onOpenMapListContextMenu(const QPoint &point) { - QModelIndex index = mapListProxyModel->mapToSource(ui->mapList->indexAt(point)); - if (!index.isValid()) { - return; - } + /// !TODO + // QModelIndex index = mapListProxyModel->mapToSource(ui->mapList->indexAt(point)); + // if (!index.isValid()) { + // return; + // } - QStandardItem *selectedItem = mapListModel->itemFromIndex(index); - QVariant itemType = selectedItem->data(MapListUserRoles::TypeRole); - if (!itemType.isValid()) { - return; - } + // QStandardItem *selectedItem = mapListModel->itemFromIndex(index); + // QVariant itemType = selectedItem->data(MapListUserRoles::TypeRole); + // if (!itemType.isValid()) { + // return; + // } - // Build custom context menu depending on which type of item was selected (map group, map name, etc.) - if (itemType == "map_group") { - QString groupName = selectedItem->data(Qt::UserRole).toString(); - int groupNum = selectedItem->data(MapListUserRoles::GroupRole).toInt(); - QMenu* menu = new QMenu(this); - QActionGroup* actions = new QActionGroup(menu); - actions->addAction(menu->addAction("Add New Map to Group"))->setData(groupNum); - connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToGroupClick); - menu->exec(QCursor::pos()); - } else if (itemType == "map_sec") { - QString secName = selectedItem->data(Qt::UserRole).toString(); - QMenu* menu = new QMenu(this); - QActionGroup* actions = new QActionGroup(menu); - actions->addAction(menu->addAction("Add New Map to Area"))->setData(secName); - connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToAreaClick); - menu->exec(QCursor::pos()); - } else if (itemType == "map_layout") { - QString layoutId = selectedItem->data(MapListUserRoles::TypeRole2).toString(); - QMenu* menu = new QMenu(this); - QActionGroup* actions = new QActionGroup(menu); - actions->addAction(menu->addAction("Add New Map with Layout"))->setData(layoutId); - connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToLayoutClick); - menu->exec(QCursor::pos()); - } + // // Build custom context menu depending on which type of item was selected (map group, map name, etc.) + // if (itemType == "map_group") { + // QString groupName = selectedItem->data(Qt::UserRole).toString(); + // int groupNum = selectedItem->data(MapListUserRoles::GroupRole).toInt(); + // QMenu* menu = new QMenu(this); + // QActionGroup* actions = new QActionGroup(menu); + // actions->addAction(menu->addAction("Add New Map to Group"))->setData(groupNum); + // connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToGroupClick); + // menu->exec(QCursor::pos()); + // } else if (itemType == "map_sec") { + // QString secName = selectedItem->data(Qt::UserRole).toString(); + // QMenu* menu = new QMenu(this); + // QActionGroup* actions = new QActionGroup(menu); + // actions->addAction(menu->addAction("Add New Map to Area"))->setData(secName); + // connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToAreaClick); + // menu->exec(QCursor::pos()); + // } else if (itemType == "map_layout") { + // QString layoutId = selectedItem->data(MapListUserRoles::TypeRole2).toString(); + // QMenu* menu = new QMenu(this); + // QActionGroup* actions = new QActionGroup(menu); + // actions->addAction(menu->addAction("Add New Map with Layout"))->setData(layoutId); + // connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToLayoutClick); + // menu->exec(QCursor::pos()); + // } } void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) @@ -1170,14 +1166,15 @@ void MainWindow::onNewMapCreated() { editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); - QStandardItem* groupItem = mapGroupItemsList->at(newMapGroup); - int numMapsInGroup = groupItem->rowCount(); + // !TODO + // QStandardItem* groupItem = mapGroupItemsList->at(newMapGroup); + // int numMapsInGroup = groupItem->rowCount(); - QStandardItem *newMapItem = createMapItem(newMapName, newMapGroup, numMapsInGroup); - groupItem->appendRow(newMapItem); - mapListIndexes.insert(newMapName, newMapItem->index()); + // QStandardItem *newMapItem = createMapItem(newMapName, newMapGroup, numMapsInGroup); + // groupItem->appendRow(newMapItem); + // mapListIndexes.insert(newMapName, newMapItem->index()); - sortMapList(); + // sortMapList(); setMap(newMapName, true); if (newMap->needsHealLocation) { @@ -1362,10 +1359,11 @@ void MainWindow::currentMetatilesSelectionChanged() } } +/// !TODO void MainWindow::on_mapList_activated(const QModelIndex &index) { QVariant data = index.data(Qt::UserRole); - if (index.data(MapListUserRoles::TypeRole) == "map_name" && !data.isNull()) { + if (index.data(MapListRoles::TypeRole) == "map_name" && !data.isNull()) { QString mapName = data.toString(); if (!setMap(mapName)) { QMessageBox msgBox(this); @@ -1378,38 +1376,43 @@ void MainWindow::on_mapList_activated(const QModelIndex &index) } } +/// !TODO something with the projectHasUnsavedChanges var void MainWindow::drawMapListIcons(QAbstractItemModel *model) { - projectHasUnsavedChanges = false; - QList list; - list.append(QModelIndex()); - while (list.length()) { - QModelIndex parent = list.takeFirst(); - for (int i = 0; i < model->rowCount(parent); i++) { - QModelIndex index = model->index(i, 0, parent); - if (model->hasChildren(index)) { - list.append(index); - } - QVariant data = index.data(Qt::UserRole); - if (!data.isNull()) { - QString map_name = data.toString(); - if (editor->project && editor->project->mapCache.contains(map_name)) { - QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name)); - map->setIcon(*mapIcon); - if (editor->project->mapCache.value(map_name)->hasUnsavedChanges()) { - map->setIcon(*mapEditedIcon); - projectHasUnsavedChanges = true; - } - if (editor->map->name == map_name) { - map->setIcon(*mapOpenedIcon); - } - } - } - } - } + // projectHasUnsavedChanges = false; + // QList list; + // list.append(QModelIndex()); + // while (list.length()) { + // QModelIndex parent = list.takeFirst(); + // for (int i = 0; i < model->rowCount(parent); i++) { + // QModelIndex index = model->index(i, 0, parent); + // if (model->hasChildren(index)) { + // list.append(index); + // } + // QVariant data = index.data(Qt::UserRole); + // if (!data.isNull()) { + // QString map_name = data.toString(); + // if (editor->project && editor->project->mapCache.contains(map_name)) { + // QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name)); + // map->setIcon(*mapIcon); + // if (editor->project->mapCache.value(map_name)->hasUnsavedChanges()) { + // map->setIcon(*mapEditedIcon); + // projectHasUnsavedChanges = true; + // } + // if (editor->map->name == map_name) { + // map->setIcon(*mapOpenedIcon); + // } + // } + // } + // } + // } } void MainWindow::updateMapList() { - drawMapListIcons(mapListModel); + //MapGroupModel *model = static_cast(this->ui->mapList->model()); + mapGroupModel->setMap(this->editor->map->name); + groupListProxyModel->layoutChanged(); + //mapGroupModel->layoutChanged(); + // drawMapListIcons(mapListModel); } void MainWindow::on_action_Save_Project_triggered() { @@ -2667,18 +2670,42 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } -void MainWindow::on_toolButton_ExpandAll_clicked() -{ - if (ui->mapList) { - ui->mapList->expandToDepth(0); - } +// void MainWindow::on_toolButton_ExpandAll_clicked() +// { +// if (ui->mapList) { +// ui->mapList->expandToDepth(0); +// } +// } + +// void MainWindow::on_toolButton_CollapseAll_clicked() +// { +// if (ui->mapList) { +// ui->mapList->collapseAll(); +// } +// } + +void MainWindow::on_toolButton_ExpandAll_Groups_clicked() { + // } -void MainWindow::on_toolButton_CollapseAll_clicked() -{ - if (ui->mapList) { - ui->mapList->collapseAll(); - } +void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { + // +} + +void MainWindow::on_toolButton_ExpandAll_Areas_clicked() { + // +} + +void MainWindow::on_toolButton_CollapseAll_Areas_clicked() { + // +} + +void MainWindow::on_toolButton_ExpandAll_Layouts_clicked() { + // +} + +void MainWindow::on_toolButton_CollapseAll_Layouts_clicked() { + // } void MainWindow::on_actionAbout_Porymap_triggered() diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp new file mode 100644 index 00000000..4a8eb262 --- /dev/null +++ b/src/ui/maplistmodels.cpp @@ -0,0 +1,219 @@ +#include "maplistmodels.h" + +#include "project.h" + + + +/* + + // QIcon mapFolderIcon; + // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + + // QIcon folderIcon; + // folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + // //folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); + + // ui->mapList->setUpdatesEnabled(false); + // mapListModel->clear(); + // mapGroupItemsList->clear(); + // QStandardItem *root = mapListModel->invisibleRootItem(); + + // switch (mapSortOrder) + // { + // case MapSortOrder::Group: + // for (int i = 0; i < project->groupNames.length(); i++) { + // QString group_name = project->groupNames.value(i); + // QStandardItem *group = new QStandardItem; + // group->setText(group_name); + // group->setIcon(mapFolderIcon); + // group->setEditable(false); + // group->setData(group_name, Qt::UserRole); + // group->setData("map_group", MapListUserRoles::TypeRole); + // group->setData(i, MapListUserRoles::GroupRole); + // root->appendRow(group); + // mapGroupItemsList->append(group); + // QStringList names = project->groupedMapNames.value(i); + // for (int j = 0; j < names.length(); j++) { + // QString map_name = names.value(j); + // QStandardItem *map = createMapItem(map_name, i, j); + // group->appendRow(map); + // mapListIndexes.insert(map_name, map->index()); + // } + // } + // break; + + // mapListModel = new QStandardItemModel; + // mapGroupItemsList = new QList; + // mapListProxyModel = new FilterChildrenProxyModel; + + // mapListProxyModel->setSourceModel(mapListModel); + // ui->mapList->setModel(mapListProxyModel); + + // createMapItem: + // QStandardItem *map = new QStandardItem; + // map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName); + // map->setIcon(*mapIcon); + // map->setEditable(false); + // map->setData(mapName, Qt::UserRole); + // map->setData("map_name", MapListUserRoles::TypeRole); + // return map; + + // scrolling: + if (scrollTreeView) { + // Make sure we clear the filter first so we actually have a scroll target + /// !TODO + // mapListProxyModel->setFilterRegularExpression(QString()); + // ui->mapList->setCurrentIndex(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name))); + // ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); + } + + // ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name)), true); + +*/ +MapGroupModel::MapGroupModel(Project *project, QObject *parent) : QStandardItemModel(parent) { + // + + this->project = project; + this->root = this->invisibleRootItem(); + + // mapIcon = new QIcon(QStringLiteral(":/icons/map.ico")); + // mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico")); + // mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico")); + + // mapFolderIcon = new QIcon(QStringLiteral(":/icons/folder_closed_map.ico")); + + //mapFolderIcon = new QIcon; + //mapFolderIcon->addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + //mapFolderIcon->addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + + initialize(); +} + +QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex) { + QStandardItem *group = new QStandardItem; + group->setText(groupName); + group->setEditable(true); + group->setData(groupName, Qt::UserRole); + group->setData("map_group", MapListRoles::TypeRole); + group->setData(groupIndex, MapListRoles::GroupRole); + // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->groupItems.insert(groupName, group); + return group; +} + +QStandardItem *MapGroupModel::createMapItem(QString mapName, int groupIndex, int mapIndex) { + QStandardItem *map = new QStandardItem; + map->setText(QString("[%1.%2] ").arg(groupIndex).arg(mapIndex, 2, 10, QLatin1Char('0')) + mapName); + map->setEditable(false); + map->setData(mapName, Qt::UserRole); + map->setData("map_name", MapListRoles::TypeRole); + // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->mapItems.insert(mapName, map); + return map; +} + +void MapGroupModel::initialize() { + for (int i = 0; i < this->project->groupNames.length(); i++) { + QString group_name = this->project->groupNames.value(i); + QStandardItem *group = createGroupItem(group_name, i); + root->appendRow(group); + QList groupItems; + QMap inGroupItems; + //mapGroupItemsList->append(group); + QStringList names = this->project->groupedMapNames.value(i); + for (int j = 0; j < names.length(); j++) { + QString map_name = names.value(j); + QStandardItem *map = createMapItem(map_name, i, j); + group->appendRow(map); + } + } +} + +QStandardItem *MapGroupModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex MapGroupModel::indexOfMap(QString mapName) { + if (this->mapItems.contains(mapName)) { + return this->mapItems[mapName]->index(); + } + return QModelIndex(); +} + + // projectHasUnsavedChanges = false; + // QList list; + // list.append(QModelIndex()); + // while (list.length()) { + // QModelIndex parent = list.takeFirst(); + // for (int i = 0; i < model->rowCount(parent); i++) { + // QModelIndex index = model->index(i, 0, parent); + // if (model->hasChildren(index)) { + // list.append(index); + // } + // QVariant data = index.data(Qt::UserRole); + // if (!data.isNull()) { + // QString map_name = data.toString(); + // if (editor->project && editor->project->mapCache.contains(map_name)) { + // QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name)); + // map->setIcon(*mapIcon); + // if (editor->project->mapCache.value(map_name)->hasUnsavedChanges()) { + // map->setIcon(*mapEditedIcon); + // projectHasUnsavedChanges = true; + // } + // if (editor->map->name == map_name) { + // map->setIcon(*mapOpenedIcon); + // } + // } + // } + // } + // } + +#include +QVariant MapGroupModel::data(const QModelIndex &index, int role) const { + int row = index.row(); + int col = index.column(); + + if (role == Qt::DecorationRole) { + static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + static QIcon mapFolderIcon; + static bool loaded = false; + if (!loaded) { + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + loaded = true; + } + + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListRoles::TypeRole).toString(); + + if (type == "map_group") { + return mapFolderIcon; + } else if (type == "map_name") { + QString mapName = item->data(Qt::UserRole).toString(); + if (mapName == this->openMap) { + return mapOpenedIcon; + } + else if (this->project->mapCache.contains(mapName)) { + if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { + return mapEditedIcon; + } + } + return mapIcon; + } + + // check if map or group + // if map, check if edited or open + //return QIcon(":/icons/porymap-icon-2.ico"); + } + + return QStandardItemModel::data(index, role); +} From 2bc51f1c291e339c7fb7964ee47f3985a9259cd6 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 1 Feb 2023 10:09:50 -0500 Subject: [PATCH 003/364] move map pixmap item and metatile rendering from Map to Layout --- forms/mainwindow.ui | 5 +- include/config.h | 8 +- include/core/editcommands.h | 39 +- include/core/map.h | 18 +- include/core/maplayout.h | 70 +++- include/core/mapparser.h | 2 +- include/editor.h | 13 +- include/mainwindow.h | 15 + include/project.h | 14 +- include/ui/bordermetatilespixmapitem.h | 10 +- include/ui/collisionpixmapitem.h | 10 +- .../{mappixmapitem.h => layoutpixmapitem.h} | 39 +- include/ui/maplistmodels.h | 38 +- include/ui/newmappopup.h | 4 +- porymap.pro | 4 +- src/config.cpp | 14 +- src/core/editcommands.cpp | 114 +++--- src/core/map.cpp | 53 +-- src/core/maplayout.cpp | 358 +++++++++++++++++- src/editor.cpp | 80 ++-- src/mainwindow.cpp | 135 +++++-- src/project.cpp | 15 +- src/ui/bordermetatilespixmapitem.cpp | 30 +- src/ui/collisionpixmapitem.cpp | 53 +-- ...mappixmapitem.cpp => layoutpixmapitem.cpp} | 269 ++++++------- src/ui/maplistmodels.cpp | 143 +++++++ src/ui/newmappopup.cpp | 6 +- 27 files changed, 1115 insertions(+), 444 deletions(-) rename include/ui/{mappixmapitem.h => layoutpixmapitem.h} (83%) rename src/ui/{mappixmapitem.cpp => layoutpixmapitem.cpp} (65%) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index fb340222..a59c0ad0 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -180,7 +180,7 @@ - + Areas @@ -443,6 +443,9 @@ QAbstractItemView::SelectItems + + false + false diff --git a/include/config.h b/include/config.h index 7f10ce91..923abae2 100644 --- a/include/config.h +++ b/include/config.h @@ -16,9 +16,9 @@ #define CONFIG_BACKWARDS_COMPATABILITY enum MapSortOrder { - Group = 0, - Area = 1, - Layout = 2, + SortByGroup = 0, + SortByArea = 1, + SortByLayout = 2, }; class KeyValueConfigBase @@ -51,7 +51,7 @@ public: virtual void reset() override { this->recentProject = ""; this->reopenOnLaunch = true; - this->mapSortOrder = MapSortOrder::Group; + this->mapSortOrder = MapSortOrder::SortByGroup; this->prettyCursors = true; this->collisionOpacity = 50; this->metatilesZoom = 30; diff --git a/include/core/editcommands.h b/include/core/editcommands.h index ea66b722..6cfaf3b9 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -7,8 +7,8 @@ #include #include -class MapPixmapItem; class Map; +class Layout; class Blockdata; class Event; class DraggablePixmapItem; @@ -43,7 +43,7 @@ enum CommandId { /// onto the map using the pencil tool. class PaintMetatile : public QUndoCommand { public: - PaintMetatile(Map *map, + PaintMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr); @@ -54,7 +54,7 @@ public: int id() const override { return CommandId::ID_PaintMetatile; } private: - Map *map; + Layout *layout; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -68,10 +68,10 @@ private: /// on the metatile collision and elevation. class PaintCollision : public PaintMetatile { public: - PaintCollision(Map *map, + PaintCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldCollision, newCollision, actionId, parent) { + : PaintMetatile(layout, oldCollision, newCollision, actionId, parent) { setText("Paint Collision"); } @@ -83,7 +83,7 @@ public: /// Implements a command to commit paint actions on the map border. class PaintBorder : public QUndoCommand { public: - PaintBorder(Map *map, + PaintBorder(Layout *layout, const Blockdata &oldBorder, const Blockdata &newBorder, unsigned actionId, QUndoCommand *parent = nullptr); @@ -94,7 +94,7 @@ public: int id() const override { return CommandId::ID_PaintBorder; } private: - Map *map; + Layout *layout; Blockdata newBorder; Blockdata oldBorder; @@ -108,10 +108,10 @@ private: /// with the bucket tool onto the map. class BucketFillMetatile : public PaintMetatile { public: - BucketFillMetatile(Map *map, + BucketFillMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldMetatiles, newMetatiles, actionId, parent) { + : PaintMetatile(layout, oldMetatiles, newMetatiles, actionId, parent) { setText("Bucket Fill Metatiles"); } @@ -124,10 +124,10 @@ public: /// on the metatile collision and elevation. class BucketFillCollision : public PaintCollision { public: - BucketFillCollision(Map *map, + BucketFillCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, QUndoCommand *parent = nullptr) - : PaintCollision(map, oldCollision, newCollision, -1, parent) { + : PaintCollision(layout, oldCollision, newCollision, -1, parent) { setText("Flood Fill Collision"); } @@ -141,10 +141,10 @@ public: /// with the bucket or paint tool onto the map. class MagicFillMetatile : public PaintMetatile { public: - MagicFillMetatile(Map *map, + MagicFillMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldMetatiles, newMetatiles, actionId, parent) { + : PaintMetatile(layout, oldMetatiles, newMetatiles, actionId, parent) { setText("Magic Fill Metatiles"); } @@ -156,10 +156,10 @@ public: /// Implements a command to commit magic fill collision actions. class MagicFillCollision : public PaintCollision { public: - MagicFillCollision(Map *map, + MagicFillCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, QUndoCommand *parent = nullptr) - : PaintCollision(map, oldCollision, newCollision, -1, parent) { + : PaintCollision(layout, oldCollision, newCollision, -1, parent) { setText("Magic Fill Collision"); } @@ -172,7 +172,7 @@ public: /// Implements a command to commit metatile shift actions. class ShiftMetatiles : public QUndoCommand { public: - ShiftMetatiles(Map *map, + ShiftMetatiles(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr); @@ -183,7 +183,7 @@ public: int id() const override { return CommandId::ID_ShiftMetatiles; } private: - Map *map; + Layout *layout= nullptr; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -196,7 +196,7 @@ private: /// Implements a command to commit a map or border resize action. class ResizeMap : public QUndoCommand { public: - ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, + ResizeMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -209,7 +209,7 @@ public: int id() const override { return CommandId::ID_ResizeMap; } private: - Map *map; + Layout *layout = nullptr; int oldMapWidth; int oldMapHeight; @@ -342,6 +342,7 @@ public: +// !TODO /// Implements a command to commit map edits from the scripting API. /// The scripting api can edit map/border blocks and dimensions. class ScriptEditMap : public QUndoCommand { diff --git a/include/core/map.h b/include/core/map.h index d0617c43..33105c50 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -25,7 +25,7 @@ // porymap will reflect changes to it, but the value is hard-coded in the projects at the moment #define BORDER_DISTANCE 7 -class MapPixmapItem; +class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; @@ -58,7 +58,7 @@ public: QMap customHeaders; - MapLayout *layout; + Layout *layout = nullptr; bool isPersistedToFile = true; bool hasUnsavedDataChanges = false; @@ -76,6 +76,7 @@ public: QList connections; + // !TODO QList metatileLayerOrder; QList metatileLayerOpacity; @@ -92,17 +93,19 @@ public: void modify(); void clean(); - QPixmap render(bool ignoreCache = false, MapLayout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); + QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); QPixmap renderCollision(bool ignoreCache); - QPixmap renderConnection(MapConnection, MapLayout *); + QPixmap renderConnection(MapConnection, Layout *); QPixmap renderBorder(bool ignoreCache = false); bool mapBlockChanged(int i, const Blockdata &cache); bool borderBlockChanged(int i, const Blockdata &cache); + // !TODO: remove void cacheBlockdata(); void cacheCollision(); + /// !TODO: remove this bool getBlock(int x, int y, Block *out); void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); @@ -133,8 +136,11 @@ public: void openScript(QString label); - MapPixmapItem *mapItem = nullptr; - void setMapItem(MapPixmapItem *item) { mapItem = item; } +private: + LayoutPixmapItem *mapItem = nullptr; + +public: + void setMapItem(LayoutPixmapItem *item) { mapItem = item; } CollisionPixmapItem *collisionItem = nullptr; void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 41fa6946..ffbe6127 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -7,12 +7,17 @@ #include #include #include +#include class Map; +class LayoutPixmapItem; +class CollisionPixmapItem; +class BorderMetatilesPixmapItem; -class MapLayout { +class Layout : public QObject { + Q_OBJECT public: - MapLayout() {} + Layout() {} static QString layoutConstantFromName(QString mapName); @@ -39,8 +44,12 @@ public: Blockdata blockdata; + QImage image; + QPixmap pixmap; QImage border_image; QPixmap border_pixmap; + QImage collision_image; + QPixmap collision_pixmap; Blockdata border; Blockdata cached_blockdata; @@ -53,10 +62,67 @@ public: QSize borderDimensions; } lastCommitBlocks; // to track map changes + QList metatileLayerOrder; + QList metatileLayerOpacity; + + LayoutPixmapItem *layoutItem = nullptr; + CollisionPixmapItem *collisionItem = nullptr; + BorderMetatilesPixmapItem *borderItem = nullptr; + + QUndoStack editHistory; + +public: int getWidth(); int getHeight(); int getBorderWidth(); int getBorderHeight(); + + bool isWithinBounds(int x, int y) { + return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); + } + + bool getBlock(int x, int y, Block *out); + void setBlock(int x, int y, Block block, bool enableScriptCallback = false); + void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); + + void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); + void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); + + void cacheBlockdata(); + void cacheCollision(); + void clearBorderCache(); + void cacheBorder(); + + bool layoutBlockChanged(int i, const Blockdata &cache); + + uint16_t getBorderMetatileId(int x, int y); + void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); + void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); + + void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + + QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); + QPixmap renderCollision(bool ignoreCache); + // QPixmap renderConnection(MapConnection, Layout *); + QPixmap renderBorder(bool ignoreCache = false); + + void setLayoutItem(LayoutPixmapItem *item) { layoutItem = item; } + void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } + void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } + +private: + void setNewDimensionsBlockdata(int newWidth, int newHeight); + void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); + +signals: + void layoutChanged(Layout *layout); + void modified(); + void layoutDimensionsChanged(const QSize &size); + void needsRedrawing(); }; +using MapLayout = Layout; + #endif // MAPLAYOUT_H diff --git a/include/core/mapparser.h b/include/core/mapparser.h index 21e3074e..4032154a 100644 --- a/include/core/mapparser.h +++ b/include/core/mapparser.h @@ -10,7 +10,7 @@ class MapParser { public: MapParser(); - MapLayout *parse(QString filepath, bool *error, Project *project); + Layout *parse(QString filepath, bool *error, Project *project); }; #endif // MAPPARSER_H diff --git a/include/editor.h b/include/editor.h index 0b0bc591..8f12d88f 100644 --- a/include/editor.h +++ b/include/editor.h @@ -20,7 +20,7 @@ #include "connectionpixmapitem.h" #include "currentselectedmetatilespixmapitem.h" #include "collisionpixmapitem.h" -#include "mappixmapitem.h" +#include "layoutpixmapitem.h" #include "settings.h" #include "movablerect.h" #include "cursortilerect.h" @@ -46,7 +46,7 @@ public: Project *project = nullptr; Map *map = nullptr; - MapLayout *layout = nullptr; /* NEW */ + Layout *layout = nullptr; /* NEW */ QUndoGroup editGroup; // Manages the undo history for each map @@ -60,6 +60,7 @@ public: void closeProject(); bool setMap(QString map_name); + void unsetMap(); Tileset *getCurrentMapPrimaryTileset(); @@ -123,7 +124,7 @@ public: QGraphicsScene *scene = nullptr; QGraphicsPixmapItem *current_view = nullptr; - MapPixmapItem *map_item = nullptr; + LayoutPixmapItem *map_item = nullptr; ConnectionPixmapItem* selected_connection_item = nullptr; QList connection_items; QGraphicsPathItem *connection_mask = nullptr; @@ -201,11 +202,11 @@ private: qint64 *pid = nullptr); private slots: - void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); - void onMapEndPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); + void onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); + void onMapEndPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void setSmartPathCursorMode(QGraphicsSceneMouseEvent *event); void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); - void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); + void mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); void onConnectionMoved(MapConnection*); void onConnectionItemSelected(ConnectionPixmapItem* connectionItem); diff --git a/include/mainwindow.h b/include/mainwindow.h index 09d39f3a..18b947f9 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -164,8 +164,13 @@ public slots: private slots: void on_action_Open_Project_triggered(); void on_action_Reload_Project_triggered(); + void on_mapList_activated(const QModelIndex &index); + void on_areaList_activated(const QModelIndex &index); + void on_layoutList_activated(const QModelIndex &index); + void on_action_Save_Project_triggered(); + void openWarpMap(QString map_name, int event_id, Event::Group event_group); void duplicate(); @@ -229,6 +234,7 @@ private slots: void on_toolButton_Move_clicked(); void on_toolButton_Shift_clicked(); + void on_mapListContainer_currentChanged(int index); void onOpenMapListContextMenu(const QPoint &point); void onAddNewMapToGroupClick(QAction* triggeredAction); void onAddNewMapToAreaClick(QAction* triggeredAction); @@ -310,6 +316,11 @@ private: FilterChildrenProxyModel *groupListProxyModel; MapGroupModel *mapGroupModel; + + FilterChildrenProxyModel *layoutListProxyModel; + LayoutTreeModel *layoutTreeModel; + + // QStandardItemModel *mapListModel; // QList *mapGroupItemsList; // QMap mapListIndexes; @@ -342,10 +353,14 @@ private: bool newMapDefaultsSet = false; MapSortOrder mapSortOrder; + enum MapListTab { Groups, Areas, Layouts }; bool tilesetNeedsRedraw = false; + bool setLayout(QString layoutName); + bool setMap(QString, bool scrollTreeView = false); + void unsetMap(); void redrawMapScene(); void refreshMapScene(); bool loadDataStructures(); diff --git a/include/project.h b/include/project.h index 05029df3..dd2e044b 100644 --- a/include/project.h +++ b/include/project.h @@ -56,8 +56,9 @@ public: QStringList mapLayoutsTable; QStringList mapLayoutsTableMaster; QString layoutsLabel; - QMap mapLayouts; - QMap mapLayoutsMaster; + QMap layoutIdsToNames; + QMap mapLayouts; + QMap mapLayoutsMaster; QMap mapSecToMapHoverName; QMap mapSectionNameToValue; QMap mapSectionValueToName; @@ -115,8 +116,8 @@ public: QStringList tilesetLabelsOrdered; Blockdata readBlockdata(QString); - bool loadBlockdata(MapLayout*); - bool loadLayoutBorder(MapLayout*); + bool loadBlockdata(Layout *); + bool loadLayoutBorder(Layout *); void saveTextFile(QString path, QString text); void appendTextFile(QString path, QString text); @@ -128,6 +129,7 @@ public: QString getProjectTitle(); QString readMapLayoutId(QString map_name); + QString readMapLayoutName(QString mapName); QString readMapLocation(QString map_name); bool readWildMonData(); @@ -143,9 +145,9 @@ public: QSet getTopLevelMapFields(); bool loadMapData(Map*); bool readMapLayouts(); - bool loadLayout(MapLayout *); + bool loadLayout(Layout *); bool loadMapLayout(Map*); - bool loadLayoutTilesets(MapLayout*); + bool loadLayoutTilesets(Layout *); void loadTilesetAssets(Tileset*); void loadTilesetTiles(Tileset*, QImage); void loadTilesetMetatiles(Tileset*); diff --git a/include/ui/bordermetatilespixmapitem.h b/include/ui/bordermetatilespixmapitem.h index f4af8bfe..cf2ba0d4 100644 --- a/include/ui/bordermetatilespixmapitem.h +++ b/include/ui/bordermetatilespixmapitem.h @@ -1,21 +1,21 @@ #ifndef BORDERMETATILESPIXMAPITEM_H #define BORDERMETATILESPIXMAPITEM_H -#include "map.h" +#include "maplayout.h" #include "metatileselector.h" #include class BorderMetatilesPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - BorderMetatilesPixmapItem(Map *map_, MetatileSelector *metatileSelector) { - this->map = map_; - this->map->setBorderItem(this); + BorderMetatilesPixmapItem(Layout *layout, MetatileSelector *metatileSelector) { + this->layout = layout; + this->layout->setBorderItem(this); this->metatileSelector = metatileSelector; setAcceptHoverEvents(true); } MetatileSelector *metatileSelector; - Map *map; + Layout *layout; void draw(); signals: void hoveredBorderMetatileSelectionChanged(uint16_t); diff --git a/include/ui/collisionpixmapitem.h b/include/ui/collisionpixmapitem.h index 2e3e74e3..0e4afd6c 100644 --- a/include/ui/collisionpixmapitem.h +++ b/include/ui/collisionpixmapitem.h @@ -3,18 +3,18 @@ #include "metatileselector.h" #include "movementpermissionsselector.h" -#include "mappixmapitem.h" +#include "layoutpixmapitem.h" #include "map.h" #include "settings.h" -class CollisionPixmapItem : public MapPixmapItem { +class CollisionPixmapItem : public LayoutPixmapItem { Q_OBJECT public: - CollisionPixmapItem(Map *map, MovementPermissionsSelector *movementPermissionsSelector, MetatileSelector *metatileSelector, Settings *settings, qreal *opacity) - : MapPixmapItem(map, metatileSelector, settings){ + CollisionPixmapItem(Layout *layout, MovementPermissionsSelector *movementPermissionsSelector, MetatileSelector *metatileSelector, Settings *settings, qreal *opacity) + : LayoutPixmapItem(layout, metatileSelector, settings){ this->movementPermissionsSelector = movementPermissionsSelector; this->opacity = opacity; - map->setCollisionItem(this); + layout->setCollisionItem(this); } MovementPermissionsSelector *movementPermissionsSelector; qreal *opacity; diff --git a/include/ui/mappixmapitem.h b/include/ui/layoutpixmapitem.h similarity index 83% rename from include/ui/mappixmapitem.h rename to include/ui/layoutpixmapitem.h index cd2d335c..ab4d94a5 100644 --- a/include/ui/mappixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -1,12 +1,13 @@ #ifndef MAPPIXMAPITEM_H #define MAPPIXMAPITEM_H -#include "map.h" #include "settings.h" #include "metatileselector.h" #include -class MapPixmapItem : public QObject, public QGraphicsPixmapItem { +class Layout; + +class LayoutPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT private: @@ -18,37 +19,49 @@ public: Metatiles, EventObjects }; - MapPixmapItem(Map *map_, MetatileSelector *metatileSelector, Settings *settings) { - this->map = map_; - this->map->setMapItem(this); + + LayoutPixmapItem(Layout *layout, MetatileSelector *metatileSelector, Settings *settings) { + this->layout = layout; + // this->map->setMapItem(this); this->metatileSelector = metatileSelector; this->settings = settings; this->paintingMode = PaintMode::Metatiles; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; this->prevStraightPathState = false; setAcceptHoverEvents(true); } - MapPixmapItem::PaintMode paintingMode; - Map *map; + + LayoutPixmapItem::PaintMode paintingMode; + + Layout *layout; + MetatileSelector *metatileSelector; + Settings *settings; + bool active; bool has_mouse = false; bool right_click; + int paint_tile_initial_x; int paint_tile_initial_y; bool prevStraightPathState; int straight_path_initial_x; int straight_path_initial_y; + QPoint metatilePos; + enum Axis { None = 0, X, Y }; - MapPixmapItem::Axis lockedAxis; + + LayoutPixmapItem::Axis lockedAxis; + QPoint selection_origin; QList selection; + virtual void paint(QGraphicsSceneMouseEvent*); virtual void floodFill(QGraphicsSceneMouseEvent*); virtual void magicFill(QGraphicsSceneMouseEvent*); @@ -70,11 +83,13 @@ public: QList selectedCollisions, bool fromScriptCall = false); void floodFillSmartPath(int initialX, int initialY, bool fromScriptCall = false); + virtual void pick(QGraphicsSceneMouseEvent*); virtual void select(QGraphicsSceneMouseEvent*); virtual void shift(QGraphicsSceneMouseEvent*); void shift(int xDelta, int yDelta, bool fromScriptCall = false); virtual void draw(bool ignoreCache = false); + void updateMetatileSelection(QGraphicsSceneMouseEvent *event); void paintNormal(int x, int y, bool fromScriptCall = false); void lockNondominantAxis(QGraphicsSceneMouseEvent *event); @@ -87,9 +102,9 @@ private: unsigned actionId_ = 0; signals: - void startPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *); - void endPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *); - void mouseEvent(QGraphicsSceneMouseEvent *, MapPixmapItem *); + void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); + void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); + void mouseEvent(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void hoveredMapMetatileChanged(const QPoint &pos); void hoveredMapMetatileCleared(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 972c8668..d15adcd3 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -46,10 +46,40 @@ private: QString openMap; - // QIcon *mapIcon = nullptr; - // QIcon *mapEditedIcon = nullptr; - // QIcon *mapOpenedIcon = nullptr; - // QIcon *mapFolderIcon = nullptr; +signals: + void edited(); +}; + + + +class LayoutTreeModel : public QStandardItemModel { + Q_OBJECT + +public: + LayoutTreeModel(Project *project, QObject *parent = nullptr); + ~LayoutTreeModel() {} + + QVariant data(const QModelIndex &index, int role) const override; + +public: + void setLayout(QString layoutName) { this->openLayout = layoutName; } + + QStandardItem *createLayoutItem(QString layoutName); + QStandardItem *createMapItem(QString mapName); + + QStandardItem *getItem(const QModelIndex &index) const; + QModelIndex indexOfLayout(QString layoutName); + + void initialize(); + +private: + Project *project; + QStandardItem *root = nullptr; + + QMap layoutItems; + QMap mapItems; + + QString openLayout; signals: void edited(); diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h index 826e1609..e668e863 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmappopup.h @@ -24,7 +24,7 @@ public: QString layoutId; void init(); void init(MapSortOrder type, QVariant data); - void init(MapLayout *); + void init(Layout *); static void setDefaultSettings(Project *project); signals: @@ -37,7 +37,7 @@ private: bool checkNewMapGroup(); void saveSettings(); void useLayout(QString layoutId); - void useLayoutSettings(MapLayout *mapLayout); + void useLayoutSettings(Layout *mapLayout); struct Settings { QString group; diff --git a/porymap.pro b/porymap.pro index 7e39f0f8..a6df6224 100644 --- a/porymap.pro +++ b/porymap.pro @@ -61,7 +61,7 @@ SOURCES += src/core/block.cpp \ src/ui/maplistmodels.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ - src/ui/mappixmapitem.cpp \ + src/ui/layoutpixmapitem.cpp \ src/ui/prefabcreationdialog.cpp \ src/ui/regionmappixmapitem.cpp \ src/ui/citymappixmapitem.cpp \ @@ -151,7 +151,7 @@ HEADERS += include/core/block.h \ include/ui/maplistmodels.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ - include/ui/mappixmapitem.h \ + include/ui/layoutpixmapitem.h \ include/ui/mapview.h \ include/ui/prefabcreationdialog.h \ include/ui/regionmappixmapitem.h \ diff --git a/src/config.cpp b/src/config.cpp index 681dc3f9..9dddcd42 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -172,15 +172,15 @@ void KeyValueConfigBase::setSaveDisabled(bool disabled) { } const QMap mapSortOrderMap = { - {MapSortOrder::Group, "group"}, - {MapSortOrder::Layout, "layout"}, - {MapSortOrder::Area, "area"}, + {MapSortOrder::SortByGroup, "group"}, + {MapSortOrder::SortByLayout, "layout"}, + {MapSortOrder::SortByArea, "area"}, }; const QMap mapSortOrderReverseMap = { - {"group", MapSortOrder::Group}, - {"layout", MapSortOrder::Layout}, - {"area", MapSortOrder::Area}, + {"group", MapSortOrder::SortByGroup}, + {"layout", MapSortOrder::SortByLayout}, + {"area", MapSortOrder::SortByArea}, }; PorymapConfig porymapConfig; @@ -209,7 +209,7 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { if (mapSortOrderReverseMap.contains(sortOrder)) { this->mapSortOrder = mapSortOrderReverseMap.value(sortOrder); } else { - this->mapSortOrder = MapSortOrder::Group; + this->mapSortOrder = MapSortOrder::SortByGroup; logWarn(QString("Invalid config value for map_sort_order: '%1'. Must be 'group', 'area', or 'layout'.").arg(value)); } } else if (key == "main_window_geometry") { diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 394c51cb..91ea18b3 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -1,5 +1,4 @@ #include "editcommands.h" -#include "mappixmapitem.h" #include "draggablepixmapitem.h" #include "bordermetatilespixmapitem.h" #include "editor.h" @@ -25,17 +24,18 @@ int getEventTypeMask(QList events) { return eventTypeMask; } -void renderMapBlocks(Map *map, bool ignoreCache = false) { - map->mapItem->draw(ignoreCache); - map->collisionItem->draw(ignoreCache); +/// !TODO: +void renderBlocks(Layout *layout, bool ignoreCache = false) { + layout->layoutItem->draw(ignoreCache); + layout->collisionItem->draw(ignoreCache); } -PaintMetatile::PaintMetatile(Map *map, +PaintMetatile::PaintMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Paint Metatiles"); - this->map = map; + this->layout = layout; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -45,23 +45,23 @@ PaintMetatile::PaintMetatile(Map *map, void PaintMetatile::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBlockdata(newMetatiles, true); + layout->setBlockdata(newMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map); + renderBlocks(layout); } void PaintMetatile::undo() { - if (!map) return; + if (!layout) return; - map->setBlockdata(oldMetatiles, true); + layout->setBlockdata(oldMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map); + renderBlocks(layout); QUndoCommand::undo(); } @@ -69,7 +69,7 @@ void PaintMetatile::undo() { bool PaintMetatile::mergeWith(const QUndoCommand *command) { const PaintMetatile *other = static_cast(command); - if (map != other->map) + if (layout != other->layout) return false; if (actionId != other->actionId) @@ -84,12 +84,12 @@ bool PaintMetatile::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -PaintBorder::PaintBorder(Map *map, +PaintBorder::PaintBorder(Layout *layout, const Blockdata &oldBorder, const Blockdata &newBorder, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Paint Border"); - this->map = map; + this->layout = layout; this->oldBorder = oldBorder; this->newBorder = newBorder; @@ -99,23 +99,23 @@ PaintBorder::PaintBorder(Map *map, void PaintBorder::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBorderBlockData(newBorder, true); + layout->setBorderBlockData(newBorder, true); - map->layout->lastCommitBlocks.border = map->layout->border; + layout->lastCommitBlocks.border = layout->border; - map->borderItem->draw(); + layout->borderItem->draw(); } void PaintBorder::undo() { - if (!map) return; + if (!layout) return; - map->setBorderBlockData(oldBorder, true); + layout->setBorderBlockData(oldBorder, true); - map->layout->lastCommitBlocks.border = map->layout->border; + layout->lastCommitBlocks.border = layout->border; - map->borderItem->draw(); + layout->borderItem->draw(); QUndoCommand::undo(); } @@ -124,12 +124,12 @@ void PaintBorder::undo() { ************************************************************************ ******************************************************************************/ -ShiftMetatiles::ShiftMetatiles(Map *map, +ShiftMetatiles::ShiftMetatiles(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Shift Metatiles"); - this->map = map; + this->layout = layout; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -139,23 +139,23 @@ ShiftMetatiles::ShiftMetatiles(Map *map, void ShiftMetatiles::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBlockdata(newMetatiles, true); + layout->setBlockdata(newMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map, true); + renderBlocks(layout, true); } void ShiftMetatiles::undo() { - if (!map) return; + if (!layout) return; - map->setBlockdata(oldMetatiles, true); + layout->setBlockdata(oldMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map, true); + renderBlocks(layout, true); QUndoCommand::undo(); } @@ -163,7 +163,7 @@ void ShiftMetatiles::undo() { bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { const ShiftMetatiles *other = static_cast(command); - if (this->map != other->map) + if (this->layout != other->layout) return false; if (actionId != other->actionId) @@ -178,14 +178,14 @@ bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -ResizeMap::ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, +ResizeMap::ResizeMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, QUndoCommand *parent) : QUndoCommand(parent) { setText("Resize Map"); - this->map = map; + this->layout = layout; this->oldMapWidth = oldMapDimensions.width(); this->oldMapHeight = oldMapDimensions.height(); @@ -209,33 +209,33 @@ ResizeMap::ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, void ResizeMap::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->layout->blockdata = newMetatiles; - map->setDimensions(newMapWidth, newMapHeight, false, true); + layout->blockdata = newMetatiles; + layout->setDimensions(newMapWidth, newMapHeight, false, true); - map->layout->border = newBorder; - map->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); + layout->border = newBorder; + layout->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); - map->layout->lastCommitBlocks.mapDimensions = QSize(map->getWidth(), map->getHeight()); - map->layout->lastCommitBlocks.borderDimensions = QSize(map->getBorderWidth(), map->getBorderHeight()); + layout->lastCommitBlocks.mapDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - map->mapNeedsRedrawing(); + layout->needsRedrawing(); } void ResizeMap::undo() { - if (!map) return; + if (!layout) return; - map->layout->blockdata = oldMetatiles; - map->setDimensions(oldMapWidth, oldMapHeight, false, true); + layout->blockdata = oldMetatiles; + layout->setDimensions(oldMapWidth, oldMapHeight, false, true); - map->layout->border = oldBorder; - map->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); + layout->border = oldBorder; + layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); - map->layout->lastCommitBlocks.mapDimensions = QSize(map->getWidth(), map->getHeight()); - map->layout->lastCommitBlocks.borderDimensions = QSize(map->getBorderWidth(), map->getBorderHeight()); + layout->lastCommitBlocks.mapDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - map->mapNeedsRedrawing(); + layout->needsRedrawing(); QUndoCommand::undo(); } @@ -538,7 +538,8 @@ void ScriptEditMap::redo() { map->layout->lastCommitBlocks.border = newBorder; map->layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); - renderMapBlocks(map); + // !TODO + renderBlocks(map->layout); map->borderItem->draw(); } @@ -564,7 +565,8 @@ void ScriptEditMap::undo() { map->layout->lastCommitBlocks.border = oldBorder; map->layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); - renderMapBlocks(map); + // !TODO + renderBlocks(map->layout); map->borderItem->draw(); QUndoCommand::undo(); diff --git a/src/core/map.cpp b/src/core/map.cpp index 176d4d2f..181cde35 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -133,48 +133,8 @@ QPixmap Map::renderCollision(bool ignoreCache) { return collision_pixmap; } -QPixmap Map::render(bool ignoreCache, MapLayout * fromLayout, QRect bounds) { - bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) { - image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->blockdata.isEmpty() || !width_ || !height_) { - pixmap = pixmap.fromImage(image); - return pixmap; - } - - QPainter painter(&image); - for (int i = 0; i < layout->blockdata.length(); i++) { - if (!ignoreCache && !mapBlockChanged(i, layout->cached_blockdata)) { - continue; - } - changed_any = true; - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - if (bounds.isValid() && !bounds.contains(map_x, map_y)) { - continue; - } - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - Block block = layout->blockdata.at(i); - QImage metatile_image = getMetatileImage( - block.metatileId, - fromLayout ? fromLayout->tileset_primary : layout->tileset_primary, - fromLayout ? fromLayout->tileset_secondary : layout->tileset_secondary, - metatileLayerOrder, - metatileLayerOpacity - ); - painter.drawImage(metatile_origin, metatile_image); - } - painter.end(); - if (changed_any) { - cacheBlockdata(); - pixmap = pixmap.fromImage(image); - } - - return pixmap; +QPixmap Map::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { + return this->layout->render(ignoreCache, fromLayout, bounds); } QPixmap Map::renderBorder(bool ignoreCache) { @@ -215,7 +175,7 @@ QPixmap Map::renderBorder(bool ignoreCache) { return layout->border_pixmap; } -QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout) { +QPixmap Map::renderConnection(MapConnection connection, Layout *fromLayout) { int x, y, w, h; if (connection.direction == "up") { x = 0; @@ -245,9 +205,10 @@ QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout) h = getHeight(); } - render(true, fromLayout, QRect(x, y, w, h)); - QImage connection_image = image.copy(x * 16, y * 16, w * 16, h * 16); - return QPixmap::fromImage(connection_image); + //render(true, fromLayout, QRect(x, y, w, h)); + //QImage connection_image = image.copy(x * 16, y * 16, w * 16, h * 16); + return render(true, fromLayout, QRect(x, y, w, h)).copy(x * 16, y * 16, w * 16, h * 16); + //return QPixmap::fromImage(connection_image); } void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 0f5deecd..70364f69 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -2,7 +2,10 @@ #include -QString MapLayout::layoutConstantFromName(QString mapName) { +#include "scripting.h" +#include "imageproviders.h" + +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"); @@ -17,18 +20,363 @@ QString MapLayout::layoutConstantFromName(QString mapName) { return constantName; } -int MapLayout::getWidth() { +int Layout::getWidth() { return width; } -int MapLayout::getHeight() { +int Layout::getHeight() { return height; } -int MapLayout::getBorderWidth() { +int Layout::getBorderWidth() { return border_width; } -int MapLayout::getBorderHeight() { +int Layout::getBorderHeight() { return border_height; } + +bool Layout::getBlock(int x, int y, Block *out) { + if (isWithinBounds(x, y)) { + int i = y * getWidth() + x; + *out = this->blockdata.value(i); + return true; + } + return false; +} + +void Layout::setBlock(int x, int y, Block block, bool enableScriptCallback) { + if (!isWithinBounds(x, y)) return; + int i = y * getWidth() + x; + if (i < this->blockdata.size()) { + Block prevBlock = this->blockdata.at(i); + this->blockdata.replace(i, block); + if (enableScriptCallback) { + Scripting::cb_MetatileChanged(x, y, prevBlock, block); + } + } +} + +void Layout::setBlockdata(Blockdata newBlockdata, bool enableScriptCallback) { + int width = getWidth(); + int size = qMin(newBlockdata.size(), this->blockdata.size()); + for (int i = 0; i < size; i++) { + Block prevBlock = this->blockdata.at(i); + Block newBlock = newBlockdata.at(i); + if (prevBlock != newBlock) { + this->blockdata.replace(i, newBlock); + if (enableScriptCallback) + Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock); + } + } +} + +void Layout::clearBorderCache() { + this->cached_border.clear(); +} + +void Layout::cacheBorder() { + this->cached_border.clear(); + for (const auto &block : this->border) + this->cached_border.append(block); +} + +void Layout::cacheBlockdata() { + this->cached_blockdata.clear(); + for (const auto &block : this->blockdata) + this->cached_blockdata.append(block); +} + +void Layout::cacheCollision() { + this->cached_collision.clear(); + for (const auto &block : this->blockdata) + this->cached_collision.append(block); +} + +bool Layout::layoutBlockChanged(int i, const Blockdata &cache) { + if (cache.length() <= i) + return true; + if (this->blockdata.length() <= i) + return true; + + return this->blockdata.at(i) != cache.at(i); +} + +uint16_t Layout::getBorderMetatileId(int x, int y) { + int i = y * getBorderWidth() + x; + return this->border[i].metatileId; +} + +void Layout::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) { + int i = y * getBorderWidth() + x; + if (i < this->border.size()) { + uint16_t prevMetatileId = this->border[i].metatileId; + this->border[i].metatileId = metatileId; + if (prevMetatileId != metatileId && enableScriptCallback) { + Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId); + } + } +} + +void Layout::setBorderBlockData(Blockdata newBlockdata, bool enableScriptCallback) { + int width = getBorderWidth(); + int size = qMin(newBlockdata.size(), this->border.size()); + for (int i = 0; i < size; i++) { + Block prevBlock = this->border.at(i); + Block newBlock = newBlockdata.at(i); + if (prevBlock != newBlock) { + this->border.replace(i, newBlock); + if (enableScriptCallback) + Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId, newBlock.metatileId); + } + } +} + +void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { + if (setNewBlockdata) { + setNewDimensionsBlockdata(newWidth, newHeight); + } + + int oldWidth = this->width; + int oldHeight = this->height; + this->width = newWidth; + this->height = newHeight; + + if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { + Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); + } + + emit layoutChanged(this); + emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); +} + +void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { + if (setNewBlockdata) { + setNewBorderDimensionsBlockdata(newWidth, newHeight); + } + + int oldWidth = this->border_width; + int oldHeight = this->border_height; + this->border_width = newWidth; + this->border_height = newHeight; + + if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { + Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); + } + + emit layoutChanged(this); +} + +void Layout::setNewDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getWidth(); + int oldHeight = getHeight(); + + Blockdata newBlockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockdata.append(this->blockdata.value(index)); + } else { + newBlockdata.append(0); + } + } + + this->blockdata = newBlockdata; +} + +void Layout::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getBorderWidth(); + int oldHeight = getBorderHeight(); + + Blockdata newBlockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockdata.append(this->border.value(index)); + } else { + newBlockdata.append(0); + } + } + + this->border = newBlockdata; +} + +void Layout::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { + QList todo; + todo.append(QPoint(x, y)); + while (todo.length()) { + QPoint point = todo.takeAt(0); + x = point.x(); + y = point.y(); + Block block; + if (!getBlock(x, y, &block)) { + continue; + } + + uint old_coll = block.collision; + uint old_elev = block.elevation; + if (old_coll == collision && old_elev == elevation) { + continue; + } + + block.collision = collision; + block.elevation = elevation; + setBlock(x, y, block, true); + if (getBlock(x + 1, y, &block) && block.collision == old_coll && block.elevation == old_elev) { + todo.append(QPoint(x + 1, y)); + } + if (getBlock(x - 1, y, &block) && block.collision == old_coll && block.elevation == old_elev) { + todo.append(QPoint(x - 1, y)); + } + if (getBlock(x, y + 1, &block) && block.collision == old_coll && block.elevation == old_elev) { + todo.append(QPoint(x, y + 1)); + } + if (getBlock(x, y - 1, &block) && block.collision == old_coll && block.elevation == old_elev) { + todo.append(QPoint(x, y - 1)); + } + } +} + +void Layout::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { + Block block; + if (getBlock(x, y, &block) && (block.collision != collision || block.elevation != elevation)) { + _floodFillCollisionElevation(x, y, collision, elevation); + } +} + +void Layout::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) { + Block block; + if (getBlock(initialX, initialY, &block) && (block.collision != collision || block.elevation != elevation)) { + uint old_coll = block.collision; + uint old_elev = block.elevation; + + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + if (getBlock(x, y, &block) && block.collision == old_coll && block.elevation == old_elev) { + block.collision = collision; + block.elevation = elevation; + setBlock(x, y, block, true); + } + } + } + } +} + +QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { + bool changed_any = false; + int width_ = getWidth(); + int height_ = getHeight(); + if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) { + image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->blockdata.isEmpty() || !width_ || !height_) { + pixmap = pixmap.fromImage(image); + return pixmap; + } + + QPainter painter(&image); + for (int i = 0; i < this->blockdata.length(); i++) { + if (!ignoreCache && !layoutBlockChanged(i, this->cached_blockdata)) { + continue; + } + changed_any = true; + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + if (bounds.isValid() && !bounds.contains(map_x, map_y)) { + continue; + } + QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); + Block block = this->blockdata.at(i); + QImage metatile_image = getMetatileImage( + block.metatileId, + fromLayout ? fromLayout->tileset_primary : this->tileset_primary, + fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary, + metatileLayerOrder, + metatileLayerOpacity + ); + painter.drawImage(metatile_origin, metatile_image); + } + painter.end(); + if (changed_any) { + cacheBlockdata(); + pixmap = pixmap.fromImage(image); + } + + return pixmap; +} + +QPixmap Layout::renderCollision(bool ignoreCache) { + bool changed_any = false; + int width_ = getWidth(); + int height_ = getHeight(); + if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { + collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->blockdata.isEmpty() || !width_ || !height_) { + collision_pixmap = collision_pixmap.fromImage(collision_image); + return collision_pixmap; + } + QPainter painter(&collision_image); + for (int i = 0; i < this->blockdata.length(); i++) { + if (!ignoreCache && !layoutBlockChanged(i, this->cached_collision)) { + continue; + } + changed_any = true; + Block block = this->blockdata.at(i); + QImage collision_metatile_image = getCollisionMetatileImage(block); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); + painter.drawImage(metatile_origin, collision_metatile_image); + } + painter.end(); + cacheCollision(); + if (changed_any) { + collision_pixmap = collision_pixmap.fromImage(collision_image); + } + return collision_pixmap; +} + +QPixmap Layout::renderBorder(bool ignoreCache) { + bool changed_any = false, border_resized = false; + int width_ = getBorderWidth(); + int height_ = getBorderHeight(); + if (this->border_image.isNull()) { + this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->border_image.width() != width_ * 16 || this->border_image.height() != height_ * 16) { + this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + border_resized = true; + } + if (this->border.isEmpty()) { + this->border_pixmap = this->border_pixmap.fromImage(this->border_image); + return this->border_pixmap; + } + QPainter painter(&this->border_image); + for (int i = 0; i < this->border.length(); i++) { + if (!ignoreCache && (!border_resized && !layoutBlockChanged(i, this->cached_border))) { + continue; + } + + changed_any = true; + Block block = this->border.at(i); + uint16_t metatileId = block.metatileId; + QImage metatile_image = getMetatileImage(metatileId, this->tileset_primary, this->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); + } + painter.end(); + if (changed_any) { + cacheBorder(); + this->border_pixmap = this->border_pixmap.fromImage(this->border_image); + } + return this->border_pixmap; +} diff --git a/src/editor.cpp b/src/editor.cpp index 4dc12885..d83ca28a 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -83,7 +83,7 @@ void Editor::closeProject() { void Editor::setEditingMap() { current_view = map_item; if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::Metatiles; + map_item->paintingMode = LayoutPixmapItem::PaintMode::Metatiles; displayMapConnections(); map_item->draw(); map_item->setVisible(true); @@ -133,7 +133,8 @@ void Editor::setEditingObjects() { events_group->setVisible(true); } if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::EventObjects; + // !TODO: change this pixmapitem paintmode + map_item->paintingMode = LayoutPixmapItem::PaintMode::EventObjects; displayMapConnections(); map_item->draw(); map_item->setVisible(true); @@ -169,7 +170,7 @@ void Editor::setMapEditingButtonsEnabled(bool enabled) { void Editor::setEditingConnections() { current_view = map_item; if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::Disabled; + map_item->paintingMode = LayoutPixmapItem::PaintMode::Disabled; map_item->draw(); map_item->setVisible(true); populateConnectionMapPickers(); @@ -1020,7 +1021,7 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { return; this->updateCursorRectPos(x, y); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { int blockIndex = y * map->getWidth() + x; int metatileId = map->layout->blockdata.at(blockIndex).metatileId; this->ui->statusBar->showMessage(QString("X: %1, Y: %2, %3, Scale = %4x") @@ -1029,7 +1030,7 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { .arg(getMetatileDisplayMessage(metatileId)) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); } - else if (map_item->paintingMode == MapPixmapItem::PaintMode::EventObjects) { + else if (map_item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { this->ui->statusBar->showMessage(QString("X: %1, Y: %2, Scale = %3x") .arg(x) .arg(y) @@ -1040,8 +1041,8 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { void Editor::onHoveredMapMetatileCleared() { this->setCursorRectVisible(false); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles - || map_item->paintingMode == MapPixmapItem::PaintMode::EventObjects) { + if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles + || map_item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); @@ -1052,7 +1053,7 @@ void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { return; this->updateCursorRectPos(x, y); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { int blockIndex = y * map->getWidth() + x; uint16_t collision = map->layout->blockdata.at(blockIndex).collision; uint16_t elevation = map->layout->blockdata.at(blockIndex).elevation; @@ -1067,7 +1068,7 @@ void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { void Editor::onHoveredMapMovementPermissionCleared() { this->setCursorRectVisible(false); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); @@ -1089,16 +1090,22 @@ QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation return message; } +void Editor::unsetMap() { + // disconnect previous map's signals so they are not firing + // multiple times if set again in the future + if (this->map) { + this->map->disconnect(this); + } + + this->map = nullptr; +} + bool Editor::setMap(QString map_name) { if (map_name.isEmpty()) { return false; } - // disconnect previous map's signals so they are not firing - // multiple times if set again in the future - if (map) { - map->disconnect(this); - } + unsetMap(); if (project) { Map *loadedMap = project->loadMap(map_name); @@ -1107,6 +1114,7 @@ bool Editor::setMap(QString map_name) { } map = loadedMap; + this->layout = map->layout; // !TODO: editGroup.addStack(&map->editHistory); editGroup.setActiveStack(&map->editHistory); @@ -1123,8 +1131,8 @@ bool Editor::setMap(QString map_name) { return true; } -void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item) { - if (item->paintingMode != MapPixmapItem::PaintMode::Metatiles) { +void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { + if (item->paintingMode != LayoutPixmapItem::PaintMode::Metatiles) { return; } @@ -1136,8 +1144,8 @@ void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *ite } } -void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *item) { - if (!(item->paintingMode == MapPixmapItem::PaintMode::Metatiles)) { +void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *item) { + if (!(item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles)) { return; } this->cursorMapTileRect->stopRightClickSelectionAnchor(); @@ -1170,15 +1178,15 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { } } -void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item) { +void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { // TODO: add event tab object painting tool buttons stuff here - if (item->paintingMode == MapPixmapItem::PaintMode::Disabled) { + if (item->paintingMode == LayoutPixmapItem::PaintMode::Disabled) { return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { if (map_edit_mode == "paint") { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); @@ -1225,7 +1233,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } item->shift(event); } - } else if (item->paintingMode == MapPixmapItem::PaintMode::EventObjects) { + } else if (item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { if (obj_edit_mode == "paint" && event->type() == QEvent::GraphicsSceneMousePress) { // Right-clicking while in paint mode will change mode to select. if (event->buttons() & Qt::RightButton) { @@ -1253,7 +1261,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } } else if (obj_edit_mode == "select") { // do nothing here, at least for now - } else if (obj_edit_mode == "shift" && item->map) { + } else if (obj_edit_mode == "shift") { static QPoint selection_origin; static unsigned actionId = 0; @@ -1269,8 +1277,8 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item QList selectedEvents; - for (DraggablePixmapItem *item : getObjects()) { - selectedEvents.append(item->event); + for (DraggablePixmapItem *pixmapItem : getObjects()) { + selectedEvents.append(pixmapItem->event); } selection_origin = QPoint(pos.x(), pos.y()); @@ -1283,7 +1291,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item) { - if (item->paintingMode != MapPixmapItem::PaintMode::Metatiles) { + if (item->paintingMode != LayoutPixmapItem::PaintMode::Metatiles) { return; } @@ -1400,12 +1408,12 @@ void Editor::displayMetatileSelector() { } void Editor::displayMapMetatiles() { - map_item = new MapPixmapItem(map, this->metatile_selector_item, this->settings); - connect(map_item, &MapPixmapItem::mouseEvent, this, &Editor::mouseEvent_map); - connect(map_item, &MapPixmapItem::startPaint, this, &Editor::onMapStartPaint); - connect(map_item, &MapPixmapItem::endPaint, this, &Editor::onMapEndPaint); - connect(map_item, &MapPixmapItem::hoveredMapMetatileChanged, this, &Editor::onHoveredMapMetatileChanged); - connect(map_item, &MapPixmapItem::hoveredMapMetatileCleared, this, &Editor::onHoveredMapMetatileCleared); + map_item = new LayoutPixmapItem(this->layout, this->metatile_selector_item, this->settings); + connect(map_item, &LayoutPixmapItem::mouseEvent, this, &Editor::mouseEvent_map); + connect(map_item, &LayoutPixmapItem::startPaint, this, &Editor::onMapStartPaint); + connect(map_item, &LayoutPixmapItem::endPaint, this, &Editor::onMapEndPaint); + connect(map_item, &LayoutPixmapItem::hoveredMapMetatileChanged, this, &Editor::onHoveredMapMetatileChanged); + connect(map_item, &LayoutPixmapItem::hoveredMapMetatileCleared, this, &Editor::onHoveredMapMetatileCleared); map_item->draw(true); scene->addItem(map_item); @@ -1425,7 +1433,7 @@ void Editor::displayMapMovementPermissions() { scene->removeItem(collision_item); delete collision_item; } - collision_item = new CollisionPixmapItem(map, this->movement_permissions_selector_item, + collision_item = new CollisionPixmapItem(this->layout, this->movement_permissions_selector_item, this->metatile_selector_item, this->settings, &this->collisionOpacity); connect(collision_item, &CollisionPixmapItem::mouseEvent, this, &Editor::mouseEvent_collision); connect(collision_item, &CollisionPixmapItem::hoveredMapMovementPermissionChanged, @@ -1444,7 +1452,7 @@ void Editor::displayBorderMetatiles() { } scene_selected_border_metatiles = new QGraphicsScene; - selected_border_metatiles_item = new BorderMetatilesPixmapItem(map, this->metatile_selector_item); + selected_border_metatiles_item = new BorderMetatilesPixmapItem(this->layout, this->metatile_selector_item); selected_border_metatiles_item->draw(); scene_selected_border_metatiles->addItem(selected_border_metatiles_item); @@ -2035,7 +2043,7 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { } void Editor::duplicateSelectedEvents() { - if (!selected_events || !selected_events->length() || !map || !current_view || map_item->paintingMode != MapPixmapItem::PaintMode::EventObjects) + if (!selected_events || !selected_events->length() || !map || !current_view || map_item->paintingMode != LayoutPixmapItem::PaintMode::EventObjects) return; QList selectedEvents; @@ -2207,7 +2215,7 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working // is clicking on the background instead of an event. void Editor::objectsView_onMousePress(QMouseEvent *event) { // make sure we are in object editing mode - if (map_item && map_item->paintingMode != MapPixmapItem::PaintMode::EventObjects) { + if (map_item && map_item->paintingMode != LayoutPixmapItem::PaintMode::EventObjects) { return; } if (this->obj_edit_mode == "paint" && event->buttons() & Qt::RightButton) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5c4aaf34..351c0e3e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -617,12 +617,32 @@ void MainWindow::on_action_Reload_Project_triggered() { } } +void MainWindow::unsetMap() { + // + logInfo("Disabling map-related edits"); + + // + this->editor->unsetMap(); + + // disable other tabs + this->ui->mainTabBar->setTabEnabled(0, true); + this->ui->mainTabBar->setTabEnabled(1, false); + this->ui->mainTabBar->setTabEnabled(2, false); + this->ui->mainTabBar->setTabEnabled(3, false); + this->ui->mainTabBar->setTabEnabled(4, false); + + // +} + bool MainWindow::setMap(QString map_name, bool scrollTreeView) { - logInfo(QString("Setting map to '%1'").arg(map_name)); + // if map name is empty, clear & disable map ui if (map_name.isEmpty()) { + unsetMap(); return false; } + logInfo(QString("Setting map to '%1'").arg(map_name)); + if (!editor->setMap(map_name)) { logWarn(QString("Failed to set map to '%1'").arg(map_name)); return false; @@ -965,6 +985,13 @@ bool MainWindow::populateMapList() { groupListProxyModel->setSourceModel(this->mapGroupModel); ui->mapList->setModel(groupListProxyModel); + this->layoutTreeModel = new LayoutTreeModel(editor->project); + this->layoutListProxyModel = new FilterChildrenProxyModel(); + this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); + ui->layoutList->setModel(layoutListProxyModel); + + //connect(this->ui->layoutList, &QTreeView::doubleClicked, this, &MainWindow::on_layoutList_activated); + // ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); // ui->mapList->setDragEnabled(true); // ui->mapList->setAcceptDrops(true); @@ -993,7 +1020,7 @@ void MainWindow::sortMapList() { // switch (mapSortOrder) // { - // case MapSortOrder::Group: + // case MapSortOrder::SortByGroup: // for (int i = 0; i < project->groupNames.length(); i++) { // QString group_name = project->groupNames.value(i); // QStandardItem *group = new QStandardItem; @@ -1014,7 +1041,7 @@ void MainWindow::sortMapList() { // } // } // break; - // case MapSortOrder::Area: + // case MapSortOrder::SortByArea: // { // QMap mapsecToGroupNum; // for (int i = 0; i < project->mapSectionNameToValue.size(); i++) { @@ -1044,7 +1071,7 @@ void MainWindow::sortMapList() { // } // break; // } - // case MapSortOrder::Layout: + // case MapSortOrder::SortByLayout: // { // QMap layoutIndices; // for (int i = 0; i < project->mapLayoutsTable.length(); i++) { @@ -1137,19 +1164,19 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) { openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::Group, triggeredAction->data()); + this->newMapPrompt->init(MapSortOrder::SortByGroup, triggeredAction->data()); } void MainWindow::onAddNewMapToAreaClick(QAction* triggeredAction) { openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::Area, triggeredAction->data()); + this->newMapPrompt->init(MapSortOrder::SortByArea, triggeredAction->data()); } void MainWindow::onAddNewMapToLayoutClick(QAction* triggeredAction) { openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::Layout, triggeredAction->data()); + this->newMapPrompt->init(MapSortOrder::SortByLayout, triggeredAction->data()); } void MainWindow::onNewMapCreated() { @@ -1359,9 +1386,23 @@ void MainWindow::currentMetatilesSelectionChanged() } } +// !TODO +void MainWindow::on_mapListContainer_currentChanged(int index) { + // + switch (index) { + case MapListTab::Groups: + break; + case MapListTab::Areas: + break; + case MapListTab::Layouts: + //setMap(nullptr); + //setLayout(nullptr); + break; + } +} + /// !TODO -void MainWindow::on_mapList_activated(const QModelIndex &index) -{ +void MainWindow::on_mapList_activated(const QModelIndex &index) { QVariant data = index.data(Qt::UserRole); if (index.data(MapListRoles::TypeRole) == "map_name" && !data.isNull()) { QString mapName = data.toString(); @@ -1376,6 +1417,24 @@ void MainWindow::on_mapList_activated(const QModelIndex &index) } } +void MainWindow::on_areaList_activated(const QModelIndex &index) { + // +} + +void MainWindow::on_layoutList_activated(const QModelIndex &index) { + if (!index.isValid()) return; + + QVariant data = index.data(Qt::UserRole); + if (index.data(MapListRoles::TypeRole) == "map_layout" && !data.isNull()) { + QString layoutName = data.toString(); + // + logInfo("Switching to a layout-only editing mode"); + setMap(QString()); + // setLayout(layout) + qDebug() << "set layout" << layoutName; + } +} + /// !TODO something with the projectHasUnsavedChanges var void MainWindow::drawMapListIcons(QAbstractItemModel *model) { // projectHasUnsavedChanges = false; @@ -2616,21 +2675,21 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() form.addRow(errorLabel); if (dialog.exec() == QDialog::Accepted) { - Map *map = editor->map; - Blockdata oldMetatiles = map->layout->blockdata; - Blockdata oldBorder = map->layout->border; - QSize oldMapDimensions(map->getWidth(), map->getHeight()); - QSize oldBorderDimensions(map->getBorderWidth(), map->getBorderHeight()); + Layout *layout = editor->layout; + Blockdata oldMetatiles = layout->blockdata; + Blockdata oldBorder = layout->border; + QSize oldMapDimensions(layout->getWidth(), layout->getHeight()); + QSize oldBorderDimensions(layout->getBorderWidth(), layout->getBorderHeight()); QSize newMapDimensions(widthSpinBox->value(), heightSpinBox->value()); QSize newBorderDimensions(bwidthSpinBox->value(), bheightSpinBox->value()); if (oldMapDimensions != newMapDimensions || oldBorderDimensions != newBorderDimensions) { - editor->map->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); - editor->map->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); - editor->map->editHistory.push(new ResizeMap(map, + layout->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); + layout->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); + editor->map->editHistory.push(new ResizeMap(layout, oldMapDimensions, newMapDimensions, - oldMetatiles, map->layout->blockdata, + oldMetatiles, layout->blockdata, oldBorderDimensions, newBorderDimensions, - oldBorder, map->layout->border + oldBorder, layout->border )); } } @@ -2670,42 +2729,40 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } -// void MainWindow::on_toolButton_ExpandAll_clicked() -// { -// if (ui->mapList) { -// ui->mapList->expandToDepth(0); -// } -// } - -// void MainWindow::on_toolButton_CollapseAll_clicked() -// { -// if (ui->mapList) { -// ui->mapList->collapseAll(); -// } -// } - void MainWindow::on_toolButton_ExpandAll_Groups_clicked() { - // + if (ui->mapList) { + ui->mapList->expandToDepth(0); + } } void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { - // + if (ui->mapList) { + ui->mapList->collapseAll(); + } } void MainWindow::on_toolButton_ExpandAll_Areas_clicked() { - // + if (ui->areaList) { + ui->areaList->expandToDepth(0); + } } void MainWindow::on_toolButton_CollapseAll_Areas_clicked() { - // + if (ui->areaList) { + ui->areaList->collapseAll(); + } } void MainWindow::on_toolButton_ExpandAll_Layouts_clicked() { - // + if (ui->layoutList) { + ui->layoutList->expandToDepth(0); + } } void MainWindow::on_toolButton_CollapseAll_Layouts_clicked() { - // + if (ui->layoutList) { + ui->layoutList->collapseAll(); + } } void MainWindow::on_actionAbout_Porymap_triggered() diff --git a/src/project.cpp b/src/project.cpp index 588d0ee4..4acaf94d 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -359,6 +359,10 @@ QString Project::readMapLayoutId(QString map_name) { return ParseUtil::jsonToQString(mapObj["layout"]); } +QString Project::readMapLayoutName(QString mapName) { + return this->layoutIdsToNames[readMapLayoutId(mapName)]; +} + QString Project::readMapLocation(QString map_name) { if (mapCache.contains(map_name)) { return mapCache.value(map_name)->location; @@ -408,6 +412,7 @@ bool Project::loadMapLayout(Map* map) { bool Project::readMapLayouts() { mapLayouts.clear(); mapLayoutsTable.clear(); + layoutIdsToNames.clear(); QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); QString fullFilepath = QString("%1/%2").arg(root).arg(layoutsFilepath); @@ -529,6 +534,7 @@ bool Project::readMapLayouts() { } mapLayouts.insert(layout->id, layout); mapLayoutsTable.append(layout->id); + layoutIdsToNames.insert(layout->id, layout->name); } // Deep copy @@ -1336,11 +1342,12 @@ void Project::updateMapLayout(Map* map) { mapLayoutsTableMaster.append(map->layoutId); } + // !TODO // Deep copy - MapLayout *layout = mapLayouts.value(map->layoutId); - MapLayout *newLayout = new MapLayout(); - *newLayout = *layout; - mapLayoutsMaster.insert(map->layoutId, newLayout); + // MapLayout *layout = mapLayouts.value(map->layoutId); + // MapLayout *newLayout = new MapLayout(); + // *newLayout = *layout; + // mapLayoutsMaster.insert(map->layoutId, newLayout); } void Project::saveAllDataStructures() { diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index 8f8bc134..6c542558 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -7,30 +7,30 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); - Blockdata oldBorder = map->layout->border; + Blockdata oldBorder = layout->border; for (int i = 0; i < selection.dimensions.x() && (i + pos.x()) < width; i++) { for (int j = 0; j < selection.dimensions.y() && (j + pos.y()) < height; j++) { MetatileSelectionItem item = selection.metatileItems.at(j * selection.dimensions.x() + i); - map->setBorderMetatileId(pos.x() + i, pos.y() + j, item.metatileId, true); + layout->setBorderMetatileId(pos.x() + i, pos.y() + j, item.metatileId, true); } } - if (map->layout->border != oldBorder) { - map->editHistory.push(new PaintBorder(map, oldBorder, map->layout->border, 0)); + if (layout->border != oldBorder) { + layout->editHistory.push(new PaintBorder(layout, oldBorder, layout->border, 0)); } emit borderMetatilesChanged(); } void BorderMetatilesPixmapItem::draw() { - map->setBorderItem(this); + layout->setBorderItem(this); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); QImage image(16 * width, 16 * height, QImage::Format_RGBA8888); QPainter painter(&image); @@ -39,11 +39,11 @@ void BorderMetatilesPixmapItem::draw() { int x = i * 16; int y = j * 16; QImage metatile_image = getMetatileImage( - map->getBorderMetatileId(i, j), - map->layout->tileset_primary, - map->layout->tileset_secondary, - map->metatileLayerOrder, - map->metatileLayerOpacity); + layout->getBorderMetatileId(i, j), + layout->tileset_primary, + layout->tileset_secondary, + layout->metatileLayerOrder, + layout->metatileLayerOpacity); QPoint metatile_origin = QPoint(x, y); painter.drawImage(metatile_origin, metatile_image); } @@ -57,7 +57,7 @@ void BorderMetatilesPixmapItem::draw() { void BorderMetatilesPixmapItem::hoverUpdate(const QPointF &pixmapPos) { QPoint pos = Metatile::coordFromPixmapCoord(pixmapPos); - uint16_t metatileId = this->map->getBorderMetatileId(pos.x(), pos.y()); + uint16_t metatileId = this->layout->getBorderMetatileId(pos.x(), pos.y()); emit this->hoveredBorderMetatileSelectionChanged(metatileId); } diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index eaa4bca1..26680677 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -8,7 +8,7 @@ void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { this->previousPos = pos; emit this->hoveredMapMovementPermissionChanged(pos.x(), pos.y()); } - if (this->settings->betterCursors && this->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (this->settings->betterCursors && this->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { setCursor(this->settings->mapCursor); } } @@ -21,7 +21,7 @@ void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { void CollisionPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { emit this->hoveredMapMovementPermissionCleared(); - if (this->settings->betterCursors && this->paintingMode == MapPixmapItem::PaintMode::Metatiles){ + if (this->settings->betterCursors && this->paintingMode == LayoutPixmapItem::PaintMode::Metatiles){ unsetCursor(); } this->has_mouse = false; @@ -49,9 +49,10 @@ void CollisionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { } void CollisionPixmapItem::draw(bool ignoreCache) { - if (map) { - map->setCollisionItem(this); - setPixmap(map->renderCollision(ignoreCache)); + if (this->layout) { + // !TODO + // this->layout->setCollisionItem(this); + setPixmap(this->layout->renderCollision(ignoreCache)); setOpacity(*this->opacity); } } @@ -59,8 +60,8 @@ void CollisionPixmapItem::draw(bool ignoreCache) { void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; - } else if (map) { - Blockdata oldCollision = map->layout->blockdata; + } else if (this->layout) { + Blockdata oldCollision = this->layout->blockdata; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); @@ -70,18 +71,18 @@ void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) { pos = this->adjustCoords(pos); } else { this->prevStraightPathState = false; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; } Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { block.collision = this->movementPermissionsSelector->getSelectedCollision(); block.elevation = this->movementPermissionsSelector->getSelectedElevation(); - map->setBlock(pos.x(), pos.y(), block, true); + this->layout->setBlock(pos.x(), pos.y(), block, true); } - if (map->layout->blockdata != oldCollision) { - map->editHistory.push(new PaintCollision(map, oldCollision, map->layout->blockdata, actionId_)); + if (this->layout->blockdata != oldCollision) { + this->layout->editHistory.push(new PaintCollision(this->layout, oldCollision, this->layout->blockdata, actionId_)); } } } @@ -89,16 +90,16 @@ void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { this->actionId_++; - } else if (map) { - Blockdata oldCollision = map->layout->blockdata; + } else if (this->layout) { + Blockdata oldCollision = this->layout->blockdata; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); uint16_t collision = this->movementPermissionsSelector->getSelectedCollision(); uint16_t elevation = this->movementPermissionsSelector->getSelectedElevation(); - map->floodFillCollisionElevation(pos.x(), pos.y(), collision, elevation); + this->layout->floodFillCollisionElevation(pos.x(), pos.y(), collision, elevation); - if (map->layout->blockdata != oldCollision) { - map->editHistory.push(new BucketFillCollision(map, oldCollision, map->layout->blockdata)); + if (this->layout->blockdata != oldCollision) { + this->layout->editHistory.push(new BucketFillCollision(this->layout, oldCollision, this->layout->blockdata)); } } } @@ -106,15 +107,15 @@ void CollisionPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { this->actionId_++; - } else if (map) { - Blockdata oldCollision = map->layout->blockdata; + } else if (this->layout) { + Blockdata oldCollision = this->layout->blockdata; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); uint16_t collision = this->movementPermissionsSelector->getSelectedCollision(); uint16_t elevation = this->movementPermissionsSelector->getSelectedElevation(); - map->magicFillCollisionElevation(pos.x(), pos.y(), collision, elevation); + this->layout->magicFillCollisionElevation(pos.x(), pos.y(), collision, elevation); - if (map->layout->blockdata != oldCollision) { - map->editHistory.push(new MagicFillCollision(map, oldCollision, map->layout->blockdata)); + if (this->layout->blockdata != oldCollision) { + this->layout->editHistory.push(new MagicFillCollision(this->layout, oldCollision, this->layout->blockdata)); } } } @@ -122,7 +123,7 @@ void CollisionPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::pick(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->movementPermissionsSelector->select(block.collision, block.elevation); } } @@ -132,12 +133,12 @@ void CollisionPixmapItem::updateMovementPermissionSelection(QGraphicsSceneMouseE // Snap point to within map bounds. if (pos.x() < 0) pos.setX(0); - if (pos.x() >= map->getWidth()) pos.setX(map->getWidth() - 1); + if (pos.x() >= this->layout->getWidth()) pos.setX(this->layout->getWidth() - 1); if (pos.y() < 0) pos.setY(0); - if (pos.y() >= map->getHeight()) pos.setY(map->getHeight() - 1); + if (pos.y() >= this->layout->getHeight()) pos.setY(this->layout->getHeight() - 1); Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->movementPermissionsSelector->select(block.collision, block.elevation); } } diff --git a/src/ui/mappixmapitem.cpp b/src/ui/layoutpixmapitem.cpp similarity index 65% rename from src/ui/mappixmapitem.cpp rename to src/ui/layoutpixmapitem.cpp index f44922a4..a595695a 100644 --- a/src/ui/mappixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -1,4 +1,4 @@ -#include "mappixmapitem.h" +#include "layoutpixmapitem.h" #include "metatile.h" #include "log.h" #include "scripting.h" @@ -7,8 +7,8 @@ #define SWAP(a, b) do { if (a != b) { a ^= b; b ^= a; a ^= b; } } while (0) -void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::paint(QGraphicsSceneMouseEvent *event) { + if (layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -20,7 +20,7 @@ void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { pos = this->adjustCoords(pos); } else { this->prevStraightPathState = false; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; } // Paint onto the map. @@ -43,8 +43,8 @@ void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::shift(QGraphicsSceneMouseEvent *event) { + if (layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -56,7 +56,7 @@ void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) { pos = this->adjustCoords(pos); } else { this->prevStraightPathState = false; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; } if (event->type() == QEvent::GraphicsSceneMousePress) { @@ -76,32 +76,32 @@ void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::shift(int xDelta, int yDelta, bool fromScriptCall) { - Blockdata oldMetatiles = map->layout->blockdata; +void LayoutPixmapItem::shift(int xDelta, int yDelta, bool fromScriptCall) { + Blockdata oldMetatiles = this->layout->blockdata; - for (int i = 0; i < map->getWidth(); i++) - for (int j = 0; j < map->getHeight(); j++) { + for (int i = 0; i < this->layout->getWidth(); i++) + for (int j = 0; j < this->layout->getHeight(); j++) { int destX = i + xDelta; int destY = j + yDelta; if (destX < 0) - do { destX += map->getWidth(); } while (destX < 0); + do { destX += this->layout->getWidth(); } while (destX < 0); if (destY < 0) - do { destY += map->getHeight(); } while (destY < 0); - destX %= map->getWidth(); - destY %= map->getHeight(); + do { destY += this->layout->getHeight(); } while (destY < 0); + destX %= this->layout->getWidth(); + destY %= this->layout->getHeight(); - int blockIndex = j * map->getWidth() + i; + int blockIndex = j * this->layout->getWidth() + i; Block srcBlock = oldMetatiles.at(blockIndex); - map->setBlock(destX, destY, srcBlock); + this->layout->setBlock(destX, destY, srcBlock); } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new ShiftMetatiles(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new ShiftMetatiles(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); Scripting::cb_MapShifted(xDelta, yDelta); } } -void MapPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { +void LayoutPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); int initialX = fromScriptCall ? x : this->paint_tile_initial_x; int initialY = fromScriptCall ? y : this->paint_tile_initial_y; @@ -117,14 +117,14 @@ void MapPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { y = initialY + (yDiff / selection.dimensions.y()) * selection.dimensions.y(); // for edit history - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); - for (int i = 0; i < selection.dimensions.x() && i + x < map->getWidth(); i++) - for (int j = 0; j < selection.dimensions.y() && j + y < map->getHeight(); j++) { + for (int i = 0; i < selection.dimensions.x() && i + x < this->layout->getWidth(); i++) + for (int j = 0; j < selection.dimensions.y() && j + y < this->layout->getHeight(); j++) { int actualX = i + x; int actualY = j + y; Block block; - if (map->getBlock(actualX, actualY, &block)) { + if (this->layout->getBlock(actualX, actualY, &block)) { int index = j * selection.dimensions.x() + i; MetatileSelectionItem item = selection.metatileItems.at(index); if (!item.enabled) @@ -135,19 +135,19 @@ void MapPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { block.collision = collisionItem.collision; block.elevation = collisionItem.elevation; } - map->setBlock(actualX, actualY, block, !fromScriptCall); + this->layout->setBlock(actualX, actualY, block, !fromScriptCall); } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new PaintMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new PaintMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } // These are tile offsets from the top-left tile in the 3x3 smart path selection. // Each entry is for one possibility from the marching squares value for a tile. // (Marching Squares: https://en.wikipedia.org/wiki/Marching_squares) -QList MapPixmapItem::smartPathTable = QList({ +QList LayoutPixmapItem::smartPathTable = QList({ 4, // 0000 4, // 0001 4, // 0010 @@ -189,7 +189,7 @@ bool isValidSmartPathSelection(MetatileSelection selection) { return true; } -void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { +void LayoutPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); if (!isValidSmartPathSelection(selection)) return; @@ -206,30 +206,30 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { } // for edit history - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); // Fill the region with the open tile. for (int i = 0; i <= 1; i++) for (int j = 0; j <= 1; j++) { - if (!map->isWithinBounds(x + i, y + j)) + if (!this->layout->isWithinBounds(x + i, y + j)) continue; int actualX = i + x; int actualY = j + y; Block block; - if (map->getBlock(actualX, actualY, &block)) { + if (this->layout->getBlock(actualX, actualY, &block)) { block.metatileId = openTile; if (setCollisions) { block.collision = openTileCollision; block.elevation = openTileElevation; } - map->setBlock(actualX, actualY, block, !fromScriptCall); + this->layout->setBlock(actualX, actualY, block, !fromScriptCall); } } // Go back and resolve the edge tiles for (int i = -1; i <= 2; i++) for (int j = -1; j <= 2; j++) { - if (!map->isWithinBounds(x + i, y + j)) + if (!this->layout->isWithinBounds(x + i, y + j)) continue; // Ignore the corners, which can't possible be affected by the smart path. if ((i == -1 && j == -1) || (i == 2 && j == -1) || @@ -240,7 +240,7 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { int actualX = i + x; int actualY = j + y; Block block; - if (!map->getBlock(actualX, actualY, &block) || !isSmartPathTile(selection.metatileItems, block.metatileId)) { + if (!this->layout->getBlock(actualX, actualY, &block) || !isSmartPathTile(selection.metatileItems, block.metatileId)) { continue; } @@ -251,13 +251,13 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { Block left; // Get marching squares value, to determine which tile to use. - if (map->getBlock(actualX, actualY - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId)) + if (this->layout->getBlock(actualX, actualY - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId)) id += 1; - if (map->getBlock(actualX + 1, actualY, &right) && isSmartPathTile(selection.metatileItems, right.metatileId)) + if (this->layout->getBlock(actualX + 1, actualY, &right) && isSmartPathTile(selection.metatileItems, right.metatileId)) id += 2; - if (map->getBlock(actualX, actualY + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId)) + if (this->layout->getBlock(actualX, actualY + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId)) id += 4; - if (map->getBlock(actualX - 1, actualY, &left) && isSmartPathTile(selection.metatileItems, left.metatileId)) + if (this->layout->getBlock(actualX - 1, actualY, &left) && isSmartPathTile(selection.metatileItems, left.metatileId)) id += 8; block.metatileId = selection.metatileItems.at(smartPathTable[id]).metatileId; @@ -266,19 +266,19 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { block.collision = collisionItem.collision; block.elevation = collisionItem.elevation; } - map->setBlock(actualX, actualY, block, !fromScriptCall); + this->layout->setBlock(actualX, actualY, block, !fromScriptCall); } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new PaintMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new PaintMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } -void MapPixmapItem::lockNondominantAxis(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::lockNondominantAxis(QGraphicsSceneMouseEvent *event) { /* Return if an axis is already locked, or if the mouse has been released. The mouse release check is necessary - * because MapPixmapItem::mouseReleaseEvent seems to get called before this function, which would unlock the axis + * because LayoutPixmapItem::mouseReleaseEvent seems to get called before this function, which would unlock the axis * and then get immediately re-locked here until the next ctrl-click. */ - if (this->lockedAxis != MapPixmapItem::Axis::None || event->type() == QEvent::GraphicsSceneMouseRelease) + if (this->lockedAxis != LayoutPixmapItem::Axis::None || event->type() == QEvent::GraphicsSceneMouseRelease) return; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); @@ -293,31 +293,31 @@ void MapPixmapItem::lockNondominantAxis(QGraphicsSceneMouseEvent *event) { int yDiff = pos.y() - this->straight_path_initial_y; if (xDiff || yDiff) { if (abs(xDiff) < abs(yDiff)) { - this->lockedAxis = MapPixmapItem::Axis::X; + this->lockedAxis = LayoutPixmapItem::Axis::X; } else { - this->lockedAxis = MapPixmapItem::Axis::Y; + this->lockedAxis = LayoutPixmapItem::Axis::Y; } } } // Adjust the cooresponding coordinate when it is locked -QPoint MapPixmapItem::adjustCoords(QPoint pos) { - if (this->lockedAxis == MapPixmapItem::Axis::X) { +QPoint LayoutPixmapItem::adjustCoords(QPoint pos) { + if (this->lockedAxis == LayoutPixmapItem::Axis::X) { pos.setX(this->straight_path_initial_x); - } else if (this->lockedAxis == MapPixmapItem::Axis::Y) { + } else if (this->lockedAxis == LayoutPixmapItem::Axis::Y) { pos.setY(this->straight_path_initial_y); } return pos; } -void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - // Snap point to within map bounds. + // Snap point to within layout bounds. if (pos.x() < 0) pos.setX(0); - if (pos.x() >= map->getWidth()) pos.setX(map->getWidth() - 1); + if (pos.x() >= this->layout->getWidth()) pos.setX(this->layout->getWidth() - 1); if (pos.y() < 0) pos.setY(0); - if (pos.y() >= map->getHeight()) pos.setY(map->getHeight() - 1); + if (pos.y() >= this->layout->getHeight()) pos.setY(this->layout->getHeight() - 1); // Update/apply copied metatiles. if (event->type() == QEvent::GraphicsSceneMousePress) { @@ -325,7 +325,7 @@ void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { selection.clear(); selection.append(QPoint(pos.x(), pos.y())); Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->metatileSelector->selectFromMap(block.metatileId, block.collision, block.elevation); } } else if (event->type() == QEvent::GraphicsSceneMouseMove) { @@ -348,11 +348,11 @@ void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { int x = point.x(); int y = point.y(); Block block; - if (map->getBlock(x, y, &block)) { + if (this->layout->getBlock(x, y, &block)) { metatiles.append(block.metatileId); } - int blockIndex = y * map->getWidth() + x; - block = map->layout->blockdata.at(blockIndex); + int blockIndex = y * this->layout->getWidth() + x; + block = this->layout->blockdata.at(blockIndex); auto collision = block.collision; auto elevation = block.elevation; collisions.append(QPair(collision, elevation)); @@ -362,8 +362,8 @@ void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { + if (this->layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -371,7 +371,7 @@ void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { Block block; MetatileSelection selection = this->metatileSelector->getMetatileSelection(); int metatileId = selection.metatileItems.first().metatileId; - if (selection.metatileItems.count() > 1 || (map->getBlock(pos.x(), pos.y(), &block) && block.metatileId != metatileId)) { + if (selection.metatileItems.count() > 1 || (this->layout->getBlock(pos.x(), pos.y(), &block) && block.metatileId != metatileId)) { bool smartPathsEnabled = event->modifiers() & Qt::ShiftModifier; if ((this->settings->smartPathsEnabled || smartPathsEnabled) && selection.dimensions.x() == 3 && selection.dimensions.y() == 3) this->floodFillSmartPath(pos.x(), pos.y()); @@ -382,8 +382,8 @@ void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { + if (this->layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -393,18 +393,18 @@ void MapPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::magicFill(int x, int y, uint16_t metatileId, bool fromScriptCall) { +void LayoutPixmapItem::magicFill(int x, int y, uint16_t metatileId, bool fromScriptCall) { QPoint selectionDimensions(1, 1); QList selectedMetatiles = QList({MetatileSelectionItem{ true, metatileId }}); this->magicFill(x, y, selectionDimensions, selectedMetatiles, QList(), fromScriptCall); } -void MapPixmapItem::magicFill(int x, int y, bool fromScriptCall) { +void LayoutPixmapItem::magicFill(int x, int y, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); this->magicFill(x, y, selection.dimensions, selection.metatileItems, selection.collisionItems, fromScriptCall); } -void MapPixmapItem::magicFill( +void LayoutPixmapItem::magicFill( int initialX, int initialY, QPoint selectionDimensions, @@ -412,18 +412,18 @@ void MapPixmapItem::magicFill( QList selectedCollisions, bool fromScriptCall) { Block block; - if (map->getBlock(initialX, initialY, &block)) { + if (this->layout->getBlock(initialX, initialY, &block)) { if (selectedMetatiles.length() == 1 && selectedMetatiles.at(0).metatileId == block.metatileId) { return; } - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); bool setCollisions = selectedCollisions.length() == selectedMetatiles.length(); uint16_t metatileId = block.metatileId; - for (int y = 0; y < map->getHeight(); y++) { - for (int x = 0; x < map->getWidth(); x++) { - if (map->getBlock(x, y, &block) && block.metatileId == metatileId) { + for (int y = 0; y < this->layout->getHeight(); y++) { + for (int x = 0; x < this->layout->getWidth(); x++) { + if (this->layout->getBlock(x, y, &block) && block.metatileId == metatileId) { int xDiff = x - initialX; int yDiff = y - initialY; int i = xDiff % selectionDimensions.x(); @@ -438,30 +438,30 @@ void MapPixmapItem::magicFill( block.collision = item.collision; block.elevation = item.elevation; } - map->setBlock(x, y, block, !fromScriptCall); + this->layout->setBlock(x, y, block, !fromScriptCall); } } } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new MagicFillMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new MagicFillMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } } -void MapPixmapItem::floodFill(int initialX, int initialY, bool fromScriptCall) { +void LayoutPixmapItem::floodFill(int initialX, int initialY, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); this->floodFill(initialX, initialY, selection.dimensions, selection.metatileItems, selection.collisionItems, fromScriptCall); } -void MapPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId, bool fromScriptCall) { +void LayoutPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId, bool fromScriptCall) { QPoint selectionDimensions(1, 1); QList selectedMetatiles = QList({MetatileSelectionItem{true, metatileId}}); this->floodFill(initialX, initialY, selectionDimensions, selectedMetatiles, QList(), fromScriptCall); } -void MapPixmapItem::floodFill( +void LayoutPixmapItem::floodFill( int initialX, int initialY, QPoint selectionDimensions, @@ -469,7 +469,7 @@ void MapPixmapItem::floodFill( QList selectedCollisions, bool fromScriptCall) { bool setCollisions = selectedCollisions.length() == selectedMetatiles.length(); - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); QSet visited; QList todo; @@ -479,11 +479,11 @@ void MapPixmapItem::floodFill( int x = point.x(); int y = point.y(); Block block; - if (!map->getBlock(x, y, &block)) { + if (!this->layout->getBlock(x, y, &block)) { continue; } - visited.insert(x + y * map->getWidth()); + visited.insert(x + y * this->layout->getWidth()); int xDiff = x - initialX; int yDiff = y - initialY; int i = xDiff % selectionDimensions.x(); @@ -500,32 +500,32 @@ void MapPixmapItem::floodFill( block.collision = item.collision; block.elevation = item.elevation; } - map->setBlock(x, y, block, !fromScriptCall); + this->layout->setBlock(x, y, block, !fromScriptCall); } - if (!visited.contains(x + 1 + y * map->getWidth()) && map->getBlock(x + 1, y, &block) && block.metatileId == old_metatileId) { + if (!visited.contains(x + 1 + y * this->layout->getWidth()) && this->layout->getBlock(x + 1, y, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x + 1, y)); - visited.insert(x + 1 + y * map->getWidth()); + visited.insert(x + 1 + y * this->layout->getWidth()); } - if (!visited.contains(x - 1 + y * map->getWidth()) && map->getBlock(x - 1, y, &block) && block.metatileId == old_metatileId) { + if (!visited.contains(x - 1 + y * this->layout->getWidth()) && this->layout->getBlock(x - 1, y, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x - 1, y)); - visited.insert(x - 1 + y * map->getWidth()); + visited.insert(x - 1 + y * this->layout->getWidth()); } - if (!visited.contains(x + (y + 1) * map->getWidth()) && map->getBlock(x, y + 1, &block) && block.metatileId == old_metatileId) { + if (!visited.contains(x + (y + 1) * this->layout->getWidth()) && this->layout->getBlock(x, y + 1, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x, y + 1)); - visited.insert(x + (y + 1) * map->getWidth()); + visited.insert(x + (y + 1) * this->layout->getWidth()); } - if (!visited.contains(x + (y - 1) * map->getWidth()) && map->getBlock(x, y - 1, &block) && block.metatileId == old_metatileId) { + if (!visited.contains(x + (y - 1) * this->layout->getWidth()) && this->layout->getBlock(x, y - 1, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x, y - 1)); - visited.insert(x + (y - 1) * map->getWidth()); + visited.insert(x + (y - 1) * this->layout->getWidth()); } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new BucketFillMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new BucketFillMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } -void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScriptCall) { +void LayoutPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); if (!isValidSmartPathSelection(selection)) return; @@ -542,7 +542,7 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri setCollisions = true; } - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); // Flood fill the region with the open tile. QList todo; @@ -552,7 +552,7 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri int x = point.x(); int y = point.y(); Block block; - if (!map->getBlock(x, y, &block)) { + if (!this->layout->getBlock(x, y, &block)) { continue; } @@ -566,17 +566,17 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri block.collision = openTileCollision; block.elevation = openTileElevation; } - map->setBlock(x, y, block, !fromScriptCall); - if (map->getBlock(x + 1, y, &block) && block.metatileId == old_metatileId) { + this->layout->setBlock(x, y, block, !fromScriptCall); + if (this->layout->getBlock(x + 1, y, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x + 1, y)); } - if (map->getBlock(x - 1, y, &block) && block.metatileId == old_metatileId) { + if (this->layout->getBlock(x - 1, y, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x - 1, y)); } - if (map->getBlock(x, y + 1, &block) && block.metatileId == old_metatileId) { + if (this->layout->getBlock(x, y + 1, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x, y + 1)); } - if (map->getBlock(x, y - 1, &block) && block.metatileId == old_metatileId) { + if (this->layout->getBlock(x, y - 1, &block) && block.metatileId == old_metatileId) { todo.append(QPoint(x, y - 1)); } } @@ -590,11 +590,11 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri int x = point.x(); int y = point.y(); Block block; - if (!map->getBlock(x, y, &block)) { + if (!this->layout->getBlock(x, y, &block)) { continue; } - visited.insert(x + y * map->getWidth()); + visited.insert(x + y * this->layout->getWidth()); int id = 0; Block top; Block right; @@ -602,13 +602,13 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri Block left; // Get marching squares value, to determine which tile to use. - if (map->getBlock(x, y - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId)) + if (this->layout->getBlock(x, y - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId)) id += 1; - if (map->getBlock(x + 1, y, &right) && isSmartPathTile(selection.metatileItems, right.metatileId)) + if (this->layout->getBlock(x + 1, y, &right) && isSmartPathTile(selection.metatileItems, right.metatileId)) id += 2; - if (map->getBlock(x, y + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId)) + if (this->layout->getBlock(x, y + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId)) id += 4; - if (map->getBlock(x - 1, y, &left) && isSmartPathTile(selection.metatileItems, left.metatileId)) + if (this->layout->getBlock(x - 1, y, &left) && isSmartPathTile(selection.metatileItems, left.metatileId)) id += 8; block.metatileId = selection.metatileItems.at(smartPathTable[id]).metatileId; @@ -617,41 +617,41 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri block.collision = item.collision; block.elevation = item.elevation; } - map->setBlock(x, y, block, !fromScriptCall); + this->layout->setBlock(x, y, block, !fromScriptCall); // Visit neighbors if they are smart-path tiles, and don't revisit any. - if (!visited.contains(x + 1 + y * map->getWidth()) && map->getBlock(x + 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { + if (!visited.contains(x + 1 + y * this->layout->getWidth()) && this->layout->getBlock(x + 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { todo.append(QPoint(x + 1, y)); - visited.insert(x + 1 + y * map->getWidth()); + visited.insert(x + 1 + y * this->layout->getWidth()); } - if (!visited.contains(x - 1 + y * map->getWidth()) && map->getBlock(x - 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { + if (!visited.contains(x - 1 + y * this->layout->getWidth()) && this->layout->getBlock(x - 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { todo.append(QPoint(x - 1, y)); - visited.insert(x - 1 + y * map->getWidth()); + visited.insert(x - 1 + y * this->layout->getWidth()); } - if (!visited.contains(x + (y + 1) * map->getWidth()) && map->getBlock(x, y + 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { + if (!visited.contains(x + (y + 1) * this->layout->getWidth()) && this->layout->getBlock(x, y + 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { todo.append(QPoint(x, y + 1)); - visited.insert(x + (y + 1) * map->getWidth()); + visited.insert(x + (y + 1) * this->layout->getWidth()); } - if (!visited.contains(x + (y - 1) * map->getWidth()) && map->getBlock(x, y - 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { + if (!visited.contains(x + (y - 1) * this->layout->getWidth()) && this->layout->getBlock(x, y - 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId)) { todo.append(QPoint(x, y - 1)); - visited.insert(x + (y - 1) * map->getWidth()); + visited.insert(x + (y - 1) * this->layout->getWidth()); } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new BucketFillMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new BucketFillMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } -void MapPixmapItem::pick(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::pick(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->metatileSelector->selectFromMap(block.metatileId, block.collision, block.elevation); } } -void MapPixmapItem::select(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::select(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (event->type() == QEvent::GraphicsSceneMousePress) { selection_origin = QPoint(pos.x(), pos.y()); @@ -681,43 +681,47 @@ void MapPixmapItem::select(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::draw(bool ignoreCache) { - if (map) { - map->setMapItem(this); - setPixmap(map->render(ignoreCache)); +void LayoutPixmapItem::draw(bool ignoreCache) { + if (this->layout) { + layout->setLayoutItem(this); + setPixmap(this->layout->render(ignoreCache)); } } -void MapPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { +void LayoutPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->metatilePos) { this->metatilePos = pos; emit this->hoveredMapMetatileChanged(pos); } - if (this->settings->betterCursors && this->paintingMode != MapPixmapItem::PaintMode::Disabled) { + if (this->settings->betterCursors && this->paintingMode != LayoutPixmapItem::PaintMode::Disabled) { setCursor(this->settings->mapCursor); } } -void MapPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { + +void LayoutPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { this->has_mouse = true; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); emit this->hoveredMapMetatileChanged(pos); } -void MapPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { + +void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { emit this->hoveredMapMetatileCleared(); - if (this->settings->betterCursors && this->paintingMode != MapPixmapItem::PaintMode::Disabled) { + if (this->settings->betterCursors && this->paintingMode != LayoutPixmapItem::PaintMode::Disabled) { unsetCursor(); } this->has_mouse = false; } -void MapPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + +void LayoutPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); this->paint_tile_initial_x = this->straight_path_initial_x = pos.x(); this->paint_tile_initial_y = this->straight_path_initial_y = pos.y(); emit startPaint(event, this); emit mouseEvent(event, this); } -void MapPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + +void LayoutPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->metatilePos) { this->metatilePos = pos; @@ -725,8 +729,9 @@ void MapPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } emit mouseEvent(event, this); } -void MapPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - this->lockedAxis = MapPixmapItem::Axis::None; + +void LayoutPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + this->lockedAxis = LayoutPixmapItem::Axis::None; emit endPaint(event, this); emit mouseEvent(event, this); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 4a8eb262..53a65154 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -217,3 +217,146 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { return QStandardItemModel::data(index, role); } + + + + + + + + + + + + + + + // case MapSortOrder::Layout: + // { + // QMap layoutIndices; + // for (int i = 0; i < project->mapLayoutsTable.length(); i++) { + // QString layoutId = project->mapLayoutsTable.value(i); + // MapLayout *layout = project->mapLayouts.value(layoutId); + // QStandardItem *layoutItem = new QStandardItem; + // layoutItem->setText(layout->name); + // layoutItem->setIcon(folderIcon); + // layoutItem->setEditable(false); + // layoutItem->setData(layout->name, Qt::UserRole); + // layoutItem->setData("map_layout", MapListUserRoles::TypeRole); + // layoutItem->setData(layout->id, MapListUserRoles::TypeRole2); + // layoutItem->setData(i, MapListUserRoles::GroupRole); + // root->appendRow(layoutItem); + // mapGroupItemsList->append(layoutItem); + // layoutIndices[layoutId] = i; + // } + // for (int i = 0; i < project->groupNames.length(); i++) { + // QStringList names = project->groupedMapNames.value(i); + // for (int j = 0; j < names.length(); j++) { + // QString map_name = names.value(j); + // QStandardItem *map = createMapItem(map_name, i, j); + // QString layoutId = project->readMapLayoutId(map_name); + // QStandardItem *layoutItem = mapGroupItemsList->at(layoutIndices.value(layoutId)); + // layoutItem->setIcon(mapFolderIcon); + // layoutItem->appendRow(map); + // mapListIndexes.insert(map_name, map->index()); + // } + // } + // break; + // } +LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : QStandardItemModel(parent) { + // + + this->project = project; + this->root = this->invisibleRootItem(); + + initialize(); +} + +QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutName) { + QStandardItem *layout = new QStandardItem; + layout->setText(layoutName); + layout->setEditable(false); + layout->setData(layoutName, Qt::UserRole); + layout->setData("map_layout", MapListRoles::TypeRole); + // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->layoutItems.insert(layoutName, layout); + return layout; +} + +QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { + QStandardItem *map = new QStandardItem; + map->setText(mapName); + map->setEditable(false); + map->setData(mapName, Qt::UserRole); + map->setData("map_name", MapListRoles::TypeRole); + map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren); + this->mapItems.insert(mapName, map); + return map; +} + +void LayoutTreeModel::initialize() { + for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { + // + QString layoutId = project->mapLayoutsTable.value(i); + MapLayout *layout = project->mapLayouts.value(layoutId); + QStandardItem *layoutItem = createLayoutItem(layout->name); + this->root->appendRow(layoutItem); + } + + for (auto mapList : this->project->groupedMapNames) { + for (auto mapName : mapList) { + // + QString layoutName = project->readMapLayoutName(mapName); + QStandardItem *map = createMapItem(mapName); + this->layoutItems[layoutName]->appendRow(map); + } + } + + // // project->readMapLayoutName +} + +QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex LayoutTreeModel::indexOfLayout(QString layoutName) { + if (this->layoutItems.contains(layoutName)) { + return this->layoutItems[layoutName]->index(); + } + return QModelIndex(); +} + +QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { + int row = index.row(); + int col = index.column(); + + if (role == Qt::DecorationRole) { + static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListRoles::TypeRole).toString(); + + if (type == "map_layout") { + return mapIcon; + } + else if (type == "map_name") { + return QVariant(); + } + + return QVariant(); + + // check if map or group + // if map, check if edited or open + //return QIcon(":/icons/porymap-icon-2.ico"); + } + + return QStandardItemModel::data(index, role); +} + diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index ba315975..eb3eae45 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -96,13 +96,13 @@ void NewMapPopup::init() { void NewMapPopup::init(MapSortOrder type, QVariant data) { switch (type) { - case MapSortOrder::Group: + case MapSortOrder::SortByGroup: settings.group = project->groupNames.at(data.toInt()); break; - case MapSortOrder::Area: + case MapSortOrder::SortByArea: settings.location = data.toString(); break; - case MapSortOrder::Layout: + case MapSortOrder::SortByLayout: useLayout(data.toString()); break; } From 90f8218c32e77c96a48a372f04a154ee86067017 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 1 Feb 2023 15:02:26 -0500 Subject: [PATCH 004/364] fix edit commands and ui things to use layout instead of map --- include/core/editcommands.h | 6 +- include/core/map.h | 66 +--- include/core/maplayout.h | 5 +- .../ui/currentselectedmetatilespixmapitem.h | 13 +- include/ui/metatileselector.h | 12 +- include/ui/prefab.h | 6 +- include/ui/prefabcreationdialog.h | 7 +- include/ui/tileseteditor.h | 11 +- include/ui/tileseteditormetatileselector.h | 7 +- src/core/editcommands.cpp | 64 ++-- src/core/map.cpp | 326 +----------------- src/core/maplayout.cpp | 8 + src/editor.cpp | 32 +- src/mainwindow.cpp | 24 +- src/scriptapi/apimap.cpp | 250 +++++++------- src/scriptapi/apioverlay.cpp | 26 +- src/scriptapi/apiutility.cpp | 16 +- src/ui/currentselectedmetatilespixmapitem.cpp | 12 +- src/ui/mapimageexporter.cpp | 28 +- src/ui/metatileselector.cpp | 6 +- src/ui/prefab.cpp | 28 +- src/ui/prefabcreationdialog.cpp | 10 +- src/ui/tileseteditor.cpp | 18 +- src/ui/tileseteditormetatileselector.cpp | 8 +- 24 files changed, 315 insertions(+), 674 deletions(-) diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 6cfaf3b9..7d89a35a 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -342,12 +342,12 @@ public: -// !TODO +// !TODO: rename map vars to layout /// Implements a command to commit map edits from the scripting API. /// The scripting api can edit map/border blocks and dimensions. class ScriptEditMap : public QUndoCommand { public: - ScriptEditMap(Map *map, + ScriptEditMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, @@ -361,7 +361,7 @@ public: int id() const override { return CommandId::ID_ScriptEditMap; } private: - Map *map; + Layout *layout = nullptr; Blockdata newMetatiles; Blockdata oldMetatiles; diff --git a/include/core/map.h b/include/core/map.h index 33105c50..37ae9b58 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -66,20 +66,11 @@ public: bool needsLayoutDir = true; bool needsHealLocation = false; - QImage collision_image; - QPixmap collision_pixmap; - QImage image; - QPixmap pixmap; - QMap> events; QList ownedEvents; // for memory management QList connections; - // !TODO - QList metatileLayerOrder; - QList metatileLayerOpacity; - void setName(QString mapName); static QString mapConstantFromName(QString mapName); @@ -89,68 +80,21 @@ public: int getBorderWidth(); int getBorderHeight(); - QUndoStack editHistory; - void modify(); - void clean(); - - QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); - QPixmap renderCollision(bool ignoreCache); - QPixmap renderConnection(MapConnection, Layout *); - QPixmap renderBorder(bool ignoreCache = false); - - bool mapBlockChanged(int i, const Blockdata &cache); - bool borderBlockChanged(int i, const Blockdata &cache); - - // !TODO: remove - void cacheBlockdata(); - void cacheCollision(); - - /// !TODO: remove this - bool getBlock(int x, int y, Block *out); - void setBlock(int x, int y, Block block, bool enableScriptCallback = false); - void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); - - uint16_t getBorderMetatileId(int x, int y); - void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); - void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); - - void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - QList getAllEvents() const; QStringList eventScriptLabels(Event::Group group = Event::Group::None) const; void removeEvent(Event *); void addEvent(Event *); - void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); - void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); - - void clearBorderCache(); - void cacheBorder(); - - bool hasUnsavedChanges(); - - bool isWithinBounds(int x, int y); - bool isWithinBorderBounds(int x, int y); - void openScript(QString label); -private: - LayoutPixmapItem *mapItem = nullptr; + QUndoStack editHistory; + void modify(); + void clean(); + bool hasUnsavedChanges(); -public: - void setMapItem(LayoutPixmapItem *item) { mapItem = item; } + QPixmap renderConnection(MapConnection, Layout *); - CollisionPixmapItem *collisionItem = nullptr; - void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } - BorderMetatilesPixmapItem *borderItem = nullptr; - void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } - -private: - void setNewDimensionsBlockdata(int newWidth, int newHeight); - void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); signals: void mapChanged(Map *map); diff --git a/include/core/maplayout.h b/include/core/maplayout.h index ffbe6127..5e7c0f7c 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -77,9 +77,8 @@ public: int getBorderWidth(); int getBorderHeight(); - bool isWithinBounds(int x, int y) { - return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); - } + bool isWithinBounds(int x, int y); + bool isWithinBorderBounds(int x, int y); bool getBlock(int x, int y, Block *out); void setBlock(int x, int y, Block block, bool enableScriptCallback = false); diff --git a/include/ui/currentselectedmetatilespixmapitem.h b/include/ui/currentselectedmetatilespixmapitem.h index 5e4bd275..109f52c5 100644 --- a/include/ui/currentselectedmetatilespixmapitem.h +++ b/include/ui/currentselectedmetatilespixmapitem.h @@ -1,23 +1,24 @@ #ifndef CURRENTSELECTEDMETATILESPIXMAPITEM_H #define CURRENTSELECTEDMETATILESPIXMAPITEM_H -#include "map.h" #include "metatileselector.h" #include +class Layout; + class CurrentSelectedMetatilesPixmapItem : public QGraphicsPixmapItem { public: - CurrentSelectedMetatilesPixmapItem(Map *map, MetatileSelector *metatileSelector) { - this->map = map; + CurrentSelectedMetatilesPixmapItem(Layout *layout, MetatileSelector *metatileSelector) { + this->layout = layout; this->metatileSelector = metatileSelector; } - Map* map = nullptr; + Layout *layout = nullptr; MetatileSelector *metatileSelector; void draw(); - void setMap(Map *map) { this->map = map; } + void setLayout(Layout *layout) { this->layout = layout; } }; -QPixmap drawMetatileSelection(MetatileSelection selection, Map *map); +QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout); #endif // CURRENTSELECTEDMETATILESPIXMAPITEM_H diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index 0c0b779f..6fe25784 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -31,13 +31,13 @@ struct MetatileSelection class MetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - MetatileSelector(int numMetatilesWide, Map *map): SelectablePixmapItem(16, 16) { + MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(16, 16) { this->externalSelection = false; this->prefabSelection = false; this->numMetatilesWide = numMetatilesWide; - this->map = map; - this->primaryTileset = map->layout->tileset_primary; - this->secondaryTileset = map->layout->tileset_secondary; + this->layout = layout; + this->primaryTileset = layout->tileset_primary; + this->secondaryTileset = layout->tileset_secondary; this->selection = MetatileSelection{}; setAcceptHoverEvents(true); } @@ -50,7 +50,7 @@ public: void setPrefabSelection(MetatileSelection selection); void setExternalSelection(int, int, QList, QList>); QPoint getMetatileIdCoordsOnWidget(uint16_t); - void setMap(Map*); + void setLayout(Layout *layout); Tileset *primaryTileset; Tileset *secondaryTileset; protected: @@ -63,7 +63,7 @@ private: bool externalSelection; bool prefabSelection; int numMetatilesWide; - Map *map; + Layout *layout; int externalSelectionWidth; int externalSelectionHeight; QList externalSelectedMetatiles; diff --git a/include/ui/prefab.h b/include/ui/prefab.h index 7bd9e0b2..2ac8fa04 100644 --- a/include/ui/prefab.h +++ b/include/ui/prefab.h @@ -20,9 +20,9 @@ struct PrefabItem class Prefab { public: - void initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Map *map); - void addPrefab(MetatileSelection selection, Map *map, QString name); - void updatePrefabUi(Map *map); + void initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Layout *layout); + void addPrefab(MetatileSelection selection, Layout *layout, QString name); + void updatePrefabUi(Layout *layout); bool tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath = ""); private: diff --git a/include/ui/prefabcreationdialog.h b/include/ui/prefabcreationdialog.h index 5748f35a..0821f751 100644 --- a/include/ui/prefabcreationdialog.h +++ b/include/ui/prefabcreationdialog.h @@ -2,10 +2,11 @@ #define PREFABCREATIONDIALOG_H #include "metatileselector.h" -#include "map.h" #include +class Layout; + namespace Ui { class PrefabCreationDialog; } @@ -15,12 +16,12 @@ class PrefabCreationDialog : public QDialog Q_OBJECT public: - explicit PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Map *map); + explicit PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Layout *layout); ~PrefabCreationDialog(); void savePrefab(); private: - Map *map; + Layout *layout = nullptr; Ui::PrefabCreationDialog *ui; MetatileSelection selection; }; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 5a46075c..5e1b3b64 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -8,7 +8,8 @@ #include "tileseteditormetatileselector.h" #include "tileseteditortileselector.h" #include "metatilelayersitem.h" -#include "map.h" + +class Layout; namespace Ui { class TilesetEditor; @@ -39,10 +40,10 @@ class TilesetEditor : public QMainWindow Q_OBJECT public: - explicit TilesetEditor(Project*, Map*, QWidget *parent = nullptr); + explicit TilesetEditor(Project *project, Layout *layout, QWidget *parent = nullptr); ~TilesetEditor(); - void update(Map *map, QString primaryTilsetLabel, QString secondaryTilesetLabel); - void updateMap(Map *map); + void update(Layout *layout, QString primaryTilsetLabel, QString secondaryTilesetLabel); + void updateLayout(Layout *layout); void updateTilesets(QString primaryTilsetLabel, QString secondaryTilesetLabel); bool selectMetatile(uint16_t metatileId); uint16_t getSelectedMetatileId(); @@ -148,7 +149,7 @@ private: MetatileLayersItem *metatileLayersItem = nullptr; PaletteEditor *paletteEditor = nullptr; Project *project = nullptr; - Map *map = nullptr; + Layout *layout = nullptr; Metatile *metatile = nullptr; Metatile *copiedMetatile = nullptr; QString copiedMetatileLabel; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index ef5255dc..ee56f013 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -3,13 +3,14 @@ #include "selectablepixmapitem.h" #include "tileset.h" -#include "map.h" + +class Layout; class TilesetEditorMetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map); - Map *map = nullptr; + TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout); + Layout *layout = nullptr; void draw(); bool select(uint16_t metatileId); void setTilesets(Tileset*, Tileset*, bool draw = true); diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 91ea18b3..a2096905 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -487,7 +487,7 @@ int EventPaste::id() const { ************************************************************************ ******************************************************************************/ -ScriptEditMap::ScriptEditMap(Map *map, +ScriptEditMap::ScriptEditMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, @@ -495,7 +495,7 @@ ScriptEditMap::ScriptEditMap(Map *map, QUndoCommand *parent) : QUndoCommand(parent) { setText("Script Edit Map"); - this->map = map; + this->layout = layout; this->newMetatiles = newMetatiles; this->oldMetatiles = oldMetatiles; @@ -517,57 +517,57 @@ ScriptEditMap::ScriptEditMap(Map *map, void ScriptEditMap::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - if (newMapWidth != map->getWidth() || newMapHeight != map->getHeight()) { - map->layout->blockdata = newMetatiles; - map->setDimensions(newMapWidth, newMapHeight, false); + if (newMapWidth != layout->getWidth() || newMapHeight != layout->getHeight()) { + layout->blockdata = newMetatiles; + layout->setDimensions(newMapWidth, newMapHeight, false); } else { - map->setBlockdata(newMetatiles); + layout->setBlockdata(newMetatiles); } - if (newBorderWidth != map->getBorderWidth() || newBorderHeight != map->getBorderHeight()) { - map->layout->border = newBorder; - map->setBorderDimensions(newBorderWidth, newBorderHeight, false); + if (newBorderWidth != layout->getBorderWidth() || newBorderHeight != layout->getBorderHeight()) { + layout->border = newBorder; + layout->setBorderDimensions(newBorderWidth, newBorderHeight, false); } else { - map->setBorderBlockData(newBorder); + layout->setBorderBlockData(newBorder); } - map->layout->lastCommitBlocks.blocks = newMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(newMapWidth, newMapHeight); - map->layout->lastCommitBlocks.border = newBorder; - map->layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); + layout->lastCommitBlocks.blocks = newMetatiles; + layout->lastCommitBlocks.mapDimensions = QSize(newMapWidth, newMapHeight); + layout->lastCommitBlocks.border = newBorder; + layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); // !TODO - renderBlocks(map->layout); - map->borderItem->draw(); + renderBlocks(layout); + layout->borderItem->draw(); } void ScriptEditMap::undo() { - if (!map) return; + if (!layout) return; - if (oldMapWidth != map->getWidth() || oldMapHeight != map->getHeight()) { - map->layout->blockdata = oldMetatiles; - map->setDimensions(oldMapWidth, oldMapHeight, false); + if (oldMapWidth != layout->getWidth() || oldMapHeight != layout->getHeight()) { + layout->blockdata = oldMetatiles; + layout->setDimensions(oldMapWidth, oldMapHeight, false); } else { - map->setBlockdata(oldMetatiles); + layout->setBlockdata(oldMetatiles); } - if (oldBorderWidth != map->getBorderWidth() || oldBorderHeight != map->getBorderHeight()) { - map->layout->border = oldBorder; - map->setBorderDimensions(oldBorderWidth, oldBorderHeight, false); + if (oldBorderWidth != layout->getBorderWidth() || oldBorderHeight != layout->getBorderHeight()) { + layout->border = oldBorder; + layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false); } else { - map->setBorderBlockData(oldBorder); + layout->setBorderBlockData(oldBorder); } - map->layout->lastCommitBlocks.blocks = oldMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(oldMapWidth, oldMapHeight); - map->layout->lastCommitBlocks.border = oldBorder; - map->layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); + layout->lastCommitBlocks.blocks = oldMetatiles; + layout->lastCommitBlocks.mapDimensions = QSize(oldMapWidth, oldMapHeight); + layout->lastCommitBlocks.border = oldBorder; + layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); // !TODO - renderBlocks(map->layout); - map->borderItem->draw(); + renderBlocks(layout); + layout->borderItem->draw(); QUndoCommand::undo(); } diff --git a/src/core/map.cpp b/src/core/map.cpp index 181cde35..fdfb030c 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -60,121 +60,6 @@ int Map::getBorderHeight() { return layout->getBorderHeight(); } -bool Map::mapBlockChanged(int i, const Blockdata &cache) { - if (cache.length() <= i) - return true; - if (layout->blockdata.length() <= i) - return true; - - return layout->blockdata.at(i) != cache.at(i); -} - -bool Map::borderBlockChanged(int i, const Blockdata &cache) { - if (cache.length() <= i) - return true; - if (layout->border.length() <= i) - return true; - - return layout->border.at(i) != cache.at(i); -} - -void Map::clearBorderCache() { - layout->cached_border.clear(); -} - -void Map::cacheBorder() { - layout->cached_border.clear(); - for (const auto &block : layout->border) - layout->cached_border.append(block); -} - -void Map::cacheBlockdata() { - layout->cached_blockdata.clear(); - for (const auto &block : layout->blockdata) - layout->cached_blockdata.append(block); -} - -void Map::cacheCollision() { - layout->cached_collision.clear(); - for (const auto &block : layout->blockdata) - layout->cached_collision.append(block); -} - -QPixmap Map::renderCollision(bool ignoreCache) { - bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { - collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->blockdata.isEmpty() || !width_ || !height_) { - collision_pixmap = collision_pixmap.fromImage(collision_image); - return collision_pixmap; - } - QPainter painter(&collision_image); - for (int i = 0; i < layout->blockdata.length(); i++) { - if (!ignoreCache && !mapBlockChanged(i, layout->cached_collision)) { - continue; - } - changed_any = true; - Block block = layout->blockdata.at(i); - QImage collision_metatile_image = getCollisionMetatileImage(block); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - painter.drawImage(metatile_origin, collision_metatile_image); - } - painter.end(); - cacheCollision(); - if (changed_any) { - collision_pixmap = collision_pixmap.fromImage(collision_image); - } - return collision_pixmap; -} - -QPixmap Map::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { - return this->layout->render(ignoreCache, fromLayout, bounds); -} - -QPixmap Map::renderBorder(bool ignoreCache) { - bool changed_any = false, border_resized = false; - int width_ = getBorderWidth(); - int height_ = getBorderHeight(); - if (layout->border_image.isNull()) { - layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->border_image.width() != width_ * 16 || layout->border_image.height() != height_ * 16) { - layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - border_resized = true; - } - if (layout->border.isEmpty()) { - layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); - return layout->border_pixmap; - } - QPainter painter(&layout->border_image); - for (int i = 0; i < layout->border.length(); i++) { - if (!ignoreCache && (!border_resized && !borderBlockChanged(i, layout->cached_border))) { - continue; - } - - changed_any = true; - Block block = layout->border.at(i); - uint16_t metatileId = block.metatileId; - QImage metatile_image = getMetatileImage(metatileId, layout->tileset_primary, layout->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); - } - painter.end(); - if (changed_any) { - cacheBorder(); - layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); - } - return layout->border_pixmap; -} - QPixmap Map::renderConnection(MapConnection connection, Layout *fromLayout) { int x, y, w, h; if (connection.direction == "up") { @@ -207,213 +92,14 @@ QPixmap Map::renderConnection(MapConnection connection, Layout *fromLayout) { //render(true, fromLayout, QRect(x, y, w, h)); //QImage connection_image = image.copy(x * 16, y * 16, w * 16, h * 16); - return render(true, fromLayout, QRect(x, y, w, h)).copy(x * 16, y * 16, w * 16, h * 16); + return this->layout->render(true, fromLayout, QRect(x, y, w, h)).copy(x * 16, y * 16, w * 16, h * 16); //return QPixmap::fromImage(connection_image); } -void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) { - int oldWidth = getWidth(); - int oldHeight = getHeight(); - - Blockdata newBlockdata; - - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if (x < oldWidth && y < oldHeight) { - int index = y * oldWidth + x; - newBlockdata.append(layout->blockdata.value(index)); - } else { - newBlockdata.append(0); - } - } - - layout->blockdata = newBlockdata; -} - -void Map::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { - int oldWidth = getBorderWidth(); - int oldHeight = getBorderHeight(); - - Blockdata newBlockdata; - - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if (x < oldWidth && y < oldHeight) { - int index = y * oldWidth + x; - newBlockdata.append(layout->border.value(index)); - } else { - newBlockdata.append(0); - } - } - - layout->border = newBlockdata; -} - -void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { - if (setNewBlockdata) { - setNewDimensionsBlockdata(newWidth, newHeight); - } - - int oldWidth = layout->width; - int oldHeight = layout->height; - layout->width = newWidth; - layout->height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); - } - - emit mapChanged(this); - emit mapDimensionsChanged(QSize(getWidth(), getHeight())); -} - -void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { - if (setNewBlockdata) { - setNewBorderDimensionsBlockdata(newWidth, newHeight); - } - - int oldWidth = layout->border_width; - int oldHeight = layout->border_height; - layout->border_width = newWidth; - layout->border_height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); - } - - emit mapChanged(this); -} - void Map::openScript(QString label) { emit openScriptRequested(label); } -bool Map::getBlock(int x, int y, Block *out) { - if (isWithinBounds(x, y)) { - int i = y * getWidth() + x; - *out = layout->blockdata.value(i); - return true; - } - return false; -} - -void Map::setBlock(int x, int y, Block block, bool enableScriptCallback) { - if (!isWithinBounds(x, y)) return; - int i = y * getWidth() + x; - if (i < layout->blockdata.size()) { - Block prevBlock = layout->blockdata.at(i); - layout->blockdata.replace(i, block); - if (enableScriptCallback) { - Scripting::cb_MetatileChanged(x, y, prevBlock, block); - } - } -} - -void Map::setBlockdata(Blockdata blockdata, bool enableScriptCallback) { - int width = getWidth(); - int size = qMin(blockdata.size(), layout->blockdata.size()); - for (int i = 0; i < size; i++) { - Block prevBlock = layout->blockdata.at(i); - Block newBlock = blockdata.at(i); - if (prevBlock != newBlock) { - layout->blockdata.replace(i, newBlock); - if (enableScriptCallback) - Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock); - } - } -} - -uint16_t Map::getBorderMetatileId(int x, int y) { - int i = y * getBorderWidth() + x; - return layout->border[i].metatileId; -} - -void Map::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) { - int i = y * getBorderWidth() + x; - if (i < layout->border.size()) { - uint16_t prevMetatileId = layout->border[i].metatileId; - layout->border[i].metatileId = metatileId; - if (prevMetatileId != metatileId && enableScriptCallback) { - Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId); - } - } -} - -void Map::setBorderBlockData(Blockdata blockdata, bool enableScriptCallback) { - int width = getBorderWidth(); - int size = qMin(blockdata.size(), layout->border.size()); - for (int i = 0; i < size; i++) { - Block prevBlock = layout->border.at(i); - Block newBlock = blockdata.at(i); - if (prevBlock != newBlock) { - layout->border.replace(i, newBlock); - if (enableScriptCallback) - Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId, newBlock.metatileId); - } - } -} - -void Map::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { - QList todo; - todo.append(QPoint(x, y)); - while (todo.length()) { - QPoint point = todo.takeAt(0); - x = point.x(); - y = point.y(); - Block block; - if (!getBlock(x, y, &block)) { - continue; - } - - uint old_coll = block.collision; - uint old_elev = block.elevation; - if (old_coll == collision && old_elev == elevation) { - continue; - } - - block.collision = collision; - block.elevation = elevation; - setBlock(x, y, block, true); - if (getBlock(x + 1, y, &block) && block.collision == old_coll && block.elevation == old_elev) { - todo.append(QPoint(x + 1, y)); - } - if (getBlock(x - 1, y, &block) && block.collision == old_coll && block.elevation == old_elev) { - todo.append(QPoint(x - 1, y)); - } - if (getBlock(x, y + 1, &block) && block.collision == old_coll && block.elevation == old_elev) { - todo.append(QPoint(x, y + 1)); - } - if (getBlock(x, y - 1, &block) && block.collision == old_coll && block.elevation == old_elev) { - todo.append(QPoint(x, y - 1)); - } - } -} - -void Map::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { - Block block; - if (getBlock(x, y, &block) && (block.collision != collision || block.elevation != elevation)) { - _floodFillCollisionElevation(x, y, collision, elevation); - } -} - -void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) { - Block block; - if (getBlock(initialX, initialY, &block) && (block.collision != collision || block.elevation != elevation)) { - uint old_coll = block.collision; - uint old_elev = block.elevation; - - for (int y = 0; y < getHeight(); y++) { - for (int x = 0; x < getWidth(); x++) { - if (getBlock(x, y, &block) && block.collision == old_coll && block.elevation == old_elev) { - block.collision = collision; - block.elevation = elevation; - setBlock(x, y, block, true); - } - } - } - } -} - QList Map::getAllEvents() const { QList all_events; for (const auto &event_list : events) { @@ -468,13 +154,5 @@ void Map::clean() { } bool Map::hasUnsavedChanges() { - return !editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile; -} - -bool Map::isWithinBounds(int x, int y) { - return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); -} - -bool Map::isWithinBorderBounds(int x, int y) { - return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); + return !editHistory.isClean() /* || !this->layout->editHistory.isClean() */ || hasUnsavedDataChanges || !isPersistedToFile; } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 70364f69..a56a982e 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -36,6 +36,14 @@ int Layout::getBorderHeight() { return border_height; } +bool Layout::isWithinBounds(int x, int y) { + return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); +} + +bool Layout::isWithinBorderBounds(int x, int y) { + return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); +} + bool Layout::getBlock(int x, int y, Block *out) { if (isWithinBounds(x, y)) { int i = y * getWidth() + x; diff --git a/src/editor.cpp b/src/editor.cpp index d83ca28a..4b1bafc5 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -111,7 +111,7 @@ void Editor::setEditingCollision() { collision_item->setVisible(true); } if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::Metatiles; + map_item->paintingMode = LayoutPixmapItem::PaintMode::Metatiles; map_item->draw(); map_item->setVisible(true); } @@ -1017,13 +1017,13 @@ void Editor::setCursorRectVisible(bool visible) { void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { int x = pos.x(); int y = pos.y(); - if (!map->isWithinBounds(x, y)) + if (!layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { - int blockIndex = y * map->getWidth() + x; - int metatileId = map->layout->blockdata.at(blockIndex).metatileId; + int blockIndex = y * layout->getWidth() + x; + int metatileId = layout->blockdata.at(blockIndex).metatileId; this->ui->statusBar->showMessage(QString("X: %1, Y: %2, %3, Scale = %4x") .arg(x) .arg(y) @@ -1049,14 +1049,14 @@ void Editor::onHoveredMapMetatileCleared() { } void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { - if (!map->isWithinBounds(x, y)) + if (!layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { - int blockIndex = y * map->getWidth() + x; - uint16_t collision = map->layout->blockdata.at(blockIndex).collision; - uint16_t elevation = map->layout->blockdata.at(blockIndex).elevation; + int blockIndex = y * layout->getWidth() + x; + uint16_t collision = layout->blockdata.at(blockIndex).collision; + uint16_t elevation = layout->blockdata.at(blockIndex).elevation; QString message = QString("X: %1, Y: %2, %3") .arg(x) .arg(y) @@ -1385,7 +1385,7 @@ void Editor::displayMetatileSelector() { } scene_metatiles = new QGraphicsScene; if (!metatile_selector_item) { - metatile_selector_item = new MetatileSelector(8, map); + metatile_selector_item = new MetatileSelector(8, this->layout); connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionChanged, this, &Editor::onHoveredMetatileSelectionChanged); connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionCleared, @@ -1394,7 +1394,7 @@ void Editor::displayMetatileSelector() { this, &Editor::onSelectedMetatilesChanged); metatile_selector_item->select(0); } else { - metatile_selector_item->setMap(map); + metatile_selector_item->setLayout(this->layout); if (metatile_selector_item->primaryTileset && metatile_selector_item->primaryTileset != map->layout->tileset_primary) emit tilesetUpdated(map->layout->tileset_primary->name); @@ -1471,14 +1471,14 @@ void Editor::displayCurrentMetatilesSelection() { } scene_current_metatile_selection = new QGraphicsScene; - current_metatile_selection_item = new CurrentSelectedMetatilesPixmapItem(map, this->metatile_selector_item); + current_metatile_selection_item = new CurrentSelectedMetatilesPixmapItem(this->layout, this->metatile_selector_item); current_metatile_selection_item->draw(); scene_current_metatile_selection->addItem(current_metatile_selection_item); } void Editor::redrawCurrentMetatilesSelection() { if (current_metatile_selection_item) { - current_metatile_selection_item->setMap(map); + current_metatile_selection_item->setLayout(this->layout); current_metatile_selection_item->draw(); emit currentMetatilesSelectionChanged(); } @@ -1635,7 +1635,7 @@ void Editor::displayMapBorder() { int borderHeight = map->getBorderHeight(); int borderHorzDist = getBorderDrawDistance(borderWidth); int borderVertDist = getBorderDrawDistance(borderHeight); - QPixmap pixmap = map->renderBorder(); + QPixmap pixmap = this->layout->renderBorder(); for (int y = -borderVertDist; y < map->getHeight() + borderVertDist; y += borderHeight) for (int x = -borderHorzDist; x < map->getWidth() + borderHorzDist; x += borderWidth) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); @@ -1648,7 +1648,7 @@ void Editor::displayMapBorder() { } void Editor::updateMapBorder() { - QPixmap pixmap = this->map->renderBorder(true); + QPixmap pixmap = this->layout->renderBorder(true); for (auto item : this->borderItems) { item->setPixmap(pixmap); } @@ -1925,7 +1925,7 @@ void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) { map->layout->tileset_primary_label = tilesetLabel; map->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); - map->clearBorderCache(); + layout->clearBorderCache(); } } @@ -1935,7 +1935,7 @@ void Editor::updateSecondaryTileset(QString tilesetLabel, bool forceLoad) { map->layout->tileset_secondary_label = tilesetLabel; map->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); - map->clearBorderCache(); + layout->clearBorderCache(); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 351c0e3e..3d0bc6b3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -536,7 +536,7 @@ bool MainWindow::openProject(QString dir) { editor->metatile_selector_item, ui->scrollAreaWidgetContents_Prefabs, ui->label_prefabHelp, - editor->map); + editor->layout); Scripting::cb_ProjectOpened(dir); return true; } @@ -674,7 +674,7 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { updateMapList(); Scripting::cb_MapOpened(map_name); - prefab.updatePrefabUi(editor->map); + prefab.updatePrefabUi(editor->layout); updateTilesetEditor(); return true; } @@ -1352,7 +1352,7 @@ void MainWindow::on_actionNew_Tileset_triggered() { void MainWindow::updateTilesetEditor() { if (this->tilesetEditor) { this->tilesetEditor->update( - this->editor->map, + this->editor->layout, editor->ui->comboBox_PrimaryTileset->currentText(), editor->ui->comboBox_SecondaryTileset->currentText() ); @@ -1532,7 +1532,7 @@ void MainWindow::copy() { case 0: { // copy the map image - QPixmap pixmap = editor->map ? editor->map->render(true) : QPixmap(); + QPixmap pixmap = editor->layout ? editor->layout->render(true) : QPixmap(); setClipboardData(pixmap.toImage()); logInfo("Copied current map image to clipboard"); break; @@ -1755,8 +1755,8 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) // User hasn't set up prefabs and hasn't been prompted before. // Ask if they'd like to import the default prefabs file. if (prefab.tryImportDefaultPrefabs(this, projectConfig.getBaseGameVersion())) - prefab.updatePrefabUi(this->editor->map); - } + prefab.updatePrefabUi(this->editor->layout); + } } editor->setCursorRectVisible(false); } @@ -2442,8 +2442,10 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT } else { this->editor->project->getTileset(secondaryTilesetLabel, true); } - if (updated) + if (updated) { + this->editor->layout->clearBorderCache(); redrawMapScene(); + } } void MainWindow::onWildMonDataChanged() { @@ -2592,7 +2594,7 @@ void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &ti redrawMapScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); - prefab.updatePrefabUi(editor->map); + prefab.updatePrefabUi(editor->layout); markMapEdited(); } } @@ -2604,7 +2606,7 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & redrawMapScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); - prefab.updatePrefabUi(editor->map); + prefab.updatePrefabUi(editor->layout); markMapEdited(); } } @@ -2725,7 +2727,7 @@ void MainWindow::on_actionTileset_Editor_triggered() } void MainWindow::initTilesetEditor() { - this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this); + this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->layout, this); connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } @@ -2889,7 +2891,7 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() { } void MainWindow::on_pushButton_CreatePrefab_clicked() { - PrefabCreationDialog dialog(this, this->editor->metatile_selector_item, this->editor->map); + PrefabCreationDialog dialog(this, this->editor->metatile_selector_item, this->editor->layout); dialog.setWindowTitle("Create Prefab"); dialog.setWindowModality(Qt::NonModal); if (dialog.exec() == QDialog::Accepted) { diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 21f38a51..155d3492 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -42,13 +42,13 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) { void MainWindow::tryCommitMapChanges(bool commitChanges) { if (commitChanges) { - Map *map = this->editor->map; - if (map) { - map->editHistory.push(new ScriptEditMap(map, - map->layout->lastCommitBlocks.mapDimensions, QSize(map->getWidth(), map->getHeight()), - map->layout->lastCommitBlocks.blocks, map->layout->blockdata, - map->layout->lastCommitBlocks.borderDimensions, QSize(map->getBorderWidth(), map->getBorderHeight()), - map->layout->lastCommitBlocks.border, map->layout->border + Layout *layout = this->editor->layout; + if (layout) { + layout->editHistory.push(new ScriptEditMap(layout, + layout->lastCommitBlocks.mapDimensions, QSize(layout->getWidth(), layout->getHeight()), + layout->lastCommitBlocks.blocks, layout->blockdata, + layout->lastCommitBlocks.borderDimensions, QSize(layout->getBorderWidth(), layout->getBorderHeight()), + layout->lastCommitBlocks.border, layout->border )); } } @@ -59,27 +59,27 @@ void MainWindow::tryCommitMapChanges(bool commitChanges) { //===================== QJSValue MainWindow::getBlock(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return QJSValue(); Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return Scripting::fromBlock(Block()); } return Scripting::fromBlock(block); } void MainWindow::setBlock(int x, int y, int metatileId, int collision, int elevation, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - this->editor->map->setBlock(x, y, Block(metatileId, collision, elevation)); + this->editor->layout->setBlock(x, y, Block(metatileId, collision, elevation)); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } void MainWindow::setBlock(int x, int y, int rawValue, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - this->editor->map->setBlock(x, y, Block(static_cast(rawValue))); + this->editor->layout->setBlock(x, y, Block(static_cast(rawValue))); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } @@ -93,73 +93,73 @@ void MainWindow::setBlocksFromSelection(int x, int y, bool forceRedraw, bool com } int MainWindow::getMetatileId(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return 0; } return block.metatileId; } void MainWindow::setMetatileId(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return; } - this->editor->map->setBlock(x, y, Block(metatileId, block.collision, block.elevation)); + this->editor->layout->setBlock(x, y, Block(metatileId, block.collision, block.elevation)); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } int MainWindow::getCollision(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return 0; } return block.collision; } void MainWindow::setCollision(int x, int y, int collision, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return; } - this->editor->map->setBlock(x, y, Block(block.metatileId, collision, block.elevation)); + this->editor->layout->setBlock(x, y, Block(block.metatileId, collision, block.elevation)); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } int MainWindow::getElevation(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return 0; } return block.elevation; } void MainWindow::setElevation(int x, int y, int elevation, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return; } - this->editor->map->setBlock(x, y, Block(block.metatileId, block.collision, elevation)); + this->editor->layout->setBlock(x, y, Block(block.metatileId, block.collision, elevation)); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } void MainWindow::bucketFill(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->floodFill(x, y, metatileId, true); this->tryCommitMapChanges(commitChanges); @@ -167,7 +167,7 @@ void MainWindow::bucketFill(int x, int y, int metatileId, bool forceRedraw, bool } void MainWindow::bucketFillFromSelection(int x, int y, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->floodFill(x, y, true); this->tryCommitMapChanges(commitChanges); @@ -175,7 +175,7 @@ void MainWindow::bucketFillFromSelection(int x, int y, bool forceRedraw, bool co } void MainWindow::magicFill(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->magicFill(x, y, metatileId, true); this->tryCommitMapChanges(commitChanges); @@ -183,7 +183,7 @@ void MainWindow::magicFill(int x, int y, int metatileId, bool forceRedraw, bool } void MainWindow::magicFillFromSelection(int x, int y, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->magicFill(x, y, true); this->tryCommitMapChanges(commitChanges); @@ -191,7 +191,7 @@ void MainWindow::magicFillFromSelection(int x, int y, bool forceRedraw, bool com } void MainWindow::shift(int xDelta, int yDelta, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->shift(xDelta, yDelta, true); this->tryCommitMapChanges(commitChanges); @@ -207,49 +207,49 @@ void MainWindow::commit() { } QJSValue MainWindow::getDimensions() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return QJSValue(); - return Scripting::dimensions(this->editor->map->getWidth(), this->editor->map->getHeight()); + return Scripting::dimensions(this->editor->layout->getWidth(), this->editor->layout->getHeight()); } int MainWindow::getWidth() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getWidth(); + return this->editor->layout->getWidth(); } int MainWindow::getHeight() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getHeight(); + return this->editor->layout->getHeight(); } void MainWindow::setDimensions(int width, int height) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; if (!Project::mapDimensionsValid(width, height)) return; - this->editor->map->setDimensions(width, height); + this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); this->onMapNeedsRedrawing(); } void MainWindow::setWidth(int width) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, this->editor->map->getHeight())) + if (!Project::mapDimensionsValid(width, this->editor->layout->getHeight())) return; - this->editor->map->setDimensions(width, this->editor->map->getHeight()); + this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); this->onMapNeedsRedrawing(); } void MainWindow::setHeight(int height) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(this->editor->map->getWidth(), height)) + if (!Project::mapDimensionsValid(this->editor->layout->getWidth(), height)) return; - this->editor->map->setDimensions(this->editor->map->getWidth(), height); + this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); this->onMapNeedsRedrawing(); } @@ -259,67 +259,67 @@ void MainWindow::setHeight(int height) { //===================== int MainWindow::getBorderMetatileId(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - if (!this->editor->map->isWithinBorderBounds(x, y)) + if (!this->editor->layout->isWithinBorderBounds(x, y)) return 0; - return this->editor->map->getBorderMetatileId(x, y); + return this->editor->layout->getBorderMetatileId(x, y); } void MainWindow::setBorderMetatileId(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - if (!this->editor->map->isWithinBorderBounds(x, y)) + if (!this->editor->layout->isWithinBorderBounds(x, y)) return; - this->editor->map->setBorderMetatileId(x, y, metatileId); + this->editor->layout->setBorderMetatileId(x, y, metatileId); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } QJSValue MainWindow::getBorderDimensions() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return QJSValue(); - return Scripting::dimensions(this->editor->map->getBorderWidth(), this->editor->map->getBorderHeight()); + return Scripting::dimensions(this->editor->layout->getBorderWidth(), this->editor->layout->getBorderHeight()); } int MainWindow::getBorderWidth() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getBorderWidth(); + return this->editor->layout->getBorderWidth(); } int MainWindow::getBorderHeight() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getBorderHeight(); + return this->editor->layout->getBorderHeight(); } void MainWindow::setBorderDimensions(int width, int height) { - if (!this->editor || !this->editor->map || !projectConfig.getUseCustomBorderSize()) + if (!this->editor || !this->editor->layout || !projectConfig.getUseCustomBorderSize()) return; if (width < 1 || height < 1 || width > MAX_BORDER_WIDTH || height > MAX_BORDER_HEIGHT) return; - this->editor->map->setBorderDimensions(width, height); + this->editor->layout->setBorderDimensions(width, height); this->tryCommitMapChanges(true); this->onMapNeedsRedrawing(); } void MainWindow::setBorderWidth(int width) { - if (!this->editor || !this->editor->map || !projectConfig.getUseCustomBorderSize()) + if (!this->editor || !this->editor->layout || !projectConfig.getUseCustomBorderSize()) return; if (width < 1 || width > MAX_BORDER_WIDTH) return; - this->editor->map->setBorderDimensions(width, this->editor->map->getBorderHeight()); + this->editor->layout->setBorderDimensions(width, this->editor->layout->getBorderHeight()); this->tryCommitMapChanges(true); this->onMapNeedsRedrawing(); } void MainWindow::setBorderHeight(int height) { - if (!this->editor || !this->editor->map || !projectConfig.getUseCustomBorderSize()) + if (!this->editor || !this->editor->layout || !projectConfig.getUseCustomBorderSize()) return; if (height < 1 || height > MAX_BORDER_HEIGHT) return; - this->editor->map->setBorderDimensions(this->editor->map->getBorderWidth(), height); + this->editor->layout->setBorderDimensions(this->editor->layout->getBorderWidth(), height); this->tryCommitMapChanges(true); this->onMapNeedsRedrawing(); } @@ -330,7 +330,7 @@ void MainWindow::setBorderHeight(int height) { void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { if (this->tilesetEditor) { - this->tilesetEditor->updateTilesets(this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label); + this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label); } this->editor->metatile_selector_item->draw(); this->editor->selected_border_metatiles_item->draw(); @@ -341,7 +341,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { } void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->map || !this->editor->layout) return; if (paletteIndex >= tileset->palettes.size()) return; @@ -357,42 +357,42 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return; - this->setTilesetPalette(this->editor->map->layout->tileset_primary, paletteIndex, colors); + this->setTilesetPalette(this->editor->layout->tileset_primary, paletteIndex, colors); if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_primary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_primary); } } void MainWindow::setPrimaryTilesetPalettes(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalette(this->editor->map->layout->tileset_primary, i, palettes[i]); + this->setTilesetPalette(this->editor->layout->tileset_primary, i, palettes[i]); } if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_primary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_primary); } } void MainWindow::setSecondaryTilesetPalette(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return; - this->setTilesetPalette(this->editor->map->layout->tileset_secondary, paletteIndex, colors); + this->setTilesetPalette(this->editor->layout->tileset_secondary, paletteIndex, colors); if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_secondary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_secondary); } } void MainWindow::setSecondaryTilesetPalettes(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalette(this->editor->map->layout->tileset_secondary, i, palettes[i]); + this->setTilesetPalette(this->editor->layout->tileset_secondary, i, palettes[i]); } if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_secondary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_secondary); } } @@ -420,27 +420,27 @@ QJSValue MainWindow::getTilesetPalettes(const QList> &palettes) { } QJSValue MainWindow::getPrimaryTilesetPalette(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_primary->palettes, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_primary->palettes, paletteIndex); } QJSValue MainWindow::getPrimaryTilesetPalettes() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_primary->palettes); + return this->getTilesetPalettes(this->editor->layout->tileset_primary->palettes); } QJSValue MainWindow::getSecondaryTilesetPalette(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_secondary->palettes, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_secondary->palettes, paletteIndex); } QJSValue MainWindow::getSecondaryTilesetPalettes() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_secondary->palettes); + return this->getTilesetPalettes(this->editor->layout->tileset_secondary->palettes); } void MainWindow::refreshAfterPalettePreviewChange() { @@ -452,7 +452,7 @@ void MainWindow::refreshAfterPalettePreviewChange() { } void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QList> colors) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->map || !this->editor->layout) return; if (paletteIndex >= tileset->palettePreviews.size()) return; @@ -467,19 +467,19 @@ void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QL } void MainWindow::setPrimaryTilesetPalettePreview(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return; - this->setTilesetPalettePreview(this->editor->map->layout->tileset_primary, paletteIndex, colors); + this->setTilesetPalettePreview(this->editor->layout->tileset_primary, paletteIndex, colors); if (forceRedraw) { this->refreshAfterPalettePreviewChange(); } } void MainWindow::setPrimaryTilesetPalettesPreview(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalettePreview(this->editor->map->layout->tileset_primary, i, palettes[i]); + this->setTilesetPalettePreview(this->editor->layout->tileset_primary, i, palettes[i]); } if (forceRedraw) { this->refreshAfterPalettePreviewChange(); @@ -487,19 +487,19 @@ void MainWindow::setPrimaryTilesetPalettesPreview(QList>> palet } void MainWindow::setSecondaryTilesetPalettePreview(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return; - this->setTilesetPalettePreview(this->editor->map->layout->tileset_secondary, paletteIndex, colors); + this->setTilesetPalettePreview(this->editor->layout->tileset_secondary, paletteIndex, colors); if (forceRedraw) { this->refreshAfterPalettePreviewChange(); } } void MainWindow::setSecondaryTilesetPalettesPreview(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalettePreview(this->editor->map->layout->tileset_secondary, i, palettes[i]); + this->setTilesetPalettePreview(this->editor->layout->tileset_secondary, i, palettes[i]); } if (forceRedraw) { this->refreshAfterPalettePreviewChange(); @@ -507,63 +507,63 @@ void MainWindow::setSecondaryTilesetPalettesPreview(QList>> pal } QJSValue MainWindow::getPrimaryTilesetPalettePreview(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_primary->palettePreviews, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_primary->palettePreviews, paletteIndex); } QJSValue MainWindow::getPrimaryTilesetPalettesPreview() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_primary->palettePreviews); + return this->getTilesetPalettes(this->editor->layout->tileset_primary->palettePreviews); } QJSValue MainWindow::getSecondaryTilesetPalettePreview(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_secondary->palettePreviews, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_secondary->palettePreviews, paletteIndex); } QJSValue MainWindow::getSecondaryTilesetPalettesPreview() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_secondary->palettePreviews); + return this->getTilesetPalettes(this->editor->layout->tileset_secondary->palettePreviews); } int MainWindow::getNumPrimaryTilesetMetatiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; - return this->editor->map->layout->tileset_primary->metatiles.length(); + return this->editor->layout->tileset_primary->metatiles.length(); } int MainWindow::getNumSecondaryTilesetMetatiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; - return this->editor->map->layout->tileset_secondary->metatiles.length(); + return this->editor->layout->tileset_secondary->metatiles.length(); } int MainWindow::getNumPrimaryTilesetTiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; - return this->editor->map->layout->tileset_primary->tiles.length(); + return this->editor->layout->tileset_primary->tiles.length(); } int MainWindow::getNumSecondaryTilesetTiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; - return this->editor->map->layout->tileset_secondary->tiles.length(); + return this->editor->layout->tileset_secondary->tiles.length(); } QString MainWindow::getPrimaryTileset() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) return QString(); - return this->editor->map->layout->tileset_primary->name; + return this->editor->layout->tileset_primary->name; } QString MainWindow::getSecondaryTileset() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) return QString(); - return this->editor->map->layout->tileset_secondary->name; + return this->editor->layout->tileset_secondary->name; } void MainWindow::setPrimaryTileset(QString tileset) { @@ -575,13 +575,13 @@ void MainWindow::setSecondaryTileset(QString tileset) { } void MainWindow::saveMetatilesByMetatileId(int metatileId) { - Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->editor->project && tileset) this->editor->project->saveTilesetMetatiles(tileset); } void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { - Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->editor->project && tileset) this->editor->project->saveTilesetMetatileAttributes(tileset); @@ -591,9 +591,9 @@ void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { } Metatile * MainWindow::getMetatile(int metatileId) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->map || !this->editor->layout) return nullptr; - return Tileset::getMetatile(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + return Tileset::getMetatile(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } QString MainWindow::getMetatileLabel(int metatileId) { @@ -603,7 +603,7 @@ QString MainWindow::getMetatileLabel(int metatileId) { } void MainWindow::setMetatileLabel(int metatileId, QString label) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->map || !this->editor->layout) return; // If the Tileset Editor is opened on this metatile we need to update the text box @@ -612,13 +612,13 @@ void MainWindow::setMetatileLabel(int metatileId, QString label) { return; } - if (!Tileset::setMetatileLabel(metatileId, label, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary)) { + if (!Tileset::setMetatileLabel(metatileId, label, this->editor->layout->tileset_primary, this->editor->map->layout->tileset_secondary)) { logError("Failed to set metatile label. Must be a valid metatile id and a label containing only letters, numbers, and underscores."); return; } if (this->editor->project) - this->editor->project->saveTilesetMetatileLabels(this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + this->editor->project->saveTilesetMetatileLabels(this->editor->layout->tileset_primary, this->editor->map->layout->tileset_secondary); } int MainWindow::getMetatileLayerType(int metatileId) { @@ -769,9 +769,9 @@ void MainWindow::setMetatileTile(int metatileId, int tileIndex, QJSValue tileObj } QJSValue MainWindow::getTilePixels(int tileId) { - if (tileId < 0 || !this->editor || !this->editor->project || !this->editor->map || !this->editor->map->layout) + if (tileId < 0 || !this->editor || !this->editor->project || !this->editor->map || !this->editor->layout) return QJSValue(); - QImage tileImage = getTileImage(tileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (tileImage.isNull() || tileImage.sizeInBytes() < 64) return QJSValue(); const uchar * pixels = tileImage.constBits(); diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index bee473ff..7e634fab 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -254,23 +254,23 @@ void MapView::addImage(int x, int y, QString filepath, int layer, bool useCache) } void MapView::createImage(int x, int y, QString filepath, int width, int height, int xOffset, int yOffset, qreal hScale, qreal vScale, int paletteId, bool setTransparency, int layer, bool useCache) { - if (!this->editor || !this->editor->map || !this->editor->map->layout - || !this->editor->map->layout->tileset_primary || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout + || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QList palette; if (paletteId != -1) - palette = Tileset::getPalette(paletteId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + palette = Tileset::getPalette(paletteId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->getOverlay(layer)->addImage(x, y, filepath, useCache, width, height, xOffset, yOffset, hScale, vScale, palette, setTransparency)) this->scene()->update(); } void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int paletteId, bool setTransparency, int layer) { - if (!this->editor || !this->editor->map || !this->editor->map->layout - || !this->editor->map->layout->tileset_primary || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout + || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QImage image = getPalettedTileImage(tileId, - this->editor->map->layout->tileset_primary, - this->editor->map->layout->tileset_secondary, + this->editor->layout->tileset_primary, + this->editor->layout->tileset_secondary, paletteId) .mirrored(xflip, yflip); if (setTransparency) @@ -285,14 +285,14 @@ void MapView::addTileImage(int x, int y, QJSValue tileObj, bool setTransparency, } void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparency, int layer) { - if (!this->editor || !this->editor->map || !this->editor->map->layout - || !this->editor->map->layout->tileset_primary || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->map || !this->editor->layout + || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QImage image = getMetatileImage(static_cast(metatileId), - this->editor->map->layout->tileset_primary, - this->editor->map->layout->tileset_secondary, - this->editor->map->metatileLayerOrder, - this->editor->map->metatileLayerOpacity); + this->editor->layout->tileset_primary, + this->editor->layout->tileset_secondary, + this->editor->layout->metatileLayerOrder, + this->editor->layout->metatileLayerOpacity); if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index 559e46af..712a8171 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -188,13 +188,13 @@ QList ScriptUtility::getCustomScripts() { } QList ScriptUtility::getMetatileLayerOrder() { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return QList(); - return window->editor->map->metatileLayerOrder; + return window->editor->layout->metatileLayerOrder; } void ScriptUtility::setMetatileLayerOrder(QList order) { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return; const int numLayers = 3; @@ -213,20 +213,20 @@ void ScriptUtility::setMetatileLayerOrder(QList order) { } if (invalid) return; - window->editor->map->metatileLayerOrder = order; + window->editor->layout->metatileLayerOrder = order; window->refreshAfterPalettePreviewChange(); } QList ScriptUtility::getMetatileLayerOpacity() { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return QList(); - return window->editor->map->metatileLayerOpacity; + return window->editor->layout->metatileLayerOpacity; } void ScriptUtility::setMetatileLayerOpacity(QList order) { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return; - window->editor->map->metatileLayerOpacity = order; + window->editor->layout->metatileLayerOpacity = order; window->refreshAfterPalettePreviewChange(); } diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp index 0967e558..e8b16f49 100644 --- a/src/ui/currentselectedmetatilespixmapitem.cpp +++ b/src/ui/currentselectedmetatilespixmapitem.cpp @@ -2,7 +2,7 @@ #include "imageproviders.h" #include -QPixmap drawMetatileSelection(MetatileSelection selection, Map *map) { +QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) { int width = selection.dimensions.x() * 16; int height = selection.dimensions.y() * 16; QImage image(width, height, QImage::Format_RGBA8888); @@ -19,10 +19,10 @@ QPixmap drawMetatileSelection(MetatileSelection selection, Map *map) { if (item.enabled) { QImage metatile_image = getMetatileImage( item.metatileId, - map->layout->tileset_primary, - map->layout->tileset_secondary, - map->metatileLayerOrder, - map->metatileLayerOpacity); + layout->tileset_primary, + layout->tileset_secondary, + layout->metatileLayerOrder, + layout->metatileLayerOpacity); painter.drawImage(metatile_origin, metatile_image); } } @@ -34,5 +34,5 @@ QPixmap drawMetatileSelection(MetatileSelection selection, Map *map) { void CurrentSelectedMetatilesPixmapItem::draw() { MetatileSelection selection = metatileSelector->getMetatileSelection(); - setPixmap(drawMetatileSelection(selection, this->map)); + setPixmap(drawMetatileSelection(selection, this->layout)); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 099f2288..e81208b8 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -90,6 +90,7 @@ void MapImageExporter::saveImage() { break; } case ImageExporterMode::Timelapse: + // !TODO: also need layout editHistory! QProgressDialog progress("Building map timelapse...", "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); @@ -358,14 +359,19 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { QPixmap pixmap; // draw background layer / base image - map->render(true); - pixmap = map->pixmap; + Layout *layout = map->layout; + if (!layout) { + return QPixmap(); + } + + layout->render(true); + pixmap = layout->pixmap; if (showCollision) { QPainter collisionPainter(&pixmap); - map->renderCollision(true); + layout->renderCollision(true); collisionPainter.setOpacity(editor->collisionOpacity); - collisionPainter.drawPixmap(0, 0, map->collision_pixmap); + collisionPainter.drawPixmap(0, 0, layout->collision_pixmap); collisionPainter.end(); } @@ -375,16 +381,16 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { bool forceDrawBorder = showUpConnections || showDownConnections || showLeftConnections || showRightConnections; if (!ignoreBorder && (showBorder || forceDrawBorder)) { int borderDistance = this->mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; - map->renderBorder(); - int borderHorzDist = editor->getBorderDrawDistance(map->getBorderWidth()); - int borderVertDist = editor->getBorderDrawDistance(map->getBorderHeight()); + layout->renderBorder(); + int borderHorzDist = editor->getBorderDrawDistance(layout->getBorderWidth()); + int borderVertDist = editor->getBorderDrawDistance(layout->getBorderHeight()); borderWidth = borderDistance * 16; borderHeight = borderDistance * 16; - QPixmap newPixmap = QPixmap(map->pixmap.width() + borderWidth * 2, map->pixmap.height() + borderHeight * 2); + QPixmap newPixmap = QPixmap(layout->pixmap.width() + borderWidth * 2, layout->pixmap.height() + borderHeight * 2); QPainter borderPainter(&newPixmap); - for (int y = borderDistance - borderVertDist; y < map->getHeight() + borderVertDist * 2; y += map->getBorderHeight()) { - for (int x = borderDistance - borderHorzDist; x < map->getWidth() + borderHorzDist * 2; x += map->getBorderWidth()) { - borderPainter.drawPixmap(x * 16, y * 16, map->layout->border_pixmap); + for (int y = borderDistance - borderVertDist; y < layout->getHeight() + borderVertDist * 2; y += layout->getBorderHeight()) { + for (int x = borderDistance - borderHorzDist; x < layout->getWidth() + borderHorzDist * 2; x += layout->getBorderWidth()) { + borderPainter.drawPixmap(x * 16, y * 16, layout->border_pixmap); } } borderPainter.drawImage(borderWidth, borderHeight, pixmap.toImage()); diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 3ce9cf55..7fb9c34b 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -26,7 +26,7 @@ void MetatileSelector::draw() { if (i >= primaryLength) { tile += Project::getNumMetatilesPrimary() - primaryLength; } - QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, map->metatileLayerOrder, map->metatileLayerOpacity); + QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, layout->metatileLayerOrder, layout->metatileLayerOpacity); int map_y = i / this->numMetatilesWide; int map_x = i % this->numMetatilesWide; QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); @@ -209,6 +209,6 @@ QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) { return pos; } -void MetatileSelector::setMap(Map *map) { - this->map = map; +void MetatileSelector::setLayout(Layout *layout) { + this->layout = layout; } diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index 9ad154f2..67fa45ac 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -160,32 +160,32 @@ QList Prefab::getPrefabsForTilesets(QString primaryTileset, QString return filteredPrefabs; } -void Prefab::initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Map *map) { +void Prefab::initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Layout *layout) { this->selector = selector; this->prefabWidget = prefabWidget; this->emptyPrefabLabel = emptyPrefabLabel; this->loadPrefabs(); - this->updatePrefabUi(map); + this->updatePrefabUi(layout); } // This function recreates the UI state for the prefab tab. // We completely delete all the prefab widgets, and recreate new widgets // from the relevant list of prefab items. // This is not very efficient, but it gets the job done. -void Prefab::updatePrefabUi(Map *map) { +void Prefab::updatePrefabUi(Layout *layout) { if (!this->selector) return; // Cleanup the PrefabFrame to have a clean slate. - auto layout = this->prefabWidget->layout(); - while (layout && layout->count() > 1) { - auto child = layout->takeAt(1); + auto uiLayout = this->prefabWidget->layout(); + while (uiLayout && uiLayout->count() > 1) { + auto child = uiLayout->takeAt(1); if (child->widget()) { delete child->widget(); } delete child; } - QList prefabs = this->getPrefabsForTilesets(map->layout->tileset_primary_label, map->layout->tileset_secondary_label); + QList prefabs = this->getPrefabsForTilesets(layout->tileset_primary_label, layout->tileset_secondary_label); if (prefabs.isEmpty()) { emptyPrefabLabel->setVisible(true); return; @@ -204,7 +204,7 @@ void Prefab::updatePrefabUi(Map *map) { frame->ui->label_Name->setText(item.name); auto scene = new QGraphicsScene; - scene->addPixmap(drawMetatileSelection(item.selection, map)); + scene->addPixmap(drawMetatileSelection(item.selection, layout)); scene->setSceneRect(scene->itemsBoundingRect()); frame->ui->graphicsView_Prefab->setScene(scene); frame->ui->graphicsView_Prefab->setFixedSize(scene->itemsBoundingRect().width() + 2, @@ -218,7 +218,7 @@ void Prefab::updatePrefabUi(Map *map) { }); // Clicking the delete button removes it from the list of known prefabs and updates the UI. - QObject::connect(frame->ui->pushButton_DeleteItem, &QPushButton::clicked, [this, item, map](){ + QObject::connect(frame->ui->pushButton_DeleteItem, &QPushButton::clicked, [this, item, layout](){ for (int i = 0; i < this->items.size(); i++) { if (this->items[i].id == item.id) { QMessageBox msgBox; @@ -236,7 +236,7 @@ void Prefab::updatePrefabUi(Map *map) { if (msgBox.clickedButton() == deleteButton) { this->items.removeAt(i); this->savePrefabs(); - this->updatePrefabUi(map); + this->updatePrefabUi(layout); } break; } @@ -248,7 +248,7 @@ void Prefab::updatePrefabUi(Map *map) { prefabWidget->layout()->addItem(verticalSpacer); } -void Prefab::addPrefab(MetatileSelection selection, Map *map, QString name) { +void Prefab::addPrefab(MetatileSelection selection, Layout *layout, QString name) { // First, determine which tilesets are actually used in this new prefab, // based on the metatile ids. bool usesPrimaryTileset = false; @@ -266,12 +266,12 @@ void Prefab::addPrefab(MetatileSelection selection, Map *map, QString name) { this->items.append(PrefabItem{ QUuid::createUuid(), name, - usesPrimaryTileset ? map->layout->tileset_primary_label : "", - usesSecondaryTileset ? map->layout->tileset_secondary_label: "", + usesPrimaryTileset ? layout->tileset_primary_label : "", + usesSecondaryTileset ? layout->tileset_secondary_label: "", selection }); this->savePrefabs(); - this->updatePrefabUi(map); + this->updatePrefabUi(layout); } bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath) { diff --git a/src/ui/prefabcreationdialog.cpp b/src/ui/prefabcreationdialog.cpp index 976a53a1..a7b3029c 100644 --- a/src/ui/prefabcreationdialog.cpp +++ b/src/ui/prefabcreationdialog.cpp @@ -6,16 +6,16 @@ #include -PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Map *map) : +PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Layout *layout) : QDialog(parent), ui(new Ui::PrefabCreationDialog) { ui->setupUi(this); - this->map = map; + this->layout = layout; this->selection = metatileSelector->getMetatileSelection(); QGraphicsScene *scene = new QGraphicsScene; - QGraphicsPixmapItem *pixmapItem = scene->addPixmap(drawMetatileSelection(this->selection, map)); + QGraphicsPixmapItem *pixmapItem = scene->addPixmap(drawMetatileSelection(this->selection, layout)); scene->setSceneRect(scene->itemsBoundingRect()); this->ui->graphicsView_Prefab->setScene(scene); this->ui->graphicsView_Prefab->setFixedSize(scene->itemsBoundingRect().width() + 2, @@ -35,7 +35,7 @@ PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *me if (this->selection.hasCollision) { this->selection.collisionItems[index].enabled = toggledState; } - pixmapItem->setPixmap(drawMetatileSelection(this->selection, map)); + pixmapItem->setPixmap(drawMetatileSelection(this->selection, layout)); }); } @@ -45,5 +45,5 @@ PrefabCreationDialog::~PrefabCreationDialog() } void PrefabCreationDialog::savePrefab() { - prefab.addPrefab(this->selection, this->map, this->ui->lineEdit_PrefabName->text()); + prefab.addPrefab(this->selection, this->layout, this->ui->lineEdit_PrefabName->text()); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index fe6b6736..cf9604bc 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -13,14 +13,14 @@ #include #include -TilesetEditor::TilesetEditor(Project *project, Map *map, QWidget *parent) : +TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) : QMainWindow(parent), ui(new Ui::TilesetEditor), project(project), - map(map), + layout(layout), hasUnsavedChanges(false) { - this->setTilesets(this->map->layout->tileset_primary_label, this->map->layout->tileset_secondary_label); + this->setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); this->initUi(); } @@ -42,14 +42,14 @@ TilesetEditor::~TilesetEditor() delete copiedMetatile; } -void TilesetEditor::update(Map *map, QString primaryTilesetLabel, QString secondaryTilesetLabel) { - this->updateMap(map); +void TilesetEditor::update(Layout *layout, QString primaryTilesetLabel, QString secondaryTilesetLabel) { + this->updateLayout(layout); this->updateTilesets(primaryTilesetLabel, secondaryTilesetLabel); } -void TilesetEditor::updateMap(Map *map) { - this->map = map; - this->metatileSelector->map = map; +void TilesetEditor::updateLayout(Layout *layout) { + this->layout = layout; + this->metatileSelector->layout = layout; } void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel) { @@ -178,7 +178,7 @@ void TilesetEditor::setMetatileLabelValidator() { void TilesetEditor::initMetatileSelector() { - this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->map); + this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout); connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileChanged, this, &TilesetEditor::onHoveredMetatileChanged); connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileCleared, diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index e89e94da..78bd5a39 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -3,11 +3,11 @@ #include "project.h" #include -TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map) +TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) : SelectablePixmapItem(32, 32, 1, 1) { this->setTilesets(primaryTileset, secondaryTileset, false); this->numMetatilesWide = 8; - this->map = map; + this->layout = layout; setAcceptHoverEvents(true); this->usedMetatiles.resize(Project::getNumMetatilesTotal()); } @@ -45,8 +45,8 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet metatileId, this->primaryTileset, this->secondaryTileset, - map->metatileLayerOrder, - map->metatileLayerOpacity, + this->layout->metatileLayerOrder, + this->layout->metatileLayerOpacity, true) .scaled(32, 32); int map_y = i / this->numMetatilesWide; From 18eb3ceb1e7e8ea02dd73b46edb455df49782054 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 1 Feb 2023 20:28:54 -0500 Subject: [PATCH 005/364] setLayout to create layout-only edit mode --- include/core/maplayout.h | 2 + include/editor.h | 3 + include/mainwindow.h | 2 +- include/project.h | 1 + include/ui/maplistmodels.h | 4 +- src/core/map.cpp | 3 +- src/core/maplayout.cpp | 4 ++ src/editor.cpp | 107 +++++++++++++++++++++------------ src/mainwindow.cpp | 77 ++++++++++++++++++++++-- src/project.cpp | 13 ++++ src/ui/collisionpixmapitem.cpp | 2 +- src/ui/maplistmodels.cpp | 24 +++++--- 12 files changed, 186 insertions(+), 56 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 5e7c0f7c..7d4eb500 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -92,6 +92,8 @@ public: void clearBorderCache(); void cacheBorder(); + bool hasUnsavedChanges(); + bool layoutBlockChanged(int i, const Blockdata &cache); uint16_t getBorderMetatileId(int x, int y); diff --git a/include/editor.h b/include/editor.h index 8f12d88f..023451f4 100644 --- a/include/editor.h +++ b/include/editor.h @@ -60,11 +60,14 @@ public: void closeProject(); bool setMap(QString map_name); + bool setLayout(QString layoutName); void unsetMap(); Tileset *getCurrentMapPrimaryTileset(); bool displayMap(); + bool displayLayout(); + void displayMetatileSelector(); void displayMapMetatiles(); void displayMapMovementPermissions(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 18b947f9..7a8b133f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -357,7 +357,7 @@ private: bool tilesetNeedsRedraw = false; - bool setLayout(QString layoutName); + bool setLayout(QString layoutId); bool setMap(QString, bool scrollTreeView = false); void unsetMap(); diff --git a/include/project.h b/include/project.h index dd2e044b..ddffe49b 100644 --- a/include/project.h +++ b/include/project.h @@ -145,6 +145,7 @@ public: QSet getTopLevelMapFields(); bool loadMapData(Map*); bool readMapLayouts(); + Layout *loadLayout(QString layoutId); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index d15adcd3..166fe79d 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -62,9 +62,9 @@ public: QVariant data(const QModelIndex &index, int role) const override; public: - void setLayout(QString layoutName) { this->openLayout = layoutName; } + void setLayout(QString layoutId) { this->openLayout = layoutId; } - QStandardItem *createLayoutItem(QString layoutName); + QStandardItem *createLayoutItem(QString layoutId); QStandardItem *createMapItem(QString mapName); QStandardItem *getItem(const QModelIndex &index) const; diff --git a/src/core/map.cpp b/src/core/map.cpp index fdfb030c..ee7a1884 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -154,5 +154,6 @@ void Map::clean() { } bool Map::hasUnsavedChanges() { - return !editHistory.isClean() /* || !this->layout->editHistory.isClean() */ || hasUnsavedDataChanges || !isPersistedToFile; + // !TODO: layout not working here? map needs to be in cache before the layout being edited works + return !editHistory.isClean() || !this->layout->editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile; } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index a56a982e..7002f908 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -388,3 +388,7 @@ QPixmap Layout::renderBorder(bool ignoreCache) { } return this->border_pixmap; } + +bool Layout::hasUnsavedChanges() { + return !this->editHistory.isClean(); +} diff --git a/src/editor.cpp b/src/editor.cpp index 4b1bafc5..ba422eda 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -81,6 +81,7 @@ void Editor::closeProject() { } void Editor::setEditingMap() { + qDebug() << "Editor::setEditingMap()"; current_view = map_item; if (map_item) { map_item->paintingMode = LayoutPixmapItem::PaintMode::Metatiles; @@ -932,8 +933,8 @@ void Editor::onHoveredMovementPermissionCleared() { } QString Editor::getMetatileDisplayMessage(uint16_t metatileId) { - Metatile *metatile = Tileset::getMetatile(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); - QString label = Tileset::getMetatileLabel(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); + Metatile *metatile = Tileset::getMetatile(metatileId, this->layout->tileset_primary, this->layout->tileset_secondary); + QString label = Tileset::getMetatileLabel(metatileId, this->layout->tileset_primary, this->layout->tileset_secondary); QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId)); if (label.size()) message += QString(" \"%1\"").arg(label); @@ -1113,17 +1114,21 @@ bool Editor::setMap(QString map_name) { return false; } - map = loadedMap; - this->layout = map->layout; // !TODO: + this->map = loadedMap; + + // remove this + //this->layout = this->map->layout; + setLayout(map->layout->id); editGroup.addStack(&map->editHistory); + + // !TODO: determine which stack is active based on edit mode too since layout will have something different editGroup.setActiveStack(&map->editHistory); selected_events->clear(); if (!displayMap()) { return false; } - map_ruler->setMapDimensions(QSize(map->getWidth(), map->getHeight())); - connect(map, &Map::mapDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); + connect(map, &Map::openScriptRequested, this, &Editor::openScript); updateSelectedEvents(); } @@ -1131,6 +1136,20 @@ bool Editor::setMap(QString map_name) { return true; } +bool Editor::setLayout(QString layoutId) { + // + this->layout = this->project->loadLayout(layoutId); + + if (!displayLayout()) { + return false; + } + + map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); + connect(map, &Map::mapDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); + + return true; +} + void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { if (item->paintingMode != LayoutPixmapItem::PaintMode::Metatiles) { return; @@ -1337,6 +1356,18 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } bool Editor::displayMap() { + + displayMapEvents(); + displayMapConnections(); + displayWildMonTables(); + + if (events_group) { + events_group->setVisible(false); + } + return true; +} + +bool Editor::displayLayout() { if (!scene) { scene = new QGraphicsScene; MapSceneEventFilter *filter = new MapSceneEventFilter(); @@ -1351,17 +1382,15 @@ bool Editor::displayMap() { scene->removeItem(this->map_ruler); } + // !TODO: disassociate these functions from Map displayMetatileSelector(); - displayMovementPermissionSelector(); displayMapMetatiles(); + displayMovementPermissionSelector(); displayMapMovementPermissions(); displayBorderMetatiles(); displayCurrentMetatilesSelection(); - displayMapEvents(); - displayMapConnections(); displayMapBorder(); displayMapGrid(); - displayWildMonTables(); this->map_ruler->setZValue(1000); scene->addItem(this->map_ruler); @@ -1372,9 +1401,7 @@ bool Editor::displayMap() { if (collision_item) { collision_item->setVisible(false); } - if (events_group) { - events_group->setVisible(false); - } + return true; } @@ -1396,12 +1423,12 @@ void Editor::displayMetatileSelector() { } else { metatile_selector_item->setLayout(this->layout); if (metatile_selector_item->primaryTileset - && metatile_selector_item->primaryTileset != map->layout->tileset_primary) - emit tilesetUpdated(map->layout->tileset_primary->name); + && metatile_selector_item->primaryTileset != this->layout->tileset_primary) + emit tilesetUpdated(this->layout->tileset_primary->name); if (metatile_selector_item->secondaryTileset - && metatile_selector_item->secondaryTileset != map->layout->tileset_secondary) - emit tilesetUpdated(map->layout->tileset_secondary->name); - metatile_selector_item->setTilesets(map->layout->tileset_primary, map->layout->tileset_secondary); + && metatile_selector_item->secondaryTileset != this->layout->tileset_secondary) + emit tilesetUpdated(this->layout->tileset_secondary->name); + metatile_selector_item->setTilesets(this->layout->tileset_primary, this->layout->tileset_secondary); } scene_metatiles->addItem(metatile_selector_item); @@ -1548,11 +1575,13 @@ void Editor::displayMapConnections() { selected_connection_item = nullptr; connection_items.clear(); - for (MapConnection *connection : map->connections) { - if (connection->direction == "dive" || connection->direction == "emerge") { - continue; + if (map) { + for (MapConnection *connection : map->connections) { + if (connection->direction == "dive" || connection->direction == "emerge") { + continue; + } + createConnectionItem(connection); } - createConnectionItem(connection); } if (!connection_items.empty()) { @@ -1611,8 +1640,8 @@ void Editor::maskNonVisibleConnectionTiles() { mask.addRect( -BORDER_DISTANCE * 16, -BORDER_DISTANCE * 16, - (map->getWidth() + BORDER_DISTANCE * 2) * 16, - (map->getHeight() + BORDER_DISTANCE * 2) * 16 + (layout->getWidth() + BORDER_DISTANCE * 2) * 16, + (layout->getHeight() + BORDER_DISTANCE * 2) * 16 ); // Mask the tiles with the current theme's background color. @@ -1631,13 +1660,13 @@ void Editor::displayMapBorder() { } borderItems.clear(); - int borderWidth = map->getBorderWidth(); - int borderHeight = map->getBorderHeight(); + int borderWidth = this->layout->getBorderWidth(); + int borderHeight = this->layout->getBorderHeight(); int borderHorzDist = getBorderDrawDistance(borderWidth); int borderVertDist = getBorderDrawDistance(borderHeight); QPixmap pixmap = this->layout->renderBorder(); - for (int y = -borderVertDist; y < map->getHeight() + borderVertDist; y += borderHeight) - for (int x = -borderHorzDist; x < map->getWidth() + borderHorzDist; x += borderWidth) { + for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += borderHeight) + for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += borderWidth) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); @@ -1692,16 +1721,16 @@ void Editor::displayMapGrid() { gridLines.clear(); ui->checkBox_ToggleGrid->disconnect(); - int pixelWidth = map->getWidth() * 16; - int pixelHeight = map->getHeight() * 16; - for (int i = 0; i <= map->getWidth(); i++) { + int pixelWidth = this->layout->getWidth() * 16; + int pixelHeight = this->layout->getHeight() * 16; + for (int i = 0; i <= this->layout->getWidth(); i++) { int x = i * 16; QGraphicsLineItem *line = new QGraphicsLineItem(x, 0, x, pixelHeight); line->setVisible(ui->checkBox_ToggleGrid->isChecked()); gridLines.append(line); connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, [=](bool checked){line->setVisible(checked);}); } - for (int j = 0; j <= map->getHeight(); j++) { + for (int j = 0; j <= this->layout->getHeight(); j++) { int y = j * 16; QGraphicsLineItem *line = new QGraphicsLineItem(0, y, pixelWidth, y); line->setVisible(ui->checkBox_ToggleGrid->isChecked()); @@ -1921,20 +1950,20 @@ void Editor::updateDiveEmergeMap(QString mapName, QString direction) { void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_primary_label != tilesetLabel || forceLoad) + if (this->layout->tileset_primary_label != tilesetLabel || forceLoad) { - map->layout->tileset_primary_label = tilesetLabel; - map->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); + this->layout->tileset_primary_label = tilesetLabel; + this->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); layout->clearBorderCache(); } } void Editor::updateSecondaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_secondary_label != tilesetLabel || forceLoad) + if (this->layout->tileset_secondary_label != tilesetLabel || forceLoad) { - map->layout->tileset_secondary_label = tilesetLabel; - map->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); + this->layout->tileset_secondary_label = tilesetLabel; + this->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); layout->clearBorderCache(); } } @@ -1956,7 +1985,7 @@ void Editor::updateCustomMapHeaderValues(QTableWidget *table) Tileset* Editor::getCurrentMapPrimaryTileset() { - QString tilesetLabel = map->layout->tileset_primary_label; + QString tilesetLabel = this->layout->tileset_primary_label; return project->getTileset(tilesetLabel); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3d0bc6b3..0892a130 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -679,6 +679,53 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { return true; } +bool MainWindow::setLayout(QString layoutId) { + // if this->editor->setLayout(layoutName); + // this->editor->layout = layout; + + if (!this->editor->setLayout(layoutId)) { + return false; + } + + layoutTreeModel->setLayout(layoutId); + + refreshMapScene(); + + // if (scrollTreeView) { + // // Make sure we clear the filter first so we actually have a scroll target + // /// !TODO: make this onto a function that scrolls the current view taking a map name or layout name + // groupListProxyModel->setFilterRegularExpression(QString()); + // ui->mapList->setCurrentIndex(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name))); + // ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); + // } + + showWindowTitle(); + + updateMapList(); + + // connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged); + // connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); + // connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); + + // displayMapProperties + ui->comboBox_PrimaryTileset->blockSignals(true); + ui->comboBox_SecondaryTileset->blockSignals(true); + ui->comboBox_PrimaryTileset->setCurrentText(this->editor->layout->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(this->editor->layout->tileset_secondary_label); + ui->comboBox_PrimaryTileset->blockSignals(false); + ui->comboBox_SecondaryTileset->blockSignals(false); + + // + // connect(editor->layout, &Layout::mapChanged, this, &MainWindow::onMapChanged); + // connect(editor->layout, &Layout::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); + // connect(editor->layout, &Layout::modified, [this](){ this->markMapEdited(); }); + + // + updateTilesetEditor(); + + return true; +} + void MainWindow::redrawMapScene() { if (!editor->displayMap()) @@ -1426,12 +1473,22 @@ void MainWindow::on_layoutList_activated(const QModelIndex &index) { QVariant data = index.data(Qt::UserRole); if (index.data(MapListRoles::TypeRole) == "map_layout" && !data.isNull()) { - QString layoutName = data.toString(); + QString layoutId = data.toString(); // logInfo("Switching to a layout-only editing mode"); setMap(QString()); + //setLayout(layoutId); // setLayout(layout) - qDebug() << "set layout" << layoutName; + qDebug() << "set layout" << layoutId; + + if (!setLayout(layoutId)) { + QMessageBox msgBox(this); + QString errorMsg = QString("There was an error opening layout %1. Please see %2 for full error details.\n\n%3") + .arg(layoutId) + .arg(getLogPath()) + .arg(getMostRecentError()); + msgBox.critical(nullptr, "Error Opening Layout", errorMsg); + } } } @@ -1468,8 +1525,15 @@ void MainWindow::drawMapListIcons(QAbstractItemModel *model) { void MainWindow::updateMapList() { //MapGroupModel *model = static_cast(this->ui->mapList->model()); - mapGroupModel->setMap(this->editor->map->name); - groupListProxyModel->layoutChanged(); + if (this->editor->map) { + mapGroupModel->setMap(this->editor->map->name); + groupListProxyModel->layoutChanged(); + } + + if (this->editor->layout) { + layoutTreeModel->setLayout(this->editor->layout->id); + layoutListProxyModel->layoutChanged(); + } //mapGroupModel->layoutChanged(); // drawMapListIcons(mapListModel); } @@ -1746,6 +1810,7 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) Scripting::cb_MapViewTabChanged(oldIndex, index); if (index == 0) { + //if () editor->setEditingMap(); } else if (index == 1) { editor->setEditingCollision(); @@ -1768,6 +1833,8 @@ void MainWindow::on_action_Exit_triggered() void MainWindow::on_mainTabBar_tabBarClicked(int index) { + //if (!editor->map) return; + int oldIndex = ui->mainTabBar->currentIndex(); ui->mainTabBar->setCurrentIndex(index); if (index != oldIndex) @@ -1787,6 +1854,8 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) } else if (index == 3) { editor->setEditingConnections(); } + + if (!editor->map) return; if (index != 4) { if (userConfig.getEncounterJsonActive()) editor->saveEncounterTabData(); diff --git a/src/project.cpp b/src/project.cpp index 4acaf94d..c5cbe2e5 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -390,6 +390,19 @@ bool Project::loadLayout(MapLayout *layout) { && loadedBorder; } +Layout *Project::loadLayout(QString layoutId) { + // + if (mapLayouts.contains(layoutId)) { + Layout *layout = mapLayouts[layoutId]; + if (loadLayout(layout)) { + return layout; + } + } + + logError(QString("Error: Failed to load layout '%1'").arg(layoutId)); + return nullptr; +} + bool Project::loadMapLayout(Map* map) { if (!map->isPersistedToFile) { return true; diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index 26680677..0c809c3f 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -51,7 +51,7 @@ void CollisionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::draw(bool ignoreCache) { if (this->layout) { // !TODO - // this->layout->setCollisionItem(this); + this->layout->setCollisionItem(this); setPixmap(this->layout->renderCollision(ignoreCache)); setOpacity(*this->opacity); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 53a65154..1add584f 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -272,14 +272,14 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : QStandardI initialize(); } -QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutName) { +QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { QStandardItem *layout = new QStandardItem; - layout->setText(layoutName); + layout->setText(layoutId); layout->setEditable(false); - layout->setData(layoutName, Qt::UserRole); + layout->setData(layoutId, Qt::UserRole); layout->setData("map_layout", MapListRoles::TypeRole); // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->layoutItems.insert(layoutName, layout); + this->layoutItems.insert(layoutId, layout); return layout; } @@ -298,17 +298,16 @@ void LayoutTreeModel::initialize() { for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { // QString layoutId = project->mapLayoutsTable.value(i); - MapLayout *layout = project->mapLayouts.value(layoutId); - QStandardItem *layoutItem = createLayoutItem(layout->name); + QStandardItem *layoutItem = createLayoutItem(layoutId); this->root->appendRow(layoutItem); } for (auto mapList : this->project->groupedMapNames) { for (auto mapName : mapList) { // - QString layoutName = project->readMapLayoutName(mapName); + QString layoutId = project->readMapLayoutId(mapName); QStandardItem *map = createMapItem(mapName); - this->layoutItems[layoutName]->appendRow(map); + this->layoutItems[layoutId]->appendRow(map); } } @@ -344,6 +343,15 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListRoles::TypeRole).toString(); if (type == "map_layout") { + QString layoutId = item->data(Qt::UserRole).toString(); + if (layoutId == this->openLayout) { + return mapOpenedIcon; + } + else if (this->project->mapLayouts.contains(layoutId)) { + if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) { + return mapEditedIcon; + } + } return mapIcon; } else if (type == "map_name") { From e2253939fc84628ed51082927cf06e77da2250c3 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 3 Feb 2023 13:09:25 -0500 Subject: [PATCH 006/364] use enum class for edit modes --- include/editor.h | 8 +++- include/mainwindow.h | 9 +--- src/editor.cpp | 38 ++++++++-------- src/mainwindow.cpp | 102 +++++++++++++++---------------------------- 4 files changed, 62 insertions(+), 95 deletions(-) diff --git a/include/editor.h b/include/editor.h index 023451f4..b207b9d3 100644 --- a/include/editor.h +++ b/include/editor.h @@ -155,9 +155,13 @@ public: QList *selected_events = nullptr; + enum class EditAction { None, Paint, Select, Fill, Shift, Pick, Move }; + EditAction mapEditAction = EditAction::Paint; + EditAction objectEditAction = EditAction::Select; + /// !TODO this - QString map_edit_mode = "paint"; - QString obj_edit_mode = "select"; + enum class EditMode { None, Map, Layout }; + EditMode editMode = EditMode::Map; int scaleIndex = 2; qreal collisionOpacity = 0.5; diff --git a/include/mainwindow.h b/include/mainwindow.h index 7a8b133f..c5643b66 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -321,12 +321,6 @@ private: LayoutTreeModel *layoutTreeModel; - // QStandardItemModel *mapListModel; - // QList *mapGroupItemsList; - // QMap mapListIndexes; - // QIcon* mapIcon; - // QIcon* mapEditedIcon; - // QIcon* mapOpenedIcon; QAction *undoAction = nullptr; QAction *redoAction = nullptr; @@ -374,12 +368,11 @@ private: void setRecentMap(QString map_name); QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); - void drawMapListIcons(QAbstractItemModel *model); void updateMapList(); void displayMapProperties(); void checkToolButtons(); - void clickToolButtonFromEditMode(QString editMode); + void clickToolButtonFromEditAction(Editor::EditAction editAction); void markMapEdited(); void showWindowTitle(); diff --git a/src/editor.cpp b/src/editor.cpp index ba422eda..ca6d3857 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -158,7 +158,7 @@ void Editor::setMapEditingButtonsEnabled(bool enabled) { this->ui->pushButton_ChangeDimensions->setEnabled(enabled); // If the fill button is pressed, unpress it and select the pointer. if (!enabled && (this->ui->toolButton_Fill->isChecked() || this->ui->toolButton_Dropper->isChecked())) { - this->map_edit_mode = "select"; + this->mapEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Fill->setChecked(false); @@ -1145,7 +1145,7 @@ bool Editor::setLayout(QString layoutId) { } map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); - connect(map, &Map::mapDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); + connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); return true; } @@ -1156,7 +1156,7 @@ void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem * } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (event->buttons() & Qt::RightButton && (map_edit_mode == "paint" || map_edit_mode == "fill")) { + if (event->buttons() & Qt::RightButton && (mapEditAction == EditAction::Paint || mapEditAction == EditAction::Fill)) { this->cursorMapTileRect->initRightClickSelectionAnchor(pos.x(), pos.y()); } else { this->cursorMapTileRect->initAnchor(pos.x(), pos.y()); @@ -1206,7 +1206,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { - if (map_edit_mode == "paint") { + if (mapEditAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->buttons() & Qt::MiddleButton) { @@ -1228,9 +1228,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } item->paint(event); } - } else if (map_edit_mode == "select") { + } else if (mapEditAction == EditAction::Select) { item->select(event); - } else if (map_edit_mode == "fill") { + } else if (mapEditAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->modifiers() & Qt::ControlModifier) { @@ -1238,13 +1238,13 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } else { item->floodFill(event); } - } else if (map_edit_mode == "pick") { + } else if (mapEditAction == EditAction::Pick) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else { item->pick(event); } - } else if (map_edit_mode == "shift") { + } else if (mapEditAction == EditAction::Shift) { this->setStraightPathCursorMode(event); if (this->cursorMapTileRect->getStraightPathMode()) { item->lockNondominantAxis(event); @@ -1253,10 +1253,10 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i item->shift(event); } } else if (item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { - if (obj_edit_mode == "paint" && event->type() == QEvent::GraphicsSceneMousePress) { + if (objectEditAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { // Right-clicking while in paint mode will change mode to select. if (event->buttons() & Qt::RightButton) { - this->obj_edit_mode = "select"; + this->objectEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Paint->setChecked(false); @@ -1278,9 +1278,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } } } - } else if (obj_edit_mode == "select") { + } else if (objectEditAction == EditAction::Select) { // do nothing here, at least for now - } else if (obj_edit_mode == "shift") { + } else if (objectEditAction == EditAction::Shift) { static QPoint selection_origin; static unsigned actionId = 0; @@ -1316,7 +1316,7 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (map_edit_mode == "paint") { + if (mapEditAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMovementPermissionSelection(event); } else if (event->buttons() & Qt::MiddleButton) { @@ -1333,9 +1333,9 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } item->paint(event); } - } else if (map_edit_mode == "select") { + } else if (mapEditAction == EditAction::Select) { item->select(event); - } else if (map_edit_mode == "fill") { + } else if (mapEditAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { item->pick(event); } else if (event->modifiers() & Qt::ControlModifier) { @@ -1343,9 +1343,9 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } else { item->floodFill(event); } - } else if (map_edit_mode == "pick") { + } else if (mapEditAction == EditAction::Pick) { item->pick(event); - } else if (map_edit_mode == "shift") { + } else if (mapEditAction == EditAction::Shift) { this->setStraightPathCursorMode(event); if (this->cursorMapTileRect->getStraightPathMode()) { item->lockNondominantAxis(event); @@ -2247,8 +2247,8 @@ void Editor::objectsView_onMousePress(QMouseEvent *event) { if (map_item && map_item->paintingMode != LayoutPixmapItem::PaintMode::EventObjects) { return; } - if (this->obj_edit_mode == "paint" && event->buttons() & Qt::RightButton) { - this->obj_edit_mode = "select"; + if (this->objectEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { + this->objectEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Paint->setChecked(false); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0892a130..3c39fbc7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -625,7 +625,6 @@ void MainWindow::unsetMap() { this->editor->unsetMap(); // disable other tabs - this->ui->mainTabBar->setTabEnabled(0, true); this->ui->mainTabBar->setTabEnabled(1, false); this->ui->mainTabBar->setTabEnabled(2, false); this->ui->mainTabBar->setTabEnabled(3, false); @@ -653,6 +652,11 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); } + this->ui->mainTabBar->setTabEnabled(1, true); + this->ui->mainTabBar->setTabEnabled(2, true); + this->ui->mainTabBar->setTabEnabled(3, true); + this->ui->mainTabBar->setTabEnabled(4, true); + refreshMapScene(); displayMapProperties(); @@ -1492,39 +1496,7 @@ void MainWindow::on_layoutList_activated(const QModelIndex &index) { } } -/// !TODO something with the projectHasUnsavedChanges var -void MainWindow::drawMapListIcons(QAbstractItemModel *model) { - // projectHasUnsavedChanges = false; - // QList list; - // list.append(QModelIndex()); - // while (list.length()) { - // QModelIndex parent = list.takeFirst(); - // for (int i = 0; i < model->rowCount(parent); i++) { - // QModelIndex index = model->index(i, 0, parent); - // if (model->hasChildren(index)) { - // list.append(index); - // } - // QVariant data = index.data(Qt::UserRole); - // if (!data.isNull()) { - // QString map_name = data.toString(); - // if (editor->project && editor->project->mapCache.contains(map_name)) { - // QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name)); - // map->setIcon(*mapIcon); - // if (editor->project->mapCache.value(map_name)->hasUnsavedChanges()) { - // map->setIcon(*mapEditedIcon); - // projectHasUnsavedChanges = true; - // } - // if (editor->map->name == map_name) { - // map->setIcon(*mapOpenedIcon); - // } - // } - // } - // } - // } -} - void MainWindow::updateMapList() { - //MapGroupModel *model = static_cast(this->ui->mapList->model()); if (this->editor->map) { mapGroupModel->setMap(this->editor->map->name); groupListProxyModel->layoutChanged(); @@ -1534,8 +1506,6 @@ void MainWindow::updateMapList() { layoutTreeModel->setLayout(this->editor->layout->id); layoutListProxyModel->layoutChanged(); } - //mapGroupModel->layoutChanged(); - // drawMapListIcons(mapListModel); } void MainWindow::on_action_Save_Project_triggered() { @@ -1846,11 +1816,11 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) if (index == 0) { ui->stackedWidget_MapEvents->setCurrentIndex(0); on_mapViewTab_tabBarClicked(ui->mapViewTab->currentIndex()); - clickToolButtonFromEditMode(editor->map_edit_mode); + clickToolButtonFromEditAction(editor->mapEditAction); } else if (index == 1) { ui->stackedWidget_MapEvents->setCurrentIndex(1); editor->setEditingObjects(); - clickToolButtonFromEditMode(editor->obj_edit_mode); + clickToolButtonFromEditAction(editor->objectEditAction); } else if (index == 3) { editor->setEditingConnections(); } @@ -2324,9 +2294,9 @@ void MainWindow::on_toolButton_deleteObject_clicked() { void MainWindow::on_toolButton_Paint_clicked() { if (ui->mainTabBar->currentIndex() == 0) - editor->map_edit_mode = "paint"; + editor->mapEditAction = Editor::EditAction::Paint; else - editor->obj_edit_mode = "paint"; + editor->objectEditAction = Editor::EditAction::Paint; editor->settings->mapCursor = QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10); @@ -2345,9 +2315,9 @@ void MainWindow::on_toolButton_Paint_clicked() void MainWindow::on_toolButton_Select_clicked() { if (ui->mainTabBar->currentIndex() == 0) - editor->map_edit_mode = "select"; + editor->mapEditAction = Editor::EditAction::Select; else - editor->obj_edit_mode = "select"; + editor->objectEditAction = Editor::EditAction::Select; editor->settings->mapCursor = QCursor(); editor->cursorMapTileRect->setSingleTileMode(); @@ -2363,9 +2333,9 @@ void MainWindow::on_toolButton_Select_clicked() void MainWindow::on_toolButton_Fill_clicked() { if (ui->mainTabBar->currentIndex() == 0) - editor->map_edit_mode = "fill"; + editor->mapEditAction = Editor::EditAction::Fill; else - editor->obj_edit_mode = "fill"; + editor->objectEditAction = Editor::EditAction::Fill; editor->settings->mapCursor = QCursor(QPixmap(":/icons/fill_color_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2381,9 +2351,9 @@ void MainWindow::on_toolButton_Fill_clicked() void MainWindow::on_toolButton_Dropper_clicked() { if (ui->mainTabBar->currentIndex() == 0) - editor->map_edit_mode = "pick"; + editor->mapEditAction = Editor::EditAction::Pick; else - editor->obj_edit_mode = "pick"; + editor->objectEditAction = Editor::EditAction::Pick; editor->settings->mapCursor = QCursor(QPixmap(":/icons/pipette_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2399,9 +2369,9 @@ void MainWindow::on_toolButton_Dropper_clicked() void MainWindow::on_toolButton_Move_clicked() { if (ui->mainTabBar->currentIndex() == 0) - editor->map_edit_mode = "move"; + editor->mapEditAction = Editor::EditAction::Move; else - editor->obj_edit_mode = "move"; + editor->objectEditAction = Editor::EditAction::Move; editor->settings->mapCursor = QCursor(QPixmap(":/icons/move.ico"), 7, 7); editor->cursorMapTileRect->setSingleTileMode(); @@ -2417,9 +2387,9 @@ void MainWindow::on_toolButton_Move_clicked() void MainWindow::on_toolButton_Shift_clicked() { if (ui->mainTabBar->currentIndex() == 0) - editor->map_edit_mode = "shift"; + editor->mapEditAction = Editor::EditAction::Shift; else - editor->obj_edit_mode = "shift"; + editor->objectEditAction = Editor::EditAction::Shift; editor->settings->mapCursor = QCursor(QPixmap(":/icons/shift_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2433,37 +2403,37 @@ void MainWindow::on_toolButton_Shift_clicked() } void MainWindow::checkToolButtons() { - QString edit_mode; + Editor::EditAction editAction; if (ui->mainTabBar->currentIndex() == 0) { - edit_mode = editor->map_edit_mode; + editAction = editor->mapEditAction; } else { - edit_mode = editor->obj_edit_mode; - if (edit_mode == "select" && editor->map_ruler) + editAction = editor->objectEditAction; + if (editAction == Editor::EditAction::Select && editor->map_ruler) editor->map_ruler->setEnabled(true); else if (editor->map_ruler) editor->map_ruler->setEnabled(false); } - ui->toolButton_Paint->setChecked(edit_mode == "paint"); - ui->toolButton_Select->setChecked(edit_mode == "select"); - ui->toolButton_Fill->setChecked(edit_mode == "fill"); - ui->toolButton_Dropper->setChecked(edit_mode == "pick"); - ui->toolButton_Move->setChecked(edit_mode == "move"); - ui->toolButton_Shift->setChecked(edit_mode == "shift"); + ui->toolButton_Paint->setChecked(editAction == Editor::EditAction::Paint); + ui->toolButton_Select->setChecked(editAction == Editor::EditAction::Select); + ui->toolButton_Fill->setChecked(editAction == Editor::EditAction::Fill); + ui->toolButton_Dropper->setChecked(editAction == Editor::EditAction::Pick); + ui->toolButton_Move->setChecked(editAction == Editor::EditAction::Move); + ui->toolButton_Shift->setChecked(editAction == Editor::EditAction::Shift); } -void MainWindow::clickToolButtonFromEditMode(QString editMode) { - if (editMode == "paint") { +void MainWindow::clickToolButtonFromEditAction(Editor::EditAction editAction) { + if (editAction == Editor::EditAction::Paint) { on_toolButton_Paint_clicked(); - } else if (editMode == "select") { + } else if (editAction == Editor::EditAction::Select) { on_toolButton_Select_clicked(); - } else if (editMode == "fill") { + } else if (editAction == Editor::EditAction::Fill) { on_toolButton_Fill_clicked(); - } else if (editMode == "pick") { + } else if (editAction == Editor::EditAction::Pick) { on_toolButton_Dropper_clicked(); - } else if (editMode == "move") { + } else if (editAction == Editor::EditAction::Move) { on_toolButton_Move_clicked(); - } else if (editMode == "shift") { + } else if (editAction == Editor::EditAction::Shift) { on_toolButton_Shift_clicked(); } } From 1497f42ab0cbecf7b236440c5ee377cd9267834e Mon Sep 17 00:00:00 2001 From: garak Date: Sun, 5 Feb 2023 14:52:40 -0500 Subject: [PATCH 007/364] save progress --- include/editor.h | 19 ++++++++++++------- src/editor.cpp | 41 +++++++++++++++++++++++----------------- src/mainwindow.cpp | 10 +++++++++- src/ui/maplistmodels.cpp | 3 ++- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/include/editor.h b/include/editor.h index b207b9d3..29709df8 100644 --- a/include/editor.h +++ b/include/editor.h @@ -84,12 +84,6 @@ public: void updateMapBorder(); void updateMapConnections(); - void setEditingMap(); - void setEditingCollision(); - void setEditingObjects(); - void setEditingConnections(); - void setMapEditingButtonsEnabled(bool enabled); - void setCurrentConnectionDirection(QString curDirection); void updateCurrentConnectionDirection(QString curDirection); void setConnectionsVisibility(bool visible); @@ -160,8 +154,19 @@ public: EditAction objectEditAction = EditAction::Select; /// !TODO this - enum class EditMode { None, Map, Layout }; + enum class EditMode { None, Disabled, Map, Layout, Objects, Connections, Encounters }; EditMode editMode = EditMode::Map; + void setEditMode(EditMode mode) { this->editMode = mode; } + EditMode getEditMode() { return this->editMode; } + + void setEditingMap(); + void setEditingCollision(); + void setEditingLayout(); + void setEditingObjects(); + void setEditingConnections(); + void setEditingEncounters(); + + void setMapEditingButtonsEnabled(bool enabled); int scaleIndex = 2; qreal collisionOpacity = 0.5; diff --git a/src/editor.cpp b/src/editor.cpp index ca6d3857..8a508bbf 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -81,7 +81,6 @@ void Editor::closeProject() { } void Editor::setEditingMap() { - qDebug() << "Editor::setEditingMap()"; current_view = map_item; if (map_item) { map_item->paintingMode = LayoutPixmapItem::PaintMode::Metatiles; @@ -104,6 +103,10 @@ void Editor::setEditingMap() { setMapEditingButtonsEnabled(true); } +void Editor::setEditingLayout() { + // +} + void Editor::setEditingCollision() { current_view = collision_item; if (collision_item) { @@ -152,22 +155,6 @@ void Editor::setEditingObjects() { setMapEditingButtonsEnabled(false); } -void Editor::setMapEditingButtonsEnabled(bool enabled) { - this->ui->toolButton_Fill->setEnabled(enabled); - this->ui->toolButton_Dropper->setEnabled(enabled); - this->ui->pushButton_ChangeDimensions->setEnabled(enabled); - // If the fill button is pressed, unpress it and select the pointer. - if (!enabled && (this->ui->toolButton_Fill->isChecked() || this->ui->toolButton_Dropper->isChecked())) { - this->mapEditAction = EditAction::Select; - this->settings->mapCursor = QCursor(); - this->cursorMapTileRect->setSingleTileMode(); - this->ui->toolButton_Fill->setChecked(false); - this->ui->toolButton_Dropper->setChecked(false); - this->ui->toolButton_Select->setChecked(true); - } - this->ui->checkBox_smartPaths->setEnabled(enabled); -} - void Editor::setEditingConnections() { current_view = map_item; if (map_item) { @@ -199,6 +186,26 @@ void Editor::setEditingConnections() { this->cursorMapTileRect->setActive(false); } +void Editor::setEditingEncounters() { + // +} + +void Editor::setMapEditingButtonsEnabled(bool enabled) { + this->ui->toolButton_Fill->setEnabled(enabled); + this->ui->toolButton_Dropper->setEnabled(enabled); + this->ui->pushButton_ChangeDimensions->setEnabled(enabled); + // If the fill button is pressed, unpress it and select the pointer. + if (!enabled && (this->ui->toolButton_Fill->isChecked() || this->ui->toolButton_Dropper->isChecked())) { + this->mapEditAction = EditAction::Select; + this->settings->mapCursor = QCursor(); + this->cursorMapTileRect->setSingleTileMode(); + this->ui->toolButton_Fill->setChecked(false); + this->ui->toolButton_Dropper->setChecked(false); + this->ui->toolButton_Select->setChecked(true); + } + this->ui->checkBox_smartPaths->setEnabled(enabled); +} + void Editor::displayWildMonTables() { QStackedWidget *stack = ui->stackedWidget_WildMons; QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3c39fbc7..9edf6262 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -335,6 +335,7 @@ void MainWindow::initMiscHeapObjects() { ui->tabWidget_EventType->clear(); } +// TODO void MainWindow::initMapSortOrder() { // QMenu *mapSortOrderMenu = new QMenu(this); // QActionGroup *mapSortOrderActionGroup = new QActionGroup(ui->toolButton_MapSortOrder); @@ -356,6 +357,7 @@ void MainWindow::initMapSortOrder() { } void MainWindow::showWindowTitle() { + // !TODO, check editor editmode if (editor->map) { setWindowTitle(QString("%1%2 - %3") .arg(editor->map->hasUnsavedChanges() ? "* " : "") @@ -363,6 +365,13 @@ void MainWindow::showWindowTitle() { .arg(editor->project->getProjectTitle()) ); } + else if (editor->layout) { + setWindowTitle(QString("%1%2 - %3") + .arg(editor->layout->hasUnsavedChanges() ? "* " : "") + .arg(editor->layout->id) + .arg(editor->project->getProjectTitle()) + ); + } } void MainWindow::markMapEdited() { @@ -1780,7 +1789,6 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) Scripting::cb_MapViewTabChanged(oldIndex, index); if (index == 0) { - //if () editor->setEditingMap(); } else if (index == 1) { editor->setEditingCollision(); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 1add584f..dcf23207 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -274,7 +274,8 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : QStandardI QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { QStandardItem *layout = new QStandardItem; - layout->setText(layoutId); + layout->setText(this->project->layoutIdsToNames[layoutId]); + //layout->setText(layoutId); layout->setEditable(false); layout->setData(layoutId, Qt::UserRole); layout->setData("map_layout", MapListRoles::TypeRole); From de8b005d77edda406d234e2d516fab1136c7088d Mon Sep 17 00:00:00 2001 From: garak Date: Mon, 6 Feb 2023 13:42:21 -0500 Subject: [PATCH 008/364] gray out map icons until map is open ... because the color of the icon does not mean anything until map has been loaded into memory for example, if the map's layout has changed then it should be marked as modified but that wouldn't happen if the map is unloaded --- resources/icons/map_grayed.ico | Bin 0 -> 1150 bytes resources/images.qrc | 1 + src/mainwindow.cpp | 4 ++-- src/ui/maplistmodels.cpp | 6 +++++- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 resources/icons/map_grayed.ico diff --git a/resources/icons/map_grayed.ico b/resources/icons/map_grayed.ico new file mode 100644 index 0000000000000000000000000000000000000000..86c3f5fb72543d3d652ece433cf88cbcf15d3722 GIT binary patch literal 1150 zcmbtUxo*Nh6nv5L0aU3dQl`&Oa8pro6cjEH_kBn}35XT}6_Bq3S|skt&W1$>BjtM5 zc-}Gd)|MrUJgLj&5_NwS>sjPQMBWrsOLe~bibT}xLAX`#{SR;h5Rb?65eNj}^?IT5 zZ$TaPN;{`N7z`@85fZcAVvE6QQ4>O~Vaq(UKmSrIpiy9hlxOh)&}sZ}6pT@uN2@_n7uK)l5 literal 0 HcmV?d00001 diff --git a/resources/images.qrc b/resources/images.qrc index 2399cbe9..e90d5849 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -17,6 +17,7 @@ icons/map_edited.ico icons/map_opened.ico icons/map.ico + icons/map_grayed.ico icons/move.ico icons/pencil_cursor.ico icons/pencil.ico diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9edf6262..c503d23d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -368,7 +368,7 @@ void MainWindow::showWindowTitle() { else if (editor->layout) { setWindowTitle(QString("%1%2 - %3") .arg(editor->layout->hasUnsavedChanges() ? "* " : "") - .arg(editor->layout->id) + .arg(editor->layout->name) .arg(editor->project->getProjectTitle()) ); } @@ -1646,7 +1646,7 @@ void MainWindow::setClipboardData(QImage image) { } void MainWindow::paste() { - if (!editor || !editor->project || !editor->map) return; + if (!editor || !editor->project || !(editor->map || editor->layout)) return; QClipboard *clipboard = QGuiApplication::clipboard(); QString clipboardText(clipboard->text()); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index dcf23207..e243541e 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -180,6 +180,7 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { int col = index.column(); if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); @@ -206,8 +207,11 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { return mapEditedIcon; } + else { + return mapIcon; + } } - return mapIcon; + return mapGrayIcon; } // check if map or group From 9918159caab306cb46d4391fe50e4e9b8af4a737 Mon Sep 17 00:00:00 2001 From: garak Date: Mon, 6 Feb 2023 15:05:22 -0500 Subject: [PATCH 009/364] ui to change map's assigned layout id --- forms/mainwindow.ui | 652 ++++++++++++++++++++++--------------------- include/core/map.h | 1 + include/mainwindow.h | 1 + src/core/map.cpp | 7 + src/editor.cpp | 12 + src/mainwindow.cpp | 23 +- 6 files changed, 378 insertions(+), 318 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index a59c0ad0..9ecc927f 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -509,7 +509,7 @@ - 3 + 0 false @@ -922,306 +922,23 @@ 3 - - - - - 0 - 0 - + + + + 10 - - QFrame::StyledPanel + + 90 - - QFrame::Raised + + 30 + + + Qt::Horizontal - - - - - Primary Tileset - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> - - - true - - - - - - - Secondary Tileset - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> - - - true - - - - - - - - - 0 - 0 - - - - - 0 - 92 - - - - - 16777215 - 92 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Selection - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Plain - - - true - - - - - 0 - 0 - 256 - 74 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - Border - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 48 - - - - <html><head/><body><p>The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> - - - QFrame::StyledPanel - - - QFrame::Sunken - - - Qt::ScrollBarAsNeeded - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + @@ -1250,10 +967,10 @@ - 0 + 8 0 - 91 - 74 + 408 + 380 @@ -1347,19 +1064,330 @@ - - - 10 + + + + 0 + 0 + - - 90 + + + 0 + 92 + - - 30 + + + 16777215 + 92 + - - Qt::Horizontal + + QFrame::NoFrame + + QFrame::Raised + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Selection + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 420 + 74 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Border + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 48 + + + + <html><head/><body><p>The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> + + + QFrame::StyledPanel + + + QFrame::Sunken + + + Qt::ScrollBarAsNeeded + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Primary Tileset + + + + + + + Qt::StrongFocus + + + <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> + + + true + + + + + + + Secondary Tileset + + + + + + + Qt::StrongFocus + + + <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> + + + true + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 2 + + + + + Layout + + + + + + + @@ -1403,8 +1431,8 @@ 0 0 - 92 - 550 + 425 + 696 @@ -1563,8 +1591,8 @@ 0 0 - 91 - 460 + 379 + 611 diff --git a/include/core/map.h b/include/core/map.h index 37ae9b58..87bfd0d9 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -59,6 +59,7 @@ public: QMap customHeaders; Layout *layout = nullptr; + void setLayout(Layout *layout); bool isPersistedToFile = true; bool hasUnsavedDataChanges = false; diff --git a/include/mainwindow.h b/include/mainwindow.h index c5643b66..d4390bbb 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -200,6 +200,7 @@ private slots: void on_comboBox_Weather_currentTextChanged(const QString &arg1); void on_comboBox_Type_currentTextChanged(const QString &arg1); void on_comboBox_BattleScene_currentTextChanged(const QString &arg1); + void on_comboBox_LayoutSelector_currentTextChanged(const QString &arg1); void on_checkBox_ShowLocation_stateChanged(int selected); void on_checkBox_AllowRunning_stateChanged(int selected); void on_checkBox_AllowBiking_stateChanged(int selected); diff --git a/src/core/map.cpp b/src/core/map.cpp index ee7a1884..fc167ea1 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -29,6 +29,13 @@ void Map::setName(QString mapName) { constantName = mapConstantFromName(mapName); } +void Map::setLayout(Layout *layout) { + this->layout = layout; + if (layout) { + this->layoutId = layout->id; + } +} + QString Map::mapConstantFromName(QString mapName) { // Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'. static const QRegularExpression caseChange("([a-z])([A-Z])"); diff --git a/src/editor.cpp b/src/editor.cpp index 8a508bbf..18915bdc 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1154,6 +1154,18 @@ bool Editor::setLayout(QString layoutId) { map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); + ui->comboBox_PrimaryTileset->blockSignals(true); + ui->comboBox_SecondaryTileset->blockSignals(true); + ui->comboBox_PrimaryTileset->setCurrentText(this->layout->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(this->layout->tileset_secondary_label); + ui->comboBox_PrimaryTileset->blockSignals(false); + ui->comboBox_SecondaryTileset->blockSignals(false); + + const QSignalBlocker b0(this->ui->comboBox_LayoutSelector); + int index = this->ui->comboBox_LayoutSelector->findText(layoutId); + if (index < 0) index = 0; + this->ui->comboBox_LayoutSelector->setCurrentIndex(index); + return true; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c503d23d..3904ca0c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -640,6 +640,7 @@ void MainWindow::unsetMap() { this->ui->mainTabBar->setTabEnabled(4, false); // + this->ui->comboBox_LayoutSelector->setEnabled(false); } bool MainWindow::setMap(QString map_name, bool scrollTreeView) { @@ -721,12 +722,7 @@ bool MainWindow::setLayout(QString layoutId) { // connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); // displayMapProperties - ui->comboBox_PrimaryTileset->blockSignals(true); - ui->comboBox_SecondaryTileset->blockSignals(true); - ui->comboBox_PrimaryTileset->setCurrentText(this->editor->layout->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(this->editor->layout->tileset_secondary_label); - ui->comboBox_PrimaryTileset->blockSignals(false); - ui->comboBox_SecondaryTileset->blockSignals(false); + // // connect(editor->layout, &Layout::mapChanged, this, &MainWindow::onMapChanged); @@ -876,6 +872,18 @@ void MainWindow::displayMapProperties() { ui->tableWidget_CustomHeaderFields->blockSignals(false); } +void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { + // + if (editor && editor->project && editor->map) { + if (editor->project->mapLayouts.contains(text)) { + editor->map->setLayout(editor->project->loadLayout(text)); + // !TODO: method to setMapLayout instead of having to do whole setMap thing, + // also edit history and bug fixes + setMap(editor->map->name); + } + } +} + void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) { if (editor && editor->map) { @@ -1012,6 +1020,7 @@ bool MainWindow::loadProjectCombos() { const QSignalBlocker blocker5(ui->comboBox_Weather); const QSignalBlocker blocker6(ui->comboBox_BattleScene); const QSignalBlocker blocker7(ui->comboBox_Type); + const QSignalBlocker blocker8(ui->comboBox_LayoutSelector); ui->comboBox_Song->clear(); ui->comboBox_Song->addItems(project->songNames); @@ -1027,6 +1036,8 @@ bool MainWindow::loadProjectCombos() { ui->comboBox_BattleScene->addItems(project->mapBattleScenes); ui->comboBox_Type->clear(); ui->comboBox_Type->addItems(project->mapTypes); + ui->comboBox_LayoutSelector->clear(); + ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); return true; } From e2ff93e5e70bb2f3367ec7858a3945969a5b0c14 Mon Sep 17 00:00:00 2001 From: garak Date: Mon, 6 Feb 2023 23:48:37 -0500 Subject: [PATCH 010/364] add areaList model and filtering, scrolling for all trees --- include/mainwindow.h | 11 +- include/ui/maplistmodels.h | 39 ++- resources/icons/application_form_edit.ico | Bin 1150 -> 1150 bytes resources/icons/connections.ico | Bin 0 -> 1150 bytes resources/images.qrc | 2 + src/mainwindow.cpp | 117 ++++++--- src/ui/maplistmodels.cpp | 280 +++++++++------------- 7 files changed, 241 insertions(+), 208 deletions(-) create mode 100644 resources/icons/connections.ico diff --git a/include/mainwindow.h b/include/mainwindow.h index d4390bbb..7bc47ff2 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -266,6 +266,8 @@ private slots: void on_actionTileset_Editor_triggered(); void on_lineEdit_filterBox_textChanged(const QString &arg1); + void on_lineEdit_filterBox_Areas_textChanged(const QString &arg1); + void on_lineEdit_filterBox_Layouts_textChanged(const QString &arg1); void moveEvent(QMoveEvent *event); void closeEvent(QCloseEvent *); @@ -318,6 +320,9 @@ private: FilterChildrenProxyModel *groupListProxyModel; MapGroupModel *mapGroupModel; + FilterChildrenProxyModel *areaListProxyModel; + MapAreaModel *mapAreaModel; + FilterChildrenProxyModel *layoutListProxyModel; LayoutTreeModel *layoutTreeModel; @@ -348,13 +353,13 @@ private: bool newMapDefaultsSet = false; MapSortOrder mapSortOrder; - enum MapListTab { Groups, Areas, Layouts }; + enum MapListTab { Groups = 0, Areas, Layouts }; bool tilesetNeedsRedraw = false; bool setLayout(QString layoutId); - bool setMap(QString, bool scrollTreeView = false); + bool setMap(QString, bool scroll = false); void unsetMap(); void redrawMapScene(); void refreshMapScene(); @@ -363,11 +368,11 @@ private: bool populateMapList(); void sortMapList(); void openSubWindow(QWidget * window); + void scrollTreeView(QString itemName); QString getExistingDirectory(QString); bool openProject(QString dir); QString getDefaultMap(); void setRecentMap(QString map_name); - QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); void updateMapList(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 166fe79d..d730c05e 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -15,7 +15,8 @@ enum MapListRoles { TypeRole2, // Used for various extra data needed. }; -// or QStandardItemModel?? + + class MapGroupModel : public QStandardItemModel { Q_OBJECT @@ -52,6 +53,42 @@ signals: +class MapAreaModel : public QStandardItemModel { + Q_OBJECT + +public: + MapAreaModel(Project *project, QObject *parent = nullptr); + ~MapAreaModel() {} + + QVariant data(const QModelIndex &index, int role) const override; + +public: + void setMap(QString mapName) { this->openMap = mapName; } + + QStandardItem *createAreaItem(QString areaName, int areaIndex); + QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); + + QStandardItem *getItem(const QModelIndex &index) const; + QModelIndex indexOfMap(QString mapName); + + void initialize(); + +private: + Project *project; + QStandardItem *root = nullptr; + + QMap areaItems; + QMap mapItems; + // TODO: if reordering, will the item be the same? + + QString openMap; + +signals: + void edited(); +}; + + + class LayoutTreeModel : public QStandardItemModel { Q_OBJECT diff --git a/resources/icons/application_form_edit.ico b/resources/icons/application_form_edit.ico index 7bb403eab9c641b0f756a6de02c6cb30a22a8b65..5d9cc7dafdf245cfecb63a9002530a2ae3990568 100644 GIT binary patch literal 1150 zcmb7@T}YEr7{`wgNTZKMr4+3!B%A3~(ir&J26 zGsN{xLZk^Hju4ch%u1fZ|CW0pUH%;;wYc!ecuUBt%+ceu@eWZXje>3d) zsc_{9oPHI@<7q%U2<)r`PTZRoPP@(vjEBp z;NTTn3>9bL7V_^Eyk_>#%!FYw8c}63;q#|3JRNPaj;q7!H#pY?WV`@0FF)X3t{QG# zD%wl7CHvepj5m$rU3nLp@(1K|WB<_LAj(Qh@zh{|cW4mq4p+jKj2=%rydy(s z)q2sYABW{(P~Ly`_xAK+ps$bUm!o8^LA7K?Rkb9;ZO-+P<{OP@(+D{A3i5Bk!Y?{^ zK6{2b7l_60#E;du+Fqc@{fl$~tOW`Tw; literal 1150 zcmb7D%W7gl5beNxKvuJ!MHbo52h8ux!bl*#B0eIh5JAmO5rZ*7#O-L7MqLRa5>XHz zQ4&K8_*l5nsPRmn9wo3DBxv|2*Wqx8ZJeE*38T>{j*pLVbaaI4>uWHM%jFWZZToUB<$(5G`2BvQQYkz< zJiuf!sTeyx2juH?x&rM6gW)ga^LY#g1GHK#WHK3?o}NPb!(y?(Y&L^DyWK9xrM$hp zDa_|{%w{tMx7&?MrGmS=JKWyhs`$!TE{s7w^R8Aaj7B4c@pz2kaEQrdqFg;55AN^p zRg82x4ZU8k^sHl>|MK#JXf%q;%S*hzz9N^)DL*df=jW(atH@@vs*aPB6WDAvIGxT- z{>R5hJUu<3-|s72U0op_kK^X%2FYX+KA#VrP6uAESH-bft*WP5{q1&J+1YOm^rxQR zy)`x$rBEnS#e$j&(0piVlf4+AGA?8cdh^L@9&%WYxiKW zSYWwag757;?d$a#@{V%`>i%@Q-AxYW(Q>hdZD{q-pZUq>y)2bVC>D#K2K~un{W8Wc f?s=a+3;L7C|7YbCV)aA*S$iKqsy6bGUFv=V+U%?a diff --git a/resources/icons/connections.ico b/resources/icons/connections.ico new file mode 100644 index 0000000000000000000000000000000000000000..effb20c982f6c95a93a7a32e20bac1c026a291bf GIT binary patch literal 1150 zcmbVLO-~b16n!NA09LL|+$iw_LZZQ#km?U`p>F&G5OyZe$^s~`(}in|E`)}tIPFYl zh7Lu64Z#>dT zU|KT+7qOD;l-d?8-Iq79@VHk|`%RI1`CxTuf9MUQzq!dlgcDtY4mi;>SQzWY-gn^Z zw*v)pa@0b-<}~FP@G4Szodg%*j>e-vVptseM2C9aQ(inRKNu l*?5}?hT7$h+NIBJ%2BKBx#s;jU`)K>Z*VYcahOhy-d`6swTA!z literal 0 HcmV?d00001 diff --git a/resources/images.qrc b/resources/images.qrc index e90d5849..676853bb 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -34,6 +34,8 @@ icons/sort_number.ico icons/tall_grass.ico icons/viewsprites.ico + icons/application_form_edit.ico + icons/connections.ico icons/ui/dark_checkbox_checked_disabled.png icons/ui/dark_checkbox_checked_disabled@2x.png icons/ui/dark_checkbox_checked.png diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3904ca0c..06387fa1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -205,8 +205,11 @@ void MainWindow::initCustomUI() { ui->mainTabBar->addTab("Map"); ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/map.ico"))); ui->mainTabBar->addTab("Events"); + ui->mainTabBar->setTabIcon(1, QIcon(QStringLiteral(":/icons/viewsprites.ico"))); ui->mainTabBar->addTab("Header"); + ui->mainTabBar->setTabIcon(2, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))); ui->mainTabBar->addTab("Connections"); + ui->mainTabBar->setTabIcon(3, QIcon(QStringLiteral(":/icons/connections.ico"))); ui->mainTabBar->addTab("Wild Pokemon"); ui->mainTabBar->setTabIcon(4, QIcon(QStringLiteral(":/icons/tall_grass.ico"))); } @@ -340,6 +343,8 @@ void MainWindow::initMapSortOrder() { // QMenu *mapSortOrderMenu = new QMenu(this); // QActionGroup *mapSortOrderActionGroup = new QActionGroup(ui->toolButton_MapSortOrder); + // porymapConfig.setMapSortOrder(mapSortOrder); + // mapSortOrderMenu->addAction(ui->actionSort_by_Group); // mapSortOrderMenu->addAction(ui->actionSort_by_Area); // mapSortOrderMenu->addAction(ui->actionSort_by_Layout); @@ -410,14 +415,40 @@ void MainWindow::on_lineEdit_filterBox_textChanged(const QString &text) { this->applyMapListFilter(text); } +void MainWindow::on_lineEdit_filterBox_Areas_textChanged(const QString &text) { + this->applyMapListFilter(text); +} + +void MainWindow::on_lineEdit_filterBox_Layouts_textChanged(const QString &text) { + this->applyMapListFilter(text); +} + void MainWindow::applyMapListFilter(QString filterText) { - /// !TODO - groupListProxyModel->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); - if (filterText.isEmpty()) { - ui->mapList->collapseAll(); - } else { - ui->mapList->expandToDepth(0); + FilterChildrenProxyModel *proxy; + QTreeView *list; + switch (this->mapSortOrder) { + case MapSortOrder::SortByGroup: + proxy = this->groupListProxyModel; + list = this->ui->mapList; + break; + case MapSortOrder::SortByArea: + proxy = this->areaListProxyModel; + list = this->ui->areaList; + break; + case MapSortOrder::SortByLayout: + proxy = this->layoutListProxyModel; + list = this->ui->layoutList; + break; } + + proxy->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); + if (filterText.isEmpty()) { + list->collapseAll(); + } else { + list->expandToDepth(0); + } + + /// !TODO // ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), true); // ui->mapList->scrollTo(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), QAbstractItemView::PositionAtCenter); } @@ -432,6 +463,9 @@ void MainWindow::loadUserSettings() { ui->checkBox_ToggleBorder->setChecked(porymapConfig.getShowBorder()); ui->checkBox_ToggleGrid->setChecked(porymapConfig.getShowGrid()); mapSortOrder = porymapConfig.getMapSortOrder(); + this->ui->mapListContainer->blockSignals(true); + this->ui->mapListContainer->setCurrentIndex(static_cast(this->mapSortOrder)); + this->ui->mapListContainer->blockSignals(false); ui->horizontalSlider_CollisionTransparency->blockSignals(true); this->editor->collisionOpacity = static_cast(porymapConfig.getCollisionOpacity()) / 100; ui->horizontalSlider_CollisionTransparency->setValue(porymapConfig.getCollisionOpacity()); @@ -643,7 +677,7 @@ void MainWindow::unsetMap() { this->ui->comboBox_LayoutSelector->setEnabled(false); } -bool MainWindow::setMap(QString map_name, bool scrollTreeView) { +bool MainWindow::setMap(QString map_name, bool scroll) { // if map name is empty, clear & disable map ui if (map_name.isEmpty()) { unsetMap(); @@ -670,12 +704,8 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { refreshMapScene(); displayMapProperties(); - if (scrollTreeView) { - // Make sure we clear the filter first so we actually have a scroll target - /// !TODO: make this onto a function that scrolls the current view taking a map name or layout name - groupListProxyModel->setFilterRegularExpression(QString()); - ui->mapList->setCurrentIndex(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name))); - ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); + if (scroll) { + scrollTreeView(map_name); } showWindowTitle(); @@ -1042,13 +1072,7 @@ bool MainWindow::loadProjectCombos() { return true; } -/// !TODO bool MainWindow::populateMapList() { - // bool success = editor->project->readMapGroups(); - // if (success) { - // sortMapList(); - // } - // return success; bool success = editor->project->readMapGroups(); this->mapGroupModel = new MapGroupModel(editor->project); @@ -1056,21 +1080,43 @@ bool MainWindow::populateMapList() { groupListProxyModel->setSourceModel(this->mapGroupModel); ui->mapList->setModel(groupListProxyModel); + this->mapAreaModel = new MapAreaModel(editor->project); + this->areaListProxyModel = new FilterChildrenProxyModel(); + areaListProxyModel->setSourceModel(this->mapAreaModel); + ui->areaList->setModel(areaListProxyModel); + this->layoutTreeModel = new LayoutTreeModel(editor->project); this->layoutListProxyModel = new FilterChildrenProxyModel(); this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); - //connect(this->ui->layoutList, &QTreeView::doubleClicked, this, &MainWindow::on_layoutList_activated); - + /// !TODO // ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); // ui->mapList->setDragEnabled(true); // ui->mapList->setAcceptDrops(true); // ui->mapList->setDropIndicatorShown(true); return success; +} - //MapGroupModel +void MainWindow::scrollTreeView(QString itemName) { + switch (ui->mapListContainer->currentIndex()) { + case MapListTab::Groups: + groupListProxyModel->setFilterRegularExpression(QString()); + ui->mapList->setCurrentIndex(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(itemName))); + ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); + break; + case MapListTab::Areas: + areaListProxyModel->setFilterRegularExpression(QString()); + ui->areaList->setCurrentIndex(areaListProxyModel->mapFromSource(mapAreaModel->indexOfMap(itemName))); + ui->areaList->scrollTo(ui->areaList->currentIndex(), QAbstractItemView::PositionAtCenter); + break; + case MapListTab::Layouts: + layoutListProxyModel->setFilterRegularExpression(QString()); + ui->layoutList->setCurrentIndex(layoutListProxyModel->mapFromSource(layoutTreeModel->indexOfLayout(itemName))); + ui->layoutList->scrollTo(ui->layoutList->currentIndex(), QAbstractItemView::PositionAtCenter); + break; + } } void MainWindow::sortMapList() { @@ -1181,19 +1227,7 @@ void MainWindow::sortMapList() { // updateMapList(); } -/// !TODO -QStandardItem* MainWindow::createMapItem(QString mapName, int groupNum, int inGroupNum) { - // QStandardItem *map = new QStandardItem; - // map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName); - // map->setIcon(*mapIcon); - // map->setEditable(false); - // map->setData(mapName, Qt::UserRole); - // map->setData("map_name", MapListUserRoles::TypeRole); - // return map; -} - -void MainWindow::onOpenMapListContextMenu(const QPoint &point) -{ +void MainWindow::onOpenMapListContextMenu(const QPoint &point) { /// !TODO // QModelIndex index = mapListProxyModel->mapToSource(ui->mapList->indexAt(point)); // if (!index.isValid()) { @@ -1462,14 +1496,19 @@ void MainWindow::on_mapListContainer_currentChanged(int index) { // switch (index) { case MapListTab::Groups: + this->mapSortOrder = MapSortOrder::SortByGroup; + if (this->editor && this->editor->map) scrollTreeView(this->editor->map->name); break; case MapListTab::Areas: + this->mapSortOrder = MapSortOrder::SortByArea; + if (this->editor && this->editor->map) scrollTreeView(this->editor->map->name); break; case MapListTab::Layouts: - //setMap(nullptr); - //setLayout(nullptr); + this->mapSortOrder = MapSortOrder::SortByLayout; + if (this->editor && this->editor->layout) scrollTreeView(this->editor->layout->id); break; } + porymapConfig.setMapSortOrder(this->mapSortOrder); } /// !TODO @@ -1489,7 +1528,7 @@ void MainWindow::on_mapList_activated(const QModelIndex &index) { } void MainWindow::on_areaList_activated(const QModelIndex &index) { - // + on_mapList_activated(index); } void MainWindow::on_layoutList_activated(const QModelIndex &index) { @@ -1520,6 +1559,8 @@ void MainWindow::updateMapList() { if (this->editor->map) { mapGroupModel->setMap(this->editor->map->name); groupListProxyModel->layoutChanged(); + mapAreaModel->setMap(this->editor->map->name); + areaListProxyModel->layoutChanged(); } if (this->editor->layout) { diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index e243541e..b904522c 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -4,89 +4,10 @@ -/* - - // QIcon mapFolderIcon; - // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - - // QIcon folderIcon; - // folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - // //folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - - // ui->mapList->setUpdatesEnabled(false); - // mapListModel->clear(); - // mapGroupItemsList->clear(); - // QStandardItem *root = mapListModel->invisibleRootItem(); - - // switch (mapSortOrder) - // { - // case MapSortOrder::Group: - // for (int i = 0; i < project->groupNames.length(); i++) { - // QString group_name = project->groupNames.value(i); - // QStandardItem *group = new QStandardItem; - // group->setText(group_name); - // group->setIcon(mapFolderIcon); - // group->setEditable(false); - // group->setData(group_name, Qt::UserRole); - // group->setData("map_group", MapListUserRoles::TypeRole); - // group->setData(i, MapListUserRoles::GroupRole); - // root->appendRow(group); - // mapGroupItemsList->append(group); - // QStringList names = project->groupedMapNames.value(i); - // for (int j = 0; j < names.length(); j++) { - // QString map_name = names.value(j); - // QStandardItem *map = createMapItem(map_name, i, j); - // group->appendRow(map); - // mapListIndexes.insert(map_name, map->index()); - // } - // } - // break; - - // mapListModel = new QStandardItemModel; - // mapGroupItemsList = new QList; - // mapListProxyModel = new FilterChildrenProxyModel; - - // mapListProxyModel->setSourceModel(mapListModel); - // ui->mapList->setModel(mapListProxyModel); - - // createMapItem: - // QStandardItem *map = new QStandardItem; - // map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName); - // map->setIcon(*mapIcon); - // map->setEditable(false); - // map->setData(mapName, Qt::UserRole); - // map->setData("map_name", MapListUserRoles::TypeRole); - // return map; - - // scrolling: - if (scrollTreeView) { - // Make sure we clear the filter first so we actually have a scroll target - /// !TODO - // mapListProxyModel->setFilterRegularExpression(QString()); - // ui->mapList->setCurrentIndex(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name))); - // ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); - } - - // ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name)), true); - -*/ MapGroupModel::MapGroupModel(Project *project, QObject *parent) : QStandardItemModel(parent) { - // - this->project = project; this->root = this->invisibleRootItem(); - // mapIcon = new QIcon(QStringLiteral(":/icons/map.ico")); - // mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico")); - // mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico")); - - // mapFolderIcon = new QIcon(QStringLiteral(":/icons/folder_closed_map.ico")); - - //mapFolderIcon = new QIcon; - //mapFolderIcon->addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - //mapFolderIcon->addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - initialize(); } @@ -118,9 +39,6 @@ void MapGroupModel::initialize() { QString group_name = this->project->groupNames.value(i); QStandardItem *group = createGroupItem(group_name, i); root->appendRow(group); - QList groupItems; - QMap inGroupItems; - //mapGroupItemsList->append(group); QStringList names = this->project->groupedMapNames.value(i); for (int j = 0; j < names.length(); j++) { QString map_name = names.value(j); @@ -146,35 +64,6 @@ QModelIndex MapGroupModel::indexOfMap(QString mapName) { return QModelIndex(); } - // projectHasUnsavedChanges = false; - // QList list; - // list.append(QModelIndex()); - // while (list.length()) { - // QModelIndex parent = list.takeFirst(); - // for (int i = 0; i < model->rowCount(parent); i++) { - // QModelIndex index = model->index(i, 0, parent); - // if (model->hasChildren(index)) { - // list.append(index); - // } - // QVariant data = index.data(Qt::UserRole); - // if (!data.isNull()) { - // QString map_name = data.toString(); - // if (editor->project && editor->project->mapCache.contains(map_name)) { - // QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name)); - // map->setIcon(*mapIcon); - // if (editor->project->mapCache.value(map_name)->hasUnsavedChanges()) { - // map->setIcon(*mapEditedIcon); - // projectHasUnsavedChanges = true; - // } - // if (editor->map->name == map_name) { - // map->setIcon(*mapOpenedIcon); - // } - // } - // } - // } - // } - -#include QVariant MapGroupModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); @@ -213,10 +102,6 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { } return mapGrayIcon; } - - // check if map or group - // if map, check if edited or open - //return QIcon(":/icons/porymap-icon-2.ico"); } return QStandardItemModel::data(index, role); @@ -224,52 +109,124 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { +MapAreaModel::MapAreaModel(Project *project, QObject *parent) : QStandardItemModel(parent) { + this->project = project; + this->root = this->invisibleRootItem(); + + initialize(); +} + +QStandardItem *MapAreaModel::createAreaItem(QString mapsecName, int areaIndex) { + QStandardItem *area = new QStandardItem; + area->setText(mapsecName); + area->setEditable(false); + area->setData(mapsecName, Qt::UserRole); + area->setData("map_section", MapListRoles::TypeRole); + area->setData(areaIndex, MapListRoles::GroupRole); + // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->areaItems.insert(mapsecName, area); + return area; +} + +QStandardItem *MapAreaModel::createMapItem(QString mapName, int groupIndex, int mapIndex) { + QStandardItem *map = new QStandardItem; + map->setText(QString("[%1.%2] ").arg(groupIndex).arg(mapIndex, 2, 10, QLatin1Char('0')) + mapName); + map->setEditable(false); + map->setData(mapName, Qt::UserRole); + map->setData("map_name", MapListRoles::TypeRole); + // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->mapItems.insert(mapName, map); + return map; +} + +void MapAreaModel::initialize() { + for (int i = 0; i < this->project->mapSectionNameToValue.size(); i++) { + QString mapsecName = project->mapSectionValueToName.value(i); + QStandardItem *areaItem = createAreaItem(mapsecName, i); + this->root->appendRow(areaItem); + } + + for (int i = 0; i < this->project->groupNames.length(); i++) { + QStringList names = this->project->groupedMapNames.value(i); + for (int j = 0; j < names.length(); j++) { + QString mapName = names.value(j); + QStandardItem *map = createMapItem(mapName, i, j); + QString mapsecName = this->project->readMapLocation(mapName); + if (this->areaItems.contains(mapsecName)) { + this->areaItems[mapsecName]->appendRow(map); + } + } + } +} + +QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex MapAreaModel::indexOfMap(QString mapName) { + if (this->mapItems.contains(mapName)) { + return this->mapItems[mapName]->index(); + } + return QModelIndex(); +} + +QVariant MapAreaModel::data(const QModelIndex &index, int role) const { + int row = index.row(); + int col = index.column(); + + if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); + static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + static QIcon mapFolderIcon; + static QIcon folderIcon; + static bool loaded = false; + if (!loaded) { + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); + loaded = true; + } + + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListRoles::TypeRole).toString(); + + if (type == "map_section") { + if (item->hasChildren()) { + return mapFolderIcon; + } + return folderIcon; + } else if (type == "map_name") { + QString mapName = item->data(Qt::UserRole).toString(); + if (mapName == this->openMap) { + return mapOpenedIcon; + } + else if (this->project->mapCache.contains(mapName)) { + if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { + return mapEditedIcon; + } + else { + return mapIcon; + } + } + return mapGrayIcon; + } + } + + return QStandardItemModel::data(index, role); +} - - - - - - - - - // case MapSortOrder::Layout: - // { - // QMap layoutIndices; - // for (int i = 0; i < project->mapLayoutsTable.length(); i++) { - // QString layoutId = project->mapLayoutsTable.value(i); - // MapLayout *layout = project->mapLayouts.value(layoutId); - // QStandardItem *layoutItem = new QStandardItem; - // layoutItem->setText(layout->name); - // layoutItem->setIcon(folderIcon); - // layoutItem->setEditable(false); - // layoutItem->setData(layout->name, Qt::UserRole); - // layoutItem->setData("map_layout", MapListUserRoles::TypeRole); - // layoutItem->setData(layout->id, MapListUserRoles::TypeRole2); - // layoutItem->setData(i, MapListUserRoles::GroupRole); - // root->appendRow(layoutItem); - // mapGroupItemsList->append(layoutItem); - // layoutIndices[layoutId] = i; - // } - // for (int i = 0; i < project->groupNames.length(); i++) { - // QStringList names = project->groupedMapNames.value(i); - // for (int j = 0; j < names.length(); j++) { - // QString map_name = names.value(j); - // QStandardItem *map = createMapItem(map_name, i, j); - // QString layoutId = project->readMapLayoutId(map_name); - // QStandardItem *layoutItem = mapGroupItemsList->at(layoutIndices.value(layoutId)); - // layoutItem->setIcon(mapFolderIcon); - // layoutItem->appendRow(map); - // mapListIndexes.insert(map_name, map->index()); - // } - // } - // break; - // } LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : QStandardItemModel(parent) { - // - this->project = project; this->root = this->invisibleRootItem(); @@ -279,7 +236,6 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : QStandardI QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { QStandardItem *layout = new QStandardItem; layout->setText(this->project->layoutIdsToNames[layoutId]); - //layout->setText(layoutId); layout->setEditable(false); layout->setData(layoutId, Qt::UserRole); layout->setData("map_layout", MapListRoles::TypeRole); @@ -301,7 +257,6 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { void LayoutTreeModel::initialize() { for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { - // QString layoutId = project->mapLayoutsTable.value(i); QStandardItem *layoutItem = createLayoutItem(layoutId); this->root->appendRow(layoutItem); @@ -315,8 +270,6 @@ void LayoutTreeModel::initialize() { this->layoutItems[layoutId]->appendRow(map); } } - - // // project->readMapLayoutName } QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { @@ -364,12 +317,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { } return QVariant(); - - // check if map or group - // if map, check if edited or open - //return QIcon(":/icons/porymap-icon-2.ico"); } return QStandardItemModel::data(index, role); } - From f7f06dab290ca08b38143f6d4a837d8c066e0fe1 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 7 Feb 2023 13:04:52 -0500 Subject: [PATCH 011/364] fix change dimensions for layouts --- include/core/maplayout.h | 4 ++- include/mainwindow.h | 2 ++ resources/icons/minimap.ico | Bin 0 -> 1150 bytes resources/images.qrc | 1 + src/editor.cpp | 2 ++ src/mainwindow.cpp | 47 ++++++++++++++++++++++++------------ src/project.cpp | 24 +++++++++++------- 7 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 resources/icons/minimap.ico diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 7d4eb500..eabe2c8e 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -21,6 +21,8 @@ public: static QString layoutConstantFromName(QString mapName); + bool loaded = false; + /// !TODO /* NEW */ QList maps; @@ -119,7 +121,7 @@ private: signals: void layoutChanged(Layout *layout); - void modified(); + //void modified(); void layoutDimensionsChanged(const QSize &size); void needsRedrawing(); }; diff --git a/include/mainwindow.h b/include/mainwindow.h index 7bc47ff2..52773f5e 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -182,6 +182,7 @@ private slots: void onLoadMapRequested(QString, QString); void onMapChanged(Map *map); void onMapNeedsRedrawing(); + void onLayoutNeedsRedrawing(); void onTilesetsSaved(QString, QString); void onWildMonDataChanged(); void openNewMapPopupWindow(); @@ -362,6 +363,7 @@ private: bool setMap(QString, bool scroll = false); void unsetMap(); void redrawMapScene(); + void redrawLayoutScene(); void refreshMapScene(); bool loadDataStructures(); bool loadProjectCombos(); diff --git a/resources/icons/minimap.ico b/resources/icons/minimap.ico new file mode 100644 index 0000000000000000000000000000000000000000..b9315712a636badcda8c8912a4fcc55123109276 GIT binary patch literal 1150 zcmbu9Ee^s!5QT??15}}r903W3AZQ$dTCfNVf*?5q1Vt(kwe=uzBv>?dnYZ+jrEGsF zZC-cscD|XmlUAgmXAp>9+cIv7v_zz%QcIneuZ#B>_WDPasVU1o?l)6M?3u@2E|H1j z-Iw5lDdWTa%s|LJj0Uz2;xI~T@u&eF_f)0-_B^-!lhxGVp0P5n!h=B#a$Mn6;gM6M zviDcSX8Zf*L=U-)-Ie_@BhHjPIV1RB=;Ix*9{KKa_>qJCaI$&lF}A+W9yQ^)e7Lw8 ruQcDzF|&^vicons/sort_map.ico icons/sort_number.ico icons/tall_grass.ico + icons/minimap.ico icons/viewsprites.ico icons/application_form_edit.ico icons/connections.ico diff --git a/src/editor.cpp b/src/editor.cpp index 18915bdc..8703aa24 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1151,6 +1151,8 @@ bool Editor::setLayout(QString layoutId) { return false; } + // !TODO: editGroup addStack + map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 06387fa1..b182782f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -203,7 +203,7 @@ void MainWindow::initCustomUI() { // Set up the tab bar while (ui->mainTabBar->count()) ui->mainTabBar->removeTab(0); ui->mainTabBar->addTab("Map"); - ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/map.ico"))); + ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/minimap.ico"))); ui->mainTabBar->addTab("Events"); ui->mainTabBar->setTabIcon(1, QIcon(QStringLiteral(":/icons/viewsprites.ico"))); ui->mainTabBar->addTab("Header"); @@ -714,6 +714,9 @@ bool MainWindow::setMap(QString map_name, bool scroll) { connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); + connect(editor->layout, &Layout::layoutChanged, [this]() { onMapChanged(nullptr); }); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing); + setRecentMap(map_name); updateMapList(); @@ -747,8 +750,9 @@ bool MainWindow::setLayout(QString layoutId) { updateMapList(); - // connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged); - // connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); + // !TODO: make sure these connections are not duplicated / cleared later + connect(editor->layout, &Layout::layoutChanged, [this]() { onMapChanged(nullptr); }); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing); // connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); // displayMapProperties @@ -765,16 +769,21 @@ bool MainWindow::setLayout(QString layoutId) { return true; } -void MainWindow::redrawMapScene() -{ +void MainWindow::redrawMapScene() { if (!editor->displayMap()) return; this->refreshMapScene(); } -void MainWindow::refreshMapScene() -{ +void MainWindow::redrawLayoutScene() { + if (!editor->displayLayout()) + return; + + this->refreshMapScene(); +} + +void MainWindow::refreshMapScene() { on_mainTabBar_tabBarClicked(ui->mainTabBar->currentIndex()); ui->graphicsView_Map->setScene(editor->scene); @@ -2519,6 +2528,11 @@ void MainWindow::onMapNeedsRedrawing() { redrawMapScene(); } +void MainWindow::onLayoutNeedsRedrawing() { + qDebug() << "MainWindow::onLayoutNeedsRedrawing"; + redrawLayoutScene(); +} + void MainWindow::onMapCacheCleared() { editor->map = nullptr; } @@ -2710,8 +2724,9 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & } } -void MainWindow::on_pushButton_ChangeDimensions_clicked() -{ +void MainWindow::on_pushButton_ChangeDimensions_clicked() { + if (!editor || !editor->layout) return; + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowTitle("Change Map Dimensions"); dialog.setWindowModality(Qt::NonModal); @@ -2730,10 +2745,10 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() heightSpinBox->setMaximum(editor->project->getMaxMapHeight()); bwidthSpinBox->setMaximum(MAX_BORDER_WIDTH); bheightSpinBox->setMaximum(MAX_BORDER_HEIGHT); - widthSpinBox->setValue(editor->map->getWidth()); - heightSpinBox->setValue(editor->map->getHeight()); - bwidthSpinBox->setValue(editor->map->getBorderWidth()); - bheightSpinBox->setValue(editor->map->getBorderHeight()); + widthSpinBox->setValue(editor->layout->getWidth()); + heightSpinBox->setValue(editor->layout->getHeight()); + bwidthSpinBox->setValue(editor->layout->getBorderWidth()); + bheightSpinBox->setValue(editor->layout->getBorderHeight()); if (projectConfig.getUseCustomBorderSize()) { form.addRow(new QLabel("Map Width"), widthSpinBox); form.addRow(new QLabel("Map Height"), heightSpinBox); @@ -2761,8 +2776,8 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() dialog.accept(); } else { 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") + "The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" + "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") .arg(maxMetatiles) .arg(widthSpinBox->value()) .arg(heightSpinBox->value()) @@ -2786,7 +2801,7 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() if (oldMapDimensions != newMapDimensions || oldBorderDimensions != newBorderDimensions) { layout->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); layout->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); - editor->map->editHistory.push(new ResizeMap(layout, + editor->layout->editHistory.push(new ResizeMap(layout, oldMapDimensions, newMapDimensions, oldMetatiles, layout->blockdata, oldBorderDimensions, newBorderDimensions, diff --git a/src/project.cpp b/src/project.cpp index c5cbe2e5..c203944c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -380,18 +380,24 @@ QString Project::readMapLocation(QString map_name) { } bool Project::loadLayout(MapLayout *layout) { - // Force these to run even if one fails - bool loadedTilesets = loadLayoutTilesets(layout); - bool loadedBlockdata = loadBlockdata(layout); - bool loadedBorder = loadLayoutBorder(layout); + // !TODO: make sure this doesn't break anything, maybe do something better. new layouts work too? + if (!layout->loaded) { + // Force these to run even if one fails + bool loadedTilesets = loadLayoutTilesets(layout); + bool loadedBlockdata = loadBlockdata(layout); + bool loadedBorder = loadLayoutBorder(layout); - return loadedTilesets - && loadedBlockdata - && loadedBorder; + if (loadedTilesets && loadedBlockdata && loadedBorder) { + layout->loaded = true; + return true; + } else { + return false; + } + } + return true; } Layout *Project::loadLayout(QString layoutId) { - // if (mapLayouts.contains(layoutId)) { Layout *layout = mapLayouts[layoutId]; if (loadLayout(layout)) { @@ -415,7 +421,7 @@ bool Project::loadMapLayout(Map* map) { return false; } - if (map->hasUnsavedChanges()) { + if (map->hasUnsavedChanges() /* || map->layout->hasUnsavedChanges() */) { return true; } else { return loadLayout(map->layout); From 72eb8f873f0cce7af72b74a8fc6ddb436f313d99 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 8 Feb 2023 09:31:39 -0500 Subject: [PATCH 012/364] create dynamic map tab icon --- include/core/maplayout.h | 8 +-- include/editor.h | 5 +- include/mainwindow.h | 1 - include/project.h | 4 +- resources/icons/minimap.ico | Bin 1150 -> 1406 bytes src/core/maplayout.cpp | 49 ++++++++++++++ src/core/mapparser.cpp | 4 +- src/mainwindow.cpp | 125 ++++-------------------------------- src/project.cpp | 33 ++++++---- src/ui/newmappopup.cpp | 12 ++-- 10 files changed, 99 insertions(+), 142 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index eabe2c8e..1e809c13 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -19,14 +19,12 @@ class Layout : public QObject { public: Layout() {} + void copyAttributesFrom(Layout *other); + static QString layoutConstantFromName(QString mapName); bool loaded = false; - /// !TODO - /* NEW */ - QList maps; - QString id; QString name; @@ -126,6 +124,4 @@ signals: void needsRedrawing(); }; -using MapLayout = Layout; - #endif // MAPLAYOUT_H diff --git a/include/editor.h b/include/editor.h index 29709df8..d6d973b5 100644 --- a/include/editor.h +++ b/include/editor.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "mapconnection.h" #include "metatileselector.h" @@ -45,8 +46,8 @@ public: QObject *parent = nullptr; Project *project = nullptr; - Map *map = nullptr; - Layout *layout = nullptr; /* NEW */ + QPointer map = nullptr; // !TODO: since removed onMapCacheCleared, make sure this works as intended + QPointer layout = nullptr; /* NEW */ QUndoGroup editGroup; // Manages the undo history for each map diff --git a/include/mainwindow.h b/include/mainwindow.h index 52773f5e..522bf0f2 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -187,7 +187,6 @@ private slots: void onWildMonDataChanged(); void openNewMapPopupWindow(); void onNewMapCreated(); - void onMapCacheCleared(); void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); diff --git a/include/project.h b/include/project.h index ddffe49b..9a58905f 100644 --- a/include/project.h +++ b/include/project.h @@ -58,7 +58,7 @@ public: QString layoutsLabel; QMap layoutIdsToNames; QMap mapLayouts; - QMap mapLayoutsMaster; +// QMap mapLayoutsMaster; QMap mapSecToMapHoverName; QMap mapSectionNameToValue; QMap mapSectionValueToName; @@ -95,6 +95,7 @@ public: void clearMapCache(); void clearTilesetCache(); + void clearLayoutsTable(); struct DataQualifiers { @@ -265,7 +266,6 @@ private: signals: void reloadProject(); void uncheckMonitorFilesAction(); - void mapCacheCleared(); void disableWildEncountersUI(); }; diff --git a/resources/icons/minimap.ico b/resources/icons/minimap.ico index b9315712a636badcda8c8912a4fcc55123109276..548c7c8fc0710ca5559d887600d8e82f4d7030a8 100644 GIT binary patch literal 1406 zcmeH@*;i9n6vjW=YOy1o>|h7&Xa}{mGp$pt8Z|8fmU+;COa>$&KtQGp1TbWPFx(I_ zk%Zg?LQLRZZf`;vB9KTCmsssvAKE{phramKmp*r&_1oXs!#Zo94J471k^j|$Q=?Q zcUTB{s~Pfk3(is5NT+fjSByev$%R~HN18Pc(vf`BS@O}OH6hEH2Z2L`GmaAEJByGv zR)E~GLX_IeATc*0-&Kghu?rBpvrysAf!JCLu_qhq{$Wr?3I*e(NFT36k+%d_tnJ7c zuR;f1fMU8FBHtxs(A8+A$=m)iH2Fjr942~r!EK4%ysOoEeT-1FW$|xE5Q$Ej1u;Lk`b! zBkU{9@UF-Sq;FA6v>7yEGNy(*;=mlKx#P`P4$Z+6Z-H}F0dKqw?s+RraSaycDU980 z107d^PAJeDVc{q0ODJJpQ-GUykXkn;ZYtr6U&G|89`2Y8fi(@G3sY;|2omNO-I)5d z8{YLEqFT6@y`cCF1lM&4Z;=?mG|}mGJ^AM>B5^B1w@Ch$9*G+?LK_BzH~KL{U~dm# zbv1xQd=i29Eaul;;OFzk9_w57{|L#DP@V`CSK+5BflwKW}Wq%%2G+lo5iKq9!^>K+*&odE=Omd~*%e^}u z+a~HL91rmR*76@R-cH_gtV`OQdz|ALL*`fSy|F7btzLTa>u)BajPc#)p8W7&(x)LQpp&ZzeZmD(DtMQZ@$0pi=A3#QvAZ_`#yT-gXdp;`L&l` S$#S`W+S1+6KG?*6yZsI7c)9@q literal 1150 zcmbu9Ee^s!5QT??15}}r903W3AZQ$dTCfNVf*?5q1Vt(kwe=uzBv>?dnYZ+jrEGsF zZC-cscD|XmlUAgmXAp>9+cIv7v_zz%QcIneuZ#B>_WDPasVU1o?l)6M?3u@2E|H1j z-Iw5lDdWTa%s|LJj0Uz2;xI~T@u&eF_f)0-_B^-!lhxGVp0P5n!h=B#a$Mn6;gM6M zviDcSX8Zf*L=U-)-Ie_@BhHjPIV1RB=;Ix*9{KKa_>qJCaI$&lF}A+W9yQ^)e7Lw8 ruQcDzF|&^v metatileLayerOrder; + // QList metatileLayerOpacity; + + // LayoutPixmapItem *layoutItem = nullptr; + // CollisionPixmapItem *collisionItem = nullptr; + // BorderMetatilesPixmapItem *borderItem = nullptr; + + // QUndoStack editHistory; +void Layout::copyAttributesFrom(Layout *other) { + // +} + 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])"); diff --git a/src/core/mapparser.cpp b/src/core/mapparser.cpp index 986c2c24..3d4258bd 100644 --- a/src/core/mapparser.cpp +++ b/src/core/mapparser.cpp @@ -7,7 +7,7 @@ MapParser::MapParser() { } -MapLayout *MapParser::parse(QString filepath, bool *error, Project *project) +Layout *MapParser::parse(QString filepath, bool *error, Project *project) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { @@ -69,7 +69,7 @@ MapLayout *MapParser::parse(QString filepath, bool *error, Project *project) } } - MapLayout *mapLayout = new MapLayout(); + Layout *mapLayout = new Layout(); mapLayout->width = mapWidth; mapLayout->height = mapHeight; mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b182782f..25e73e73 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -377,6 +377,15 @@ void MainWindow::showWindowTitle() { .arg(editor->project->getProjectTitle()) ); } + if (editor && editor->layout) { + // // QPixmap pixmap = editor->layout ? editor->layout->render(true) : QPixmap(); + QPixmap pixmap = editor->layout ? editor->layout->render(false) : QPixmap(); + if (!pixmap.isNull()) { + ui->mainTabBar->setTabIcon(0, QIcon(pixmap.scaled(16, 16))); + } else { + ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/map.ico"))); + } + } } void MainWindow::markMapEdited() { @@ -449,6 +458,7 @@ void MainWindow::applyMapListFilter(QString filterText) { } /// !TODO + // ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); // ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), true); // ui->mapList->scrollTo(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), QAbstractItemView::PositionAtCenter); } @@ -541,7 +551,6 @@ bool MainWindow::openProject(QString dir) { editor->closeProject(); editor->project = new Project(this); QObject::connect(editor->project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered); - QObject::connect(editor->project, &Project::mapCacheCleared, this, &MainWindow::onMapCacheCleared); QObject::connect(editor->project, &Project::disableWildEncountersUI, [this]() { this->setWildEncountersUIEnabled(false); }); QObject::connect(editor->project, &Project::uncheckMonitorFilesAction, [this]() { porymapConfig.setMonitorFiles(false); @@ -555,6 +564,7 @@ bool MainWindow::openProject(QString dir) { } else { QString open_map = editor->map->name; editor->project->fileWatcher.removePaths(editor->project->fileWatcher.files()); + editor->project->clearLayoutsTable(); editor->project->clearMapCache(); editor->project->clearTilesetCache(); success = loadDataStructures() && populateMapList() && setMap(open_map, true); @@ -701,6 +711,8 @@ bool MainWindow::setMap(QString map_name, bool scroll) { this->ui->mainTabBar->setTabEnabled(3, true); this->ui->mainTabBar->setTabEnabled(4, true); + this->ui->comboBox_LayoutSelector->setEnabled(true); + refreshMapScene(); displayMapProperties(); @@ -1129,111 +1141,6 @@ void MainWindow::scrollTreeView(QString itemName) { } void MainWindow::sortMapList() { - // Project *project = editor->project; - - // QIcon mapFolderIcon; - // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - // mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - - // QIcon folderIcon; - // folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - // //folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - - // ui->mapList->setUpdatesEnabled(false); - // mapListModel->clear(); - // mapGroupItemsList->clear(); - // QStandardItem *root = mapListModel->invisibleRootItem(); - - // switch (mapSortOrder) - // { - // case MapSortOrder::SortByGroup: - // for (int i = 0; i < project->groupNames.length(); i++) { - // QString group_name = project->groupNames.value(i); - // QStandardItem *group = new QStandardItem; - // group->setText(group_name); - // group->setIcon(mapFolderIcon); - // group->setEditable(false); - // group->setData(group_name, Qt::UserRole); - // group->setData("map_group", MapListUserRoles::TypeRole); - // group->setData(i, MapListUserRoles::GroupRole); - // root->appendRow(group); - // mapGroupItemsList->append(group); - // QStringList names = project->groupedMapNames.value(i); - // for (int j = 0; j < names.length(); j++) { - // QString map_name = names.value(j); - // QStandardItem *map = createMapItem(map_name, i, j); - // group->appendRow(map); - // mapListIndexes.insert(map_name, map->index()); - // } - // } - // break; - // case MapSortOrder::SortByArea: - // { - // QMap mapsecToGroupNum; - // for (int i = 0; i < project->mapSectionNameToValue.size(); i++) { - // QString mapsec_name = project->mapSectionValueToName.value(i); - // QStandardItem *mapsec = new QStandardItem; - // mapsec->setText(mapsec_name); - // mapsec->setIcon(folderIcon); - // mapsec->setEditable(false); - // mapsec->setData(mapsec_name, Qt::UserRole); - // mapsec->setData("map_sec", MapListUserRoles::TypeRole); - // mapsec->setData(i, MapListUserRoles::GroupRole); - // root->appendRow(mapsec); - // mapGroupItemsList->append(mapsec); - // mapsecToGroupNum.insert(mapsec_name, i); - // } - // for (int i = 0; i < project->groupNames.length(); i++) { - // QStringList names = project->groupedMapNames.value(i); - // for (int j = 0; j < names.length(); j++) { - // QString map_name = names.value(j); - // QStandardItem *map = createMapItem(map_name, i, j); - // QString location = project->readMapLocation(map_name); - // QStandardItem *mapsecItem = mapGroupItemsList->at(mapsecToGroupNum[location]); - // mapsecItem->setIcon(mapFolderIcon); - // mapsecItem->appendRow(map); - // mapListIndexes.insert(map_name, map->index()); - // } - // } - // break; - // } - // case MapSortOrder::SortByLayout: - // { - // QMap layoutIndices; - // for (int i = 0; i < project->mapLayoutsTable.length(); i++) { - // QString layoutId = project->mapLayoutsTable.value(i); - // MapLayout *layout = project->mapLayouts.value(layoutId); - // QStandardItem *layoutItem = new QStandardItem; - // layoutItem->setText(layout->name); - // layoutItem->setIcon(folderIcon); - // layoutItem->setEditable(false); - // layoutItem->setData(layout->name, Qt::UserRole); - // layoutItem->setData("map_layout", MapListUserRoles::TypeRole); - // layoutItem->setData(layout->id, MapListUserRoles::TypeRole2); - // layoutItem->setData(i, MapListUserRoles::GroupRole); - // root->appendRow(layoutItem); - // mapGroupItemsList->append(layoutItem); - // layoutIndices[layoutId] = i; - // } - // for (int i = 0; i < project->groupNames.length(); i++) { - // QStringList names = project->groupedMapNames.value(i); - // for (int j = 0; j < names.length(); j++) { - // QString map_name = names.value(j); - // QStandardItem *map = createMapItem(map_name, i, j); - // QString layoutId = project->readMapLayoutId(map_name); - // QStandardItem *layoutItem = mapGroupItemsList->at(layoutIndices.value(layoutId)); - // layoutItem->setIcon(mapFolderIcon); - // layoutItem->appendRow(map); - // mapListIndexes.insert(map_name, map->index()); - // } - // } - // break; - // } - // } - - // ui->mapList->setUpdatesEnabled(true); - // ui->mapList->repaint(); - // updateMapList(); } void MainWindow::onOpenMapListContextMenu(const QPoint &point) { @@ -2533,10 +2440,6 @@ void MainWindow::onLayoutNeedsRedrawing() { redrawLayoutScene(); } -void MainWindow::onMapCacheCleared() { - editor->map = nullptr; -} - void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { // If saved tilesets are currently in-use, update them and redraw // Otherwise overwrite the cache for the saved tileset @@ -2612,7 +2515,7 @@ void MainWindow::importMapFromAdvanceMap1_92() this->editor->project->setImportExportPath(filepath); MapParser parser; bool error = false; - MapLayout *mapLayout = parser.parse(filepath, &error, editor->project); + Layout *mapLayout = parser.parse(filepath, &error, editor->project); if (error) { QMessageBox msgBox(this); msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); diff --git a/src/project.cpp b/src/project.cpp index c203944c..f4672fab 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -45,6 +45,7 @@ Project::Project(QWidget *parent) : Project::~Project() { + clearLayoutsTable(); clearMapCache(); clearTilesetCache(); } @@ -108,7 +109,6 @@ void Project::clearMapCache() { delete map; } mapCache.clear(); - emit mapCacheCleared(); } void Project::clearTilesetCache() { @@ -119,6 +119,17 @@ void Project::clearTilesetCache() { tilesetCache.clear(); } +void Project::clearLayoutsTable() { + // clearMapLayouts + // QMap mapLayouts; + // QMap mapLayoutsMaster; + for (Layout *layout : mapLayouts.values()) { + if (layout) + delete layout; + } + mapLayouts.clear(); +} + Map* Project::loadMap(QString map_name) { Map *map; if (mapCache.contains(map_name)) { @@ -379,7 +390,7 @@ QString Project::readMapLocation(QString map_name) { return ParseUtil::jsonToQString(mapObj["region_map_section"]); } -bool Project::loadLayout(MapLayout *layout) { +bool Project::loadLayout(Layout *layout) { // !TODO: make sure this doesn't break anything, maybe do something better. new layouts work too? if (!layout->loaded) { // Force these to run even if one fails @@ -476,7 +487,7 @@ bool Project::readMapLayouts() { logError(QString("Layout %1 is missing field(s) in %2.").arg(i).arg(layoutsFilepath)); return false; } - MapLayout *layout = new MapLayout(); + Layout *layout = new Layout(); layout->id = ParseUtil::jsonToQString(layoutObj["id"]); if (layout->id.isEmpty()) { logError(QString("Missing 'id' value on layout %1 in %2").arg(i).arg(layoutsFilepath)); @@ -557,8 +568,6 @@ bool Project::readMapLayouts() { } // Deep copy - mapLayoutsMaster = mapLayouts; - mapLayoutsMaster.detach(); mapLayoutsTableMaster = mapLayoutsTable; mapLayoutsTableMaster.detach(); return true; @@ -578,7 +587,7 @@ void Project::saveMapLayouts() { bool useCustomBorderSize = projectConfig.getUseCustomBorderSize(); OrderedJson::array layoutsArr; for (QString layoutId : mapLayoutsTableMaster) { - MapLayout *layout = mapLayouts.value(layoutId); + Layout *layout = mapLayouts.value(layoutId); OrderedJson::object layoutObj; layoutObj["id"] = layout->id; layoutObj["name"] = layout->name; @@ -1046,7 +1055,7 @@ void Project::saveTilesetPalettes(Tileset *tileset) { } } -bool Project::loadLayoutTilesets(MapLayout *layout) { +bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); @@ -1114,7 +1123,7 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { return tileset; } -bool Project::loadBlockdata(MapLayout *layout) { +bool Project::loadBlockdata(Layout *layout) { QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); layout->blockdata = readBlockdata(path); layout->lastCommitBlocks.blocks = layout->blockdata; @@ -1143,7 +1152,7 @@ void Project::setNewMapBlockdata(Map *map) { map->layout->lastCommitBlocks.mapDimensions = QSize(width, height); } -bool Project::loadLayoutBorder(MapLayout *layout) { +bool Project::loadLayoutBorder(Layout *layout) { QString path = QString("%1/%2").arg(root).arg(layout->border_path); layout->border = readBlockdata(path); layout->lastCommitBlocks.border = layout->border; @@ -1361,10 +1370,10 @@ void Project::updateMapLayout(Map* map) { mapLayoutsTableMaster.append(map->layoutId); } - // !TODO + // !TODO: why is[was] this a deep copy?? // Deep copy - // MapLayout *layout = mapLayouts.value(map->layoutId); - // MapLayout *newLayout = new MapLayout(); + // Layout *layout = mapLayouts.value(map->layoutId); + // Layout *newLayout = new Layout(); // *newLayout = *layout; // mapLayoutsMaster.insert(map->layoutId, newLayout); } diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index eb3eae45..597d53d1 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -110,12 +110,12 @@ void NewMapPopup::init(MapSortOrder type, QVariant data) { } // Creating new map from AdvanceMap import -void NewMapPopup::init(MapLayout *mapLayout) { +void NewMapPopup::init(Layout *mapLayout) { this->importedMap = true; useLayoutSettings(mapLayout); this->map = new Map(); - this->map->layout = new MapLayout(); + this->map->layout = new Layout(); this->map->layout->blockdata = mapLayout->blockdata; if (!mapLayout->border.isEmpty()) { @@ -203,7 +203,7 @@ void NewMapPopup::saveSettings() { settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value(); } -void NewMapPopup::useLayoutSettings(MapLayout *layout) { +void NewMapPopup::useLayoutSettings(Layout *layout) { if (!layout) return; settings.width = layout->width; settings.height = layout->height; @@ -241,7 +241,7 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { return; } Map *newMap = new Map; - MapLayout *layout; + Layout *layout; // If map name is not unique, use default value. Also use only valid characters. // After stripping invalid characters, strip any leading digits. @@ -266,8 +266,8 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { layout = this->project->mapLayouts.value(this->layoutId); newMap->needsLayoutDir = false; } else { - layout = new MapLayout; - layout->id = MapLayout::layoutConstantFromName(newMapName); + layout = new Layout; + layout->id = Layout::layoutConstantFromName(newMapName); layout->name = QString("%1_Layout").arg(newMap->name); layout->width = this->ui->spinBox_NewMap_Width->value(); layout->height = this->ui->spinBox_NewMap_Height->value(); From f8c7ada585c2cbeea2850a0be637a624b41a73b3 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 8 Feb 2023 11:48:42 -0500 Subject: [PATCH 013/364] fix layout undo history --- include/core/maplayout.h | 2 ++ include/project.h | 2 +- src/core/maplayout.cpp | 4 ++++ src/editor.cpp | 18 ++++++++++++++++++ src/mainwindow.cpp | 27 +++++++++++++++++++++++---- src/project.cpp | 2 +- 6 files changed, 49 insertions(+), 6 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 1e809c13..968b8960 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -109,6 +109,8 @@ public: // QPixmap renderConnection(MapConnection, Layout *); QPixmap renderBorder(bool ignoreCache = false); + QPixmap getLayoutItemPixmap(); + void setLayoutItem(LayoutPixmapItem *item) { layoutItem = item; } void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } diff --git a/include/project.h b/include/project.h index 9a58905f..24f14611 100644 --- a/include/project.h +++ b/include/project.h @@ -34,7 +34,7 @@ class Project : public QObject { Q_OBJECT public: - Project(QWidget *parent = nullptr); + Project(QObject *parent = nullptr); ~Project(); Project(const Project &) = delete; diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 1923cf3d..4fd05b4f 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -438,6 +438,10 @@ QPixmap Layout::renderBorder(bool ignoreCache) { return this->border_pixmap; } +QPixmap Layout::getLayoutItemPixmap() { + return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); +} + bool Layout::hasUnsavedChanges() { return !this->editHistory.isClean(); } diff --git a/src/editor.cpp b/src/editor.cpp index 8703aa24..7d6d2780 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -100,6 +100,10 @@ void Editor::setEditingMap() { this->cursorMapTileRect->stopSingleTileMode(); this->cursorMapTileRect->setActive(true); + if (this->layout) { + this->editGroup.setActiveStack(&this->layout->editHistory); + } + setMapEditingButtonsEnabled(true); } @@ -128,6 +132,10 @@ void Editor::setEditingCollision() { this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(true); + if (this->layout) { + this->editGroup.setActiveStack(&this->layout->editHistory); + } + setMapEditingButtonsEnabled(true); } @@ -152,6 +160,10 @@ void Editor::setEditingObjects() { this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(false); + if (this->map) { + this->editGroup.setActiveStack(&this->map->editHistory); + } + setMapEditingButtonsEnabled(false); } @@ -184,6 +196,10 @@ void Editor::setEditingConnections() { setConnectionsEditable(true); this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(false); + + if (this->map) { + this->editGroup.setActiveStack(&this->map->editHistory); + } } void Editor::setEditingEncounters() { @@ -1153,6 +1169,8 @@ bool Editor::setLayout(QString layoutId) { // !TODO: editGroup addStack + editGroup.addStack(&layout->editHistory); + map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 25e73e73..b62aa339 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -221,6 +221,14 @@ void MainWindow::initExtraSignals() { connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + ui->areaList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->areaList, &QTreeView::customContextMenuRequested, + this, &MainWindow::onOpenMapListContextMenu); + + ui->layoutList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->layoutList, &QTreeView::customContextMenuRequested, + this, &MainWindow::onOpenMapListContextMenu); + // other signals connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this, &MainWindow::addNewEvent); connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged); @@ -296,7 +304,7 @@ void MainWindow::initEditor() { ui->menuEdit->addAction(showHistory); // Toggle an asterisk in the window title when the undo state is changed - connect(&editor->editGroup, &QUndoGroup::cleanChanged, this, &MainWindow::showWindowTitle); + connect(&editor->editGroup, &QUndoGroup::indexChanged, this, &MainWindow::showWindowTitle); // selecting objects from the spinners connect(this->ui->spinner_ObjectID, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -378,8 +386,8 @@ void MainWindow::showWindowTitle() { ); } if (editor && editor->layout) { - // // QPixmap pixmap = editor->layout ? editor->layout->render(true) : QPixmap(); - QPixmap pixmap = editor->layout ? editor->layout->render(false) : QPixmap(); + //QPixmap pixmap = editor->layout ? editor->layout->render(false) : QPixmap(); + QPixmap pixmap = editor->layout->pixmap;//getLayoutItemPixmap(); if (!pixmap.isNull()) { ui->mainTabBar->setTabIcon(0, QIcon(pixmap.scaled(16, 16))); } else { @@ -549,7 +557,7 @@ bool MainWindow::openProject(QString dir) { bool already_open = isProjectOpen() && (editor->project->root == dir); if (!already_open) { editor->closeProject(); - editor->project = new Project(this); + editor->project = new Project(editor); QObject::connect(editor->project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered); QObject::connect(editor->project, &Project::disableWildEncountersUI, [this]() { this->setWildEncountersUIEnabled(false); }); QObject::connect(editor->project, &Project::uncheckMonitorFilesAction, [this]() { @@ -931,6 +939,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te // !TODO: method to setMapLayout instead of having to do whole setMap thing, // also edit history and bug fixes setMap(editor->map->name); + markMapEdited(); } } } @@ -1150,6 +1159,16 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { // return; // } + switch (ui->mapListContainer->currentIndex()) { + // + case MapListTab::Groups: + break; + case MapListTab::Areas: + break; + case MapListTab::Layouts: + break; + } + // QStandardItem *selectedItem = mapListModel->itemFromIndex(index); // QVariant itemType = selectedItem->data(MapListUserRoles::TypeRole); // if (!itemType.isValid()) { diff --git a/src/project.cpp b/src/project.cpp index f4672fab..15122fac 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -35,7 +35,7 @@ int Project::max_map_data_size = 10240; // 0x2800 int Project::default_map_size = 20; int Project::max_object_events = 64; -Project::Project(QWidget *parent) : +Project::Project(QObject *parent) : QObject(parent), eventScriptLabelModel(this), eventScriptLabelCompleter(this) From a4fdb0de6410e3cff659bdfb1b8598be29a6013c Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 8 Feb 2023 17:08:42 -0500 Subject: [PATCH 014/364] fix new map popup window to allow layout selection --- forms/newmappopup.ui | 154 +++++++++++++++++++++++++++++++-------- include/ui/newmappopup.h | 3 + src/mainwindow.cpp | 101 +++++++++++++------------ src/ui/maplistmodels.cpp | 1 + src/ui/newmappopup.cpp | 61 +++++++++++++--- 5 files changed, 234 insertions(+), 86 deletions(-) diff --git a/forms/newmappopup.ui b/forms/newmappopup.ui index b102b1cc..3a83073f 100644 --- a/forms/newmappopup.ui +++ b/forms/newmappopup.ui @@ -7,7 +7,7 @@ 0 0 410 - 621 + 687 @@ -73,14 +73,14 @@ - + Map Width - + <html><head/><body><p>Width (in blocks) of the new map.</p></body></html> @@ -90,14 +90,14 @@ - + Map Height - + <html><head/><body><p>Height (in blocks) of the new map.</p></body></html> @@ -107,14 +107,14 @@ - + Border Width - + <html><head/><body><p>Width (in blocks) of the new map's border.</p></body></html> @@ -124,14 +124,14 @@ - + Border Height - + <html><head/><body><p>Height (in blocks) of the new map's border.</p></body></html> @@ -141,14 +141,14 @@ - + Primary Tileset - + <html><head/><body><p>The primary tileset for the new map.</p></body></html> @@ -158,14 +158,14 @@ - + Secondary Tileset - + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> @@ -175,14 +175,14 @@ - + 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> @@ -192,14 +192,14 @@ - + 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> @@ -209,14 +209,14 @@ - + Song - + <html><head/><body><p>The default background music for this map.</p></body></html> @@ -226,14 +226,14 @@ - + Can Fly To - + <html><head/><body><p>Whether to add a heal location to the new map.</p></body></html> @@ -243,14 +243,14 @@ - + Show Location Name - + <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> @@ -260,14 +260,14 @@ - + Allow Running - + <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> @@ -277,14 +277,14 @@ - + Allow Biking - + <html><head/><body><p>Allows the player to use a Bike</p></body></html> @@ -294,14 +294,14 @@ - + Allow Dig & Escape Rope - + <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> @@ -311,14 +311,14 @@ - + Floor Number - + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> @@ -328,6 +328,96 @@ + + + + false + + + + + + + Layout + + + + + + + + + Use Existing Layout + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -396,7 +486,7 @@ 0 0 410 - 21 + 22 diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h index e668e863..3d24715d 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmappopup.h @@ -23,6 +23,7 @@ public: bool importedMap; QString layoutId; void init(); + void initUi(); void init(MapSortOrder type, QVariant data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -60,6 +61,8 @@ private: 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 &); }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b62aa339..47ce878b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1153,68 +1153,79 @@ void MainWindow::sortMapList() { } void MainWindow::onOpenMapListContextMenu(const QPoint &point) { - /// !TODO - // QModelIndex index = mapListProxyModel->mapToSource(ui->mapList->indexAt(point)); - // if (!index.isValid()) { - // return; - // } + QStandardItemModel *model; + int dataRole; + FilterChildrenProxyModel *proxy; + QTreeView *list; + void (MainWindow::*addFunction)(QAction *); + QString actionText; - switch (ui->mapListContainer->currentIndex()) { - // - case MapListTab::Groups: + switch (this->mapSortOrder) { + case MapSortOrder::SortByGroup: + model = this->mapGroupModel; + dataRole = MapListRoles::GroupRole; + proxy = this->groupListProxyModel; + list = this->ui->mapList; + addFunction = &MainWindow::onAddNewMapToGroupClick; + actionText = "Add New Map to Group"; break; - case MapListTab::Areas: + case MapSortOrder::SortByArea: + model = this->mapAreaModel; + dataRole = Qt::UserRole; + proxy = this->areaListProxyModel; + list = this->ui->areaList; + addFunction = &MainWindow::onAddNewMapToAreaClick; + actionText = "Add New Map to Area"; break; - case MapListTab::Layouts: + case MapSortOrder::SortByLayout: + model = this->layoutTreeModel; + dataRole = Qt::UserRole; + proxy = this->layoutListProxyModel; + list = this->ui->layoutList; + addFunction = &MainWindow::onAddNewMapToLayoutClick; + actionText = "Add New Map with Layout"; break; } - // QStandardItem *selectedItem = mapListModel->itemFromIndex(index); - // QVariant itemType = selectedItem->data(MapListUserRoles::TypeRole); - // if (!itemType.isValid()) { - // return; - // } + QModelIndex index = proxy->mapToSource(list->indexAt(point)); + if (!index.isValid()) { + return; + } - // // Build custom context menu depending on which type of item was selected (map group, map name, etc.) - // if (itemType == "map_group") { - // QString groupName = selectedItem->data(Qt::UserRole).toString(); - // int groupNum = selectedItem->data(MapListUserRoles::GroupRole).toInt(); - // QMenu* menu = new QMenu(this); - // QActionGroup* actions = new QActionGroup(menu); - // actions->addAction(menu->addAction("Add New Map to Group"))->setData(groupNum); - // connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToGroupClick); - // menu->exec(QCursor::pos()); - // } else if (itemType == "map_sec") { - // QString secName = selectedItem->data(Qt::UserRole).toString(); - // QMenu* menu = new QMenu(this); - // QActionGroup* actions = new QActionGroup(menu); - // actions->addAction(menu->addAction("Add New Map to Area"))->setData(secName); - // connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToAreaClick); - // menu->exec(QCursor::pos()); - // } else if (itemType == "map_layout") { - // QString layoutId = selectedItem->data(MapListUserRoles::TypeRole2).toString(); - // QMenu* menu = new QMenu(this); - // QActionGroup* actions = new QActionGroup(menu); - // actions->addAction(menu->addAction("Add New Map with Layout"))->setData(layoutId); - // connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToLayoutClick); - // menu->exec(QCursor::pos()); - // } + QStandardItem *selectedItem = model->itemFromIndex(index); + + if (selectedItem->parent()) { + return; + } + + QVariant itemData = selectedItem->data(dataRole); + if (!itemData.isValid()) { + return; + } + + QMenu menu(this); + QActionGroup actions(&menu); + actions.addAction(menu.addAction(actionText))->setData(itemData); + (this->*addFunction)(menu.exec(QCursor::pos())); } -void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) -{ +void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) { + if (!triggeredAction) return; + openNewMapPopupWindow(); this->newMapPrompt->init(MapSortOrder::SortByGroup, triggeredAction->data()); } -void MainWindow::onAddNewMapToAreaClick(QAction* triggeredAction) -{ +void MainWindow::onAddNewMapToAreaClick(QAction* triggeredAction) { + if (!triggeredAction) return; + openNewMapPopupWindow(); this->newMapPrompt->init(MapSortOrder::SortByArea, triggeredAction->data()); } -void MainWindow::onAddNewMapToLayoutClick(QAction* triggeredAction) -{ +void MainWindow::onAddNewMapToLayoutClick(QAction* triggeredAction) { + if (!triggeredAction) return; + openNewMapPopupWindow(); this->newMapPrompt->init(MapSortOrder::SortByLayout, triggeredAction->data()); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index b904522c..bc9ead2f 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -29,6 +29,7 @@ QStandardItem *MapGroupModel::createMapItem(QString mapName, int groupIndex, int map->setEditable(false); map->setData(mapName, Qt::UserRole); map->setData("map_name", MapListRoles::TypeRole); + map->setData(groupIndex, MapListRoles::GroupRole); // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->mapItems.insert(mapName, map); return map; diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index 597d53d1..0b13c474 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -26,7 +26,7 @@ NewMapPopup::~NewMapPopup() delete ui; } -void NewMapPopup::init() { +void NewMapPopup::initUi() { // Populate combo boxes ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels); ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels); @@ -35,6 +35,10 @@ void NewMapPopup::init() { ui->comboBox_NewMap_Type->addItems(project->mapTypes); ui->comboBox_NewMap_Location->addItems(project->mapSectionValueToName.values()); + const QSignalBlocker b(ui->comboBox_Layout); + ui->comboBox_Layout->addItems(project->mapLayoutsTable); + this->layoutId = project->mapLayoutsTable.first(); + // Set spin box limits ui->spinBox_NewMap_Width->setMinimum(1); ui->spinBox_NewMap_Height->setMinimum(1); @@ -66,6 +70,10 @@ void NewMapPopup::init() { ui->spinBox_NewMap_Floor_Number->setVisible(hasFloorNumber); ui->label_NewMap_Floor_Number->setVisible(hasFloorNumber); + this->updateGeometry(); +} + +void NewMapPopup::init() { // Restore previous settings ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); ui->comboBox_NewMap_Group->setTextItem(settings.group); @@ -86,6 +94,7 @@ void NewMapPopup::init() { ui->spinBox_NewMap_Floor_Number->setValue(settings.floorNumber); // Connect signals + // !TODO: make sure this doesnt reconnect a million times connect(ui->spinBox_NewMap_Width, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); connect(ui->spinBox_NewMap_Height, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); @@ -94,6 +103,7 @@ void NewMapPopup::init() { // Creating new map by right-clicking in the map list void NewMapPopup::init(MapSortOrder type, QVariant data) { + initUi(); switch (type) { case MapSortOrder::SortByGroup: @@ -103,6 +113,7 @@ void NewMapPopup::init(MapSortOrder type, QVariant data) { settings.location = data.toString(); break; case MapSortOrder::SortByLayout: + this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked); useLayout(data.toString()); break; } @@ -205,26 +216,58 @@ void NewMapPopup::saveSettings() { void NewMapPopup::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 NewMapPopup::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; - useLayoutSettings(project->mapLayouts.value(this->layoutId)); - // Dimensions and tilesets can't be changed for new maps using an existing layout - ui->spinBox_NewMap_Width->setDisabled(true); - ui->spinBox_NewMap_Height->setDisabled(true); - ui->spinBox_NewMap_BorderWidth->setDisabled(true); - ui->spinBox_NewMap_BorderHeight->setDisabled(true); - ui->comboBox_NewMap_Primary_Tileset->setDisabled(true); - ui->comboBox_NewMap_Secondary_Tileset->setDisabled(true); + this->ui->comboBox_Layout->setCurrentIndex(this->ui->comboBox_Layout->findText(layoutId)); + + useLayoutSettings(project->mapLayouts.value(this->layoutId)); +} + +void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) { + bool layoutEditsEnabled = (state == Qt::Unchecked); + + this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled); + + 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); + + if (!layoutEditsEnabled) { + useLayout(this->layoutId);//this->ui->comboBox_Layout->currentText()); + } else { + this->existingLayout = false; + } +} + +void NewMapPopup::on_comboBox_Layout_currentTextChanged(const QString &text) { + if (this->project->mapLayoutsTable.contains(text)) { + useLayout(text); + } } void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { From a14e70ef5339d874ed5fbbeeea2a90c9b97c3faf Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 8 Feb 2023 19:59:45 -0500 Subject: [PATCH 015/364] update map lists when new maps and layouts are added --- include/ui/maplistmodels.h | 6 +++++ src/mainwindow.cpp | 4 ++++ src/project.cpp | 1 + src/ui/maplistmodels.cpp | 48 +++++++++++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index d730c05e..2465cc29 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -32,6 +32,8 @@ public: QStandardItem *createGroupItem(QString groupName, int groupIndex); QStandardItem *createMapItem(QString mapName, int groupIndex, int mapIndex); + QStandardItem *insertMapItem(QString mapName, QString groupName); + QStandardItem *getItem(const QModelIndex &index) const; QModelIndex indexOfMap(QString mapName); @@ -68,6 +70,8 @@ public: QStandardItem *createAreaItem(QString areaName, int areaIndex); QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); + QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); + QStandardItem *getItem(const QModelIndex &index) const; QModelIndex indexOfMap(QString mapName); @@ -104,6 +108,8 @@ public: QStandardItem *createLayoutItem(QString layoutId); QStandardItem *createMapItem(QString mapName); + QStandardItem *insertMapItem(QString mapName, QString layoutId); + QStandardItem *getItem(const QModelIndex &index) const; QModelIndex indexOfLayout(QString layoutName); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 47ce878b..18b7d9b9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1248,6 +1248,10 @@ void MainWindow::onNewMapCreated() { // QStandardItem* groupItem = mapGroupItemsList->at(newMapGroup); // int numMapsInGroup = groupItem->rowCount(); + this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); + this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); + this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); + // QStandardItem *newMapItem = createMapItem(newMapName, newMapGroup, numMapsInGroup); // groupItem->appendRow(newMapItem); // mapListIndexes.insert(newMapName, newMapItem->index()); diff --git a/src/project.cpp b/src/project.cpp index 15122fac..ba273f17 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1775,6 +1775,7 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool if (!existingLayout) { mapLayouts.insert(newMap->layoutId, newMap->layout); mapLayoutsTable.append(newMap->layoutId); + layoutIdsToNames.insert(newMap->layout->id, newMap->layout->name); if (!importedMap) { setNewMapBlockdata(newMap); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index bc9ead2f..71c4caf1 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -35,7 +35,21 @@ QStandardItem *MapGroupModel::createMapItem(QString mapName, int groupIndex, int return map; } +QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { + int groupIndex = this->project->groupNames.indexOf(groupName); + QStandardItem *group = this->groupItems[groupName]; + if (!group) { + return nullptr; + } + int mapIndex = group->rowCount(); + QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex); + group->appendRow(map); + return map; +} + void MapGroupModel::initialize() { + this->groupItems.clear(); + this->mapItems.clear(); for (int i = 0; i < this->project->groupNames.length(); i++) { QString group_name = this->project->groupNames.value(i); QStandardItem *group = createGroupItem(group_name, i); @@ -140,7 +154,21 @@ QStandardItem *MapAreaModel::createMapItem(QString mapName, int groupIndex, int return map; } +QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) { + // int areaIndex = this->project->mapSectionNameToValue[areaName]; + QStandardItem *area = this->areaItems[areaName]; + if (!area) { + return nullptr; + } + int mapIndex = area->rowCount(); + QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex); + area->appendRow(map); + return map; +} + void MapAreaModel::initialize() { + this->areaItems.clear(); + this->mapItems.clear(); for (int i = 0; i < this->project->mapSectionNameToValue.size(); i++) { QString mapsecName = project->mapSectionValueToName.value(i); QStandardItem *areaItem = createAreaItem(mapsecName, i); @@ -256,7 +284,26 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { return map; } +QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { + QStandardItem *layout = nullptr; + if (this->layoutItems.contains(layoutId)) { + layout = this->layoutItems[layoutId]; + } + else { + layout = createLayoutItem(layoutId); + this->root->appendRow(layout); + } + if (!layout) { + return nullptr; + } + QStandardItem *map = createMapItem(mapName); + layout->appendRow(map); + return map; +} + void LayoutTreeModel::initialize() { + this->layoutItems.clear(); + this->mapItems.clear(); for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { QString layoutId = project->mapLayoutsTable.value(i); QStandardItem *layoutItem = createLayoutItem(layoutId); @@ -265,7 +312,6 @@ void LayoutTreeModel::initialize() { for (auto mapList : this->project->groupedMapNames) { for (auto mapName : mapList) { - // QString layoutId = project->readMapLayoutId(mapName); QStandardItem *map = createMapItem(mapName); this->layoutItems[layoutId]->appendRow(map); From 0ec8f4fee5064fa1df5ec76ce50a70a6e1367cb0 Mon Sep 17 00:00:00 2001 From: garak Date: Mon, 13 Feb 2023 17:44:56 -0500 Subject: [PATCH 016/364] add drag-drop reordering for maps in groups --- forms/mainwindow.ui | 11 ++- include/ui/eventfilters.h | 12 +++ include/ui/maplistmodels.h | 28 ++++++- include/ui/montabwidget.h | 2 - porymap.pro | 2 + src/mainwindow.cpp | 41 ++++++--- src/ui/eventfilters.cpp | 10 +++ src/ui/maplistmodels.cpp | 165 ++++++++++++++++++++++++++++++++++--- src/ui/montabwidget.cpp | 10 +-- 9 files changed, 242 insertions(+), 39 deletions(-) create mode 100644 include/ui/eventfilters.h create mode 100644 src/ui/eventfilters.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 9ecc927f..fe6a0ca2 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -154,7 +154,7 @@ - + 0 @@ -289,7 +289,7 @@ - + 0 @@ -424,7 +424,7 @@ - + 0 @@ -3607,6 +3607,11 @@ QWidget
mapview.h
+ + MapTree + QTreeView +
maplistmodels.h
+
diff --git a/include/ui/eventfilters.h b/include/ui/eventfilters.h new file mode 100644 index 00000000..984ce23a --- /dev/null +++ b/include/ui/eventfilters.h @@ -0,0 +1,12 @@ +#include +#include + + + +class WheelFilter : public QObject { + Q_OBJECT +public: + WheelFilter(QObject *parent) : QObject(parent) {} + virtual ~WheelFilter() {} + bool eventFilter(QObject *obj, QEvent *event) override; +}; diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 2465cc29..6a5c1e92 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -2,6 +2,8 @@ #ifndef MAPLISTMODELS_H #define MAPLISTMODELS_H +#include +#include #include #include @@ -17,6 +19,20 @@ enum MapListRoles { +class MapTree : public QTreeView { + Q_OBJECT +public: + MapTree(QWidget *parent) : QTreeView(parent) { + this->setDropIndicatorShown(true); + this->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + } + +public slots: + void removeSelected(); +}; + + + class MapGroupModel : public QStandardItemModel { Q_OBJECT @@ -26,11 +42,16 @@ public: QVariant data(const QModelIndex &index, int role) const override; + Qt::DropActions supportedDropActions() const override; + QStringList mimeTypes() const override; + virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + public: void setMap(QString mapName) { this->openMap = mapName; } QStandardItem *createGroupItem(QString groupName, int groupIndex); - QStandardItem *createMapItem(QString mapName, int groupIndex, int mapIndex); + QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); QStandardItem *insertMapItem(QString mapName, QString groupName); @@ -39,6 +60,9 @@ public: void initialize(); +private: + void updateProject(); + private: Project *project; QStandardItem *root = nullptr; @@ -50,7 +74,7 @@ private: QString openMap; signals: - void edited(); + void dragMoveCompleted(); }; diff --git a/include/ui/montabwidget.h b/include/ui/montabwidget.h index 4b66969c..6d916068 100644 --- a/include/ui/montabwidget.h +++ b/include/ui/montabwidget.h @@ -32,8 +32,6 @@ public slots: void deactivateTab(int tabIndex); private: - bool eventFilter(QObject *object, QEvent *event); - void actionCopyTab(int index); void actionAddDeleteTab(int index); diff --git a/porymap.pro b/porymap.pro index a6df6224..04fc6e00 100644 --- a/porymap.pro +++ b/porymap.pro @@ -57,6 +57,7 @@ SOURCES += src/core/block.cpp \ src/ui/cursortilerect.cpp \ src/ui/customattributestable.cpp \ src/ui/eventframes.cpp \ + src/ui/eventfilters.cpp \ src/ui/filterchildrenproxymodel.cpp \ src/ui/maplistmodels.cpp \ src/ui/graphicsview.cpp \ @@ -147,6 +148,7 @@ HEADERS += include/core/block.h \ include/ui/cursortilerect.h \ include/ui/customattributestable.h \ include/ui/eventframes.h \ + include/ui/eventfilters.h \ include/ui/filterchildrenproxymodel.h \ include/ui/maplistmodels.h \ include/ui/graphicsview.h \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 18b7d9b9..6445d010 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -20,6 +20,7 @@ #include "montabwidget.h" #include "imageexport.h" #include "maplistmodels.h" +#include "eventfilters.h" #include #include @@ -212,6 +213,10 @@ void MainWindow::initCustomUI() { ui->mainTabBar->setTabIcon(3, QIcon(QStringLiteral(":/icons/connections.ico"))); ui->mainTabBar->addTab("Wild Pokemon"); ui->mainTabBar->setTabIcon(4, QIcon(QStringLiteral(":/icons/tall_grass.ico"))); + + WheelFilter *wheelFilter = new WheelFilter(this); + ui->mainTabBar->installEventFilter(wheelFilter); + this->ui->mapListContainer->tabBar()->installEventFilter(wheelFilter); } void MainWindow::initExtraSignals() { @@ -1110,6 +1115,16 @@ bool MainWindow::populateMapList() { groupListProxyModel->setSourceModel(this->mapGroupModel); ui->mapList->setModel(groupListProxyModel); + // + // connect(this->mapGroupModel, &QStandardItemModel::dataChanged, [=](const QModelIndex &, const QModelIndex &, const QList &){ + // qDebug() << "mapGroupModel dataChanged"; + // }); + + // connect(this->mapGroupModel, &MapGroupModel::edited, [=, this](){ + // qDebug() << "model edited with" << this->ui->mapList->selectionModel()->selection().size() << "items"; + // }); removeSelected + connect(this->mapGroupModel, &MapGroupModel::dragMoveCompleted, this->ui->mapList, &MapTree::removeSelected); + this->mapAreaModel = new MapAreaModel(editor->project); this->areaListProxyModel = new FilterChildrenProxyModel(); areaListProxyModel->setSourceModel(this->mapAreaModel); @@ -1121,10 +1136,11 @@ bool MainWindow::populateMapList() { ui->layoutList->setModel(layoutListProxyModel); /// !TODO - // ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); - // ui->mapList->setDragEnabled(true); - // ui->mapList->setAcceptDrops(true); - // ui->mapList->setDropIndicatorShown(true); + ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); + ui->mapList->setDragEnabled(true); + ui->mapList->setAcceptDrops(true); + ui->mapList->setDropIndicatorShown(true); + ui->mapList->setDragDropMode(QAbstractItemView::InternalMove); return success; } @@ -1244,19 +1260,11 @@ void MainWindow::onNewMapCreated() { editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); - // !TODO - // QStandardItem* groupItem = mapGroupItemsList->at(newMapGroup); - // int numMapsInGroup = groupItem->rowCount(); - + // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); - // QStandardItem *newMapItem = createMapItem(newMapName, newMapGroup, numMapsInGroup); - // groupItem->appendRow(newMapItem); - // mapListIndexes.insert(newMapName, newMapItem->index()); - - // sortMapList(); setMap(newMapName, true); if (newMap->needsHealLocation) { @@ -1512,11 +1520,18 @@ void MainWindow::updateMapList() { mapAreaModel->setMap(this->editor->map->name); areaListProxyModel->layoutChanged(); } + else { + // !TODO + qDebug() << "need to clear map list"; + } if (this->editor->layout) { layoutTreeModel->setLayout(this->editor->layout->id); layoutListProxyModel->layoutChanged(); } + else { + qDebug() << "need to clear layout list"; + } } void MainWindow::on_action_Save_Project_triggered() { diff --git a/src/ui/eventfilters.cpp b/src/ui/eventfilters.cpp new file mode 100644 index 00000000..24f2e0bd --- /dev/null +++ b/src/ui/eventfilters.cpp @@ -0,0 +1,10 @@ +#include "eventfilters.h" + + + +bool WheelFilter::eventFilter(QObject *, QEvent *event) { + if (event->type() == QEvent::Wheel) { + return true; + } + return false; +} diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 71c4caf1..dd0b254d 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -1,6 +1,18 @@ #include "maplistmodels.h" +#include + #include "project.h" +#include "filterchildrenproxymodel.h" + + + +void MapTree::removeSelected() { + while (!this->selectedIndexes().isEmpty()) { + QModelIndex i = this->selectedIndexes().takeLast(); + this->model()->removeRow(i.row(), i.parent()); + } +} @@ -11,6 +23,122 @@ MapGroupModel::MapGroupModel(Project *project, QObject *parent) : QStandardItemM initialize(); } +Qt::DropActions MapGroupModel::supportedDropActions() const { + return Qt::MoveAction; +} + +QStringList MapGroupModel::mimeTypes() const { + QStringList types; + types << "application/porymap.mapgroupmodel.map" + << "application/porymap.mapgroupmodel.group"; + return types; +} + +QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { + QMimeData *mimeData = QStandardItemModel::mimeData(indexes); + QByteArray encodedData; + + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + for (const QModelIndex &index : indexes) { + if (index.isValid()) { + QString mapName = data(index, Qt::UserRole).toString(); + stream << mapName; + } + } + + mimeData->setData("application/porymap.mapgroupmodel.map", encodedData); + return mimeData; +} + +bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parentIndex) { + if (action == Qt::IgnoreAction) + return true; + + if (!data->hasFormat("application/porymap.mapgroupmodel.map")) + return false; + + if (!parentIndex.isValid()) + return false; + + int firstRow = 0; + + if (row != -1) { + firstRow = row; + } + else if (parentIndex.isValid()) { + firstRow = rowCount(parentIndex); + } + + QByteArray encodedData = data->data("application/porymap.mapgroupmodel.map"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + QStringList droppedMaps; + int rowCount = 0; + + QList newItems; + + while (!stream.atEnd()) { + QString mapName; + stream >> mapName; + droppedMaps << mapName; + rowCount++; + } + + this->insertRows(firstRow, rowCount, parentIndex); + + int newItemIndex = 0; + for (QString mapName : droppedMaps) { + QModelIndex mapIndex = index(firstRow, 0, parentIndex); + QStandardItem *mapItem = this->itemFromIndex(mapIndex); + createMapItem(mapName, mapItem); + firstRow++; + } + + // updateProject(); + + emit dragMoveCompleted(); + + return false; +} + + +/* + QStringList groupNames; + QMap mapGroups; + QList groupedMapNames; + QStringList mapNames; +*/ +void MapGroupModel::updateProject() { + // + QStringList groups; + int numGroups = this->root->rowCount(); + qDebug() << "group count:" << numGroups; + + for (int g = 0; g < this->root->rowCount(); g++) { + QStandardItem *groupItem = this->item(g); + qDebug() << g << "group item" << groupItem->text(); //data(Qt::UserRole).toString(); + for (int m = 0; m < groupItem->rowCount(); m++) { + // + QStandardItem *mapItem = groupItem->child(m); + qDebug() << " " << m << "map item" << mapItem->data(Qt::UserRole).toString(); + } + } + + QList maps; + for (auto mapName : this->mapItems.keys()) { + // + QStandardItem *mapItem = this->mapItems[mapName]; + QStandardItem *groupItem = mapItem->parent(); + if (!groupItem) { + qDebug() << "FAIL: no parent" << mapName; + continue; + } + auto mapIndex = this->indexFromItem(mapItem).row(); + auto groupIndex = this->indexFromItem(groupItem).row(); + // qDebug().nospace() << "map: " << mapName << "[" << parentIndex.row() << "." << mapIndex.row() << "]"; + } +} + QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex) { QStandardItem *group = new QStandardItem; group->setText(groupName); @@ -18,31 +146,30 @@ QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex) group->setData(groupName, Qt::UserRole); group->setData("map_group", MapListRoles::TypeRole); group->setData(groupIndex, MapListRoles::GroupRole); - // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + group->setFlags(Qt::ItemIsEditable | /* Qt::ItemIsSelectable | */ Qt::ItemIsEnabled | /* Qt::ItemIsDragEnabled | */ Qt::ItemIsDropEnabled); this->groupItems.insert(groupName, group); return group; } -QStandardItem *MapGroupModel::createMapItem(QString mapName, int groupIndex, int mapIndex) { - QStandardItem *map = new QStandardItem; - map->setText(QString("[%1.%2] ").arg(groupIndex).arg(mapIndex, 2, 10, QLatin1Char('0')) + mapName); +QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { + if (!map) map = new QStandardItem; map->setEditable(false); map->setData(mapName, Qt::UserRole); map->setData("map_name", MapListRoles::TypeRole); - map->setData(groupIndex, MapListRoles::GroupRole); - // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->mapItems.insert(mapName, map); + // map->setData(groupIndex, MapListRoles::GroupRole); + map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->mapItems[mapName] = map; return map; } QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { - int groupIndex = this->project->groupNames.indexOf(groupName); + //int groupIndex = this->project->groupNames.indexOf(groupName); QStandardItem *group = this->groupItems[groupName]; if (!group) { return nullptr; } - int mapIndex = group->rowCount(); - QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex); + //int mapIndex = group->rowCount(); + QStandardItem *map = createMapItem(mapName); group->appendRow(map); return map; } @@ -54,10 +181,12 @@ void MapGroupModel::initialize() { QString group_name = this->project->groupNames.value(i); QStandardItem *group = createGroupItem(group_name, i); root->appendRow(group); + //this->setItem(0, i, group); QStringList names = this->project->groupedMapNames.value(i); for (int j = 0; j < names.length(); j++) { QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); + QStandardItem *map = createMapItem(map_name); + //this->setItem(i, j, map); group->appendRow(map); } } @@ -90,10 +219,13 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); static QIcon mapFolderIcon; + static QIcon folderIcon; static bool loaded = false; if (!loaded) { mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); loaded = true; } @@ -101,6 +233,9 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListRoles::TypeRole).toString(); if (type == "map_group") { + if (!item->hasChildren()) { + return folderIcon; + } return mapFolderIcon; } else if (type == "map_name") { QString mapName = item->data(Qt::UserRole).toString(); @@ -118,6 +253,14 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { return mapGrayIcon; } } + else if (role == Qt::DisplayRole) { + // + QStandardItem *item = this->getItem(index)->child(row, col); + + if (item->data(MapListRoles::TypeRole).toString() == "map_name") { + return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(Qt::UserRole).toString(); + } + } return QStandardItemModel::data(index, role); } diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp index 6845a02b..e31739bd 100644 --- a/src/ui/montabwidget.cpp +++ b/src/ui/montabwidget.cpp @@ -3,6 +3,7 @@ #include "editor.h" #include "encountertablemodel.h" #include "encountertabledelegates.h" +#include "eventfilters.h" @@ -11,20 +12,13 @@ static WildMonInfo encounterClipboard; MonTabWidget::MonTabWidget(Editor *editor, QWidget *parent) : QTabWidget(parent) { this->editor = editor; populate(); - this->tabBar()->installEventFilter(this); + this->tabBar()->installEventFilter(new WheelFilter(this)); } MonTabWidget::~MonTabWidget() { } -bool MonTabWidget::eventFilter(QObject *, QEvent *event) { - if (event->type() == QEvent::Wheel) { - return true; - } - return false; -} - void MonTabWidget::populate() { EncounterFields fields = editor->project->wildMonFields; activeTabs.resize(fields.size()); From d6f3bb100803598da3df2693987aa8d9bdbc9eb5 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 14 Feb 2023 01:50:14 -0500 Subject: [PATCH 017/364] allow editing map group names --- include/ui/maplistmodels.h | 25 +++++++++- src/mainwindow.cpp | 2 + src/ui/maplistmodels.cpp | 97 ++++++++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 37 deletions(-) diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 6a5c1e92..ef21d8ae 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -33,12 +34,31 @@ public slots: +class GroupNameDelegate : public QStyledItemDelegate { + Q_OBJECT + +public: + GroupNameDelegate(Project *project, QObject *parent = nullptr) : QStyledItemDelegate(parent), project(project) {} + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + Project *project = nullptr; +}; + + + +class QRegularExpressionValidator; + class MapGroupModel : public QStandardItemModel { Q_OBJECT public: MapGroupModel(Project *project, QObject *parent = nullptr); - ~MapGroupModel() {} + ~MapGroupModel() { } QVariant data(const QModelIndex &index, int role) const override; @@ -47,6 +67,8 @@ public: virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + public: void setMap(QString mapName) { this->openMap = mapName; } @@ -69,7 +91,6 @@ private: QMap groupItems; QMap mapItems; - // TODO: if reordering, will the item be the same? QString openMap; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6445d010..d5c985b3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1115,6 +1115,8 @@ bool MainWindow::populateMapList() { groupListProxyModel->setSourceModel(this->mapGroupModel); ui->mapList->setModel(groupListProxyModel); + this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this)); + // // connect(this->mapGroupModel, &QStandardItemModel::dataChanged, [=](const QModelIndex &, const QModelIndex &, const QList &){ // qDebug() << "mapGroupModel dataChanged"; diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index dd0b254d..6c0a06d1 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -1,6 +1,7 @@ #include "maplistmodels.h" #include +#include #include "project.h" #include "filterchildrenproxymodel.h" @@ -16,6 +17,33 @@ void MapTree::removeSelected() { +QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { + QLineEdit *editor = new QLineEdit(parent); + static const QRegularExpression expression("gMapGroup_[A-Za-z0-9_]+"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, parent); + editor->setValidator(validator); + editor->setFrame(false); + return editor; +} + +void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { + QString groupName = index.data(Qt::UserRole).toString(); + QLineEdit *le = static_cast(editor); + le->setText(groupName); +} + +void GroupNameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { + QLineEdit *le = static_cast(editor); + QString groupName = le->text(); + model->setData(index, groupName, Qt::UserRole); +} + +void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const { + editor->setGeometry(option.rect); +} + + + MapGroupModel::MapGroupModel(Project *project, QObject *parent) : QStandardItemModel(parent) { this->project = project; this->root = this->invisibleRootItem(); @@ -94,81 +122,66 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i firstRow++; } - // updateProject(); - emit dragMoveCompleted(); + updateProject(); return false; } +void MapGroupModel::updateProject() { + if (!this->project) return; -/* QStringList groupNames; QMap mapGroups; QList groupedMapNames; QStringList mapNames; -*/ -void MapGroupModel::updateProject() { - // - QStringList groups; - int numGroups = this->root->rowCount(); - qDebug() << "group count:" << numGroups; for (int g = 0; g < this->root->rowCount(); g++) { QStandardItem *groupItem = this->item(g); - qDebug() << g << "group item" << groupItem->text(); //data(Qt::UserRole).toString(); + QString groupName = groupItem->data(Qt::UserRole).toString(); + groupNames.append(groupName); + mapGroups[groupName] = g; + QStringList mapsInGroup; for (int m = 0; m < groupItem->rowCount(); m++) { - // QStandardItem *mapItem = groupItem->child(m); - qDebug() << " " << m << "map item" << mapItem->data(Qt::UserRole).toString(); + QString mapName = mapItem->data(Qt::UserRole).toString(); + mapsInGroup.append(mapName); + mapNames.append(mapName); } + groupedMapNames.append(mapsInGroup); } - QList maps; - for (auto mapName : this->mapItems.keys()) { - // - QStandardItem *mapItem = this->mapItems[mapName]; - QStandardItem *groupItem = mapItem->parent(); - if (!groupItem) { - qDebug() << "FAIL: no parent" << mapName; - continue; - } - auto mapIndex = this->indexFromItem(mapItem).row(); - auto groupIndex = this->indexFromItem(groupItem).row(); - // qDebug().nospace() << "map: " << mapName << "[" << parentIndex.row() << "." << mapIndex.row() << "]"; - } + this->project->groupNames = groupNames; + this->project->mapGroups = mapGroups; + this->project->groupedMapNames = groupedMapNames; + this->project->mapNames = mapNames; } QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex) { QStandardItem *group = new QStandardItem; group->setText(groupName); - group->setEditable(true); group->setData(groupName, Qt::UserRole); group->setData("map_group", MapListRoles::TypeRole); group->setData(groupIndex, MapListRoles::GroupRole); - group->setFlags(Qt::ItemIsEditable | /* Qt::ItemIsSelectable | */ Qt::ItemIsEnabled | /* Qt::ItemIsDragEnabled | */ Qt::ItemIsDropEnabled); + group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable); this->groupItems.insert(groupName, group); return group; } QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { if (!map) map = new QStandardItem; - map->setEditable(false); map->setData(mapName, Qt::UserRole); map->setData("map_name", MapListRoles::TypeRole); - // map->setData(groupIndex, MapListRoles::GroupRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->mapItems[mapName] = map; return map; } QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { - //int groupIndex = this->project->groupNames.indexOf(groupName); QStandardItem *group = this->groupItems[groupName]; if (!group) { return nullptr; } - //int mapIndex = group->rowCount(); QStandardItem *map = createMapItem(mapName); group->appendRow(map); return map; @@ -181,12 +194,10 @@ void MapGroupModel::initialize() { QString group_name = this->project->groupNames.value(i); QStandardItem *group = createGroupItem(group_name, i); root->appendRow(group); - //this->setItem(0, i, group); QStringList names = this->project->groupedMapNames.value(i); for (int j = 0; j < names.length(); j++) { QString map_name = names.value(j); QStandardItem *map = createMapItem(map_name); - //this->setItem(i, j, map); group->appendRow(map); } } @@ -256,15 +267,31 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { else if (role == Qt::DisplayRole) { // QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListRoles::TypeRole).toString(); - if (item->data(MapListRoles::TypeRole).toString() == "map_name") { + if (type == "map_name") { return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(Qt::UserRole).toString(); } + else if (type == "map_group") { + return item->data(Qt::UserRole).toString(); + } } return QStandardItemModel::data(index, role); } +bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role == Qt::UserRole && data(index, MapListRoles::TypeRole).toString() == "map_group") { + // verify uniqueness of new group name + if (this->project->groupNames.contains(value.toString())) { + return false; + } + } + if (QStandardItemModel::setData(index, value, role)) { + this->updateProject(); + } +} + MapAreaModel::MapAreaModel(Project *project, QObject *parent) : QStandardItemModel(parent) { @@ -288,7 +315,7 @@ QStandardItem *MapAreaModel::createAreaItem(QString mapsecName, int areaIndex) { QStandardItem *MapAreaModel::createMapItem(QString mapName, int groupIndex, int mapIndex) { QStandardItem *map = new QStandardItem; - map->setText(QString("[%1.%2] ").arg(groupIndex).arg(mapIndex, 2, 10, QLatin1Char('0')) + mapName); + map->setText(mapName); map->setEditable(false); map->setData(mapName, Qt::UserRole); map->setData("map_name", MapListRoles::TypeRole); From 5d98f8e2f8796a1fabac8c06c379b82f27059b9e Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 14 Feb 2023 03:11:18 -0500 Subject: [PATCH 018/364] fix crash in model data function --- src/ui/maplistmodels.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 6c0a06d1..b4f6e9dc 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -220,6 +220,8 @@ QModelIndex MapGroupModel::indexOfMap(QString mapName) { } QVariant MapGroupModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) return QVariant(); + int row = index.row(); int col = index.column(); From 2ea0590f6e27d1571516269c3a57923447324e4f Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 14 Feb 2023 12:09:22 -0500 Subject: [PATCH 019/364] save changes to layouts --- include/core/maplayout.h | 5 +-- include/project.h | 11 ++++--- src/core/maplayout.cpp | 23 +++++++++++-- src/editor.cpp | 9 +++-- src/mainwindow.cpp | 71 ++++++++++++---------------------------- src/project.cpp | 48 +++++++++++++++++---------- 6 files changed, 87 insertions(+), 80 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 968b8960..c17248ce 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -19,8 +19,6 @@ class Layout : public QObject { public: Layout() {} - void copyAttributesFrom(Layout *other); - static QString layoutConstantFromName(QString mapName); bool loaded = false; @@ -72,6 +70,9 @@ public: QUndoStack editHistory; public: + Layout *copy(); + void copyFrom(Layout *other); + int getWidth(); int getHeight(); int getBorderWidth(); diff --git a/include/project.h b/include/project.h index 24f14611..34a6a49d 100644 --- a/include/project.h +++ b/include/project.h @@ -58,7 +58,7 @@ public: QString layoutsLabel; QMap layoutIdsToNames; QMap mapLayouts; -// QMap mapLayoutsMaster; + QMap mapLayoutsMaster; QMap mapSecToMapHoverName; QMap mapSectionNameToValue; QMap mapSectionValueToName; @@ -157,11 +157,12 @@ public: void loadTilesetPalettes(Tileset*); void readTilesetPaths(Tileset* tileset); - void saveLayoutBlockdata(Map*); - void saveLayoutBorder(Map*); + void saveLayout(Layout *); + void saveLayoutBlockdata(Layout *); + void saveLayoutBorder(Layout *); void writeBlockdata(QString, const Blockdata &); void saveAllMaps(); - void saveMap(Map*); + void saveMap(Map *); void saveAllDataStructures(); void saveMapLayouts(); void saveMapGroups(); @@ -238,7 +239,7 @@ public: static int getMaxObjectEvents(); private: - void updateMapLayout(Map*); + void updateLayout(Layout *); void setNewMapBlockdata(Map* map); void setNewMapBorder(Map *map); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 4fd05b4f..3dd9b9f2 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -50,8 +50,27 @@ // BorderMetatilesPixmapItem *borderItem = nullptr; // QUndoStack editHistory; -void Layout::copyAttributesFrom(Layout *other) { - // +Layout *Layout::copy() { + Layout *layout = new Layout; + layout->copyFrom(this); + return layout; +} + +void Layout::copyFrom(Layout *other) { + this->id = other->id; + this->name = other->name; + this->width = other->width; + this->height = other->height; + this->border_width = other->border_width; + this->border_height = other->border_height; + this->border_path = other->border_path; + this->blockdata_path = other->blockdata_path; + this->tileset_primary_label = other->tileset_primary_label; + this->tileset_secondary_label = other->tileset_secondary_label; + this->tileset_primary = other->tileset_primary; + this->tileset_secondary = other->tileset_secondary; + this->blockdata = other->blockdata; + this->border = other->border; } QString Layout::layoutConstantFromName(QString mapName) { diff --git a/src/editor.cpp b/src/editor.cpp index 7d6d2780..fc77b2b6 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -62,10 +62,13 @@ void Editor::saveProject() { } void Editor::save() { - if (project && map) { + if (this->project && this->map) { saveUiFields(); - project->saveMap(map); - project->saveAllDataStructures(); + this->project->saveMap(this->map); + this->project->saveAllDataStructures(); + } + else if (this->project && this->layout) { + this->project->saveLayout(this->layout); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d5c985b3..db766c0c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -684,10 +684,6 @@ void MainWindow::on_action_Reload_Project_triggered() { } void MainWindow::unsetMap() { - // - logInfo("Disabling map-related edits"); - - // this->editor->unsetMap(); // disable other tabs @@ -696,7 +692,6 @@ void MainWindow::unsetMap() { this->ui->mainTabBar->setTabEnabled(3, false); this->ui->mainTabBar->setTabEnabled(4, false); - // this->ui->comboBox_LayoutSelector->setEnabled(false); } @@ -752,8 +747,12 @@ bool MainWindow::setMap(QString map_name, bool scroll) { } bool MainWindow::setLayout(QString layoutId) { - // if this->editor->setLayout(layoutName); - // this->editor->layout = layout; + if (this->editor->map) + logInfo("Switching to a layout-only editing mode. Disabling map-related edits."); + + setMap(QString()); + + logInfo(QString("Setting layout to '%1'").arg(layoutId)); if (!this->editor->setLayout(layoutId)) { return false; @@ -762,17 +761,7 @@ bool MainWindow::setLayout(QString layoutId) { layoutTreeModel->setLayout(layoutId); refreshMapScene(); - - // if (scrollTreeView) { - // // Make sure we clear the filter first so we actually have a scroll target - // /// !TODO: make this onto a function that scrolls the current view taking a map name or layout name - // groupListProxyModel->setFilterRegularExpression(QString()); - // ui->mapList->setCurrentIndex(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name))); - // ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); - // } - showWindowTitle(); - updateMapList(); // !TODO: make sure these connections are not duplicated / cleared later @@ -780,15 +769,6 @@ bool MainWindow::setLayout(QString layoutId) { connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing); // connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); - // displayMapProperties - - - // - // connect(editor->layout, &Layout::mapChanged, this, &MainWindow::onMapChanged); - // connect(editor->layout, &Layout::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); - // connect(editor->layout, &Layout::modified, [this](){ this->markMapEdited(); }); - - // updateTilesetEditor(); return true; @@ -1116,15 +1096,6 @@ bool MainWindow::populateMapList() { ui->mapList->setModel(groupListProxyModel); this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this)); - - // - // connect(this->mapGroupModel, &QStandardItemModel::dataChanged, [=](const QModelIndex &, const QModelIndex &, const QList &){ - // qDebug() << "mapGroupModel dataChanged"; - // }); - - // connect(this->mapGroupModel, &MapGroupModel::edited, [=, this](){ - // qDebug() << "model edited with" << this->ui->mapList->selectionModel()->selection().size() << "items"; - // }); removeSelected connect(this->mapGroupModel, &MapGroupModel::dragMoveCompleted, this->ui->mapList, &MapTree::removeSelected); this->mapAreaModel = new MapAreaModel(editor->project); @@ -1497,12 +1468,6 @@ void MainWindow::on_layoutList_activated(const QModelIndex &index) { QVariant data = index.data(Qt::UserRole); if (index.data(MapListRoles::TypeRole) == "map_layout" && !data.isNull()) { QString layoutId = data.toString(); - // - logInfo("Switching to a layout-only editing mode"); - setMap(QString()); - //setLayout(layoutId); - // setLayout(layout) - qDebug() << "set layout" << layoutId; if (!setLayout(layoutId)) { QMessageBox msgBox(this); @@ -1517,22 +1482,28 @@ void MainWindow::on_layoutList_activated(const QModelIndex &index) { void MainWindow::updateMapList() { if (this->editor->map) { - mapGroupModel->setMap(this->editor->map->name); - groupListProxyModel->layoutChanged(); - mapAreaModel->setMap(this->editor->map->name); - areaListProxyModel->layoutChanged(); + this->mapGroupModel->setMap(this->editor->map->name); + this->groupListProxyModel->layoutChanged(); + this->mapAreaModel->setMap(this->editor->map->name); + this->areaListProxyModel->layoutChanged(); } else { - // !TODO - qDebug() << "need to clear map list"; + this->mapGroupModel->setMap(QString()); + this->groupListProxyModel->layoutChanged(); + this->ui->mapList->clearSelection(); + this->mapAreaModel->setMap(QString()); + this->areaListProxyModel->layoutChanged(); + this->ui->areaList->clearSelection(); } if (this->editor->layout) { - layoutTreeModel->setLayout(this->editor->layout->id); - layoutListProxyModel->layoutChanged(); + this->layoutTreeModel->setLayout(this->editor->layout->id); + this->layoutListProxyModel->layoutChanged(); } else { - qDebug() << "need to clear layout list"; + this->layoutTreeModel->setLayout(QString()); + this->layoutListProxyModel->layoutChanged(); + this->ui->layoutList->clearSelection(); } } diff --git a/src/project.cpp b/src/project.cpp index ba273f17..245f3fb8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -563,13 +563,12 @@ bool Project::readMapLayouts() { return false; } mapLayouts.insert(layout->id, layout); + mapLayoutsMaster.insert(layout->id, layout->copy()); mapLayoutsTable.append(layout->id); + mapLayoutsTableMaster.append(layout->id); layoutIdsToNames.insert(layout->id, layout->name); } - // Deep copy - mapLayoutsTableMaster = mapLayoutsTable; - mapLayoutsTableMaster.detach(); return true; } @@ -587,7 +586,7 @@ void Project::saveMapLayouts() { bool useCustomBorderSize = projectConfig.getUseCustomBorderSize(); OrderedJson::array layoutsArr; for (QString layoutId : mapLayoutsTableMaster) { - Layout *layout = mapLayouts.value(layoutId); + Layout *layout = mapLayoutsMaster.value(layoutId); OrderedJson::object layoutObj; layoutObj["id"] = layout->id; layoutObj["name"] = layout->name; @@ -1191,14 +1190,14 @@ void Project::setNewMapBorder(Map *map) { map->layout->lastCommitBlocks.borderDimensions = QSize(width, height); } -void Project::saveLayoutBorder(Map *map) { - QString path = QString("%1/%2").arg(root).arg(map->layout->border_path); - writeBlockdata(path, map->layout->border); +void Project::saveLayoutBorder(Layout *layout) { + QString path = QString("%1/%2").arg(root).arg(layout->border_path); + writeBlockdata(path, layout->border); } -void Project::saveLayoutBlockdata(Map* map) { - QString path = QString("%1/%2").arg(root).arg(map->layout->blockdata_path); - writeBlockdata(path, map->layout->blockdata); +void Project::saveLayoutBlockdata(Layout *layout) { + QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); + writeBlockdata(path, layout->blockdata); } void Project::writeBlockdata(QString path, const Blockdata &blockdata) { @@ -1353,21 +1352,28 @@ void Project::saveMap(Map *map) { jsonDoc.dump(&mapFile); mapFile.close(); - saveLayoutBorder(map); - saveLayoutBlockdata(map); + saveLayout(map->layout); saveHealLocations(map); - // Update global data structures with current map data. - updateMapLayout(map); - map->isPersistedToFile = true; map->hasUnsavedDataChanges = false; map->editHistory.setClean(); } -void Project::updateMapLayout(Map* map) { - if (!mapLayoutsTableMaster.contains(map->layoutId)) { - mapLayoutsTableMaster.append(map->layoutId); +void Project::saveLayout(Layout *layout) { + // + saveLayoutBorder(layout); + saveLayoutBlockdata(layout); + + // Update global data structures with current map data. + updateLayout(layout); + + layout->editHistory.setClean(); +} + +void Project::updateLayout(Layout *layout) { + if (!mapLayoutsTableMaster.contains(layout->id)) { + mapLayoutsTableMaster.append(layout->id); } // !TODO: why is[was] this a deep copy?? @@ -1376,6 +1382,12 @@ void Project::updateMapLayout(Map* map) { // Layout *newLayout = new Layout(); // *newLayout = *layout; // mapLayoutsMaster.insert(map->layoutId, newLayout); + if (mapLayoutsMaster.contains(layout->id)) { + mapLayoutsMaster[layout->id]->copyFrom(layout); + } + else { + mapLayoutsMaster.insert(layout->id, layout->copy()); + } } void Project::saveAllDataStructures() { From ff086a6fe623763afa0b692a576ff6cfad0ccd8f Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 14 Feb 2023 12:32:37 -0500 Subject: [PATCH 020/364] remove redundant mapsceneeventfilter file --- include/ui/eventfilters.h | 16 ++++++++++++++++ include/ui/mapsceneeventfilter.h | 19 ------------------- porymap.pro | 2 -- src/editor.cpp | 2 +- src/project.cpp | 6 ------ src/ui/eventfilters.cpp | 16 ++++++++++++++++ src/ui/mapsceneeventfilter.cpp | 23 ----------------------- 7 files changed, 33 insertions(+), 51 deletions(-) delete mode 100644 include/ui/mapsceneeventfilter.h delete mode 100644 src/ui/mapsceneeventfilter.cpp diff --git a/include/ui/eventfilters.h b/include/ui/eventfilters.h index 984ce23a..851c344b 100644 --- a/include/ui/eventfilters.h +++ b/include/ui/eventfilters.h @@ -3,6 +3,7 @@ +/// Prevent wheel scroll class WheelFilter : public QObject { Q_OBJECT public: @@ -10,3 +11,18 @@ public: virtual ~WheelFilter() {} bool eventFilter(QObject *obj, QEvent *event) override; }; + + + +/// Ctrl+Wheel = zoom +class MapSceneEventFilter : public QObject { + Q_OBJECT +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +public: + explicit MapSceneEventFilter(QObject *parent = nullptr) : QObject(parent) {} + +signals: + void wheelZoom(int delta); +public slots: +}; diff --git a/include/ui/mapsceneeventfilter.h b/include/ui/mapsceneeventfilter.h deleted file mode 100644 index 7de427e3..00000000 --- a/include/ui/mapsceneeventfilter.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef MAPSCENEEVENTFILTER_H -#define MAPSCENEEVENTFILTER_H - -#include - -class MapSceneEventFilter : public QObject -{ - Q_OBJECT -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -public: - explicit MapSceneEventFilter(QObject *parent = nullptr); - -signals: - void wheelZoom(int delta); -public slots: -}; - -#endif // MAPSCENEEVENTFILTER_H diff --git a/porymap.pro b/porymap.pro index 04fc6e00..bf9a200f 100644 --- a/porymap.pro +++ b/porymap.pro @@ -66,7 +66,6 @@ SOURCES += src/core/block.cpp \ src/ui/prefabcreationdialog.cpp \ src/ui/regionmappixmapitem.cpp \ src/ui/citymappixmapitem.cpp \ - src/ui/mapsceneeventfilter.cpp \ src/ui/metatilelayersitem.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ @@ -158,7 +157,6 @@ HEADERS += include/core/block.h \ include/ui/prefabcreationdialog.h \ include/ui/regionmappixmapitem.h \ include/ui/citymappixmapitem.h \ - include/ui/mapsceneeventfilter.h \ include/ui/metatilelayersitem.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ diff --git a/src/editor.cpp b/src/editor.cpp index fc77b2b6..48b8ce4e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -4,7 +4,7 @@ #include "log.h" #include "mapconnection.h" #include "currentselectedmetatilespixmapitem.h" -#include "mapsceneeventfilter.h" +#include "eventfilters.h" #include "metatile.h" #include "montabwidget.h" #include "encountertablemodel.h" diff --git a/src/project.cpp b/src/project.cpp index 245f3fb8..f2615ebf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1376,12 +1376,6 @@ void Project::updateLayout(Layout *layout) { mapLayoutsTableMaster.append(layout->id); } - // !TODO: why is[was] this a deep copy?? - // Deep copy - // Layout *layout = mapLayouts.value(map->layoutId); - // Layout *newLayout = new Layout(); - // *newLayout = *layout; - // mapLayoutsMaster.insert(map->layoutId, newLayout); if (mapLayoutsMaster.contains(layout->id)) { mapLayoutsMaster[layout->id]->copyFrom(layout); } diff --git a/src/ui/eventfilters.cpp b/src/ui/eventfilters.cpp index 24f2e0bd..1e7b2b80 100644 --- a/src/ui/eventfilters.cpp +++ b/src/ui/eventfilters.cpp @@ -1,5 +1,7 @@ #include "eventfilters.h" +#include + bool WheelFilter::eventFilter(QObject *, QEvent *event) { @@ -8,3 +10,17 @@ bool WheelFilter::eventFilter(QObject *, QEvent *event) { } return false; } + + + +bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) { + if (event->type() == QEvent::GraphicsSceneWheel) { + QGraphicsSceneWheelEvent *wheelEvent = static_cast(event); + if (wheelEvent->modifiers() & Qt::ControlModifier) { + emit wheelZoom(wheelEvent->delta() > 0 ? 1 : -1); + event->accept(); + return true; + } + } + return false; +} diff --git a/src/ui/mapsceneeventfilter.cpp b/src/ui/mapsceneeventfilter.cpp deleted file mode 100644 index f8ae14cb..00000000 --- a/src/ui/mapsceneeventfilter.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "mapsceneeventfilter.h" -#include -#include - -MapSceneEventFilter::MapSceneEventFilter(QObject *parent) : QObject(parent) -{ - -} - -bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) -{ - if (event->type() == QEvent::GraphicsSceneWheel) - { - QGraphicsSceneWheelEvent *wheelEvent = static_cast(event); - if (wheelEvent->modifiers() & Qt::ControlModifier) - { - emit wheelZoom(wheelEvent->delta() > 0 ? 1 : -1); - event->accept(); - return true; - } - } - return false; -} From ac83e0fbe35a49f605e2851360dcb65715344183 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 14 Feb 2023 20:44:20 -0500 Subject: [PATCH 021/364] no need to manually crop map tab icon --- src/mainwindow.cpp | 46 ++++++++-------------------------------------- src/project.cpp | 2 +- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index db766c0c..e9e1e912 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -330,18 +330,6 @@ void MainWindow::initEditor() { } void MainWindow::initMiscHeapObjects() { - // mapIcon = new QIcon(QStringLiteral(":/icons/map.ico")); - // mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico")); - // mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico")); - - /// !TODO - // mapListModel = new QStandardItemModel; - // mapGroupItemsList = new QList; - // mapListProxyModel = new FilterChildrenProxyModel; - - // mapListProxyModel->setSourceModel(mapListModel); - // ui->mapList->setModel(mapListProxyModel); - eventTabObjectWidget = ui->tab_Objects; eventTabWarpWidget = ui->tab_Warps; eventTabTriggerWidget = ui->tab_Triggers; @@ -351,27 +339,13 @@ void MainWindow::initMiscHeapObjects() { ui->tabWidget_EventType->clear(); } -// TODO +// !TODO: scroll view on first showing void MainWindow::initMapSortOrder() { - // QMenu *mapSortOrderMenu = new QMenu(this); - // QActionGroup *mapSortOrderActionGroup = new QActionGroup(ui->toolButton_MapSortOrder); + mapSortOrder = porymapConfig.getMapSortOrder(); + if (mapSortOrder == MapSortOrder::SortByLayout) + mapSortOrder = MapSortOrder::SortByGroup; - // porymapConfig.setMapSortOrder(mapSortOrder); - - // mapSortOrderMenu->addAction(ui->actionSort_by_Group); - // mapSortOrderMenu->addAction(ui->actionSort_by_Area); - // mapSortOrderMenu->addAction(ui->actionSort_by_Layout); - // ui->toolButton_MapSortOrder->setMenu(mapSortOrderMenu); - - // mapSortOrderActionGroup->addAction(ui->actionSort_by_Group); - // mapSortOrderActionGroup->addAction(ui->actionSort_by_Area); - // mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout); - - // connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed); - - // QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[mapSortOrder]; - // ui->toolButton_MapSortOrder->setIcon(sortOrder->icon()); - // sortOrder->setChecked(true); + this->ui->mapListContainer->setCurrentIndex(static_cast(this->mapSortOrder)); } void MainWindow::showWindowTitle() { @@ -391,14 +365,14 @@ void MainWindow::showWindowTitle() { ); } if (editor && editor->layout) { - //QPixmap pixmap = editor->layout ? editor->layout->render(false) : QPixmap(); - QPixmap pixmap = editor->layout->pixmap;//getLayoutItemPixmap(); + QPixmap pixmap = editor->layout->pixmap; if (!pixmap.isNull()) { - ui->mainTabBar->setTabIcon(0, QIcon(pixmap.scaled(16, 16))); + ui->mainTabBar->setTabIcon(0, QIcon(pixmap)); } else { ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/map.ico"))); } } + updateMapList(); } void MainWindow::markMapEdited() { @@ -485,10 +459,6 @@ void MainWindow::loadUserSettings() { this->editor->settings->cursorTileRectEnabled = porymapConfig.getShowCursorTile(); ui->checkBox_ToggleBorder->setChecked(porymapConfig.getShowBorder()); ui->checkBox_ToggleGrid->setChecked(porymapConfig.getShowGrid()); - mapSortOrder = porymapConfig.getMapSortOrder(); - this->ui->mapListContainer->blockSignals(true); - this->ui->mapListContainer->setCurrentIndex(static_cast(this->mapSortOrder)); - this->ui->mapListContainer->blockSignals(false); ui->horizontalSlider_CollisionTransparency->blockSignals(true); this->editor->collisionOpacity = static_cast(porymapConfig.getCollisionOpacity()) / 100; ui->horizontalSlider_CollisionTransparency->setValue(porymapConfig.getCollisionOpacity()); diff --git a/src/project.cpp b/src/project.cpp index f2615ebf..0dfb7763 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -432,7 +432,7 @@ bool Project::loadMapLayout(Map* map) { return false; } - if (map->hasUnsavedChanges() /* || map->layout->hasUnsavedChanges() */) { + if (map->hasUnsavedChanges() || map->layout->hasUnsavedChanges()) { return true; } else { return loadLayout(map->layout); From e79b6e2fcace94df3eb9348b2f6a52ad8203220b Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 21 Feb 2023 11:03:33 -0500 Subject: [PATCH 022/364] add placeholder text for mapgroup label --- src/ui/maplistmodels.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index b4f6e9dc..e3dfba14 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -20,6 +20,7 @@ void MapTree::removeSelected() { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); static const QRegularExpression expression("gMapGroup_[A-Za-z0-9_]+"); + editor->setPlaceholderText("gMapGroup_"); QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, parent); editor->setValidator(validator); editor->setFrame(false); From f485ebdd3e236b10925f7f9e81d340bfbf47bc91 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 22 Feb 2023 15:41:29 -0500 Subject: [PATCH 023/364] preserve layout in config --- include/config.h | 4 ++++ include/mainwindow.h | 8 ++++++-- src/config.cpp | 12 +++++++++++ src/editor.cpp | 2 ++ src/mainwindow.cpp | 49 ++++++++++++++++++++++++++++++++++++-------- 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/include/config.h b/include/config.h index 923abae2..d306fb5d 100644 --- a/include/config.h +++ b/include/config.h @@ -344,12 +344,15 @@ public: } virtual void reset() override { this->recentMap = QString(); + this->recentLayout = QString(); this->useEncounterJson = true; this->customScripts.clear(); this->readKeys.clear(); } void setRecentMap(const QString &map); QString getRecentMap(); + void setRecentLayout(const QString &map); + QString getRecentLayout(); void setEncounterJsonActive(bool active); bool getEncounterJsonActive(); void setProjectDir(QString projectDir); @@ -371,6 +374,7 @@ protected: private: QString projectDir; QString recentMap; + QString recentLayout; bool useEncounterJson; QMap customScripts; QStringList readKeys; diff --git a/include/mainwindow.h b/include/mainwindow.h index 522bf0f2..d09fb917 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -357,10 +357,12 @@ private: bool tilesetNeedsRedraw = false; + bool setDefaultView(); + bool setRecentView(); bool setLayout(QString layoutId); - bool setMap(QString, bool scroll = false); void unsetMap(); + void redrawMapScene(); void redrawLayoutScene(); void refreshMapScene(); @@ -373,7 +375,9 @@ private: QString getExistingDirectory(QString); bool openProject(QString dir); QString getDefaultMap(); - void setRecentMap(QString map_name); + QString getDefaultLayout(); + void setRecentMapConfig(QString map_name); + void setRecentLayoutConfig(QString layoutId); void updateMapList(); diff --git a/src/config.cpp b/src/config.cpp index 9dddcd42..39c8e807 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1102,6 +1102,8 @@ QString UserConfig::getConfigFilepath() { void UserConfig::parseConfigKeyValue(QString key, QString value) { if (key == "recent_map") { this->recentMap = value; + } else if (key == "recent_layout") { + this->recentLayout = value; } else if (key == "use_encounter_json") { this->useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { @@ -1118,6 +1120,7 @@ void UserConfig::setUnreadKeys() { QMap UserConfig::getKeyValueMap() { QMap map; map.insert("recent_map", this->recentMap); + map.insert("recent_layout", this->recentLayout); map.insert("use_encounter_json", QString::number(this->useEncounterJson)); map.insert("custom_scripts", this->outputCustomScripts()); return map; @@ -1146,6 +1149,15 @@ QString UserConfig::getRecentMap() { return this->recentMap; } +void UserConfig::setRecentLayout(const QString &layout) { + this->recentLayout = layout; + this->save(); +} + +QString UserConfig::getRecentLayout() { + return this->recentLayout; +} + void UserConfig::setEncounterJsonActive(bool active) { this->useEncounterJson = active; this->save(); diff --git a/src/editor.cpp b/src/editor.cpp index 48b8ce4e..d5d48e6a 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1164,6 +1164,8 @@ bool Editor::setMap(QString map_name) { bool Editor::setLayout(QString layoutId) { // + if (layoutId.isEmpty()) return false; + this->layout = this->project->loadLayout(layoutId); if (!displayLayout()) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e9e1e912..02ae9ace 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -342,8 +342,8 @@ void MainWindow::initMiscHeapObjects() { // !TODO: scroll view on first showing void MainWindow::initMapSortOrder() { mapSortOrder = porymapConfig.getMapSortOrder(); - if (mapSortOrder == MapSortOrder::SortByLayout) - mapSortOrder = MapSortOrder::SortByGroup; + // if (mapSortOrder == MapSortOrder::SortByLayout) + // mapSortOrder = MapSortOrder::SortByGroup; this->ui->mapListContainer->setCurrentIndex(static_cast(this->mapSortOrder)); } @@ -541,16 +541,13 @@ bool MainWindow::openProject(QString dir) { this->preferenceEditor->updateFields(); }); editor->project->set_root(dir); - success = loadDataStructures() - && populateMapList() - && setMap(getDefaultMap(), true); + success = loadDataStructures() && populateMapList() && setDefaultView(); } else { - QString open_map = editor->map->name; editor->project->fileWatcher.removePaths(editor->project->fileWatcher.files()); editor->project->clearLayoutsTable(); editor->project->clearMapCache(); editor->project->clearTilesetCache(); - success = loadDataStructures() && populateMapList() && setMap(open_map, true); + success = loadDataStructures() && populateMapList() && setRecentView(); } projectOpenFailure = !success; @@ -581,6 +578,22 @@ bool MainWindow::isProjectOpen() { return !projectOpenFailure && editor && editor->project; } +bool MainWindow::setDefaultView() { + if (this->mapSortOrder == MapSortOrder::SortByLayout) { + return setLayout(getDefaultLayout()); + } else { + return setMap(getDefaultMap(), true); + } +} + +bool MainWindow::setRecentView() { + if (this->mapSortOrder == MapSortOrder::SortByLayout) { + return setLayout(userConfig.getRecentLayout()); + } else { + return setMap(userConfig.getRecentMap(), true); + } +} + QString MainWindow::getDefaultMap() { if (editor && editor->project) { QList names = editor->project->groupedMapNames; @@ -618,6 +631,18 @@ void MainWindow::openSubWindow(QWidget * window) { } } +QString MainWindow::getDefaultLayout() { + if (editor && editor->project) { + QString recentLayout = userConfig.getRecentLayout(); + if (!recentLayout.isEmpty() && editor->project->mapLayoutsTable.contains(recentLayout)) { + return recentLayout; + } else if (!editor->project->mapLayoutsTable.isEmpty()) { + return editor->project->mapLayoutsTable.first(); + } + } + return QString(); +} + QString MainWindow::getExistingDirectory(QString dir) { return QFileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly); } @@ -707,7 +732,7 @@ bool MainWindow::setMap(QString map_name, bool scroll) { connect(editor->layout, &Layout::layoutChanged, [this]() { onMapChanged(nullptr); }); connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing); - setRecentMap(map_name); + setRecentMapConfig(map_name); updateMapList(); Scripting::cb_MapOpened(map_name); @@ -741,6 +766,8 @@ bool MainWindow::setLayout(QString layoutId) { updateTilesetEditor(); + setRecentLayoutConfig(layoutId); + return true; } @@ -825,10 +852,14 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } } -void MainWindow::setRecentMap(QString mapName) { +void MainWindow::setRecentMapConfig(QString mapName) { userConfig.setRecentMap(mapName); } +void MainWindow::setRecentLayoutConfig(QString layoutId) { + userConfig.setRecentLayout(layoutId); +} + void MainWindow::displayMapProperties() { // Block signals to the comboboxes while they are being modified const QSignalBlocker blocker1(ui->comboBox_Song); From 2d2b7f723bc1439732732bfbd61d5f3101f558f2 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 22 Feb 2023 15:51:16 -0500 Subject: [PATCH 024/364] api util setMainTab ignores command when in layout only mode --- src/scriptapi/apiutility.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index 712a8171..1b8d8afe 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -144,6 +144,9 @@ void ScriptUtility::setMainTab(int index) { // Can't select Wild Encounters tab if it's disabled if (index == 4 && !userConfig.getEncounterJsonActive()) return; + // don't change tab when not editing a map + if (!window->editor || !window->editor->map) + return; window->on_mainTabBar_tabBarClicked(index); } From f4cd57c9887cdc5216d1ef3e1734e8b99a6b0055 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 7 Apr 2023 21:50:46 -0400 Subject: [PATCH 025/364] some code cleanup --- include/core/editcommands.h | 35 ++--- include/core/maplayout.h | 2 +- include/core/regionmapeditcommands.h | 4 +- include/editor.h | 19 +-- include/ui/layoutpixmapitem.h | 14 +- src/core/editcommands.cpp | 55 +++---- src/core/regionmapeditcommands.cpp | 10 +- src/editor.cpp | 226 ++++++++++++--------------- src/mainwindow.cpp | 10 +- src/project.cpp | 4 +- src/scriptapi/apimap.cpp | 4 +- src/ui/collisionpixmapitem.cpp | 4 +- src/ui/layoutpixmapitem.cpp | 4 +- src/ui/mapimageexporter.cpp | 4 +- src/ui/newmappopup.cpp | 1 - src/ui/regionmapeditor.cpp | 4 +- 16 files changed, 187 insertions(+), 213 deletions(-) diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 7d89a35a..691746c0 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -22,9 +22,9 @@ enum CommandId { ID_PaintCollision, ID_BucketFillCollision, ID_MagicFillCollision, - ID_ResizeMap, + ID_ResizeLayout, ID_PaintBorder, - ID_ScriptEditMap, + ID_ScriptEditLayout, ID_EventMove, ID_EventShift, ID_EventCreate, @@ -194,9 +194,9 @@ private: /// Implements a command to commit a map or border resize action. -class ResizeMap : public QUndoCommand { +class ResizeLayout : public QUndoCommand { public: - ResizeMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensions, + ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -206,15 +206,15 @@ public: void redo() override; bool mergeWith(const QUndoCommand *) override { return false; } - int id() const override { return CommandId::ID_ResizeMap; } + int id() const override { return CommandId::ID_ResizeLayout; } private: Layout *layout = nullptr; - int oldMapWidth; - int oldMapHeight; - int newMapWidth; - int newMapHeight; + int oldLayoutWidth; + int oldLayoutHeight; + int newLayoutWidth; + int newLayoutHeight; int oldBorderWidth; int oldBorderHeight; @@ -342,13 +342,12 @@ public: -// !TODO: rename map vars to layout /// Implements a command to commit map edits from the scripting API. /// The scripting api can edit map/border blocks and dimensions. -class ScriptEditMap : public QUndoCommand { +class ScriptEditLayout : public QUndoCommand { public: - ScriptEditMap(Layout *layout, - QSize oldMapDimensions, QSize newMapDimensions, + ScriptEditLayout(Layout *layout, + QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -358,7 +357,7 @@ public: void redo() override; bool mergeWith(const QUndoCommand *) override { return false; } - int id() const override { return CommandId::ID_ScriptEditMap; } + int id() const override { return CommandId::ID_ScriptEditLayout; } private: Layout *layout = nullptr; @@ -369,10 +368,10 @@ private: Blockdata newBorder; Blockdata oldBorder; - int oldMapWidth; - int oldMapHeight; - int newMapWidth; - int newMapHeight; + int oldLayoutWidth; + int oldLayoutHeight; + int newLayoutWidth; + int newLayoutHeight; int oldBorderWidth; int oldBorderHeight; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index c17248ce..7ec240b6 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -55,7 +55,7 @@ public: Blockdata cached_border; struct { Blockdata blocks; - QSize mapDimensions; + QSize layoutDimensions; Blockdata border; QSize borderDimensions; } lastCommitBlocks; // to track map changes diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index 69bea251..e142c5cc 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -64,9 +64,9 @@ private: /// Edit Layout Dimensions -class ResizeLayout : public QUndoCommand { +class ResizeRMLayout : public QUndoCommand { public: - ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, + ResizeRMLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, QMap> oldLayouts, QMap> newLayouts, QUndoCommand *parent = nullptr); void undo() override; diff --git a/include/editor.h b/include/editor.h index d6d973b5..c3e0c5ff 100644 --- a/include/editor.h +++ b/include/editor.h @@ -46,8 +46,8 @@ public: QObject *parent = nullptr; Project *project = nullptr; - QPointer map = nullptr; // !TODO: since removed onMapCacheCleared, make sure this works as intended - QPointer layout = nullptr; /* NEW */ + QPointer map = nullptr; + QPointer layout = nullptr; QUndoGroup editGroup; // Manages the undo history for each map @@ -118,8 +118,6 @@ public: void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); - - QGraphicsScene *scene = nullptr; QGraphicsPixmapItem *current_view = nullptr; LayoutPixmapItem *map_item = nullptr; @@ -154,15 +152,18 @@ public: EditAction mapEditAction = EditAction::Paint; EditAction objectEditAction = EditAction::Select; - /// !TODO this - enum class EditMode { None, Disabled, Map, Layout, Objects, Connections, Encounters }; - EditMode editMode = EditMode::Map; + enum class EditMode { None, Disabled, Metatiles, Collision, Header, Events, Connections, Encounters }; + EditMode editMode = EditMode::None; void setEditMode(EditMode mode) { this->editMode = mode; } EditMode getEditMode() { return this->editMode; } - void setEditingMap(); + bool getEditingLayout(); + + void setEditorView(); + + void setEditingMetatiles(); void setEditingCollision(); - void setEditingLayout(); + void setEditingHeader(); void setEditingObjects(); void setEditingConnections(); void setEditingEncounters(); diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h index ab4d94a5..08496c54 100644 --- a/include/ui/layoutpixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -14,25 +14,16 @@ private: using QGraphicsPixmapItem::paint; public: - enum class PaintMode { - Disabled, - Metatiles, - EventObjects - }; - LayoutPixmapItem(Layout *layout, MetatileSelector *metatileSelector, Settings *settings) { this->layout = layout; // this->map->setMapItem(this); this->metatileSelector = metatileSelector; this->settings = settings; - this->paintingMode = PaintMode::Metatiles; this->lockedAxis = LayoutPixmapItem::Axis::None; this->prevStraightPathState = false; setAcceptHoverEvents(true); } - LayoutPixmapItem::PaintMode paintingMode; - Layout *layout; MetatileSelector *metatileSelector; @@ -95,12 +86,17 @@ public: void lockNondominantAxis(QGraphicsSceneMouseEvent *event); QPoint adjustCoords(QPoint pos); + void setEditsEnabled(bool enabled) { this->editsEnabled = enabled; } + bool getEditsEnabled() { return this->editsEnabled; } + private: void paintSmartPath(int x, int y, bool fromScriptCall = false); static QList smartPathTable; unsigned actionId_ = 0; + bool editsEnabled = true; + signals: void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index a2096905..22227b42 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -24,7 +24,6 @@ int getEventTypeMask(QList events) { return eventTypeMask; } -/// !TODO: void renderBlocks(Layout *layout, bool ignoreCache = false) { layout->layoutItem->draw(ignoreCache); layout->collisionItem->draw(ignoreCache); @@ -178,7 +177,7 @@ bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -ResizeMap::ResizeMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensions, +ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -187,11 +186,11 @@ ResizeMap::ResizeMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensi this->layout = layout; - this->oldMapWidth = oldMapDimensions.width(); - this->oldMapHeight = oldMapDimensions.height(); + this->oldLayoutWidth = oldLayoutDimensions.width(); + this->oldLayoutHeight = oldLayoutDimensions.height(); - this->newMapWidth = newMapDimensions.width(); - this->newMapHeight = newMapDimensions.height(); + this->newLayoutWidth = newLayoutDimensions.width(); + this->newLayoutHeight = newLayoutDimensions.height(); this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -206,33 +205,33 @@ ResizeMap::ResizeMap(Layout *layout, QSize oldMapDimensions, QSize newMapDimensi this->newBorder = newBorder; } -void ResizeMap::redo() { +void ResizeLayout::redo() { QUndoCommand::redo(); if (!layout) return; layout->blockdata = newMetatiles; - layout->setDimensions(newMapWidth, newMapHeight, false, true); + layout->setDimensions(newLayoutWidth, newLayoutHeight, false, true); layout->border = newBorder; layout->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); - layout->lastCommitBlocks.mapDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); layout->needsRedrawing(); } -void ResizeMap::undo() { +void ResizeLayout::undo() { if (!layout) return; layout->blockdata = oldMetatiles; - layout->setDimensions(oldMapWidth, oldMapHeight, false, true); + layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false, true); layout->border = oldBorder; layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); - layout->lastCommitBlocks.mapDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); layout->needsRedrawing(); @@ -487,23 +486,23 @@ int EventPaste::id() const { ************************************************************************ ******************************************************************************/ -ScriptEditMap::ScriptEditMap(Layout *layout, - QSize oldMapDimensions, QSize newMapDimensions, +ScriptEditLayout::ScriptEditLayout(Layout *layout, + QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, QUndoCommand *parent) : QUndoCommand(parent) { - setText("Script Edit Map"); + setText("Script Edit Layout"); this->layout = layout; this->newMetatiles = newMetatiles; this->oldMetatiles = oldMetatiles; - this->oldMapWidth = oldMapDimensions.width(); - this->oldMapHeight = oldMapDimensions.height(); - this->newMapWidth = newMapDimensions.width(); - this->newMapHeight = newMapDimensions.height(); + this->oldLayoutWidth = oldLayoutDimensions.width(); + this->oldLayoutHeight = oldLayoutDimensions.height(); + this->newLayoutWidth = newLayoutDimensions.width(); + this->newLayoutHeight = newLayoutDimensions.height(); this->oldBorder = oldBorder; this->newBorder = newBorder; @@ -514,14 +513,14 @@ ScriptEditMap::ScriptEditMap(Layout *layout, this->newBorderHeight = newBorderDimensions.height(); } -void ScriptEditMap::redo() { +void ScriptEditLayout::redo() { QUndoCommand::redo(); if (!layout) return; - if (newMapWidth != layout->getWidth() || newMapHeight != layout->getHeight()) { + if (newLayoutWidth != layout->getWidth() || newLayoutHeight != layout->getHeight()) { layout->blockdata = newMetatiles; - layout->setDimensions(newMapWidth, newMapHeight, false); + layout->setDimensions(newLayoutWidth, newLayoutHeight, false); } else { layout->setBlockdata(newMetatiles); } @@ -534,21 +533,20 @@ void ScriptEditMap::redo() { } layout->lastCommitBlocks.blocks = newMetatiles; - layout->lastCommitBlocks.mapDimensions = QSize(newMapWidth, newMapHeight); + layout->lastCommitBlocks.layoutDimensions = QSize(newLayoutWidth, newLayoutHeight); layout->lastCommitBlocks.border = newBorder; layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); - // !TODO renderBlocks(layout); layout->borderItem->draw(); } -void ScriptEditMap::undo() { +void ScriptEditLayout::undo() { if (!layout) return; - if (oldMapWidth != layout->getWidth() || oldMapHeight != layout->getHeight()) { + if (oldLayoutWidth != layout->getWidth() || oldLayoutHeight != layout->getHeight()) { layout->blockdata = oldMetatiles; - layout->setDimensions(oldMapWidth, oldMapHeight, false); + layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false); } else { layout->setBlockdata(oldMetatiles); } @@ -561,11 +559,10 @@ void ScriptEditMap::undo() { } layout->lastCommitBlocks.blocks = oldMetatiles; - layout->lastCommitBlocks.mapDimensions = QSize(oldMapWidth, oldMapHeight); + layout->lastCommitBlocks.layoutDimensions = QSize(oldLayoutWidth, oldLayoutHeight); layout->lastCommitBlocks.border = oldBorder; layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); - // !TODO renderBlocks(layout); layout->borderItem->draw(); diff --git a/src/core/regionmapeditcommands.cpp b/src/core/regionmapeditcommands.cpp index e718d596..1be247b0 100644 --- a/src/core/regionmapeditcommands.cpp +++ b/src/core/regionmapeditcommands.cpp @@ -90,7 +90,7 @@ bool EditLayout::mergeWith(const QUndoCommand *command) { /// -ResizeLayout::ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, +ResizeRMLayout::ResizeRMLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, QMap> oldLayouts, QMap> newLayouts, QUndoCommand *parent) : QUndoCommand(parent) { setText("Change Layout Dimensions"); @@ -104,7 +104,7 @@ ResizeLayout::ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newW this->newLayouts = newLayouts; } -void ResizeLayout::redo() { +void ResizeRMLayout::redo() { QUndoCommand::redo(); if (!map) return; @@ -113,7 +113,7 @@ void ResizeLayout::redo() { map->setAllLayouts(this->newLayouts); } -void ResizeLayout::undo() { +void ResizeRMLayout::undo() { if (!map) return; map->setLayoutDimensions(oldWidth, oldHeight, false); @@ -122,8 +122,8 @@ void ResizeLayout::undo() { QUndoCommand::undo(); } -bool ResizeLayout::mergeWith(const QUndoCommand *command) { - const ResizeLayout *other = static_cast(command); +bool ResizeRMLayout::mergeWith(const QUndoCommand *command) { + const ResizeRMLayout *other = static_cast(command); if (this->map != other->map) return false; diff --git a/src/editor.cpp b/src/editor.cpp index d5d48e6a..06cc771f 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -83,130 +83,119 @@ void Editor::closeProject() { } } -void Editor::setEditingMap() { - current_view = map_item; - if (map_item) { - map_item->paintingMode = LayoutPixmapItem::PaintMode::Metatiles; - displayMapConnections(); - map_item->draw(); - map_item->setVisible(true); - } - if (collision_item) { - collision_item->setVisible(false); - } - if (events_group) { - events_group->setVisible(false); - } - setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked()); - setConnectionItemsVisible(ui->checkBox_ToggleBorder->isChecked()); - setConnectionsEditable(false); - this->cursorMapTileRect->stopSingleTileMode(); - this->cursorMapTileRect->setActive(true); - - if (this->layout) { - this->editGroup.setActiveStack(&this->layout->editHistory); - } - - setMapEditingButtonsEnabled(true); +bool Editor::getEditingLayout() { + return this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision; } -void Editor::setEditingLayout() { - // -} +void Editor::setEditorView() { + // based on editMode + if (!map_item || !collision_item) return; + if (!this->layout) return; -void Editor::setEditingCollision() { - current_view = collision_item; - if (collision_item) { - displayMapConnections(); - collision_item->draw(); - collision_item->setVisible(true); - } - if (map_item) { - map_item->paintingMode = LayoutPixmapItem::PaintMode::Metatiles; - map_item->draw(); - map_item->setVisible(true); - } - if (events_group) { - events_group->setVisible(false); + map_item->setVisible(true); // is map item ever not visible + collision_item->setVisible(false); + + switch (this->editMode) { + case EditMode::Metatiles: + case EditMode::Connections: + case EditMode::Events: + current_view = map_item; + break; + case EditMode::Collision: + current_view = collision_item; + break; + default: + current_view = nullptr; + return; } + + map_item->draw(); + collision_item->draw(); + displayMapConnections(); + + current_view->setVisible(true); + setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked()); setConnectionItemsVisible(ui->checkBox_ToggleBorder->isChecked()); setConnectionsEditable(false); this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(true); - if (this->layout) { + switch (this->editMode) { + case EditMode::Metatiles: + case EditMode::Collision: this->editGroup.setActiveStack(&this->layout->editHistory); - } - - setMapEditingButtonsEnabled(true); -} - -void Editor::setEditingObjects() { - current_view = map_item; - if (events_group) { - events_group->setVisible(true); - } - if (map_item) { - // !TODO: change this pixmapitem paintmode - map_item->paintingMode = LayoutPixmapItem::PaintMode::EventObjects; - displayMapConnections(); - map_item->draw(); - map_item->setVisible(true); - } - if (collision_item) { - collision_item->setVisible(false); - } - setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked()); - setConnectionItemsVisible(ui->checkBox_ToggleBorder->isChecked()); - setConnectionsEditable(false); - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(false); - - if (this->map) { - this->editGroup.setActiveStack(&this->map->editHistory); - } - - setMapEditingButtonsEnabled(false); -} - -void Editor::setEditingConnections() { - current_view = map_item; - if (map_item) { - map_item->paintingMode = LayoutPixmapItem::PaintMode::Disabled; - map_item->draw(); - map_item->setVisible(true); - populateConnectionMapPickers(); + break; + case EditMode::Events: + if (this->map) { + this->editGroup.setActiveStack(&this->map->editHistory); + } + break; + case EditMode::Connections: + populateConnectionMapPickers(); // !TODO: move to setmap or sumn/ displaymapconnections type ish ui->label_NumConnections->setText(QString::number(map->connections.length())); setDiveEmergeControls(); - bool controlsEnabled = selected_connection_item != nullptr; - setConnectionEditControlsEnabled(controlsEnabled); + + setConnectionEditControlsEnabled(selected_connection_item != nullptr); if (selected_connection_item) { onConnectionOffsetChanged(selected_connection_item->connection->offset); setConnectionMap(selected_connection_item->connection->map_name); setCurrentConnectionDirection(selected_connection_item->connection->direction); } maskNonVisibleConnectionTiles(); - } - if (collision_item) { - collision_item->setVisible(false); - } - if (events_group) { - events_group->setVisible(false); - } - setBorderItemsVisible(true, 0.4); - setConnectionItemsVisible(true); - setConnectionsEditable(true); - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(false); - if (this->map) { - this->editGroup.setActiveStack(&this->map->editHistory); + setBorderItemsVisible(true, 0.4); + setConnectionItemsVisible(true); + setConnectionsEditable(true); + this->cursorMapTileRect->setActive(false); + map_item->setEditsEnabled(false); // !TODO + case EditMode::Header: + case EditMode::Encounters: + default: + this->editGroup.setActiveStack(nullptr); + break; } + + if (this->events_group) { + this->events_group->setVisible(this->editMode == EditMode::Events); + } + setMapEditingButtonsEnabled(this->editMode != EditMode::Events); +} + +void Editor::setEditingMetatiles() { + this->editMode = EditMode::Metatiles; + + setEditorView(); +} + +void Editor::setEditingCollision() { + this->editMode = EditMode::Collision; + + setEditorView(); +} + +void Editor::setEditingHeader() { + this->editMode = EditMode::Header; + + setEditorView(); +} + +void Editor::setEditingObjects() { + this->editMode = EditMode::Events; + + setEditorView(); +} + +void Editor::setEditingConnections() { + this->editMode = EditMode::Connections; + + setEditorView(); } void Editor::setEditingEncounters() { - // + this->editMode = EditMode::Encounters; + + setEditorView(); } void Editor::setMapEditingButtonsEnabled(bool enabled) { @@ -1048,7 +1037,7 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { return; this->updateCursorRectPos(x, y); - if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { + if (this->getEditingLayout()) { int blockIndex = y * layout->getWidth() + x; int metatileId = layout->blockdata.at(blockIndex).metatileId; this->ui->statusBar->showMessage(QString("X: %1, Y: %2, %3, Scale = %4x") @@ -1057,19 +1046,19 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { .arg(getMetatileDisplayMessage(metatileId)) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); } - else if (map_item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { + else if (this->editMode == EditMode::Events) { this->ui->statusBar->showMessage(QString("X: %1, Y: %2, Scale = %3x") .arg(x) .arg(y) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); } + Scripting::cb_BlockHoverChanged(x, y); } void Editor::onHoveredMapMetatileCleared() { this->setCursorRectVisible(false); - if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles - || map_item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { + if (!map_item->getEditsEnabled()) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); @@ -1080,7 +1069,7 @@ void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { return; this->updateCursorRectPos(x, y); - if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { + if (this->getEditingLayout()) { int blockIndex = y * layout->getWidth() + x; uint16_t collision = layout->blockdata.at(blockIndex).collision; uint16_t elevation = layout->blockdata.at(blockIndex).elevation; @@ -1095,7 +1084,7 @@ void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { void Editor::onHoveredMapMovementPermissionCleared() { this->setCursorRectVisible(false); - if (map_item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { + if (this->getEditingLayout()) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); @@ -1142,14 +1131,11 @@ bool Editor::setMap(QString map_name) { this->map = loadedMap; - // remove this - //this->layout = this->map->layout; setLayout(map->layout->id); editGroup.addStack(&map->editHistory); - - // !TODO: determine which stack is active based on edit mode too since layout will have something different editGroup.setActiveStack(&map->editHistory); + selected_events->clear(); if (!displayMap()) { return false; @@ -1163,7 +1149,6 @@ bool Editor::setMap(QString map_name) { } bool Editor::setLayout(QString layoutId) { - // if (layoutId.isEmpty()) return false; this->layout = this->project->loadLayout(layoutId); @@ -1172,8 +1157,6 @@ bool Editor::setLayout(QString layoutId) { return false; } - // !TODO: editGroup addStack - editGroup.addStack(&layout->editHistory); map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); @@ -1195,7 +1178,7 @@ bool Editor::setLayout(QString layoutId) { } void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { - if (item->paintingMode != LayoutPixmapItem::PaintMode::Metatiles) { + if (!this->getEditingLayout()) { return; } @@ -1208,7 +1191,7 @@ void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem * } void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *item) { - if (!(item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles)) { + if (!this->getEditingLayout()) { return; } this->cursorMapTileRect->stopRightClickSelectionAnchor(); @@ -1243,13 +1226,13 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { // TODO: add event tab object painting tool buttons stuff here - if (item->paintingMode == LayoutPixmapItem::PaintMode::Disabled) { + if (!item->getEditsEnabled()) { return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (item->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { + if (this->getEditingLayout()) { if (mapEditAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); @@ -1296,7 +1279,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } item->shift(event); } - } else if (item->paintingMode == LayoutPixmapItem::PaintMode::EventObjects) { + } else if (this->editMode == EditMode::Events) { if (objectEditAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { // Right-clicking while in paint mode will change mode to select. if (event->buttons() & Qt::RightButton) { @@ -1354,7 +1337,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item) { - if (item->paintingMode != LayoutPixmapItem::PaintMode::Metatiles) { + if (!item->getEditsEnabled()) { return; } @@ -1426,7 +1409,6 @@ bool Editor::displayLayout() { scene->removeItem(this->map_ruler); } - // !TODO: disassociate these functions from Map displayMetatileSelector(); displayMapMetatiles(); displayMovementPermissionSelector(); @@ -2116,7 +2098,7 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { } void Editor::duplicateSelectedEvents() { - if (!selected_events || !selected_events->length() || !map || !current_view || map_item->paintingMode != LayoutPixmapItem::PaintMode::EventObjects) + if (!selected_events || !selected_events->length() || !map || !current_view || this->getEditingLayout()) return; QList selectedEvents; @@ -2288,7 +2270,7 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working // is clicking on the background instead of an event. void Editor::objectsView_onMousePress(QMouseEvent *event) { // make sure we are in object editing mode - if (map_item && map_item->paintingMode != LayoutPixmapItem::PaintMode::EventObjects) { + if (map_item && this->editMode != EditMode::Events) { return; } if (this->objectEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 02ae9ace..5844afaa 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1780,11 +1780,11 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) Scripting::cb_MapViewTabChanged(oldIndex, index); if (index == 0) { - editor->setEditingMap(); + editor->setEditingMetatiles(); } else if (index == 1) { editor->setEditingCollision(); } else if (index == 2) { - editor->setEditingMap(); + editor->setEditingMetatiles(); if (projectConfig.getPrefabFilepath().isEmpty() && !projectConfig.getPrefabImportPrompted()) { // User hasn't set up prefabs and hasn't been prompted before. // Ask if they'd like to import the default prefabs file. @@ -1802,8 +1802,6 @@ void MainWindow::on_action_Exit_triggered() void MainWindow::on_mainTabBar_tabBarClicked(int index) { - //if (!editor->map) return; - int oldIndex = ui->mainTabBar->currentIndex(); ui->mainTabBar->setCurrentIndex(index); if (index != oldIndex) @@ -1822,6 +1820,8 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) clickToolButtonFromEditAction(editor->objectEditAction); } else if (index == 3) { editor->setEditingConnections(); + } else if (index == 4) { + editor->setEditingEncounters(); } if (!editor->map) return; @@ -2727,7 +2727,7 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { if (oldMapDimensions != newMapDimensions || oldBorderDimensions != newBorderDimensions) { layout->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); layout->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); - editor->layout->editHistory.push(new ResizeMap(layout, + editor->layout->editHistory.push(new ResizeLayout(layout, oldMapDimensions, newMapDimensions, oldMetatiles, layout->blockdata, oldBorderDimensions, newBorderDimensions, diff --git a/src/project.cpp b/src/project.cpp index 0dfb7763..09657955 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1126,7 +1126,7 @@ bool Project::loadBlockdata(Layout *layout) { QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); layout->blockdata = readBlockdata(path); layout->lastCommitBlocks.blocks = layout->blockdata; - layout->lastCommitBlocks.mapDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) { logWarn(QString("Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata.") @@ -1148,7 +1148,7 @@ void Project::setNewMapBlockdata(Map *map) { map->layout->blockdata.append(block); } map->layout->lastCommitBlocks.blocks = map->layout->blockdata; - map->layout->lastCommitBlocks.mapDimensions = QSize(width, height); + map->layout->lastCommitBlocks.layoutDimensions = QSize(width, height); } bool Project::loadLayoutBorder(Layout *layout) { diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 155d3492..ea18e0cc 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -44,8 +44,8 @@ void MainWindow::tryCommitMapChanges(bool commitChanges) { if (commitChanges) { Layout *layout = this->editor->layout; if (layout) { - layout->editHistory.push(new ScriptEditMap(layout, - layout->lastCommitBlocks.mapDimensions, QSize(layout->getWidth(), layout->getHeight()), + layout->editHistory.push(new ScriptEditLayout(layout, + layout->lastCommitBlocks.layoutDimensions, QSize(layout->getWidth(), layout->getHeight()), layout->lastCommitBlocks.blocks, layout->blockdata, layout->lastCommitBlocks.borderDimensions, QSize(layout->getBorderWidth(), layout->getBorderHeight()), layout->lastCommitBlocks.border, layout->border diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index 0c809c3f..3a3c62a0 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -8,7 +8,7 @@ void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { this->previousPos = pos; emit this->hoveredMapMovementPermissionChanged(pos.x(), pos.y()); } - if (this->settings->betterCursors && this->paintingMode == LayoutPixmapItem::PaintMode::Metatiles) { + if (this->settings->betterCursors && this->getEditsEnabled()) { setCursor(this->settings->mapCursor); } } @@ -21,7 +21,7 @@ void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { void CollisionPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { emit this->hoveredMapMovementPermissionCleared(); - if (this->settings->betterCursors && this->paintingMode == LayoutPixmapItem::PaintMode::Metatiles){ + if (this->settings->betterCursors && this->getEditsEnabled()){ unsetCursor(); } this->has_mouse = false; diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index a595695a..93489c7e 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -694,7 +694,7 @@ void LayoutPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { this->metatilePos = pos; emit this->hoveredMapMetatileChanged(pos); } - if (this->settings->betterCursors && this->paintingMode != LayoutPixmapItem::PaintMode::Disabled) { + if (this->settings->betterCursors && this->editsEnabled) { setCursor(this->settings->mapCursor); } } @@ -707,7 +707,7 @@ void LayoutPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { emit this->hoveredMapMetatileCleared(); - if (this->settings->betterCursors && this->paintingMode != LayoutPixmapItem::PaintMode::Disabled) { + if (this->settings->betterCursors && this->editsEnabled) { unsetCursor(); } this->has_mouse = false; diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index e81208b8..66c0dd0b 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -183,8 +183,8 @@ bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) { case CommandId::ID_BucketFillMetatile: case CommandId::ID_MagicFillMetatile: case CommandId::ID_ShiftMetatiles: - case CommandId::ID_ResizeMap: - case CommandId::ID_ScriptEditMap: + case CommandId::ID_ResizeLayout: + case CommandId::ID_ScriptEditLayout: return true; case CommandId::ID_PaintCollision: case CommandId::ID_BucketFillCollision: diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index 0b13c474..3da703a7 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -94,7 +94,6 @@ void NewMapPopup::init() { ui->spinBox_NewMap_Floor_Number->setValue(settings.floorNumber); // Connect signals - // !TODO: make sure this doesnt reconnect a million times connect(ui->spinBox_NewMap_Width, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); connect(ui->spinBox_NewMap_Height, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 39d79ca7..74cbac4a 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -1101,7 +1101,7 @@ void RegionMapEditor::on_spinBox_RM_LayoutWidth_valueChanged(int value) { int newHeight = this->region_map->layoutHeight(); QMap> newLayouts = this->region_map->getAllLayouts(); - ResizeLayout *commit = new ResizeLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); + ResizeRMLayout *commit = new ResizeRMLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); this->region_map->editHistory.push(commit); } } @@ -1118,7 +1118,7 @@ void RegionMapEditor::on_spinBox_RM_LayoutHeight_valueChanged(int value) { int newHeight = this->region_map->layoutHeight(); QMap> newLayouts = this->region_map->getAllLayouts(); - ResizeLayout *commit = new ResizeLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); + ResizeRMLayout *commit = new ResizeRMLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); this->region_map->editHistory.push(commit); } } From c0a46ae05472e807639fb77cc2c6f21dd5430556 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 27 Apr 2023 14:35:22 -0400 Subject: [PATCH 026/364] fix layout redraw when changing used tileset --- src/mainwindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5844afaa..19bb0d9d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2628,9 +2628,9 @@ void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) { - if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->map) { + if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updatePrimaryTileset(tilesetLabel); - redrawMapScene(); + redrawLayoutScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); @@ -2640,9 +2640,9 @@ void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &ti void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString &tilesetLabel) { - if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->map) { + if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updateSecondaryTileset(tilesetLabel); - redrawMapScene(); + redrawLayoutScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); From 3f7913b69468317c63c176201a9d4d65c0cecee7 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 23 Jun 2023 13:51:52 -0400 Subject: [PATCH 027/364] fix segfault in map image exporter --- src/ui/mapimageexporter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 66c0dd0b..c6aef0cd 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -98,8 +98,11 @@ void MapImageExporter::saveImage() { progress.setMaximum(1); progress.setValue(0); - int maxWidth = this->map->getWidth() * 16; - int maxHeight = this->map->getHeight() * 16; + Layout *layout = this->map->layout; + if (!layout) break; + + int maxWidth = layout->getWidth() * 16; + int maxHeight = layout->getHeight() * 16; if (showBorder) { maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; From 95c21a4572824d7e9900467f16ab071557a0204e Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 28 Sep 2023 19:56:54 -0400 Subject: [PATCH 028/364] do not show nonexistent map sections --- src/ui/maplistmodels.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index e3dfba14..277147e0 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -268,7 +268,6 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { } } else if (role == Qt::DisplayRole) { - // QStandardItem *item = this->getItem(index)->child(row, col); QString type = item->data(MapListRoles::TypeRole).toString(); @@ -342,7 +341,9 @@ QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, in void MapAreaModel::initialize() { this->areaItems.clear(); this->mapItems.clear(); - for (int i = 0; i < this->project->mapSectionNameToValue.size(); i++) { + this->setSortRole(MapListRoles::GroupRole); + + for (int i : this->project->mapSectionNameToValue) { QString mapsecName = project->mapSectionValueToName.value(i); QStandardItem *areaItem = createAreaItem(mapsecName, i); this->root->appendRow(areaItem); @@ -359,6 +360,7 @@ void MapAreaModel::initialize() { } } } + this->sort(0, Qt::AscendingOrder); } QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const { @@ -422,6 +424,16 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { return mapGrayIcon; } } + else if (role == Qt::DisplayRole) { + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListRoles::TypeRole).toString(); + + if (type == "map_section") { + return QString("[0x%1] %2") + .arg(QString("%1").arg(item->data(MapListRoles::GroupRole).toInt(), 2, 16, QLatin1Char('0')).toUpper()) + .arg(item->data(Qt::UserRole).toString()); + } + } return QStandardItemModel::data(index, role); } From 46ada327331116f6cc5fac4eec29daf6c3c7d853 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 25 Oct 2023 12:25:19 -0400 Subject: [PATCH 029/364] fix map tab icon --- src/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 19bb0d9d..39494686 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -365,6 +365,7 @@ void MainWindow::showWindowTitle() { ); } if (editor && editor->layout) { + ui->mainTabBar->setTabIcon(0, QIcon()); QPixmap pixmap = editor->layout->pixmap; if (!pixmap.isNull()) { ui->mainTabBar->setTabIcon(0, QIcon(pixmap)); From 6041c46abf6ca067ad970a7857bdee7671de461a Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 26 Oct 2023 14:13:32 -0400 Subject: [PATCH 030/364] fix scripting api usage of map/layout pointers --- src/scriptapi/apimap.cpp | 62 ++++++++++++++++++------------------ src/scriptapi/apioverlay.cpp | 9 ++---- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index ea18e0cc..3dcb8071 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -24,7 +24,7 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) { this->editor->updateMapBorder(); this->editor->updateMapConnections(); if (this->tilesetEditor) - this->tilesetEditor->updateTilesets(this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label); + this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label); if (this->editor->metatile_selector_item) this->editor->metatile_selector_item->draw(); if (this->editor->selected_border_metatiles_item) @@ -341,7 +341,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { } void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors) { - if (!this->editor || !this->editor->map || !this->editor->layout) + if (!this->editor || !this->editor->layout) return; if (paletteIndex >= tileset->palettes.size()) return; @@ -357,7 +357,7 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; this->setTilesetPalette(this->editor->layout->tileset_primary, paletteIndex, colors); if (forceRedraw) { @@ -366,7 +366,7 @@ void MainWindow::setPrimaryTilesetPalette(int paletteIndex, QList> co } void MainWindow::setPrimaryTilesetPalettes(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; for (int i = 0; i < palettes.size(); i++) { this->setTilesetPalette(this->editor->layout->tileset_primary, i, palettes[i]); @@ -377,7 +377,7 @@ void MainWindow::setPrimaryTilesetPalettes(QList>> palettes, bo } void MainWindow::setSecondaryTilesetPalette(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; this->setTilesetPalette(this->editor->layout->tileset_secondary, paletteIndex, colors); if (forceRedraw) { @@ -386,7 +386,7 @@ void MainWindow::setSecondaryTilesetPalette(int paletteIndex, QList> } void MainWindow::setSecondaryTilesetPalettes(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; for (int i = 0; i < palettes.size(); i++) { this->setTilesetPalette(this->editor->layout->tileset_secondary, i, palettes[i]); @@ -420,25 +420,25 @@ QJSValue MainWindow::getTilesetPalettes(const QList> &palettes) { } QJSValue MainWindow::getPrimaryTilesetPalette(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); return this->getTilesetPalette(this->editor->layout->tileset_primary->palettes, paletteIndex); } QJSValue MainWindow::getPrimaryTilesetPalettes() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); return this->getTilesetPalettes(this->editor->layout->tileset_primary->palettes); } QJSValue MainWindow::getSecondaryTilesetPalette(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); return this->getTilesetPalette(this->editor->layout->tileset_secondary->palettes, paletteIndex); } QJSValue MainWindow::getSecondaryTilesetPalettes() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); return this->getTilesetPalettes(this->editor->layout->tileset_secondary->palettes); } @@ -452,7 +452,7 @@ void MainWindow::refreshAfterPalettePreviewChange() { } void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QList> colors) { - if (!this->editor || !this->editor->map || !this->editor->layout) + if (!this->editor || !this->editor->layout) return; if (paletteIndex >= tileset->palettePreviews.size()) return; @@ -467,7 +467,7 @@ void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QL } void MainWindow::setPrimaryTilesetPalettePreview(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; this->setTilesetPalettePreview(this->editor->layout->tileset_primary, paletteIndex, colors); if (forceRedraw) { @@ -476,7 +476,7 @@ void MainWindow::setPrimaryTilesetPalettePreview(int paletteIndex, QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; for (int i = 0; i < palettes.size(); i++) { this->setTilesetPalettePreview(this->editor->layout->tileset_primary, i, palettes[i]); @@ -487,7 +487,7 @@ void MainWindow::setPrimaryTilesetPalettesPreview(QList>> palet } void MainWindow::setSecondaryTilesetPalettePreview(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; this->setTilesetPalettePreview(this->editor->layout->tileset_secondary, paletteIndex, colors); if (forceRedraw) { @@ -496,7 +496,7 @@ void MainWindow::setSecondaryTilesetPalettePreview(int paletteIndex, QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; for (int i = 0; i < palettes.size(); i++) { this->setTilesetPalettePreview(this->editor->layout->tileset_secondary, i, palettes[i]); @@ -507,61 +507,61 @@ void MainWindow::setSecondaryTilesetPalettesPreview(QList>> pal } QJSValue MainWindow::getPrimaryTilesetPalettePreview(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); return this->getTilesetPalette(this->editor->layout->tileset_primary->palettePreviews, paletteIndex); } QJSValue MainWindow::getPrimaryTilesetPalettesPreview() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); return this->getTilesetPalettes(this->editor->layout->tileset_primary->palettePreviews); } QJSValue MainWindow::getSecondaryTilesetPalettePreview(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); return this->getTilesetPalette(this->editor->layout->tileset_secondary->palettePreviews, paletteIndex); } QJSValue MainWindow::getSecondaryTilesetPalettesPreview() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); return this->getTilesetPalettes(this->editor->layout->tileset_secondary->palettePreviews); } int MainWindow::getNumPrimaryTilesetMetatiles() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; return this->editor->layout->tileset_primary->metatiles.length(); } int MainWindow::getNumSecondaryTilesetMetatiles() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; return this->editor->layout->tileset_secondary->metatiles.length(); } int MainWindow::getNumPrimaryTilesetTiles() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; return this->editor->layout->tileset_primary->tiles.length(); } int MainWindow::getNumSecondaryTilesetTiles() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; return this->editor->layout->tileset_secondary->tiles.length(); } QString MainWindow::getPrimaryTileset() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QString(); return this->editor->layout->tileset_primary->name; } QString MainWindow::getSecondaryTileset() { - if (!this->editor || !this->editor->map || !this->editor->layout || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QString(); return this->editor->layout->tileset_secondary->name; } @@ -591,19 +591,19 @@ void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { } Metatile * MainWindow::getMetatile(int metatileId) { - if (!this->editor || !this->editor->map || !this->editor->layout) + if (!this->editor || !this->editor->layout) return nullptr; return Tileset::getMetatile(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } QString MainWindow::getMetatileLabel(int metatileId) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->layout) return QString(); - return Tileset::getMetatileLabel(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + return Tileset::getMetatileLabel(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } void MainWindow::setMetatileLabel(int metatileId, QString label) { - if (!this->editor || !this->editor->map || !this->editor->layout) + if (!this->editor || !this->editor->layout) return; // If the Tileset Editor is opened on this metatile we need to update the text box @@ -612,13 +612,13 @@ void MainWindow::setMetatileLabel(int metatileId, QString label) { return; } - if (!Tileset::setMetatileLabel(metatileId, label, this->editor->layout->tileset_primary, this->editor->map->layout->tileset_secondary)) { + if (!Tileset::setMetatileLabel(metatileId, label, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary)) { logError("Failed to set metatile label. Must be a valid metatile id and a label containing only letters, numbers, and underscores."); return; } if (this->editor->project) - this->editor->project->saveTilesetMetatileLabels(this->editor->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + this->editor->project->saveTilesetMetatileLabels(this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } int MainWindow::getMetatileLayerType(int metatileId) { diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index 7e634fab..b12f5f09 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -254,8 +254,7 @@ void MapView::addImage(int x, int y, QString filepath, int layer, bool useCache) } void MapView::createImage(int x, int y, QString filepath, int width, int height, int xOffset, int yOffset, qreal hScale, qreal vScale, int paletteId, bool setTransparency, int layer, bool useCache) { - if (!this->editor || !this->editor->map || !this->editor->layout - || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QList palette; if (paletteId != -1) @@ -265,8 +264,7 @@ void MapView::createImage(int x, int y, QString filepath, int width, int height, } void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int paletteId, bool setTransparency, int layer) { - if (!this->editor || !this->editor->map || !this->editor->layout - || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QImage image = getPalettedTileImage(tileId, this->editor->layout->tileset_primary, @@ -285,8 +283,7 @@ void MapView::addTileImage(int x, int y, QJSValue tileObj, bool setTransparency, } void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparency, int layer) { - if (!this->editor || !this->editor->map || !this->editor->layout - || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QImage image = getMetatileImage(static_cast(metatileId), this->editor->layout->tileset_primary, From 263e45fe200bd97b6a49e8441050221a3c305c09 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 27 Oct 2023 11:16:21 -0400 Subject: [PATCH 031/364] fix new map popup population issue --- src/mainwindow.cpp | 1 + src/ui/newmappopup.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 39494686..692509f9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1269,6 +1269,7 @@ void MainWindow::openNewMapPopupWindow() { void MainWindow::on_action_NewMap_triggered() { openNewMapPopupWindow(); + this->newMapPrompt->initUi(); this->newMapPrompt->init(); } diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index 3da703a7..95a22f9d 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -121,6 +121,7 @@ void NewMapPopup::init(MapSortOrder type, QVariant data) { // Creating new map from AdvanceMap import void NewMapPopup::init(Layout *mapLayout) { + initUi(); this->importedMap = true; useLayoutSettings(mapLayout); From a00558a0d15bb969ad6f3416f63c77a08a17d02b Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 27 Oct 2023 14:19:29 -0400 Subject: [PATCH 032/364] drop gMapGroup_ prefix necessity for renaming groups --- src/ui/maplistmodels.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 277147e0..15baedaa 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -19,7 +19,7 @@ void MapTree::removeSelected() { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); - static const QRegularExpression expression("gMapGroup_[A-Za-z0-9_]+"); + static const QRegularExpression expression("[A-Za-z0-9_]+"); editor->setPlaceholderText("gMapGroup_"); QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, parent); editor->setValidator(validator); From cd5b1f98d2531b008ca58fa89b205a826dda220d Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 9 Jan 2024 21:50:22 -0500 Subject: [PATCH 033/364] add toggleable button to hide empty map folders --- forms/mainwindow.ui | 75 ++++++++++++++++++++++++++ include/mainwindow.h | 3 ++ include/ui/filterchildrenproxymodel.h | 4 +- resources/icons/folder_eye_closed.ico | Bin 0 -> 4286 bytes resources/icons/folder_eye_open.ico | Bin 0 -> 4286 bytes resources/images.qrc | 2 + src/mainwindow.cpp | 21 ++++++++ src/ui/filterchildrenproxymodel.cpp | 9 ++++ 8 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 resources/icons/folder_eye_closed.ico create mode 100644 resources/icons/folder_eye_open.ico diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 7d14043a..244f1b29 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -79,6 +79,31 @@ 3 + + + + <html><head/><body><p>Toggle hide all empty map folders</p></body></html> + + + + + + + :/icons/folder_eye_closed.ico + :/icons/folder_eye_open.ico + + + + QToolButton::InstantPopup + + + true + + + true + + + @@ -214,6 +239,31 @@ 3 + + + + <html><head/><body><p>Toggle hide all empty mapsection folders</p></body></html> + + + + + + + :/icons/folder_eye_closed.ico + :/icons/folder_eye_open.ico + + + + QToolButton::InstantPopup + + + true + + + true + + + @@ -349,6 +399,31 @@ 3 + + + + <html><head/><body><p>Toggle hide all unused layouts</p></body></html> + + + + + + + :/icons/folder_eye_closed.ico + :/icons/folder_eye_open.ico + + + + QToolButton::InstantPopup + + + true + + + true + + + diff --git a/include/mainwindow.h b/include/mainwindow.h index 1e496139..4a5d5777 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -279,10 +279,13 @@ private slots: void on_horizontalSlider_CollisionTransparency_valueChanged(int value); + void on_toolButton_HideShow_Groups_clicked(); void on_toolButton_ExpandAll_Groups_clicked(); void on_toolButton_CollapseAll_Groups_clicked(); + void on_toolButton_HideShow_Areas_clicked(); void on_toolButton_ExpandAll_Areas_clicked(); void on_toolButton_CollapseAll_Areas_clicked(); + void on_toolButton_HideShow_Layouts_clicked(); void on_toolButton_ExpandAll_Layouts_clicked(); void on_toolButton_CollapseAll_Layouts_clicked(); diff --git a/include/ui/filterchildrenproxymodel.h b/include/ui/filterchildrenproxymodel.h index b73cbd62..5853d625 100644 --- a/include/ui/filterchildrenproxymodel.h +++ b/include/ui/filterchildrenproxymodel.h @@ -9,9 +9,11 @@ class FilterChildrenProxyModel : public QSortFilterProxyModel public: explicit FilterChildrenProxyModel(QObject *parent = nullptr); + void toggleHideEmpty() { this->hideEmpty = !this->hideEmpty; } protected: bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; - +private: + bool hideEmpty = false; }; #endif // FILTERCHILDRENPROXYMODEL_H diff --git a/resources/icons/folder_eye_closed.ico b/resources/icons/folder_eye_closed.ico new file mode 100644 index 0000000000000000000000000000000000000000..354abadb4e6511d9f13ef4a089aa3e29e2380084 GIT binary patch literal 4286 zcmeI0drXs87{=>Vm`3B`B3@^VOEz7Mn`o3xK|nS)H8XBAG0RkxiKr+S5fJNTP9+Kq z5m4i0>gEug%z<}6Wr|!BC>Ki+s8mp#=w#rHwv=bj`J`AXNO4QtA0F~+doJI3-tRrn zIbVxR)`5S62Fb*$EcOkV%tj`Y4IyljITF#{S^*$?#mmbbrY87w_Cg`j@_Qs2T@+L7aLR-Y>OeVYl6FUyE zm-k(hTpl`0St>J?Cu=rTAcz0BnTz_(%m} z^}vn?K=ggf*r>UK$%oV!zP$+jHYqS5Bo}W9LQPrFRN&pnLQIJ($IuN)r>XyT zV+=MEw&h{w(K4(psKt(IU{e(kVga*GRKR}!b&T9q(k8Y$OE97lm)LSl*_U_LsP7b= z3purCCur~s^*2-+5PY+3x~)jUz>o}lo=}BJM{eQMxJpbse6vMFpBN*?ifg(CoxW?- z7wf+DZUY>*<{{>9AiS~x-&Yv0t_=dO+(&2s6X?7C0$l&7!QAr?F#B9BW}U5h%DEc2 zrqses^l?m|_2*$7a1wpiT^wZ87i%B$2>4{T5g)CR&H`p9R-j8@BCG>X zVeC&u@JM?I|9rqduR$V!pJyai!FE@Hq}yZVIrR5A@P4DVc(ZZXLj8xp*T>7TAgK~Z zxCSdV^^ljIU#-+%+ssy@TZH26z_fu!MLSxvGOVSxWrci7&?oYxGFGQxB(z z%qr^kC9gfvS0KIy2)b^7M|w3T^1MvrIdp+JNj2djKc^G#B0YRmI`|Z7EyjyuJ(XIV zp^kf838?!HdEKagob3T>dQQ`TFAiVBn2<{t9dh{@?ASJy=csp4J-iBaSbRk*;mP;s zQlN!LzLx81K;)&{7`7-{PHpMAXHWm1qP|$8fKqyno)6L(qOJhT}{{WHv~5B=uXvtFZCEODth9chMGq7FskBe;DGv;&r zn|ddwsO)1R29bxB?(ONG$v#RUv1k6X^6IzW%lqy3Kgm7AFc!^t8mkAfCgS^G-_LI)6}C%RTMmG;)klW_JbD+ilE3XC}OqPLM)(i zDPYw|6;ZLoD{_jpigI593M{9fXsL>#?8Vb(R#ssJU2T(os6&3c%)Il?GxMM4KkvH| zNiY5l86pv%lDPLI5~)NY`IxXtVnsxIPX&PF4gXyXdfJ!QY#+q?Mw78Rj9~p8(p~Ap zM!H608)2Q-!G!nC>|+~wu(wx|qPKT4o{0`L2pzhx>9h4p+2cc6=QMY&e}l z@u8h2AQjqE!}1ZQ1fm}T5fAjkzw#!&K3knoOW6 z8>)L%h+Q)jVcYF7$aFXc8k=FDi8=iJY;eHQ6p(XKDnT=DH{ZhiU-Qpt^{pcc;7e_r zlQlR*eg8%^0vcY$#i5TE_Nt+8&vG=i$fUowJV0-0b z{L%z$u2ta&J(wF`35jPCq+2f_@FxeH+c*-(g63iW4jVL=B?I*tXprS0JiQS8HzmVh zeHu(Qr?2W*yZDk$38j83u<%SB98(&Rz&+Shqk?a>3Y+v`ZbB`Lwr0W5muvIB@C2`P z#wG<4m$O`NGWz(Ohp~S)CWhoIs5zMQ7UC=#{Ufx7SJ@*>+f#xQ+@`=fCDzN7@X-^O z!iSiBv=*a6%kW7Dqe4m{_$<5>3+dI9T-S2OtDo7ZG1S$bhf+)C{84U?Un4MiM;>-p zx8cCecDR=-u&zP@FCDyDe^oqr&)!>VY`fBg&)xS2Q~!OjcovQRZ}i>wH-P!atKf0* z4pQ&7!|9p=&csE_bp>3B2l0GG+}S^%Mv3G*Dp-eP-lncGF?gb%j}!L+pIS8>F5kth zgXPHJIrIl(aZD3Wtu)dPEkLKJ}cEzSDto^R853L$z8HquH@Cl*@tA$3TgmTxGxO zD!`Iw9<}>v_0Kc^gxB1O$4l@m=e%TU%sVE7MQ}DIynyk+IT#+qb@)Hc-WU!=YhJ>ptMPw9d1vnS(Yf8{>xGh2GZj@R~8v|YzXb*U1WjJYMxc%h|H?E0b6 z&!_&X;x>A?jehRR8rB_n&AzA`M0*dcWPe-wv!o50xKq}stF)%3(J$un$F7h%yvLE_ zJw<4;J&t)(&UKV?tXJVW{aK~%;y2wX7>W3oaoi>OaxBYl!*U&#vYyIk!Z^OeeHkKr zl>bUR@msFeZ@T*Z#Ptg1gqXuMuJJk8F0M}wdzv%4WBil4p>=rfX)y;8hwj$)EN600 zQAp&>zgGBS^5*y7|2qb>fVR|_(^&74zC^sAi8Hb@x^2ANZ4vi~ah}naK_icons/fill_color.ico icons/folder_closed_map.ico icons/folder_closed.ico + icons/folder_eye_closed.ico + icons/folder_eye_open.ico icons/folder_map_edited.ico icons/folder_map_opened.ico icons/folder_map.ico diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 08b18d43..d9e23785 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2821,6 +2821,13 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } +void MainWindow::on_toolButton_HideShow_Groups_clicked() { + if (ui->mapList) { + this->groupListProxyModel->toggleHideEmpty(); + this->groupListProxyModel->setFilterRegularExpression(this->ui->lineEdit_filterBox->text()); + } +} + void MainWindow::on_toolButton_ExpandAll_Groups_clicked() { if (ui->mapList) { ui->mapList->expandToDepth(0); @@ -2833,6 +2840,13 @@ void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { } } +void MainWindow::on_toolButton_HideShow_Areas_clicked() { + if (ui->areaList) { + this->areaListProxyModel->toggleHideEmpty(); + this->areaListProxyModel->setFilterRegularExpression(this->ui->lineEdit_filterBox->text()); + } +} + void MainWindow::on_toolButton_ExpandAll_Areas_clicked() { if (ui->areaList) { ui->areaList->expandToDepth(0); @@ -2845,6 +2859,13 @@ void MainWindow::on_toolButton_CollapseAll_Areas_clicked() { } } +void MainWindow::on_toolButton_HideShow_Layouts_clicked() { + if (ui->layoutList) { + this->layoutListProxyModel->toggleHideEmpty(); + this->layoutListProxyModel->setFilterRegularExpression(this->ui->lineEdit_filterBox->text()); + } +} + void MainWindow::on_toolButton_ExpandAll_Layouts_clicked() { if (ui->layoutList) { ui->layoutList->expandToDepth(0); diff --git a/src/ui/filterchildrenproxymodel.cpp b/src/ui/filterchildrenproxymodel.cpp index a08c150c..99464ae6 100644 --- a/src/ui/filterchildrenproxymodel.cpp +++ b/src/ui/filterchildrenproxymodel.cpp @@ -8,6 +8,15 @@ FilterChildrenProxyModel::FilterChildrenProxyModel(QObject *parent) : bool FilterChildrenProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + if (this->hideEmpty && source_parent.row() < 0) // want to hide children + { + QModelIndex source_index = sourceModel()->index(source_row, this->filterKeyColumn(), source_parent) ; + if(source_index.isValid()) + { + if (!sourceModel()->hasChildren(source_index)) + return false; + } + } // custom behaviour : if(filterRegularExpression().pattern().isEmpty() == false) { From 99eb92c3b298e2d411947a36bd3d1266374a43b7 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 10 Jan 2024 14:34:48 -0500 Subject: [PATCH 034/364] timelapse replay layout edits then map edits --- include/ui/mapimageexporter.h | 1 + src/mainwindow.cpp | 12 +++ src/ui/mapimageexporter.cpp | 195 +++++++++++++++++++--------------- 3 files changed, 121 insertions(+), 87 deletions(-) diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 6d8ae643..39cc24aa 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -27,6 +27,7 @@ public: private: Ui::MapImageExporter *ui; + Layout *layout = nullptr; Map *map = nullptr; Editor *editor = nullptr; QGraphicsScene *scene = nullptr; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d9e23785..1c7da36c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1208,6 +1208,7 @@ void MainWindow::scrollTreeView(QString itemName) { } } +// !TODO: remove this? void MainWindow::sortMapList() { } @@ -2564,6 +2565,17 @@ void MainWindow::on_action_Export_Map_Image_triggered() { } void MainWindow::on_actionExport_Stitched_Map_Image_triggered() { + if (!this->editor->map) { + QMessageBox warning(this); + warning.setText("Notice"); + warning.setInformativeText("Map stich images are not possible without a map selected."); + warning.setStandardButtons(QMessageBox::Ok); + warning.setDefaultButton(QMessageBox::Cancel); + warning.setIcon(QMessageBox::Warning); + + warning.exec(); + return; + } showExportMapImageWindow(ImageExporterMode::Stitch); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index c6aef0cd..44d41f19 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -29,15 +29,18 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor { ui->setupUi(this); this->map = editor_->map; + this->layout = editor_->layout; this->editor = editor_; this->mode = mode; this->setWindowTitle(getTitle(this->mode)); this->ui->groupBox_Connections->setVisible(this->mode == ImageExporterMode::Normal); this->ui->groupBox_Timelapse->setVisible(this->mode == ImageExporterMode::Timelapse); - this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); - this->ui->comboBox_MapSelection->setCurrentText(map->name); - this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + if (this->map) { + this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); + this->ui->comboBox_MapSelection->setCurrentText(map->name); + this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + } updatePreview(); } @@ -53,13 +56,13 @@ void MapImageExporter::saveImage() { switch (this->mode) { case ImageExporterMode::Normal: - defaultFilename = map->name; + defaultFilename = this->map? this->map->name : this->layout->name; break; case ImageExporterMode::Stitch: - defaultFilename = QString("Stitch_From_%1").arg(map->name); + defaultFilename = QString("Stitch_From_%1").arg(this->map? this->map->name : this->layout->name); break; case ImageExporterMode::Timelapse: - defaultFilename = QString("Timelapse_%1").arg(map->name); + defaultFilename = QString("Timelapse_%1").arg(this->map? this->map->name : this->layout->name); break; } @@ -91,89 +94,98 @@ void MapImageExporter::saveImage() { } case ImageExporterMode::Timelapse: // !TODO: also need layout editHistory! - QProgressDialog progress("Building map timelapse...", "Cancel", 0, 1, this); - progress.setAutoClose(true); - progress.setWindowModality(Qt::WindowModal); - progress.setModal(true); - progress.setMaximum(1); - progress.setValue(0); - - Layout *layout = this->map->layout; - if (!layout) break; - - int maxWidth = layout->getWidth() * 16; - int maxHeight = layout->getHeight() * 16; - if (showBorder) { - maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - } - // Rewind to the specified start of the map edit history. - int i = 0; - while (this->map->editHistory.canUndo()) { - progress.setValue(i); - this->map->editHistory.undo(); - int width = this->map->getWidth() * 16; - int height = this->map->getHeight() * 16; - if (showBorder) { - width += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - height += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - } - if (width > maxWidth) { - maxWidth = width; - } - if (height > maxHeight) { - maxHeight = height; - } - i++; - } - QGifImage timelapseImg(QSize(maxWidth, maxHeight)); + QGifImage timelapseImg; timelapseImg.setDefaultDelay(timelapseDelayMs); timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); - // Draw each frame, skpping the specified number of map edits in - // the undo history. - progress.setMaximum(i); - while (i > 0) { - if (progress.wasCanceled()) { - progress.close(); - while (i > 0 && this->map->editHistory.canRedo()) { - i--; - this->map->editHistory.redo(); + + auto generateTimelapseFromHistory = [=, this, &timelapseImg](QString progressText, QUndoStack &historyStack){ + // + QProgressDialog progress(progressText, "Cancel", 0, 1, this); + progress.setAutoClose(true); + progress.setWindowModality(Qt::WindowModal); + progress.setModal(true); + progress.setMaximum(1); + progress.setValue(0); + + int maxWidth = this->layout->getWidth() * 16; + int maxHeight = this->layout->getHeight() * 16; + if (showBorder) { + maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; + maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; + } + // Rewind to the specified start of the map edit history. + int i = 0; + while (historyStack.canUndo()) { + progress.setValue(i); + historyStack.undo(); + int width = this->layout->getWidth() * 16; + int height = this->layout->getHeight() * 16; + if (showBorder) { + width += 2 * STITCH_MODE_BORDER_DISTANCE * 16; + height += 2 * STITCH_MODE_BORDER_DISTANCE * 16; } - return; + if (width > maxWidth) { + maxWidth = width; + } + if (height > maxHeight) { + maxHeight = height; + } + i++; } - while (this->map->editHistory.canRedo() && - !historyItemAppliesToFrame(this->map->editHistory.command(this->map->editHistory.index()))) { - i--; - this->map->editHistory.redo(); - } - progress.setValue(progress.maximum() - i); - QPixmap pixmap = this->getFormattedMapPixmap(this->map, !this->showBorder); - if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) { - QPixmap pixmap2 = QPixmap(maxWidth, maxHeight); - QPainter painter(&pixmap2); - pixmap2.fill(QColor(0, 0, 0)); - painter.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap); - painter.end(); - pixmap = pixmap2; - } - timelapseImg.addFrame(pixmap.toImage()); - for (int j = 0; j < timelapseSkipAmount; j++) { - if (i > 0) { - i--; - this->map->editHistory.redo(); - while (this->map->editHistory.canRedo() && - !historyItemAppliesToFrame(this->map->editHistory.command(this->map->editHistory.index()))) { + + // Draw each frame, skpping the specified number of map edits in + // the undo history. + progress.setMaximum(i); + while (i > 0) { + if (progress.wasCanceled()) { + progress.close(); + while (i > 0 && historyStack.canRedo()) { i--; - this->map->editHistory.redo(); + historyStack.redo(); + } + return; + } + while (historyStack.canRedo() && + !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + i--; + historyStack.redo(); + } + progress.setValue(progress.maximum() - i); + QPixmap pixmap = this->getFormattedMapPixmap(this->map, !this->showBorder); + if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) { + QPixmap pixmap2 = QPixmap(maxWidth, maxHeight); + QPainter painter(&pixmap2); + pixmap2.fill(QColor(0, 0, 0)); + painter.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap); + painter.end(); + pixmap = pixmap2; + } + timelapseImg.addFrame(pixmap.toImage()); + for (int j = 0; j < timelapseSkipAmount; j++) { + if (i > 0) { + i--; + historyStack.redo(); + while (historyStack.canRedo() && + !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + i--; + historyStack.redo(); + } } } } - } - // The latest map state is the last animated frame. - QPixmap pixmap = this->getFormattedMapPixmap(this->map, !this->showBorder); - timelapseImg.addFrame(pixmap.toImage()); + // The latest map state is the last animated frame. + QPixmap pixmap = this->getFormattedMapPixmap(this->map, !this->showBorder); + timelapseImg.addFrame(pixmap.toImage()); + progress.close(); + }; + + if (this->layout) + generateTimelapseFromHistory("Building layout timelapse...", this->layout->editHistory); + + if (this->map) + generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory); + timelapseImg.save(filepath); - progress.close(); break; } this->close(); @@ -358,17 +370,22 @@ void MapImageExporter::updatePreview() { scene->itemsBoundingRect().height() + 2); } +// THIS QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { QPixmap pixmap; - // draw background layer / base image - Layout *layout = map->layout; - if (!layout) { - return QPixmap(); - } + Layout *layout; - layout->render(true); - pixmap = layout->pixmap; + // draw background layer / base image + if (!this->map) { + layout = this->layout; + layout->render(true); + pixmap = layout->pixmap; + } else { + layout = map->layout; + map->layout->render(true); + pixmap = map->layout->pixmap; + } if (showCollision) { QPainter collisionPainter(&pixmap); @@ -401,6 +418,10 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { pixmap = newPixmap; } + if (!this->map) { + return pixmap; + } + if (!this->mode) { // if showing connections, draw on outside of image QPainter connectionPainter(&pixmap); From abc433bc78ed53e33459f0fb699221d16b1022eb Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 12 Jan 2024 14:39:49 -0500 Subject: [PATCH 035/364] allow dragging and dropping to rearrange map groups --- include/ui/maplistmodels.h | 2 +- src/ui/maplistmodels.cpp | 96 +++++++++++++++++++++++++++----------- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index ef21d8ae..10270a13 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -72,7 +72,7 @@ public: public: void setMap(QString mapName) { this->openMap = mapName; } - QStandardItem *createGroupItem(QString groupName, int groupIndex); + QStandardItem *createGroupItem(QString groupName, int groupIndex, QStandardItem *fromItem = nullptr); QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); QStandardItem *insertMapItem(QString mapName, QString groupName); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 15baedaa..d22f2af6 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -59,7 +59,9 @@ Qt::DropActions MapGroupModel::supportedDropActions() const { QStringList MapGroupModel::mimeTypes() const { QStringList types; types << "application/porymap.mapgroupmodel.map" - << "application/porymap.mapgroupmodel.group"; + << "application/porymap.mapgroupmodel.group" + << "application/porymap.mapgroupmodel.source.row" + << "application/porymap.mapgroupmodel.source.column"; return types; } @@ -69,6 +71,17 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { QDataStream stream(&encodedData, QIODevice::WriteOnly); + // if dropping a selection containing a group(s) and map(s), clear all selection but first group. + for (const QModelIndex &index : indexes) { + if (index.isValid() && data(index, MapListRoles::TypeRole).toString() == "map_group") { + QString groupName = data(index, Qt::UserRole).toString(); + stream << groupName; + mimeData->setData("application/porymap.mapgroupmodel.group", encodedData); + mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row())); + return mimeData; + } + } + for (const QModelIndex &index : indexes) { if (index.isValid()) { QString mapName = data(index, Qt::UserRole).toString(); @@ -83,11 +96,8 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parentIndex) { if (action == Qt::IgnoreAction) return true; - - if (!data->hasFormat("application/porymap.mapgroupmodel.map")) - return false; - if (!parentIndex.isValid()) + if (!parentIndex.isValid() && !data->hasFormat("application/porymap.mapgroupmodel.group")) return false; int firstRow = 0; @@ -99,34 +109,68 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i firstRow = rowCount(parentIndex); } - QByteArray encodedData = data->data("application/porymap.mapgroupmodel.map"); - QDataStream stream(&encodedData, QIODevice::ReadOnly); - QStringList droppedMaps; - int rowCount = 0; + if (data->hasFormat("application/porymap.mapgroupmodel.group")) { + if (parentIndex.row() != -1 || parentIndex.column() != -1) { + return false; + } + QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + QString groupName; + int rowCount = 1; - QList newItems; + while (!stream.atEnd()) { + stream >> groupName; + } - while (!stream.atEnd()) { - QString mapName; - stream >> mapName; - droppedMaps << mapName; - rowCount++; + this->insertRow(row, parentIndex); + + // copy children to new node + int sourceRow = data->data("application/porymap.mapgroupmodel.source.row").toInt(); + QModelIndex originIndex = this->index(sourceRow, 0); + QModelIndexList children; + QStringList mapsToMove; + for (int i = 0; i < this->rowCount(originIndex); ++i ) { + children << this->index( i, 0, originIndex); + mapsToMove << this->index( i, 0 , originIndex).data(Qt::UserRole).toString(); + } + + QModelIndex groupIndex = index(row, 0, parentIndex); + QStandardItem *groupItem = this->itemFromIndex(groupIndex); + createGroupItem(groupName, row, groupItem); + + for (QString mapName : mapsToMove) { + QStandardItem *mapItem = createMapItem(mapName); + groupItem->appendRow(mapItem); + } } + else if (data->hasFormat("application/porymap.mapgroupmodel.map")) { + QByteArray encodedData = data->data("application/porymap.mapgroupmodel.map"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + QStringList droppedMaps; + int rowCount = 0; - this->insertRows(firstRow, rowCount, parentIndex); + while (!stream.atEnd()) { + QString mapName; + stream >> mapName; + droppedMaps << mapName; + rowCount++; + } - int newItemIndex = 0; - for (QString mapName : droppedMaps) { - QModelIndex mapIndex = index(firstRow, 0, parentIndex); - QStandardItem *mapItem = this->itemFromIndex(mapIndex); - createMapItem(mapName, mapItem); - firstRow++; + this->insertRows(firstRow, rowCount, parentIndex); + + int newItemIndex = 0; + for (QString mapName : droppedMaps) { + QModelIndex mapIndex = index(firstRow, 0, parentIndex); + QStandardItem *mapItem = this->itemFromIndex(mapIndex); + createMapItem(mapName, mapItem); + firstRow++; + } } emit dragMoveCompleted(); updateProject(); - return false; + return true; } void MapGroupModel::updateProject() { @@ -158,13 +202,13 @@ void MapGroupModel::updateProject() { this->project->mapNames = mapNames; } -QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex) { - QStandardItem *group = new QStandardItem; +QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *group) { + if (!group) group = new QStandardItem; group->setText(groupName); group->setData(groupName, Qt::UserRole); group->setData("map_group", MapListRoles::TypeRole); group->setData(groupIndex, MapListRoles::GroupRole); - group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable); + group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); this->groupItems.insert(groupName, group); return group; } From b620e3d81644082a7a5d123e61928d5a6dc05636 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 12 Jan 2024 15:48:51 -0500 Subject: [PATCH 036/364] add button to lock group edits --- forms/mainwindow.ui | 37 ++++++++++++++++++++++++++------ include/mainwindow.h | 1 + resources/icons/lock_edit.ico | Bin 0 -> 4418 bytes resources/icons/unlock_edit.ico | Bin 0 -> 4286 bytes resources/images.qrc | 2 ++ src/mainwindow.cpp | 29 ++++++++++++++++++++----- 6 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 resources/icons/lock_edit.ico create mode 100644 resources/icons/unlock_edit.ico diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 244f1b29..92bf0b13 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -89,8 +89,8 @@ - :/icons/folder_eye_closed.ico - :/icons/folder_eye_open.ico + :/icons/folder_eye_open.ico + :/icons/folder_eye_closed.ico @@ -144,6 +144,31 @@ + + + + <html><head/><body><p>Toggle editability of group folders</p></body></html> + + + + + + + :/icons/lock_edit.ico + :/icons/unlock_edit.ico + + + + QToolButton::InstantPopup + + + true + + + true + + + @@ -249,8 +274,8 @@ - :/icons/folder_eye_closed.ico - :/icons/folder_eye_open.ico + :/icons/folder_eye_open.ico + :/icons/folder_eye_closed.ico @@ -409,8 +434,8 @@ - :/icons/folder_eye_closed.ico - :/icons/folder_eye_open.ico + :/icons/folder_eye_open.ico + :/icons/folder_eye_closed.ico diff --git a/include/mainwindow.h b/include/mainwindow.h index 4a5d5777..536f659c 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -282,6 +282,7 @@ private slots: void on_toolButton_HideShow_Groups_clicked(); void on_toolButton_ExpandAll_Groups_clicked(); void on_toolButton_CollapseAll_Groups_clicked(); + void on_toolButton_EnableDisable_EditGroups_clicked(); void on_toolButton_HideShow_Areas_clicked(); void on_toolButton_ExpandAll_Areas_clicked(); void on_toolButton_CollapseAll_Areas_clicked(); diff --git a/resources/icons/lock_edit.ico b/resources/icons/lock_edit.ico new file mode 100644 index 0000000000000000000000000000000000000000..f30f424914e61f251359e82f311b28b90d34cfd2 GIT binary patch literal 4418 zcmb`Kc~I5Y6~}*ofC~87d1KRRn`xWO#G1OqnKlH)IK=7Hg|w#sL`^kB4U51~RFqA8 zpb+J;KHveG;1XO450u6lR-Y`&j*mqnAc!n+1eHWt-nZvGmcWyTkm+H*-2408bI<48 zbMNoR4?)nQpT)lx__y$ug&^1pg75}uG0Bm{_kX4t(mYerJd*y;j4|yr00K*gF`HH< z{FcPa`oHEe#-70E^@>O~qr63~9$w$C-1NyYy0#~ASr?9uaXwRt=W+~=HLYp!*#O2-?DpPq0{c8bZ_107>`dSp35;f*0ecj@j2(^qTOk&BKHee z;C2C~ZWm$fuE5$I2e8A(AA55j;rDK*VBvBKW}X>X;BgV`D^O7VL))xcoZ>Tk`Qwi{HdK-L;dXU}@1eXtDLu>`!+atpVx5%%QH0tl9 z9EOupjkhQU$09KAg-+Ar+gf;KU{e{e@^T-jqS2L{74hh?+s$h z&rtjOwxW9|#Q zpFcnvgk!-lmZj7q_*xSl4FOxP0oL(=m~(K6f+MYY4f)w$`w<@&zrf$hhG9;z2s|sk zrFS;ARSs+T;-m)5r}fW|WL3gAss<~3l?c2CH2(nn^IO1XJO?RkQ=70t(FJGXe_TEU zjx}4pl}`>_YSdUhVg2i9UF!-+MZ=gITZ?xN+(2*(&_bIrF$afFrLZp;f^+F0c2=ul zNwH?l_x%PQsuBLg^_x%)%%W=`N~nk6oPt&UWiRDmM|<(^j!1l14!G3=R{O5bns2@T zzwl|Ky;l6R|6}WSDxx*X8=>bM2VqkTRvjuua5L3m!d}?QfIsh$VjtDV=3wsZ`3G|0 z{~hp0;@eHwe^Y5C%;V}{mHHj#Y>a?D$u#Be$X?KSn3Mz6;k{ikunQ_;{%=S|q$y4N z{M#HXz>#J(-p+iCHKXUB_uh&2-hygx_OC1$Za$7VBopeJ(lfep-*p6O}0q-k@|=Gz4-5!(SQeQEu?_rzc83&XzzXvBT1p5i(T6`Pv;hRW%1T4vT42N%Dz%g$CYx(&v9EK>i4i<@xREN77ao;>n zMel|o@*Biz^8Hip0RG@zfP<7n{R`lHN;FO;#KYUu13vp)U&=v?FIp(6z=b~G?X1UG z5Z?g3@N(!!RKVbiN*JD~hLN-e#?o4iM*20H`v%9WAfjBB{JWF>ehlJw-ZyaVVj8+1 z+(%JiK9b}z_yu|)aZfqtu!=-qX@cgvXDkQ0H2kHcyul^63t9bu;u(-l?PaRv6;h54 z@fiEDf96wa>3VD4BjlvXaId}=ot+OLITnPnh*A`UT!rlO63$^NKBr{3Ftn=HDpUp2 zkUE$hxecqZdWger;}>Bycuh-uPaIYUOY%2k|Bw-X8%b3q#hRaaHW`U>DeioE2UQ6b zC^=Dx)V*cQ*F2A7Yv*=?*@o|T1)e{FGUpP$@bln7TVe^spCrD=W9` z<3AeDs8FIpM`jzE&Z=1WM`*zQiF|f9$~{(%Z$hgkv3WojKO`ss{lw47RvF%0J6J95A4t#c4+<)D} z_4CxP&!(ZZr5P=)t%x`(VZP>kQ#-Bc;Li;Y4^uTZHlReQgd*b-VyFghzWK%$3bvHQ zf74h?`Cd%qaa329Yxr$#Z3sUWrlPUAq*s~_zW(8$fL#~TzeeZ74owZx&z*rRCJJ7A zJx)8WS-pql<>7uhJ~kRx|8oVZx>`JW(uabAd;|p?+C|_Q{ja8j&v_fgMoS-7S61N8 zohIa5&O&zPC46-<1t*gekt$Ebxie`f%*#dj%`$x7(}V7=E+oXqKIFL^U?w&VU)a8F zt7T$*EGll@L{E1QDyc2X&%<>Vt+TXPiI(PjcuMQ;>gvSV(_dlR){iY`!X5K@1z*_Z zveSajUk9Iodev<_>FYyZZy&Y2c=q%u+S}W3Ls^7VDe{ibKHX_S{ISq8YNz9K0OE6_ zpA;XvooZh5Ws00x<4Tby)Fi~kY-bq(vkV$(Up?rWWe=I_Mks{>nf^La!-F_({(NV+;u*s09KG I4wM4$KSovGegFUf literal 0 HcmV?d00001 diff --git a/resources/icons/unlock_edit.ico b/resources/icons/unlock_edit.ico new file mode 100644 index 0000000000000000000000000000000000000000..85a99e980c95ca2f81fc4b8b63fea7d9a0d10f8f GIT binary patch literal 4286 zcmb_fX;4&G7Jh($3fSy)IhoO#R87WA95XJlY9<7QSj3vDh$Lg?Ph7@?sKZV%7*Pa7 zcBAY|w}2^71DZesQgIBcEsH?2*(@5tB`g|CP)L;Rn{ysZu%!u9&7nT-efOSwzVDoS z-{T?(I`p%0rNF<1^QMAeEeOIN2{a^ZBJj9210XQ8m>G@#iLja=B`hK668FCg{z16j~kr!L{i_Dz-cWf@SHaAT8_c7<}?j{_U9be5jGPX2=Bi2-amr( zY~Kpo9s7ruJMNF5u`QExJU<(FEyv(kb867wXU*#*`!nxI+%I66+XWcAU4)^#0uFl* zVXtohJc}RUHMcV`b2$SOuUstixCr)@kgvooht}jwJX4HG4IEReY4BT_x}NV`EmLD_ zC9w6{xY#;Y0dHsDgI~o%WZwsZ>qhW?LOtF&kc{_g$*-C)>F=l?!BX)Wo=kW z`7b@iP{A;^3G4lo2)qq+{sjE%JHTpMLkz6bJFs5S2WR5{xNa02YoYuWzD00p8ppaB z`EQ}Twv-Vn#;`b{8E+lBhTtxsi#B7X2D?vVU|Ti{=jsuB(l`!tinU;Vzi;88p5ULE zzY*2IB(4dPlvW7NY1kN0^O6P|+KadMp27!pfLkkIaq#kj`7J;HFMQi+uNA-S|5W~t z6_k^_9Xig55S-$%@klj-JE;yc_QEyqWhH znYlNk|jWm=Ykm|tShg~JW(o`TSzzhC0;g>`R8>!0&(g7$gZTFiUNxI@}V$ebYoWeH%u}Zv>mj_pimn z__I$L4wHt~=Ri(c95Pap;N#^1--E6%Y0%)8EDx>6g#qBr{Kr_9)CQfXI_O5%L+^wN z`X?J<5Yq(1m}U_Z{U&i-?~4XVNXzPPdg<@S2>#%64OcE^;=vDhQBhtBnLHW(f!;_x zP{$fJ68K&t-g~Ar(4+ouL+jdHiu;g11Skf90%{AXmX`=cTEKJc$NqUwsio(gC67>) znT*@5&FJm@0ilsWsEMvdS;%E1|GSDc%*M}>^p}ShWW+!aVmFi zXP9-==dQrBCr}oBjT8PJJo!3t3B>;bc#KSsU_`K-1owH2&7eF~jqbdA=s2fFj(_bm zH&1Hg4>{t8XFm<$@+Aer0{wZ-8w4F*GmA0bYXtt*Fu2cm#3?fuYVGS>E^Q1QZ zV}VC7G&F>w!hD1t3*vQrmz#~B$E>~G_Gg423q*KGAiUiVWY8GbV%msbApYs+sg5>& z<{TOv!leQQB0@u$eXt3!=h9nSaiEAI*!Vh2Jci^5Ogo z6uC&3rNGP6Bb^q!N?;9qb{X7%)zjTIJ^O4H?sRpctLqLuYQ2Y(Idi_Btv`OGI2PD@ApsQmpw?%DbYbNg%jlfJrC*WKyz)F!Td_PH#v{@hQKr!>*I*iC-(+V9DD(+%^F z8`8Z=&_OB5nFo%Nj~!tJK|(Mj=uM%E`u%pghW#t_B>lhBg9u}4gicons/folder_map_opened.ico icons/folder_map.ico icons/folder.ico + icons/lock_edit.ico + icons/unlock_edit.ico icons/map_edited.ico icons/map_opened.ico icons/map.ico diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1c7da36c..b609be59 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1179,11 +1179,12 @@ bool MainWindow::populateMapList() { ui->layoutList->setModel(layoutListProxyModel); /// !TODO - ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); - ui->mapList->setDragEnabled(true); - ui->mapList->setAcceptDrops(true); - ui->mapList->setDropIndicatorShown(true); - ui->mapList->setDragDropMode(QAbstractItemView::InternalMove); + // ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); + // ui->mapList->setDragEnabled(true); + // ui->mapList->setAcceptDrops(true); + // ui->mapList->setDropIndicatorShown(true); + // ui->mapList->setDragDropMode(QAbstractItemView::InternalMove); + on_toolButton_EnableDisable_EditGroups_clicked(); return success; } @@ -2852,6 +2853,24 @@ void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { } } +void MainWindow::on_toolButton_EnableDisable_EditGroups_clicked() { + if (this->ui->toolButton_EnableDisable_EditGroups->isChecked()) { + ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); + ui->mapList->setDragEnabled(true); + ui->mapList->setAcceptDrops(true); + ui->mapList->setDropIndicatorShown(true); + ui->mapList->setDragDropMode(QAbstractItemView::InternalMove); + ui->mapList->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + } else { + ui->mapList->setSelectionMode(QAbstractItemView::NoSelection); + ui->mapList->setDragEnabled(false); + ui->mapList->setAcceptDrops(false); + ui->mapList->setDropIndicatorShown(false); + ui->mapList->setDragDropMode(QAbstractItemView::NoDragDrop); + ui->mapList->setEditTriggers(QAbstractItemView::NoEditTriggers); + } +} + void MainWindow::on_toolButton_HideShow_Areas_clicked() { if (ui->areaList) { this->areaListProxyModel->toggleHideEmpty(); From 858c8078563afce2f279d1e26a877a052cc5ba39 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 12 Jan 2024 19:22:54 -0500 Subject: [PATCH 037/364] fix bad merge --- src/project.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 32b1acfa..41f9ccf9 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -35,9 +35,7 @@ int Project::default_map_size = 20; int Project::max_object_events = 64; Project::Project(QObject *parent) : - QObject(parent), - eventScriptLabelModel(this), - eventScriptLabelCompleter(this) + QObject(parent) { initSignals(); } From 23b55a1074572520856ef150d178fe88da6c5ccf Mon Sep 17 00:00:00 2001 From: garak Date: Sun, 4 Feb 2024 12:58:41 -0500 Subject: [PATCH 038/364] fix bug disabling map edits after tab switches --- src/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor.cpp b/src/editor.cpp index 12332051..5ed97782 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -129,6 +129,7 @@ void Editor::setEditorView() { switch (this->editMode) { case EditMode::Metatiles: case EditMode::Collision: + map_item->setEditsEnabled(true); this->editGroup.setActiveStack(&this->layout->editHistory); break; case EditMode::Events: From ad1b651f96ace3735ddea7ef2704b5bc7fc7aaf3 Mon Sep 17 00:00:00 2001 From: garak Date: Sun, 4 Feb 2024 14:59:03 -0500 Subject: [PATCH 039/364] clear selection sticking when edits toggled for map list --- src/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b609be59..61265135 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2854,6 +2854,7 @@ void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { } void MainWindow::on_toolButton_EnableDisable_EditGroups_clicked() { + this->ui->mapList->clearSelection(); if (this->ui->toolButton_EnableDisable_EditGroups->isChecked()) { ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->mapList->setDragEnabled(true); From 963b09c86612cf6ed917c662978a04573294d625 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 13 Feb 2024 20:23:23 -0500 Subject: [PATCH 040/364] create buttons to add items to map trees --- include/mainwindow.h | 5 +++ include/ui/maplistmodels.h | 2 + src/mainwindow.cpp | 76 +++++++++++++++++++++++++++++++++++--- src/ui/maplistmodels.cpp | 40 +++++++++++++++----- 4 files changed, 108 insertions(+), 15 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 536f659c..0c3bd4ee 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -389,6 +389,11 @@ private: void refreshRecentProjectsMenu(); void updateMapList(); + void mapListAddItem(); + void mapListRemoveItem(); + void mapListAddGroup(); + void mapListAddLayout(); + void mapListAddArea(); void displayMapProperties(); void checkToolButtons(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 10270a13..a2babda6 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -75,6 +75,7 @@ public: QStandardItem *createGroupItem(QString groupName, int groupIndex, QStandardItem *fromItem = nullptr); QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); + QStandardItem *insertGroupItem(QString groupName); QStandardItem *insertMapItem(QString mapName, QString groupName); QStandardItem *getItem(const QModelIndex &index) const; @@ -83,6 +84,7 @@ public: void initialize(); private: + friend class MapTree; void updateProject(); private: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 61265135..4d31fb6c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -210,6 +210,24 @@ void MainWindow::initCustomUI() { WheelFilter *wheelFilter = new WheelFilter(this); ui->mainTabBar->installEventFilter(wheelFilter); this->ui->mapListContainer->tabBar()->installEventFilter(wheelFilter); + + // Create buttons for adding and removing items from the mapList + QFrame *frame = new QFrame(this->ui->mapListContainer); + frame->setFrameShape(QFrame::NoFrame); + QHBoxLayout *layout = new QHBoxLayout(frame); + + QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); + connect(buttonAdd, &QPushButton::clicked, [this]() { this->mapListAddItem(); }); + QPushButton *buttonRemove = new QPushButton(QIcon(":/icons/delete.ico"), ""); + connect(buttonRemove, &QPushButton::clicked, [this]() { this->mapListRemoveItem(); }); + + layout->addWidget(buttonAdd); + layout->addWidget(buttonRemove); + + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + + this->ui->mapListContainer->setCornerWidget(frame, Qt::TopRightCorner); } void MainWindow::initExtraSignals() { @@ -1178,12 +1196,6 @@ bool MainWindow::populateMapList() { this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); - /// !TODO - // ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); - // ui->mapList->setDragEnabled(true); - // ui->mapList->setAcceptDrops(true); - // ui->mapList->setDropIndicatorShown(true); - // ui->mapList->setDragDropMode(QAbstractItemView::InternalMove); on_toolButton_EnableDisable_EditGroups_clicked(); return success; @@ -1270,6 +1282,58 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { (this->*addFunction)(menu.exec(QCursor::pos())); } +void MainWindow::mapListAddGroup() { + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::ApplicationModal); + QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + connect(&newItemButtonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + QLineEdit *newNameEdit = new QLineEdit(&dialog); + newNameEdit->setClearButtonEnabled(true); + + static const QRegularExpression re_validChars("[_A-Za-z0-9]*$"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); + newNameEdit->setValidator(validator); + + QFormLayout form(&dialog); + + form.addRow("New Group Name", newNameEdit); + form.addRow(&newItemButtonBox); + + if (dialog.exec() == QDialog::Accepted) { + QString newFieldName = newNameEdit->text(); + if (newFieldName.isEmpty()) return; + this->mapGroupModel->insertGroupItem(newFieldName); + } +} + +void MainWindow::mapListAddLayout() { + // this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); +} + +void MainWindow::mapListAddArea() { + // this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); +} + +void MainWindow::mapListAddItem() { + switch (this->ui->mapListContainer->currentIndex()) { + case 0: + this->mapListAddGroup(); + break; + case 1: + this->mapListAddLayout(); + break; + case 2: + this->mapListAddArea(); + break; + } +} + +void MainWindow::mapListRemoveItem() { + // !TODO +} + void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) { if (!triggeredAction) return; diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index d22f2af6..ee60affc 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -156,15 +156,26 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i rowCount++; } - this->insertRows(firstRow, rowCount, parentIndex); - - int newItemIndex = 0; - for (QString mapName : droppedMaps) { - QModelIndex mapIndex = index(firstRow, 0, parentIndex); - QStandardItem *mapItem = this->itemFromIndex(mapIndex); - createMapItem(mapName, mapItem); - firstRow++; + QStandardItem *groupItem = this->itemFromIndex(parentIndex); + if (groupItem->hasChildren()) { + this->insertRows(firstRow, rowCount, parentIndex); + for (QString mapName : droppedMaps) { + QModelIndex mapIndex = index(firstRow, 0, parentIndex); + QStandardItem *mapItem = this->itemFromIndex(mapIndex); + createMapItem(mapName, mapItem); + firstRow++; + } } + // for whatever reason insertRows doesn't work as I expected with childless items + // so just append all the new maps instead + else { + for (QString mapName : droppedMaps) { + QStandardItem *mapItem = createMapItem(mapName); + groupItem->appendRow(mapItem); + firstRow++; + } + } + } emit dragMoveCompleted(); @@ -189,6 +200,10 @@ void MapGroupModel::updateProject() { QStringList mapsInGroup; for (int m = 0; m < groupItem->rowCount(); m++) { QStandardItem *mapItem = groupItem->child(m); + if (!mapItem) { + logError("An error occured while trying to apply updates to map group structure."); + return; + } QString mapName = mapItem->data(Qt::UserRole).toString(); mapsInGroup.append(mapName); mapNames.append(mapName); @@ -222,10 +237,17 @@ QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) return map; } +QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { + QStandardItem *group = createGroupItem(groupName, this->groupItems.size()); + this->root->appendRow(group); + this->updateProject(); + return group; +} + QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { QStandardItem *group = this->groupItems[groupName]; if (!group) { - return nullptr; + group = insertGroupItem(groupName); } QStandardItem *map = createMapItem(mapName); group->appendRow(map); From 22b4108a7f11afb3edcb132059f9373a5052d677 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 15 Feb 2024 22:19:49 -0500 Subject: [PATCH 041/364] create 'add layout' button --- include/core/maplayout.h | 11 ++++ include/project.h | 5 +- include/ui/maplistmodels.h | 1 + src/editor.cpp | 1 + src/mainwindow.cpp | 120 ++++++++++++++++++++++++++++++++++++- src/project.cpp | 88 +++++++++++++++++++++------ src/ui/maplistmodels.cpp | 9 +++ 7 files changed, 213 insertions(+), 22 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 7ec240b6..cdd3b5d6 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -69,6 +69,17 @@ public: QUndoStack editHistory; + // to simplify new layout settings transfer between functions + struct SimpleSettings { + QString id; + QString name; + int width; + int height; + QString tileset_primary_label; + QString tileset_secondary_label; + QString from_id = QString(); + }; + public: Layout *copy(); void copyFrom(Layout *other); diff --git a/include/project.h b/include/project.h index 85c8e3de..49bcd6aa 100644 --- a/include/project.h +++ b/include/project.h @@ -139,6 +139,7 @@ public: bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); + Layout *createNewLayout(Layout::SimpleSettings &layoutSettings); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -235,8 +236,8 @@ public: private: void updateLayout(Layout *); - void setNewMapBlockdata(Map* map); - void setNewMapBorder(Map *map); + void setNewLayoutBlockdata(Layout *layout); + void setNewLayoutBorder(Layout *layout); void setNewMapEvents(Map *map); void setNewMapConnections(Map *map); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index a2babda6..bb2fd07a 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -155,6 +155,7 @@ public: QStandardItem *createLayoutItem(QString layoutId); QStandardItem *createMapItem(QString mapName); + QStandardItem *insertLayoutItem(QString layoutId); QStandardItem *insertMapItem(QString mapName, QString layoutId); QStandardItem *getItem(const QModelIndex &index) const; diff --git a/src/editor.cpp b/src/editor.cpp index 5ed97782..aaad47db 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -74,6 +74,7 @@ void Editor::save() { } else if (this->project && this->layout) { this->project->saveLayout(this->layout); + this->project->saveAllDataStructures(); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4d31fb6c..8a9f95b5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1309,7 +1309,121 @@ void MainWindow::mapListAddGroup() { } void MainWindow::mapListAddLayout() { - // this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); + 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-z0-9]*$"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); + newNameEdit->setValidator(validator); + + 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->mapLayoutsTable); + 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%) }"); + QString errorMessage; + + 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 + bool issue = false; + QString tryLayoutName = newNameEdit->text(); + // name not empty + if (tryLayoutName.isEmpty()) { + errorMessage = "Name cannot be empty"; + issue = true; + } + // unique layout name & id + else if (this->editor->project->mapLayoutsTable.contains(newId->text()) + || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { + errorMessage = "Layout Name / ID is not unique"; + issue = true; + } + // from id is existing value + else if (useExistingCheck->isChecked()) { + if (!this->editor->project->mapLayoutsTable.contains(useExistingCombo->currentText())) { + errorMessage = "Existing layout ID is not valid"; + issue = true; + } + } + + if (issue) { + // 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); + QStandardItem *item = this->layoutTreeModel->insertLayoutItem(newLayout->id); + setLayout(newLayout->id); + } } void MainWindow::mapListAddArea() { @@ -1322,10 +1436,10 @@ void MainWindow::mapListAddItem() { this->mapListAddGroup(); break; case 1: - this->mapListAddLayout(); + this->mapListAddArea(); break; case 2: - this->mapListAddArea(); + this->mapListAddLayout(); break; } } diff --git a/src/project.cpp b/src/project.cpp index 41f9ccf9..81f472ed 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -385,6 +385,60 @@ QString Project::readMapLocation(QString map_name) { return ParseUtil::jsonToQString(mapObj["region_map_section"]); } +Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { + QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); + Layout *layout; + + // Handle the case where we are copying from an existing layout first. + if (!layoutSettings.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); + } + 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); + + setNewLayoutBlockdata(layout); + setNewLayoutBorder(layout); + } + + // Create a new directory for the layout + QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); + if (!QDir::root().mkdir(newLayoutDir)) { + logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); + delete layout; + return nullptr; + } + + mapLayouts.insert(layout->id, layout); + mapLayoutsMaster.insert(layout->id, layout->copy()); + mapLayoutsTable.append(layout->id); + mapLayoutsTableMaster.append(layout->id); + layoutIdsToNames.insert(layout->id, layout->name); + + saveLayout(layout); + + this->loadLayout(layout); + + return layout; +} + bool Project::loadLayout(Layout *layout) { // !TODO: make sure this doesn't break anything, maybe do something better. new layouts work too? if (!layout->loaded) { @@ -1119,16 +1173,16 @@ bool Project::loadBlockdata(Layout *layout) { return true; } -void Project::setNewMapBlockdata(Map *map) { - map->layout->blockdata.clear(); - int width = map->getWidth(); - int height = map->getHeight(); +void Project::setNewLayoutBlockdata(Layout *layout) { + layout->blockdata.clear(); + int width = layout->getWidth(); + int height = layout->getHeight(); Block block(projectConfig.getDefaultMetatileId(), projectConfig.getDefaultCollision(), projectConfig.getDefaultElevation()); for (int i = 0; i < width * height; i++) { - map->layout->blockdata.append(block); + layout->blockdata.append(block); } - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; - map->layout->lastCommitBlocks.layoutDimensions = QSize(width, height); + layout->lastCommitBlocks.blocks = layout->blockdata; + layout->lastCommitBlocks.layoutDimensions = QSize(width, height); } bool Project::loadLayoutBorder(Layout *layout) { @@ -1147,27 +1201,27 @@ bool Project::loadLayoutBorder(Layout *layout) { return true; } -void Project::setNewMapBorder(Map *map) { - map->layout->border.clear(); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); +void Project::setNewLayoutBorder(Layout *layout) { + layout->border.clear(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); const QList configMetatileIds = projectConfig.getNewMapBorderMetatileIds(); if (configMetatileIds.length() != width * height) { // Border size doesn't match the number of default border metatiles. // Fill the border with empty metatiles. for (int i = 0; i < width * height; i++) { - map->layout->border.append(0); + layout->border.append(0); } } else { // Fill the border with the default metatiles from the config. for (int i = 0; i < width * height; i++) { - map->layout->border.append(configMetatileIds.at(i)); + layout->border.append(configMetatileIds.at(i)); } } - map->layout->lastCommitBlocks.border = map->layout->border; - map->layout->lastCommitBlocks.borderDimensions = QSize(width, height); + layout->lastCommitBlocks.border = layout->border; + layout->lastCommitBlocks.borderDimensions = QSize(width, height); } void Project::saveLayoutBorder(Layout *layout) { @@ -1809,10 +1863,10 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool mapLayoutsTable.append(newMap->layoutId); layoutIdsToNames.insert(newMap->layout->id, newMap->layout->name); if (!importedMap) { - setNewMapBlockdata(newMap); + setNewLayoutBlockdata(newMap->layout); } if (newMap->layout->border.isEmpty()) { - setNewMapBorder(newMap); + setNewLayoutBorder(newMap->layout); } } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index ee60affc..a572c7eb 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -535,6 +535,11 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { return map; } +QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { + QStandardItem *layoutItem = this->createLayoutItem(layoutId); + this->root->appendRow(layoutItem); +} + QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { QStandardItem *layout = nullptr; if (this->layoutItems.contains(layoutId)) { @@ -591,6 +596,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { int col = index.column(); if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); @@ -607,6 +613,9 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) { return mapEditedIcon; } + else if (!this->project->mapLayouts[layoutId]->loaded) { + return mapGrayIcon; + } } return mapIcon; } From 74e4e2647c280998a51c3a7118da73aab36adce5 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 16 Feb 2024 19:17:56 -0500 Subject: [PATCH 042/364] add new area (map section) functionality --- include/project.h | 6 ++++ include/ui/maplistmodels.h | 1 + src/mainwindow.cpp | 30 ++++++++++++++++- src/project.cpp | 66 ++++++++++++++++++++++++++++++++++++++ src/ui/maplistmodels.cpp | 8 +++++ 5 files changed, 110 insertions(+), 1 deletion(-) diff --git a/include/project.h b/include/project.h index 49bcd6aa..b8426453 100644 --- a/include/project.h +++ b/include/project.h @@ -80,6 +80,9 @@ public: QString importExportPath; QSet disabledSettingsNames; + // For files that are read and could contain extra text + QMap extraFileText; + void set_root(QString); void initSignals(); @@ -135,6 +138,8 @@ public: bool readSpeciesIconPaths(); QMap speciesToIconPath; + int appendMapsec(QString name); + QSet getTopLevelMapFields(); bool loadMapData(Map*); bool readMapLayouts(); @@ -159,6 +164,7 @@ public: void saveAllDataStructures(); void saveMapLayouts(); void saveMapGroups(); + void saveMapSections(); void saveWildMonData(); void saveMapConstantsHeader(); void saveHealLocations(Map*); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index bb2fd07a..5787a622 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -117,6 +117,7 @@ public: QStandardItem *createAreaItem(QString areaName, int areaIndex); QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); + QStandardItem *insertAreaItem(QString areaName); QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); QStandardItem *getItem(const QModelIndex &index) const; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8a9f95b5..dc0ac54f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1427,7 +1427,35 @@ void MainWindow::mapListAddLayout() { } void MainWindow::mapListAddArea() { - // this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); + // Note: there is no checking here for the limits on map section count + 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->setText(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); + newNameEdit->setClearButtonEnabled(false); + + QRegularExpression re_validChars(QString("%1[_A-Za-z0-9]+$").arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); + newNameEdit->setValidator(validator); + + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ + if (!this->editor->project->mapSectionNameToValue.contains(newNameEdit->text())) + dialog.accept(); + }); + + QFormLayout form(&dialog); + + form.addRow("New Map Section Name", newNameEdit); + form.addRow(&newItemButtonBox); + + if (dialog.exec() == QDialog::Accepted) { + QString newFieldName = newNameEdit->text(); + if (newFieldName.isEmpty()) return; + this->mapAreaModel->insertAreaItem(newFieldName); + } } void MainWindow::mapListAddItem() { diff --git a/src/project.cpp b/src/project.cpp index 81f472ed..6ee18551 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -700,6 +700,34 @@ void Project::saveMapGroups() { mapGroupsFile.close(); } +void Project::saveMapSections() { + QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections); + + QString text = QString("#ifndef GUARD_REGIONMAPSEC_H\n"); + text += QString("#define GUARD_REGIONMAPSEC_H\n\n"); + + int longestLength = 0; + for (QString label : this->mapSectionNameToValue.keys()) { + if (label.size() > longestLength) + longestLength = label.size(); + } + + // mapSectionValueToName + for (int value : this->mapSectionValueToName.keys()) { + QString line = QString("#define %1 0x%2\n") + .arg(this->mapSectionValueToName[value], -1 * longestLength) + .arg(QString("%1").arg(value, 2, 16, QLatin1Char('0')).toUpper()); + text += line; + } + + text += "\n" + this->extraFileText[projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections)] + "\n"; + + text += QString("#endif // GUARD_REGIONMAPSEC_H\n"); + + ignoreWatchedFileTemporarily(filepath); + saveTextFile(filepath, text); +} + void Project::saveWildMonData() { if (!userConfig.getEncounterJsonActive()) return; @@ -1421,6 +1449,7 @@ void Project::updateLayout(Layout *layout) { void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); + saveMapSections(); saveMapConstantsHeader(); saveWildMonData(); } @@ -2158,9 +2187,46 @@ bool Project::readRegionMapSections() { for (QString defineName : this->mapSectionNameToValue.keys()) { this->mapSectionValueToName.insert(this->mapSectionNameToValue[defineName], defineName); } + + // extra text + QString extraText; + QString fileText = ParseUtil::readTextFile(root + "/" + filename); + QTextStream stream(&fileText); + QString currentLine; + while (stream.readLineInto(¤tLine)) { + // is this line something that porymap will output again? + if (currentLine.isEmpty()) { + continue; + } + // include guards + else if (currentLine.contains("GUARD_REGIONMAPSEC_H")) { + continue; + } + // defines captured (not considering comments) + else if (currentLine.contains("#define " + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))) { + continue; + } + // everything else should be kept here + else { + extraText += currentLine + "\n"; + } + } + stream.seek(0); + this->extraFileText[filename] = extraText; return true; } +int Project::appendMapsec(QString name) { + // This function assumes a valid and unique name. + // Will return the new index. + int noneBefore = this->mapSectionNameToValue[projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + "NONE"]; + this->mapSectionNameToValue[name] = noneBefore; + this->mapSectionValueToName[noneBefore] = name; + this->mapSectionNameToValue[projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + "NONE"] = noneBefore + 1; + this->mapSectionValueToName[noneBefore + 1] = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + "NONE"; + return noneBefore; +} + // Read the constants to preserve any "unused" heal locations when writing the file later bool Project::readHealLocationConstants() { this->healLocationNameToValue.clear(); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a572c7eb..8dbc24b8 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -392,6 +392,14 @@ QStandardItem *MapAreaModel::createMapItem(QString mapName, int groupIndex, int return map; } +QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { + int newAreaIndex = this->project->appendMapsec(areaName); + QStandardItem *item = createAreaItem(areaName, newAreaIndex); + this->root->insertRow(newAreaIndex, item); + this->areaItems["MAPSEC_NONE"]->setData(newAreaIndex + 1, MapListRoles::GroupRole); + return item; +} + QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) { // int areaIndex = this->project->mapSectionNameToValue[areaName]; QStandardItem *area = this->areaItems[areaName]; From 879bb44bc0abd9a574fa269a39fb583a582ca2ca Mon Sep 17 00:00:00 2001 From: garak Date: Sat, 17 Feb 2024 22:47:48 -0500 Subject: [PATCH 043/364] functions to remove map groups and map sections --- include/mainwindow.h | 3 ++ include/ui/maplistmodels.h | 2 ++ src/mainwindow.cpp | 57 +++++++++++++++++++++++++++++++++++++- src/project.cpp | 2 ++ src/ui/maplistmodels.cpp | 14 ++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 0c3bd4ee..73d16a91 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -394,6 +394,9 @@ private: void mapListAddGroup(); void mapListAddLayout(); void mapListAddArea(); + void mapListRemoveGroup(); + void mapListRemoveArea(); + void mapListRemoveLayout(); void displayMapProperties(); void checkToolButtons(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 5787a622..7d56da17 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -77,6 +77,7 @@ public: QStandardItem *insertGroupItem(QString groupName); QStandardItem *insertMapItem(QString mapName, QString groupName); + void removeGroup(int groupIndex); QStandardItem *getItem(const QModelIndex &index) const; QModelIndex indexOfMap(QString mapName); @@ -119,6 +120,7 @@ public: QStandardItem *insertAreaItem(QString areaName); QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); + void removeArea(int groupIndex); QStandardItem *getItem(const QModelIndex &index) const; QModelIndex indexOfMap(QString mapName); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index dc0ac54f..b96003a9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1459,6 +1459,8 @@ void MainWindow::mapListAddArea() { } void MainWindow::mapListAddItem() { + if (!this->editor || !this->editor->project) return; + switch (this->ui->mapListContainer->currentIndex()) { case 0: this->mapListAddGroup(); @@ -1472,8 +1474,61 @@ void MainWindow::mapListAddItem() { } } +void MainWindow::mapListRemoveGroup() { + QItemSelectionModel *selectionModel = this->ui->mapList->selectionModel(); + if (selectionModel->hasSelection()) { + QModelIndexList selectedIndexes = selectionModel->selectedRows(); + for (QModelIndex proxyIndex : selectedIndexes) { + QModelIndex index = this->groupListProxyModel->mapToSource(proxyIndex); + QStandardItem *item = this->mapGroupModel->getItem(index)->child(index.row(), index.column()); + if (!item) continue; + QString type = item->data(MapListRoles::TypeRole).toString(); + if (type == "map_group" && !item->hasChildren()) { + QString groupName = item->data(Qt::UserRole).toString(); + // delete empty group + this->mapGroupModel->removeGroup(index.row()); + } + } + } +} + +void MainWindow::mapListRemoveArea() { + QItemSelectionModel *selectionModel = this->ui->areaList->selectionModel(); + if (selectionModel->hasSelection()) { + QModelIndexList selectedIndexes = selectionModel->selectedRows(); + for (QModelIndex proxyIndex : selectedIndexes) { + QModelIndex index = this->areaListProxyModel->mapToSource(proxyIndex); + QStandardItem *item = this->mapAreaModel->getItem(index)->child(index.row(), index.column()); + if (!item) continue; + QString type = item->data(MapListRoles::TypeRole).toString(); + if (type == "map_section" && !item->hasChildren()) { + QString groupName = item->data(Qt::UserRole).toString(); + // delete empty section + this->mapAreaModel->removeArea(index.row()); + } + } + } +} + +void MainWindow::mapListRemoveLayout() { + // TODO: consider this + // do nothing, for now at least +} + void MainWindow::mapListRemoveItem() { - // !TODO + if (!this->editor || !this->editor->project) return; + + switch (this->ui->mapListContainer->currentIndex()) { + case 0: + this->mapListRemoveGroup(); + break; + case 1: + this->mapListRemoveArea(); + break; + case 2: + this->mapListRemoveLayout(); + break; + } } void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) { diff --git a/src/project.cpp b/src/project.cpp index 6ee18551..0f641c91 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -712,6 +712,8 @@ void Project::saveMapSections() { longestLength = label.size(); } + longestLength += 1; + // mapSectionValueToName for (int value : this->mapSectionValueToName.keys()) { QString line = QString("#define %1 0x%2\n") diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 8dbc24b8..880a98ca 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -244,6 +244,11 @@ QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { return group; } +void MapGroupModel::removeGroup(int groupIndex) { + this->removeRow(groupIndex); + this->updateProject(); +} + QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { QStandardItem *group = this->groupItems[groupName]; if (!group) { @@ -412,6 +417,11 @@ QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, in return map; } +void MapAreaModel::removeArea(int areaIndex) { + this->removeRow(areaIndex); + this->project->mapSectionNameToValue.remove(this->project->mapSectionValueToName.take(areaIndex)); +} + void MapAreaModel::initialize() { this->areaItems.clear(); this->mapItems.clear(); @@ -454,6 +464,8 @@ QModelIndex MapAreaModel::indexOfMap(QString mapName) { } QVariant MapAreaModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) return QVariant(); + int row = index.row(); int col = index.column(); @@ -600,6 +612,8 @@ QModelIndex LayoutTreeModel::indexOfLayout(QString layoutName) { } QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) return QVariant(); + int row = index.row(); int col = index.column(); From 05beed21cada72ff095ce31b9c453fe614ce75a6 Mon Sep 17 00:00:00 2001 From: garak Date: Sun, 18 Feb 2024 19:56:52 -0500 Subject: [PATCH 044/364] disable deletion of map sections and layouts --- src/mainwindow.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b96003a9..d6e8ece2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1511,8 +1511,7 @@ void MainWindow::mapListRemoveArea() { } void MainWindow::mapListRemoveLayout() { - // TODO: consider this - // do nothing, for now at least + // TODO: consider this in the future } void MainWindow::mapListRemoveItem() { @@ -1523,10 +1522,12 @@ void MainWindow::mapListRemoveItem() { this->mapListRemoveGroup(); break; case 1: - this->mapListRemoveArea(); + // Disabled + // this->mapListRemoveArea(); break; case 2: - this->mapListRemoveLayout(); + // Disabled + // this->mapListRemoveLayout(); break; } } @@ -1758,7 +1759,6 @@ void MainWindow::currentMetatilesSelectionChanged() // !TODO void MainWindow::on_mapListContainer_currentChanged(int index) { - // switch (index) { case MapListTab::Groups: this->mapSortOrder = MapSortOrder::SortByGroup; From 70c6e414f118acb9d6f13ef938846f5aa95041a6 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 28 Mar 2024 10:21:35 -0400 Subject: [PATCH 045/364] reopen porymap on layout view when applicable --- include/mainwindow.h | 1 + src/mainwindow.cpp | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 04c04cef..177196bb 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -387,6 +387,7 @@ private: void setRecentMapConfig(QString map_name); void setRecentLayoutConfig(QString layoutId); bool setInitialMap(); + bool setInitialLayout(); void setRecentMap(QString map_name); QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); void refreshRecentProjectsMenu(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4ff36eef..842f9fb3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -617,7 +617,7 @@ bool MainWindow::openProject(const QString &dir, bool initial) { this->projectOpenFailure = !(loadDataStructures() && populateMapList() - && setInitialMap()); + && (this->mapSortOrder == MapSortOrder::SortByLayout ? setInitialLayout() : setInitialMap())); if (this->projectOpenFailure) { this->statusBar()->showMessage(QString("Failed to open %1").arg(projectString)); @@ -713,6 +713,26 @@ bool MainWindow::setInitialMap() { return false; } +bool MainWindow::setInitialLayout() { + QStringList names; + if (editor && editor->project) + names = editor->project->mapLayoutsTable; + + // Try to set most recently-opened layout, if it's still in the list. + QString recentLayout = userConfig.getRecentLayout(); + if (!recentLayout.isEmpty() && names.contains(recentLayout) && setLayout(recentLayout)) + return true; + + // Failing that, try loading maps in the map list sequentially. + for (auto name : names) { + if (name != recentLayout && setLayout(name)) + return true; + } + + logError("Failed to load any layouts."); + return false; +} + void MainWindow::refreshRecentProjectsMenu() { ui->menuOpen_Recent_Project->clear(); QStringList recentProjects = porymapConfig.getRecentProjects(); @@ -980,10 +1000,12 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ void MainWindow::setRecentMapConfig(QString mapName) { userConfig.setRecentMap(mapName); + userConfig.setRecentLayout(""); } void MainWindow::setRecentLayoutConfig(QString layoutId) { userConfig.setRecentLayout(layoutId); + userConfig.setRecentMap(""); } void MainWindow::displayMapProperties() { From 89fb4019a527acb0afb13ed38390685812f0bbba Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 18 Apr 2024 12:21:09 -0400 Subject: [PATCH 046/364] cleanup shortcuts --- include/mainwindow.h | 3 +++ src/core/map.cpp | 1 - src/editor.cpp | 4 +-- src/mainwindow.cpp | 62 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 177196bb..12af3585 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -280,6 +280,9 @@ private slots: void on_horizontalSlider_CollisionTransparency_valueChanged(int value); + void on_toolButton_HideShow_clicked(); + void on_toolButton_ExpandAll_clicked(); + void on_toolButton_CollapseAll_clicked(); void on_toolButton_HideShow_Groups_clicked(); void on_toolButton_ExpandAll_Groups_clicked(); void on_toolButton_CollapseAll_Groups_clicked(); diff --git a/src/core/map.cpp b/src/core/map.cpp index f9782266..1d52a0ba 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -186,6 +186,5 @@ void Map::clean() { } bool Map::hasUnsavedChanges() { - // !TODO: layout not working here? map needs to be in cache before the layout being edited works return !editHistory.isClean() || !this->layout->editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile; } diff --git a/src/editor.cpp b/src/editor.cpp index aaad47db..1ae94b40 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -139,7 +139,7 @@ void Editor::setEditorView() { } break; case EditMode::Connections: - populateConnectionMapPickers(); // !TODO: move to setmap or sumn/ displaymapconnections type ish + populateConnectionMapPickers(); ui->label_NumConnections->setText(QString::number(map->connections.length())); setDiveEmergeControls(); @@ -155,7 +155,7 @@ void Editor::setEditorView() { setConnectionItemsVisible(true); setConnectionsEditable(true); this->cursorMapTileRect->setActive(false); - map_item->setEditsEnabled(false); // !TODO + map_item->setEditsEnabled(false); case EditMode::Header: case EditMode::Encounters: default: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 842f9fb3..5675d79e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -162,14 +162,17 @@ void MainWindow::initExtraShortcuts() { shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths"); shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths"); - /// !TODO - // auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked())); - // shortcutExpand_All->setObjectName("shortcutExpand_All"); - // shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); + auto *shortcutHide_Show = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_HideShow_clicked())); + shortcutHide_Show->setObjectName("shortcutHide_Show"); + shortcutHide_Show->setWhatsThis("Map List: Hide/Show Empty Folders"); - // auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked())); - // shortcutCollapse_All->setObjectName("shortcutCollapse_All"); - // shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); + auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked())); + shortcutExpand_All->setObjectName("shortcutExpand_All"); + shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); + + auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked())); + shortcutCollapse_All->setObjectName("shortcutCollapse_All"); + shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); auto *shortcut_Open_Scripts = new Shortcut(QKeySequence(), ui->toolButton_Open_Scripts, SLOT(click())); shortcut_Open_Scripts->setObjectName("shortcut_Open_Scripts"); @@ -905,8 +908,7 @@ bool MainWindow::setLayout(QString layoutId) { updateMapList(); // !TODO: make sure these connections are not duplicated / cleared later - connect(editor->layout, &Layout::layoutChanged, [this]() { onMapChanged(nullptr); }); - connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); // connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); updateTilesetEditor(); @@ -3178,6 +3180,48 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } +void MainWindow::on_toolButton_ExpandAll_clicked() { + switch (ui->mapListContainer->currentIndex()) { + case MapListTab::Groups: + this->on_toolButton_ExpandAll_Groups_clicked(); + break; + case MapListTab::Areas: + this->on_toolButton_ExpandAll_Areas_clicked(); + break; + case MapListTab::Layouts: + this->on_toolButton_ExpandAll_Layouts_clicked(); + break; + } +} + +void MainWindow::on_toolButton_CollapseAll_clicked() { + switch (ui->mapListContainer->currentIndex()) { + case MapListTab::Groups: + this->on_toolButton_CollapseAll_Groups_clicked(); + break; + case MapListTab::Areas: + this->on_toolButton_CollapseAll_Areas_clicked(); + break; + case MapListTab::Layouts: + this->on_toolButton_CollapseAll_Layouts_clicked(); + break; + } +} + +void MainWindow::on_toolButton_HideShow_clicked() { + switch (ui->mapListContainer->currentIndex()) { + case MapListTab::Groups: + this->on_toolButton_HideShow_Groups_clicked(); + break; + case MapListTab::Areas: + this->on_toolButton_HideShow_Areas_clicked(); + break; + case MapListTab::Layouts: + this->on_toolButton_HideShow_Layouts_clicked(); + break; + } +} + void MainWindow::on_toolButton_HideShow_Groups_clicked() { if (ui->mapList) { this->groupListProxyModel->toggleHideEmpty(); From 5bb0983c3319a7e4d71e89546ffe705eb82c41e1 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 18 Apr 2024 13:25:06 -0400 Subject: [PATCH 047/364] cleanup: resolve map list scrolling --- include/mainwindow.h | 1 - src/mainwindow.cpp | 20 ++++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 12af3585..d2350d72 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -379,7 +379,6 @@ private: bool loadDataStructures(); bool loadProjectCombos(); bool populateMapList(); - void sortMapList(); void openSubWindow(QWidget * window); void scrollTreeView(QString itemName); QString getExistingDirectory(QString); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5675d79e..6afeca14 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -250,7 +250,6 @@ void MainWindow::initCustomUI() { } void MainWindow::initExtraSignals() { - /// !TODO // Right-clicking on items in the map list tree view brings up a context menu. ui->mapList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->mapList, &QTreeView::customContextMenuRequested, @@ -402,17 +401,12 @@ void MainWindow::initMiscHeapObjects() { ui->tabWidget_EventType->clear(); } -// !TODO: scroll view on first showing void MainWindow::initMapSortOrder() { mapSortOrder = porymapConfig.getMapSortOrder(); - // if (mapSortOrder == MapSortOrder::SortByLayout) - // mapSortOrder = MapSortOrder::SortByGroup; - this->ui->mapListContainer->setCurrentIndex(static_cast(this->mapSortOrder)); } void MainWindow::showWindowTitle() { - // !TODO, check editor editmode if (editor->map) { setWindowTitle(QString("%1%2 - %3") .arg(editor->map->hasUnsavedChanges() ? "* " : "") @@ -490,18 +484,22 @@ void MainWindow::on_lineEdit_filterBox_Layouts_textChanged(const QString &text) void MainWindow::applyMapListFilter(QString filterText) { FilterChildrenProxyModel *proxy; QTreeView *list; + QModelIndex sourceIndex; switch (this->mapSortOrder) { case MapSortOrder::SortByGroup: proxy = this->groupListProxyModel; list = this->ui->mapList; + sourceIndex = mapGroupModel->indexOfMap(editor->map->name); break; case MapSortOrder::SortByArea: proxy = this->areaListProxyModel; list = this->ui->areaList; + sourceIndex = mapAreaModel->indexOfMap(editor->map->name); break; case MapSortOrder::SortByLayout: proxy = this->layoutListProxyModel; list = this->ui->layoutList; + sourceIndex = layoutTreeModel->indexOfLayout(editor->layout->id); break; } @@ -512,10 +510,8 @@ void MainWindow::applyMapListFilter(QString filterText) { list->expandToDepth(0); } - /// !TODO - // ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); - // ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), true); - // ui->mapList->scrollTo(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), QAbstractItemView::PositionAtCenter); + list->setExpanded(proxy->mapFromSource(sourceIndex), true); + list->scrollTo(proxy->mapFromSource(sourceIndex), QAbstractItemView::PositionAtCenter); } void MainWindow::loadUserSettings() { @@ -1285,10 +1281,6 @@ void MainWindow::scrollTreeView(QString itemName) { } } -// !TODO: remove this? -void MainWindow::sortMapList() { -} - void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QStandardItemModel *model; int dataRole; From f46ac36a94d7bee7aefdabbfad5929295ce4be50 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 18 Apr 2024 14:38:15 -0400 Subject: [PATCH 048/364] cleanup: shortcuts, setLayout --- include/mainwindow.h | 7 ++++--- src/core/maplayout.cpp | 43 ------------------------------------------ src/mainwindow.cpp | 34 +++++++++++++++------------------ 3 files changed, 19 insertions(+), 65 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index d2350d72..7ee2786c 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -185,6 +185,7 @@ private slots: void onLoadMapRequested(QString, QString); void onMapChanged(Map *map); + void onLayoutChanged(Layout *layout); void onMapNeedsRedrawing(); void onLayoutNeedsRedrawing(); void onTilesetsSaved(QString, QString); @@ -280,9 +281,9 @@ private slots: void on_horizontalSlider_CollisionTransparency_valueChanged(int value); - void on_toolButton_HideShow_clicked(); - void on_toolButton_ExpandAll_clicked(); - void on_toolButton_CollapseAll_clicked(); + void do_HideShow(); + void do_ExpandAll(); + void do_CollapseAll(); void on_toolButton_HideShow_Groups_clicked(); void on_toolButton_ExpandAll_Groups_clicked(); void on_toolButton_CollapseAll_Groups_clicked(); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 4f639373..9e283e26 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -5,51 +5,8 @@ #include "scripting.h" #include "imageproviders.h" - // QString id; - // QString name; - // int width; - // int height; - // int border_width; - // int border_height; - // QString border_path; - // QString blockdata_path; - - // QString tileset_primary_label; - // QString tileset_secondary_label; - - // Tileset *tileset_primary = nullptr; - // Tileset *tileset_secondary = nullptr; - - // Blockdata blockdata; - - // QImage image; - // QPixmap pixmap; - // QImage border_image; - // QPixmap border_pixmap; - // QImage collision_image; - // QPixmap collision_pixmap; - - // Blockdata border; - // Blockdata cached_blockdata; - // Blockdata cached_collision; - // Blockdata cached_border; - // struct { - // Blockdata blocks; - // QSize mapDimensions; - // Blockdata border; - // QSize borderDimensions; - // } lastCommitBlocks; // to track map changes - - // QList metatileLayerOrder; - // QList metatileLayerOpacity; - - // LayoutPixmapItem *layoutItem = nullptr; - // CollisionPixmapItem *collisionItem = nullptr; - // BorderMetatilesPixmapItem *borderItem = nullptr; - - // QUndoStack editHistory; Layout *Layout::copy() { Layout *layout = new Layout; layout->copyFrom(this); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6afeca14..f7884ad6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -162,15 +162,15 @@ void MainWindow::initExtraShortcuts() { shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths"); shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths"); - auto *shortcutHide_Show = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_HideShow_clicked())); + auto *shortcutHide_Show = new Shortcut(QKeySequence(), this, SLOT(do_HideShow())); shortcutHide_Show->setObjectName("shortcutHide_Show"); shortcutHide_Show->setWhatsThis("Map List: Hide/Show Empty Folders"); - auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked())); + auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(do_ExpandAll())); shortcutExpand_All->setObjectName("shortcutExpand_All"); shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); - auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked())); + auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(do_CollapseAll())); shortcutCollapse_All->setObjectName("shortcutCollapse_All"); shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); @@ -847,7 +847,6 @@ bool MainWindow::setMap(QString map_name, bool scroll) { } if (editor->map && !editor->map->name.isNull()) { - // !TODO: function to act on current view? or that does all the views ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); } @@ -869,12 +868,12 @@ bool MainWindow::setMap(QString map_name, bool scroll) { showWindowTitle(); - connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged); - connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); - connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); + connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged, Qt::UniqueConnection); + connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing, Qt::UniqueConnection); + connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); - connect(editor->layout, &Layout::layoutChanged, [this]() { onMapChanged(nullptr); }); - connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing); + connect(editor->layout, &Layout::layoutChanged, this, &MainWindow::onLayoutChanged, Qt::UniqueConnection); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); setRecentMapConfig(map_name); updateMapList(); @@ -903,9 +902,7 @@ bool MainWindow::setLayout(QString layoutId) { showWindowTitle(); updateMapList(); - // !TODO: make sure these connections are not duplicated / cleared later connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); - // connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); updateTilesetEditor(); @@ -1064,12 +1061,9 @@ void MainWindow::displayMapProperties() { } void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { - // if (editor && editor->project && editor->map) { if (editor->project->mapLayouts.contains(text)) { editor->map->setLayout(editor->project->loadLayout(text)); - // !TODO: method to setMapLayout instead of having to do whole setMap thing, - // also edit history and bug fixes setMap(editor->map->name); markMapEdited(); } @@ -1834,7 +1828,6 @@ void MainWindow::currentMetatilesSelectionChanged() { scrollMetatileSelectorToSelection(); } -// !TODO void MainWindow::on_mapListContainer_currentChanged(int index) { switch (index) { case MapListTab::Groups: @@ -1853,7 +1846,6 @@ void MainWindow::on_mapListContainer_currentChanged(int index) { porymapConfig.setMapSortOrder(this->mapSortOrder); } -/// !TODO void MainWindow::on_mapList_activated(const QModelIndex &index) { QVariant data = index.data(Qt::UserRole); if (index.data(MapListRoles::TypeRole) == "map_name" && !data.isNull()) { @@ -2844,6 +2836,10 @@ void MainWindow::onMapChanged(Map *) { updateMapList(); } +void MainWindow::onLayoutChanged(Layout *) { + updateMapList(); +} + void MainWindow::onMapNeedsRedrawing() { redrawMapScene(); } @@ -3172,7 +3168,7 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } -void MainWindow::on_toolButton_ExpandAll_clicked() { +void MainWindow::do_ExpandAll() { switch (ui->mapListContainer->currentIndex()) { case MapListTab::Groups: this->on_toolButton_ExpandAll_Groups_clicked(); @@ -3186,7 +3182,7 @@ void MainWindow::on_toolButton_ExpandAll_clicked() { } } -void MainWindow::on_toolButton_CollapseAll_clicked() { +void MainWindow::do_CollapseAll() { switch (ui->mapListContainer->currentIndex()) { case MapListTab::Groups: this->on_toolButton_CollapseAll_Groups_clicked(); @@ -3200,7 +3196,7 @@ void MainWindow::on_toolButton_CollapseAll_clicked() { } } -void MainWindow::on_toolButton_HideShow_clicked() { +void MainWindow::do_HideShow() { switch (ui->mapListContainer->currentIndex()) { case MapListTab::Groups: this->on_toolButton_HideShow_Groups_clicked(); From 34478e69d9745df151a1b7e123540c750f81df74 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 19 Apr 2024 18:41:52 -0400 Subject: [PATCH 049/364] cleanup: resolve remaining (outdated) TODOs --- src/project.cpp | 1 - src/ui/collisionpixmapitem.cpp | 1 - src/ui/mapimageexporter.cpp | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index af5e2f2f..f74eaffe 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -440,7 +440,6 @@ Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { } bool Project::loadLayout(Layout *layout) { - // !TODO: make sure this doesn't break anything, maybe do something better. new layouts work too? if (!layout->loaded) { // Force these to run even if one fails bool loadedTilesets = loadLayoutTilesets(layout); diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index f72e496f..e0587b08 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -50,7 +50,6 @@ void CollisionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::draw(bool ignoreCache) { if (this->layout) { - // !TODO this->layout->setCollisionItem(this); setPixmap(this->layout->renderCollision(ignoreCache)); setOpacity(*this->opacity); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 44d41f19..f649ce35 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -93,13 +93,14 @@ void MapImageExporter::saveImage() { break; } case ImageExporterMode::Timelapse: - // !TODO: also need layout editHistory! + // Timelapse will play in order of layout changes then map changes (events) + // TODO: potentially update in the future? QGifImage timelapseImg; timelapseImg.setDefaultDelay(timelapseDelayMs); timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); + // lambda to avoid redundancy auto generateTimelapseFromHistory = [=, this, &timelapseImg](QString progressText, QUndoStack &historyStack){ - // QProgressDialog progress(progressText, "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); From bc454d6b132c38958b8cd888e1a9fb4323545371 Mon Sep 17 00:00:00 2001 From: garak Date: Fri, 19 Apr 2024 18:57:27 -0400 Subject: [PATCH 050/364] fix some map combos not being populated with new items --- src/mainwindow.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f7884ad6..e311eb21 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1617,6 +1617,8 @@ void MainWindow::onNewMapCreated() { editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); + loadProjectCombos(); // need to maybe repopulate layout combo + // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); @@ -1760,6 +1762,8 @@ void MainWindow::on_actionNew_Tileset_triggered() { } insertTilesetLabel(&editor->project->tilesetLabelsOrdered, createTilesetDialog->fullSymbolName); + loadProjectCombos(); // need to reload tileset combos + QMessageBox msgBox(this); msgBox.setText("Successfully created tileset."); QString message = QString("Tileset \"%1\" was created successfully.").arg(createTilesetDialog->friendlyName); From 7c8d5d0d63710121f13edd3fc249f6ae603263ad Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 9 Sep 2024 14:49:18 -0400 Subject: [PATCH 051/364] Include latest commit hash in version info --- porymap.pro | 8 ++++++++ src/ui/aboutporymap.cpp | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/porymap.pro b/porymap.pro index f36f536f..c83dd1d7 100644 --- a/porymap.pro +++ b/porymap.pro @@ -18,6 +18,14 @@ RC_ICONS = resources/icons/porymap-icon-2.ico ICON = resources/icons/porymap.icns QMAKE_CXXFLAGS += -std=c++17 -Wall QMAKE_TARGET_BUNDLE_PREFIX = com.pret + +# Get latest commit hash if we can (to display alongside version information). +GIT_PATH = $$system(which git) +!isEmpty(GIT_PATH) { + LATEST_COMMIT = $$system($$GIT_PATH rev-parse --short HEAD) +} +DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\" + VERSION = 5.4.1 DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" diff --git a/src/ui/aboutporymap.cpp b/src/ui/aboutporymap.cpp index 22c5fabc..24d76ce5 100644 --- a/src/ui/aboutporymap.cpp +++ b/src/ui/aboutporymap.cpp @@ -9,7 +9,13 @@ AboutPorymap::AboutPorymap(QWidget *parent) : ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - this->ui->label_Version->setText(QString("Version %1 - %2").arg(QCoreApplication::applicationVersion()).arg(QStringLiteral(__DATE__))); + QString versionInfo = QString("Version %1 - %2").arg(QCoreApplication::applicationVersion()).arg(QStringLiteral(__DATE__)); + + static const QString commitHash = PORYMAP_LATEST_COMMIT; + if (!commitHash.isEmpty()) + versionInfo.append(QString("\nCommit %1").arg(commitHash)); + + this->ui->label_Version->setText(versionInfo); this->ui->textBrowser->setSource(QUrl("qrc:/CHANGELOG.md")); } From 1e4ba6a668b22fdbac06c684d9f501bc1e2bfddb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 9 Sep 2024 19:02:37 -0400 Subject: [PATCH 052/364] Silence error if build directory is not a git repository --- porymap.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/porymap.pro b/porymap.pro index c83dd1d7..69186288 100644 --- a/porymap.pro +++ b/porymap.pro @@ -22,7 +22,7 @@ QMAKE_TARGET_BUNDLE_PREFIX = com.pret # Get latest commit hash if we can (to display alongside version information). GIT_PATH = $$system(which git) !isEmpty(GIT_PATH) { - LATEST_COMMIT = $$system($$GIT_PATH rev-parse --short HEAD) + LATEST_COMMIT = $$system($$GIT_PATH rev-parse --short HEAD 2>/dev/null) } DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\" From 6d39d3afd439f9a47251f5e909098a0e7ec74323 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 24 Sep 2024 11:59:44 -0400 Subject: [PATCH 053/364] fix project close order and clear new layout combo --- src/mainwindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3df6b0e1..0f123149 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1304,6 +1304,7 @@ void MainWindow::clearProjectUI() { const QSignalBlocker blocker8(ui->comboBox_DiveMap); const QSignalBlocker blocker9(ui->comboBox_EmergeMap); const QSignalBlocker blockerA(ui->lineEdit_filterBox); + const QSignalBlocker blockerB(ui->comboBox_LayoutSelector); ui->comboBox_Song->clear(); ui->comboBox_Location->clear(); @@ -1315,6 +1316,7 @@ void MainWindow::clearProjectUI() { ui->comboBox_DiveMap->clear(); ui->comboBox_EmergeMap->clear(); ui->lineEdit_filterBox->clear(); + ui->comboBox_LayoutSelector->clear(); // Clear map models if (this->mapGroupModel) { @@ -3676,8 +3678,8 @@ bool MainWindow::closeProject() { return false; } } - clearProjectUI(); editor->closeProject(); + clearProjectUI(); setWindowDisabled(true); setWindowTitle(QCoreApplication::applicationName()); From 7bfb064e80bd47380237f8bde17f6e5cba428d85 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 2 Oct 2024 02:50:41 -0400 Subject: [PATCH 054/364] fix main tab icon initialization --- src/editor.cpp | 1 + src/mainwindow.cpp | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 1e4791b3..56c82aaf 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1247,6 +1247,7 @@ void Editor::unsetMap() { for (auto connection : map->getConnections()) disconnectMapConnection(connection); } + clearMapConnections(); this->map = nullptr; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e1122c7f..6666196f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -220,23 +220,23 @@ void MainWindow::applyUserShortcuts() { shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut)); } -static const QMap mainTabNames = { - {MainTab::Map, "Map"}, - {MainTab::Events, "Events"}, - {MainTab::Header, "Header"}, - {MainTab::Connections, "Connections"}, - {MainTab::WildPokemon, "Wild Pokemon"}, -}; - -static const QMap mainTabIcons = { - {MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))}, - {MainTab::Events, QIcon(QStringLiteral(":/icons/viewsprites.ico"))}, - {MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))}, - {MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))}, - {MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))}, -}; - void MainWindow::initCustomUI() { + static const QMap mainTabNames = { + {MainTab::Map, "Map"}, + {MainTab::Events, "Events"}, + {MainTab::Header, "Header"}, + {MainTab::Connections, "Connections"}, + {MainTab::WildPokemon, "Wild Pokemon"}, + }; + + static const QMap mainTabIcons = { + {MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))}, + {MainTab::Events, QIcon(QStringLiteral(":/icons/viewsprites.ico"))}, + {MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))}, + {MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))}, + {MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))}, + }; + // Set up the tab bar while (ui->mainTabBar->count()) ui->mainTabBar->removeTab(0); From eed641f5ffd7e2a75ff3185a37463b6cbe180ae2 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 3 Oct 2024 11:06:40 -0400 Subject: [PATCH 055/364] fix connection mask in layout display --- src/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor.cpp b/src/editor.cpp index 56c82aaf..a032ab7e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1568,6 +1568,7 @@ bool Editor::displayLayout() { scene->installEventFilter(this->map_ruler); } + clearConnectionMask(); displayMetatileSelector(); displayMapMetatiles(); displayMovementPermissionSelector(); From 09eaef4dbf48aebaff30ca963bb4d9f6a04e16b6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 16 Oct 2024 14:23:28 -0400 Subject: [PATCH 056/364] Update help buttons, chart help text --- forms/customscriptseditor.ui | 91 ++++++++++++++++-------------- forms/projectsettingseditor.ui | 86 ++++++++++++---------------- include/ui/customscriptseditor.h | 1 + include/ui/projectsettingseditor.h | 2 + src/ui/customscriptseditor.cpp | 6 ++ src/ui/projectsettingseditor.cpp | 12 ++++ src/ui/wildmonchart.cpp | 31 +++++++--- 7 files changed, 128 insertions(+), 101 deletions(-) diff --git a/forms/customscriptseditor.ui b/forms/customscriptseditor.ui index cc7dd4f1..e2efa2af 100644 --- a/forms/customscriptseditor.ui +++ b/forms/customscriptseditor.ui @@ -6,7 +6,7 @@ 0 0 - 540 + 582 355 @@ -33,21 +33,30 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - QFrame::StyledPanel - - - QFrame::Raised + QFrame::Shape::StyledPanel + + 6 + + + 6 + + + 6 + + + 6 + @@ -91,9 +100,9 @@ - + - Qt::Horizontal + Qt::Orientation::Horizontal @@ -103,35 +112,33 @@ + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + - - - - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/scripting-capabilities.html"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 5 - - - - @@ -142,32 +149,32 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false - QAbstractItemView::DragOnly + QAbstractItemView::DragDropMode::DragOnly - Qt::IgnoreAction + Qt::DropAction::IgnoreAction - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection - Qt::ElideLeft + Qt::TextElideMode::ElideLeft - QListView::Free + QListView::Movement::Free - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index dc6d730e..9aa521a8 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -125,7 +125,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -194,10 +194,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -276,10 +276,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -287,7 +287,6 @@ 12 - 75 true @@ -319,7 +318,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +337,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -567,10 +566,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -578,7 +577,6 @@ 12 - 75 true @@ -693,7 +691,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -712,7 +710,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -783,10 +781,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -794,7 +792,6 @@ 12 - 75 true @@ -839,10 +836,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -924,10 +921,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::MinimumExpanding + QSizePolicy::Policy::MinimumExpanding @@ -975,7 +972,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1212,7 +1209,7 @@ true - Qt::NoTextInteraction + Qt::TextInteractionFlag::NoTextInteraction Use the dropbown and buttons to add behaviors to the list... @@ -1242,10 +1239,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1253,7 +1250,6 @@ 12 - 75 true @@ -1316,7 +1312,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1335,7 +1331,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1372,18 +1368,13 @@ - + - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/project-files.html#files"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> + ... - - Qt::RichText - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - true + + + :/icons/help.ico:/icons/help.ico @@ -1419,7 +1410,7 @@ 0 0 533 - 440 + 428 @@ -1466,18 +1457,13 @@ - + - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/project-files.html#identifiers"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> + ... - - Qt::RichText - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - true + + + :/icons/help.ico:/icons/help.ico @@ -1513,7 +1499,7 @@ 0 0 533 - 440 + 428 @@ -1544,7 +1530,7 @@ - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults diff --git a/include/ui/customscriptseditor.h b/include/ui/customscriptseditor.h index c93c7b7d..8d261570 100644 --- a/include/ui/customscriptseditor.h +++ b/include/ui/customscriptseditor.h @@ -49,6 +49,7 @@ private: void restoreWindowState(); void initShortcuts(); QObjectList shortcutableObjects() const; + void openManual(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index 41affa8b..a0a26982 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -65,6 +65,8 @@ private: void updateMaskOverlapWarning(QLabel * warning, QList masks); QStringList getWarpBehaviorsList(); void setWarpBehaviorsList(QStringList list); + void openFilesHelp(); + void openIdentifiersHelp(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index 40195d18..284ea333 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -23,6 +23,7 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) : for (int i = 0; i < paths.length(); i++) this->displayScript(paths.at(i), enabled.at(i)); + connect(ui->button_Help, &QAbstractButton::clicked, this, &CustomScriptsEditor::openManual); connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript); connect(ui->button_LoadScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::loadScript); connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::userRefreshScripts); @@ -229,6 +230,11 @@ void CustomScriptsEditor::openSelectedScripts() { this->openScript(item); } +void CustomScriptsEditor::openManual() { + static const QUrl url("https://huderlem.github.io/porymap/manual/scripting-capabilities.html"); + QDesktopServices::openUrl(url); +} + // When the user refreshes the scripts we show a little tooltip as feedback. // We don't want this tooltip to display when we refresh programmatically, like when changes are saved. void CustomScriptsEditor::userRefreshScripts() { diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index ec476558..346591f4 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -37,6 +37,8 @@ ProjectSettingsEditor::~ProjectSettingsEditor() } void ProjectSettingsEditor::connectSignals() { + connect(ui->button_HelpFiles, &QAbstractButton::clicked, this, &ProjectSettingsEditor::openFilesHelp); + connect(ui->button_HelpIdentifiers, &QAbstractButton::clicked, this, &ProjectSettingsEditor::openIdentifiersHelp); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &ProjectSettingsEditor::dialogButtonClicked); connect(ui->button_ImportDefaultPrefabs, &QAbstractButton::clicked, this, &ProjectSettingsEditor::importDefaultPrefabsClicked); connect(ui->comboBox_BaseGameVersion, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::promptRestoreDefaults); @@ -658,6 +660,16 @@ void ProjectSettingsEditor::dialogButtonClicked(QAbstractButton *button) { } } +void ProjectSettingsEditor::openFilesHelp() { + static const QUrl url("https://huderlem.github.io/porymap/manual/project-files.html#files"); + QDesktopServices::openUrl(url); +} + +void ProjectSettingsEditor::openIdentifiersHelp() { + static const QUrl url("https://huderlem.github.io/porymap/manual/project-files.html#identifiers"); + QDesktopServices::openUrl(url); +} + // Close event triggered by a project reload. User doesn't need any prompts, just close the window. void ProjectSettingsEditor::closeQuietly() { // Turn off flags that trigger prompts diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 375edbf4..32ff9bc9 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -82,6 +82,7 @@ void WildMonChart::clearTableData() { ui->comboBox_Species->clear(); ui->comboBox_Group->clear(); ui->comboBox_Group->setEnabled(false); + ui->label_Group->setEnabled(false); } // Extract all the data from the table that we need for the charts @@ -152,7 +153,9 @@ void WildMonChart::readTable() { ui->comboBox_Species->addItems(getSpeciesNamesAlphabetical()); ui->comboBox_Group->clear(); ui->comboBox_Group->addItems(this->groupNames); - ui->comboBox_Group->setEnabled(usesGroupLabels()); + bool enableGroupSelection = usesGroupLabels(); + ui->comboBox_Group->setEnabled(enableGroupSelection); + ui->label_Group->setEnabled(enableGroupSelection); } void WildMonChart::refresh() { @@ -438,23 +441,33 @@ void WildMonChart::limitChartAnimation() { void WildMonChart::showHelpDialog() { static const QString text = "This window provides some visualizations of the data in your current Wild Pokémon tab"; - static const QString informative = - "The Species Distribution tab shows the cumulative encounter chance for each species " + + // Describe the Species Distribution tab + static const QString speciesTabInfo = + "The Species Distribution tab shows the cumulative encounter chance for each species " "in the table. In other words, it answers the question \"What is the likelihood of encountering " - "each species in a single encounter?\"" - "

" + "each species in a single encounter?\""; + + // Describe the Level Distribution tab + static const QString levelTabInfo = "The Level Distribution tab shows the chance of encountering each species at a particular level. " "In the top left under Group you can select which encounter group to show data for. " - "In the top right under Species you can select which species to show data for. " + "In the top right you can enable Individual Mode. When enabled data will be shown for only the selected species." "

" - "Individual Mode on the Level Distribution tab toggles whether data is shown for all species in the table. " - "The percentages will update to reflect whether you're showing all species or just that individual species. " "In other words, while Individual Mode is checked the chart is answering the question \"If a species x " "is encountered, what is the likelihood that it will be level y\", and while Individual Mode is not checked, " "it answers the question \"For a single encounter, what is the likelihood of encountering a species x at level y.\""; + + QString informativeText; + if (ui->tabWidget->currentWidget() == ui->tabSpecies) { + informativeText = speciesTabInfo; + } else if (ui->tabWidget->currentWidget() == ui->tabLevels) { + informativeText = levelTabInfo; + } + QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this); msgBox.setTextFormat(Qt::RichText); - msgBox.setInformativeText(informative); + msgBox.setInformativeText(informativeText); msgBox.exec(); } From 5e9ab4c7c7736effea1ab35722efe7677ed2426b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Oct 2024 11:46:47 -0400 Subject: [PATCH 057/364] Reopen Porymap to last-opened map/layout --- include/config.h | 6 +-- include/mainwindow.h | 8 --- src/config.cpp | 27 ++++------ src/editor.cpp | 4 +- src/mainwindow.cpp | 121 +++++++++---------------------------------- 5 files changed, 38 insertions(+), 128 deletions(-) diff --git a/include/config.h b/include/config.h index 79e7e56e..01a7b09c 100644 --- a/include/config.h +++ b/include/config.h @@ -406,8 +406,7 @@ public: reset(); } virtual void reset() override { - this->recentMap = QString(); - this->recentLayout = QString(); + this->recentMapOrLayout = QString(); this->useEncounterJson = true; this->customScripts.clear(); this->readKeys.clear(); @@ -419,8 +418,7 @@ public: QList getCustomScriptsEnabled(); QString projectDir; - QString recentMap; - QString recentLayout; + QString recentMapOrLayout; bool useEncounterJson; protected: diff --git a/include/mainwindow.h b/include/mainwindow.h index 194cc52c..fc277193 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -373,8 +373,6 @@ private: bool tilesetNeedsRedraw = false; - bool setDefaultView(); - bool setRecentView(); bool setLayout(QString layoutId); bool setMap(QString, bool scroll = false); void unsetMap(); @@ -398,12 +396,6 @@ private: QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); bool setInitialMap(); - bool setInitialLayout(); - QString getDefaultMap(); - QString getDefaultLayout(); - - void setRecentMapConfig(QString map_name); - void setRecentLayoutConfig(QString layoutId); void saveGlobalConfigs(); void refreshRecentProjectsMenu(); diff --git a/src/config.cpp b/src/config.cpp index ef543327..0d72542d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -278,13 +278,7 @@ uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_ return qMin(max, qMax(min, result)); } -const QMap mapSortOrderMap = { - {MapSortOrder::SortByGroup, "group"}, - {MapSortOrder::SortByLayout, "layout"}, - {MapSortOrder::SortByArea, "area"}, -}; - -const QMap mapSortOrderReverseMap = { +const QMap mapSortOrderMap = { {"group", MapSortOrder::SortByGroup}, {"layout", MapSortOrder::SortByLayout}, {"area", MapSortOrder::SortByArea}, @@ -316,8 +310,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->prettyCursors = getConfigBool(key, value); } else if (key == "map_sort_order") { QString sortOrder = value.toLower(); - if (mapSortOrderReverseMap.contains(sortOrder)) { - this->mapSortOrder = mapSortOrderReverseMap.value(sortOrder); + if (mapSortOrderMap.contains(sortOrder)) { + this->mapSortOrder = mapSortOrderMap.value(sortOrder); } else { this->mapSortOrder = MapSortOrder::SortByGroup; logWarn(QString("Invalid config value for map_sort_order: '%1'. Must be 'group', 'area', or 'layout'.").arg(value)); @@ -438,7 +432,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("project_manually_closed", this->projectManuallyClosed ? "1" : "0"); map.insert("reopen_on_launch", this->reopenOnLaunch ? "1" : "0"); map.insert("pretty_cursors", this->prettyCursors ? "1" : "0"); - map.insert("map_sort_order", mapSortOrderMap.value(this->mapSortOrder)); + map.insert("map_sort_order", mapSortOrderMap.key(this->mapSortOrder)); map.insert("main_window_geometry", stringFromByteArray(this->mainWindowGeometry)); map.insert("main_window_state", stringFromByteArray(this->mainWindowState)); map.insert("map_splitter_state", stringFromByteArray(this->mapSplitterState)); @@ -740,8 +734,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "enable_map_allow_flags") { this->mapAllowFlagsEnabled = getConfigBool(key, value); #ifdef CONFIG_BACKWARDS_COMPATABILITY - } else if (key == "recent_map") { - userConfig.recentMap = value; + } else if (key == "recent_map_or_layout") { + userConfig.recentMapOrLayout = value; } else if (key == "use_encounter_json") { userConfig.useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { @@ -1035,10 +1029,8 @@ QString UserConfig::getConfigFilepath() { } void UserConfig::parseConfigKeyValue(QString key, QString value) { - if (key == "recent_map") { - this->recentMap = value; - } else if (key == "recent_layout") { - this->recentLayout = value; + if (key == "recent_map_or_layout") { + this->recentMapOrLayout = value; } else if (key == "use_encounter_json") { this->useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { @@ -1054,8 +1046,7 @@ void UserConfig::setUnreadKeys() { QMap UserConfig::getKeyValueMap() { QMap map; - map.insert("recent_map", this->recentMap); - map.insert("recent_layout", this->recentLayout); + map.insert("recent_map_or_layout", this->recentMapOrLayout); map.insert("use_encounter_json", QString::number(this->useEncounterJson)); map.insert("custom_scripts", this->outputCustomScripts()); return map; diff --git a/src/editor.cpp b/src/editor.cpp index f674e864..68169432 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1319,7 +1319,7 @@ bool Editor::setLayout(QString layoutId) { return true; } -void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { +void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *) { if (!this->getEditingLayout()) { return; } @@ -1332,7 +1332,7 @@ void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem * } } -void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *item) { +void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *) { if (!this->getEditingLayout()) { return; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 21d2b348..4d3a72e7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -696,83 +696,29 @@ bool MainWindow::isProjectOpen() { return editor && editor->project; } -bool MainWindow::setDefaultView() { - if (porymapConfig.mapSortOrder == MapSortOrder::SortByLayout) { - return setLayout(getDefaultLayout()); - } else { - return setMap(getDefaultMap(), true); - } -} - -bool MainWindow::setRecentView() { - if (porymapConfig.mapSortOrder == MapSortOrder::SortByLayout) { - return setLayout(userConfig.recentLayout); - } else { - return setMap(userConfig.recentMap, true); - } -} - -QString MainWindow::getDefaultMap() { - if (editor && editor->project) { - QList names = editor->project->groupedMapNames; - if (!names.isEmpty()) { - QString recentMap = userConfig.recentMap; - if (!recentMap.isNull() && recentMap.length() > 0) { - for (int i = 0; i < names.length(); i++) { - if (names.value(i).contains(recentMap)) { - return recentMap; - } - } - } - // Failing that, just get the first map in the list. - for (int i = 0; i < names.length(); i++) { - QStringList list = names.value(i); - if (list.length()) { - return list.value(0); - } - } - } - } - return QString(); -} - bool MainWindow::setInitialMap() { - QStringList names; - if (editor && editor->project) - names = editor->project->mapNames; - - // Try to set most recently-opened map, if it's still in the list. - QString recentMap = userConfig.recentMap; - if (!recentMap.isEmpty() && names.contains(recentMap) && setMap(recentMap, true)) - return true; - - // Failing that, try loading maps in the map list sequentially. - for (auto name : names) { - if (name != recentMap && setMap(name, true)) + const QString recent = userConfig.recentMapOrLayout; + if (editor->project->mapNames.contains(recent)) { + // User recently had a map open that still exists. + if (setMap(recent, true)) + return true; + } else if (editor->project->mapLayoutsTable.contains(recent)) { + // User recently had a layout open that still exists. + if (setLayout(recent)) return true; } - logError("Failed to load any maps."); - return false; -} - -bool MainWindow::setInitialLayout() { - QStringList names; - if (editor && editor->project) - names = editor->project->mapLayoutsTable; - - // Try to set most recently-opened layout, if it's still in the list. - QString recentLayout = userConfig.recentLayout; - if (!recentLayout.isEmpty() && names.contains(recentLayout) && setLayout(recentLayout)) - return true; - - // Failing that, try loading maps in the map list sequentially. - for (auto name : names) { - if (name != recentLayout && setLayout(name)) + // Failed to open recent map/layout, or no recent map/layout. Try opening maps then layouts sequentially. + for (const auto &name : editor->project->mapNames) { + if (name != recent && setMap(name, true)) + return true; + } + for (const auto &id : editor->project->mapLayoutsTable) { + if (id != recent && setLayout(id)) return true; } - logError("Failed to load any layouts."); + logError("Failed to load any maps or layouts."); return false; } @@ -824,25 +770,13 @@ void MainWindow::openSubWindow(QWidget * window) { } } -QString MainWindow::getDefaultLayout() { - if (editor && editor->project) { - QString recentLayout = userConfig.recentLayout; - if (!recentLayout.isEmpty() && editor->project->mapLayoutsTable.contains(recentLayout)) { - return recentLayout; - } else if (!editor->project->mapLayoutsTable.isEmpty()) { - return editor->project->mapLayoutsTable.first(); - } - } - return QString(); -} - QString MainWindow::getExistingDirectory(QString dir) { return FileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly); } void MainWindow::on_action_Open_Project_triggered() { - QString dir = getExistingDirectory(!userConfig.recentMap.isEmpty() ? userConfig.recentMap : "."); + QString dir = getExistingDirectory(!projectConfig.projectDir.isEmpty() ? userConfig.projectDir : "."); if (!dir.isEmpty()) openProject(dir); } @@ -904,11 +838,16 @@ bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { bool MainWindow::setMap(QString map_name, bool scroll) { // if map name is empty, clear & disable map ui - if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { + if (map_name.isEmpty()) { unsetMap(); return false; } + if (map_name == DYNAMIC_MAP_NAME) { + logInfo(QString("Cannot set map to '%1'").arg(DYNAMIC_MAP_NAME)); + return false; + } + logInfo(QString("Setting map to '%1'").arg(map_name)); if (!editor || !editor->setMap(map_name)) { @@ -944,7 +883,7 @@ bool MainWindow::setMap(QString map_name, bool scroll) { connect(editor->layout, &Layout::layoutChanged, this, &MainWindow::onLayoutChanged, Qt::UniqueConnection); connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); - userConfig.recentMap = map_name; + userConfig.recentMapOrLayout = map_name; Scripting::cb_MapOpened(map_name); prefab.updatePrefabUi(editor->layout); @@ -974,7 +913,7 @@ bool MainWindow::setLayout(QString layoutId) { updateTilesetEditor(); - setRecentLayoutConfig(layoutId); + userConfig.recentMapOrLayout = layoutId; return true; } @@ -1050,16 +989,6 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } } -void MainWindow::setRecentMapConfig(QString mapName) { - userConfig.recentMap = mapName; - userConfig.recentLayout = ""; -} - -void MainWindow::setRecentLayoutConfig(QString layoutId) { - userConfig.recentLayout = layoutId; - userConfig.recentMap = ""; -} - void MainWindow::displayMapProperties() { // Block signals to the comboboxes while they are being modified const QSignalBlocker blocker1(ui->comboBox_Song); From 728355d202b2c9495f83af86410b10c7e81f1476 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Oct 2024 12:12:33 -0400 Subject: [PATCH 058/364] Fix some missing constant usage --- include/mainwindow.h | 1 + src/mainwindow.cpp | 37 +++++++++++++++++++------------------ src/project.cpp | 9 ++++++--- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index fc277193..677bd89f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -380,6 +380,7 @@ private: void redrawMapScene(); void redrawLayoutScene(); void refreshMapScene(); + void setLayoutOnlyMode(bool layoutOnly); bool checkProjectSanity(); bool loadProjectData(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4d3a72e7..dea3d210 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -425,12 +425,14 @@ void MainWindow::showWindowTitle() { ); } if (editor && editor->layout) { - ui->mainTabBar->setTabIcon(0, QIcon()); + // For some reason (perhaps on Qt < 6?) we had to clear the icon first here or mainTabBar wouldn't display correctly. + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon()); + QPixmap pixmap = editor->layout->pixmap; if (!pixmap.isNull()) { - ui->mainTabBar->setTabIcon(0, QIcon(pixmap)); + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(pixmap)); } else { - ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/map.ico"))); + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(QStringLiteral(":/icons/map.ico"))); } } updateMapList(); @@ -801,14 +803,7 @@ void MainWindow::on_action_Close_Project_triggered() { void MainWindow::unsetMap() { this->editor->unsetMap(); - - // disable other tabs - this->ui->mainTabBar->setTabEnabled(1, false); - this->ui->mainTabBar->setTabEnabled(2, false); - this->ui->mainTabBar->setTabEnabled(3, false); - this->ui->mainTabBar->setTabEnabled(4, false); - - this->ui->comboBox_LayoutSelector->setEnabled(false); + setLayoutOnlyMode(true); } // setMap, but with a visible error message in case of failure. @@ -859,13 +854,7 @@ bool MainWindow::setMap(QString map_name, bool scroll) { ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); } - this->ui->mainTabBar->setTabEnabled(1, true); - this->ui->mainTabBar->setTabEnabled(2, true); - this->ui->mainTabBar->setTabEnabled(3, true); - this->ui->mainTabBar->setTabEnabled(4, true); - - this->ui->comboBox_LayoutSelector->setEnabled(true); - + setLayoutOnlyMode(false); this->lastSelectedEvent.clear(); refreshMapScene(); @@ -891,6 +880,18 @@ bool MainWindow::setMap(QString map_name, bool scroll) { return true; } +// These parts of the UI only make sense when editing maps. +// When editing in layout-only mode they are disabled. +void MainWindow::setLayoutOnlyMode(bool layoutOnly) { + bool mapEditingEnabled = !layoutOnly; + this->ui->mainTabBar->setTabEnabled(MainTab::Events, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::Header, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::Connections, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled); + + this->ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled); +} + bool MainWindow::setLayout(QString layoutId) { if (this->editor->map) logInfo("Switching to a layout-only editing mode. Disabling map-related edits."); diff --git a/src/project.cpp b/src/project.cpp index 194f2ecd..32c61fed 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2327,11 +2327,14 @@ bool Project::readRegionMapSections() { int Project::appendMapsec(QString name) { // This function assumes a valid and unique name. // Will return the new index. - int noneBefore = this->mapSectionNameToValue[projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + "NONE"]; + const QString emptyMapsecName = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); + + int noneBefore = this->mapSectionNameToValue[emptyMapsecName]; this->mapSectionNameToValue[name] = noneBefore; this->mapSectionValueToName[noneBefore] = name; - this->mapSectionNameToValue[projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + "NONE"] = noneBefore + 1; - this->mapSectionValueToName[noneBefore + 1] = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + "NONE"; + this->mapSectionNameToValue[emptyMapsecName] = noneBefore + 1; + this->mapSectionValueToName[noneBefore + 1] = emptyMapsecName; return noneBefore; } From 10aa6f6c3f6872a904e65368979ca356bad0d411 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Oct 2024 14:01:27 -0400 Subject: [PATCH 059/364] Fix new name regexes, some assumptions about MAPSEC_NONE, memory leak --- include/project.h | 2 +- src/mainwindow.cpp | 58 +++++++++++++++++++++--------------- src/project.cpp | 46 +++++++++++++++++----------- src/scriptapi/apiutility.cpp | 3 -- src/ui/maplistmodels.cpp | 13 +++++--- 5 files changed, 73 insertions(+), 49 deletions(-) diff --git a/include/project.h b/include/project.h index d93f5601..6782769b 100644 --- a/include/project.h +++ b/include/project.h @@ -132,7 +132,6 @@ public: QString getProjectTitle(); QString readMapLayoutId(QString map_name); - QString readMapLayoutName(QString mapName); QString readMapLocation(QString map_name); bool readWildMonData(); @@ -243,6 +242,7 @@ public: static bool mapDimensionsValid(int width, int height); bool calculateDefaultMapSize(); static int getMaxObjectEvents(); + static QString getEmptyMapsecName(); private: void updateLayout(Layout *); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index dea3d210..2946dbeb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1370,9 +1370,13 @@ void MainWindow::mapListAddGroup() { QLineEdit *newNameEdit = new QLineEdit(&dialog); newNameEdit->setClearButtonEnabled(true); - static const QRegularExpression re_validChars("[_A-Za-z0-9]*$"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); - newNameEdit->setValidator(validator); + static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); + newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); + + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ + if (!this->editor->project->groupNames.contains(newNameEdit->text())) + dialog.accept(); + }); QFormLayout form(&dialog); @@ -1397,10 +1401,10 @@ void MainWindow::mapListAddLayout() { QLineEdit *newNameEdit = new QLineEdit(&dialog); newNameEdit->setClearButtonEnabled(true); - static const QRegularExpression re_validChars("[_A-Za-z0-9]*$"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); - newNameEdit->setValidator(validator); + 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"))); @@ -1499,7 +1503,7 @@ void MainWindow::mapListAddLayout() { layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); } Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); - QStandardItem *item = this->layoutTreeModel->insertLayoutItem(newLayout->id); + this->layoutTreeModel->insertLayoutItem(newLayout->id); setLayout(newLayout->id); } } @@ -1511,13 +1515,18 @@ void MainWindow::mapListAddArea() { QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); QLineEdit *newNameEdit = new QLineEdit(&dialog); - newNameEdit->setText(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); - newNameEdit->setClearButtonEnabled(false); + QLineEdit *newNameDisplay = new QLineEdit(&dialog); + newNameDisplay->setText(prefix); + newNameDisplay->setEnabled(false); + connect(newNameEdit, &QLineEdit::textEdited, [newNameDisplay, prefix] (const QString &text) { + // As the user types a name, update the label to show the name with the prefix. + newNameDisplay->setText(prefix + text); + }); - QRegularExpression re_validChars(QString("%1[_A-Za-z0-9]+$").arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); - newNameEdit->setValidator(validator); + static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); + newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ if (!this->editor->project->mapSectionNameToValue.contains(newNameEdit->text())) @@ -1527,12 +1536,12 @@ void MainWindow::mapListAddArea() { QFormLayout form(&dialog); form.addRow("New Map Section Name", newNameEdit); + form.addRow("Constant Name", newNameDisplay); form.addRow(&newItemButtonBox); if (dialog.exec() == QDialog::Accepted) { - QString newFieldName = newNameEdit->text(); - if (newFieldName.isEmpty()) return; - this->mapAreaModel->insertAreaItem(newFieldName); + if (newNameEdit->text().isEmpty()) return; + this->mapAreaModel->insertAreaItem(newNameDisplay->text()); } } @@ -1540,13 +1549,13 @@ void MainWindow::mapListAddItem() { if (!this->editor || !this->editor->project) return; switch (this->ui->mapListContainer->currentIndex()) { - case 0: + case MapListTab::Groups: this->mapListAddGroup(); break; - case 1: + case MapListTab::Areas: this->mapListAddArea(); break; - case 2: + case MapListTab::Layouts: this->mapListAddLayout(); break; } @@ -1596,14 +1605,14 @@ void MainWindow::mapListRemoveItem() { if (!this->editor || !this->editor->project) return; switch (this->ui->mapListContainer->currentIndex()) { - case 0: + case MapListTab::Groups: this->mapListRemoveGroup(); break; - case 1: + case MapListTab::Areas: // Disabled // this->mapListRemoveArea(); break; - case 2: + case MapListTab::Layouts: // Disabled // this->mapListRemoveLayout(); break; @@ -2913,10 +2922,8 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT } else { this->editor->project->getTileset(secondaryTilesetLabel, true); } - if (updated) { - this->editor->layout->clearBorderCache(); + if (updated) redrawMapScene(); - } } void MainWindow::onMapRulerStatusChanged(const QString &status) { @@ -3232,6 +3239,7 @@ void MainWindow::do_CollapseAll() { } } +// TODO: Save this state in porymapConfig void MainWindow::do_HideShow() { switch (ui->mapListContainer->currentIndex()) { case MapListTab::Groups: @@ -3265,6 +3273,7 @@ void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { } } +// TODO: Save this state in porymapConfig void MainWindow::on_toolButton_EnableDisable_EditGroups_clicked() { this->ui->mapList->clearSelection(); if (this->ui->toolButton_EnableDisable_EditGroups->isChecked()) { @@ -3607,6 +3616,7 @@ bool MainWindow::closeProject() { return true; // Check loaded maps for unsaved changes + // TODO: This needs to check for unsaved changes in layouts too. bool unsavedChanges = false; for (auto map : editor->project->mapCache.values()) { if (map && map->hasUnsavedChanges()) { diff --git a/src/project.cpp b/src/project.cpp index 32c61fed..e6cd7a8e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -412,10 +412,6 @@ QString Project::readMapLayoutId(QString map_name) { return ParseUtil::jsonToQString(mapObj["layout"]); } -QString Project::readMapLayoutName(QString mapName) { - return this->layoutIdsToNames[readMapLayoutId(mapName)]; -} - QString Project::readMapLocation(QString map_name) { if (mapCache.contains(map_name)) { return mapCache.value(map_name)->location; @@ -537,6 +533,8 @@ bool Project::loadMapLayout(Map* map) { void Project::clearMapLayouts() { qDeleteAll(mapLayouts); mapLayouts.clear(); + qDeleteAll(mapLayoutsMaster); + mapLayoutsMaster.clear(); mapLayoutsTable.clear(); layoutIdsToNames.clear(); } @@ -763,7 +761,7 @@ void Project::saveMapSections() { longestLength += 1; - // mapSectionValueToName + // TODO: Maybe print as an enum now that we can? for (int value : this->mapSectionValueToName.keys()) { QString line = QString("#define %1 0x%2\n") .arg(this->mapSectionValueToName[value], -1 * longestLength) @@ -771,6 +769,8 @@ void Project::saveMapSections() { text += line; } + // TODO: We should maybe consider another way to update MAPSEC values in this file, in case we break anything by relocating it to the bottom of the file. + // (or alternatively keep separate strings for text before/after the MAPSEC values) text += "\n" + this->extraFileText[projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections)] + "\n"; text += QString("#endif // GUARD_REGIONMAPSEC_H\n"); @@ -2307,10 +2307,12 @@ bool Project::readRegionMapSections() { continue; } // include guards + // TODO: Assuming guard name is the same across projects (it isn't) else if (currentLine.contains("GUARD_REGIONMAPSEC_H")) { continue; } - // defines captured (not considering comments) + // defines captured + // TODO: Regex to consider comments/extra space else if (currentLine.contains("#define " + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))) { continue; } @@ -2324,18 +2326,28 @@ bool Project::readRegionMapSections() { return true; } -int Project::appendMapsec(QString name) { - // This function assumes a valid and unique name. - // Will return the new index. - const QString emptyMapsecName = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) - + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); +QString Project::getEmptyMapsecName() { + return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); +} - int noneBefore = this->mapSectionNameToValue[emptyMapsecName]; - this->mapSectionNameToValue[name] = noneBefore; - this->mapSectionValueToName[noneBefore] = name; - this->mapSectionNameToValue[emptyMapsecName] = noneBefore + 1; - this->mapSectionValueToName[noneBefore + 1] = emptyMapsecName; - return noneBefore; +// This function assumes a valid and unique name. +// Will return the new index. +// TODO: We're not currently tracking map/layout agonstic changes like this as unsaved, so there's no warning if you close the project after doing this. +int Project::appendMapsec(QString name) { + const QString emptyMapsecName = getEmptyMapsecName(); + int newMapsecValue = mapSectionValueToName.isEmpty() ? 0 : mapSectionValueToName.lastKey(); + + // If the user has the 'empty' MAPSEC value defined last in the list we'll shift it so that it stays last in the list. + if (this->mapSectionNameToValue.contains(emptyMapsecName) && this->mapSectionNameToValue.value(emptyMapsecName) == newMapsecValue) { + this->mapSectionNameToValue.insert(emptyMapsecName, newMapsecValue + 1); + this->mapSectionValueToName.insert(newMapsecValue + 1, emptyMapsecName); + } + + // TODO: Update 'define_map_section_count'? + + this->mapSectionNameToValue[name] = newMapsecValue; + this->mapSectionValueToName[newMapsecValue] = name; + return newMapsecValue; } // Read the constants to preserve any "unused" heal locations when writing the file later diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index c12db39a..27bd5677 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -154,9 +154,6 @@ void ScriptUtility::setMainTab(int index) { // Can't select tab if it's disabled if (!window->ui->mainTabBar->isTabEnabled(index)) return; - // don't change tab when not editing a map - if (!window->editor || !window->editor->map) - return; window->on_mainTabBar_tabBarClicked(index); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index f5835357..7e0d7e62 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -19,10 +19,9 @@ void MapTree::removeSelected() { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); - static const QRegularExpression expression("[A-Za-z0-9_]+"); + static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); editor->setPlaceholderText("gMapGroup_"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, parent); - editor->setValidator(validator); + editor->setValidator(new QRegularExpressionValidator(expression, parent)); editor->setFrame(false); return editor; } @@ -401,7 +400,12 @@ QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { int newAreaIndex = this->project->appendMapsec(areaName); QStandardItem *item = createAreaItem(areaName, newAreaIndex); this->root->insertRow(newAreaIndex, item); - this->areaItems["MAPSEC_NONE"]->setData(newAreaIndex + 1, MapListUserRoles::GroupRole); + + // MAPSEC_NONE may have shifted to accomodate the new item, update it in the list. + const QString emptyMapsecName = Project::getEmptyMapsecName(); + if (this->areaItems.contains(emptyMapsecName)) + this->areaItems[emptyMapsecName]->setData(this->project->mapSectionNameToValue.value(emptyMapsecName), MapListUserRoles::GroupRole); + return item; } @@ -427,6 +431,7 @@ void MapAreaModel::initialize() { this->mapItems.clear(); this->setSortRole(MapListUserRoles::GroupRole); + // TODO: Ignore 'define_map_section_count' and/or 'define_map_section_empty'? for (int i : this->project->mapSectionNameToValue) { QString mapsecName = project->mapSectionValueToName.value(i); QStandardItem *areaItem = createAreaItem(mapsecName, i); From d674856b1800a63dd63275e8281736a03a017d35 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 28 Oct 2024 11:42:44 -0400 Subject: [PATCH 060/364] Render API images as pixmaps --- include/ui/overlay.h | 10 +++++----- src/ui/overlay.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/ui/overlay.h b/include/ui/overlay.h index 8f64d058..86fe0963 100644 --- a/include/ui/overlay.h +++ b/include/ui/overlay.h @@ -50,19 +50,19 @@ private: QColor fillColor; }; -class OverlayImage : public OverlayItem { +class OverlayPixmap : public OverlayItem { public: - OverlayImage(int x, int y, QImage image) { + OverlayPixmap(int x, int y, QPixmap pixmap) { this->x = x; this->y = y; - this->image = image; + this->pixmap = pixmap; } - ~OverlayImage() {} + ~OverlayPixmap() {} virtual void render(QPainter *painter); private: int x; int y; - QImage image; + QPixmap pixmap; }; class Overlay diff --git a/src/ui/overlay.cpp b/src/ui/overlay.cpp index 4642afa1..d1ba4ef8 100644 --- a/src/ui/overlay.cpp +++ b/src/ui/overlay.cpp @@ -16,8 +16,8 @@ void OverlayPath::render(QPainter *painter) { painter->drawPath(this->path); } -void OverlayImage::render(QPainter *painter) { - painter->drawImage(this->x, this->y, this->image); +void OverlayPixmap::render(QPainter *painter) { + painter->drawPixmap(this->x, this->y, this->pixmap); } void Overlay::renderItems(QPainter *painter) { @@ -244,7 +244,7 @@ bool Overlay::addImage(int x, int y, QString filepath, bool useCache, int width, if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); - this->items.append(new OverlayImage(x, y, image)); + this->items.append(new OverlayPixmap(x, y, QPixmap::fromImage(image))); return true; } @@ -253,6 +253,6 @@ bool Overlay::addImage(int x, int y, QImage image) { logError(QString("Failed to load custom image")); return false; } - this->items.append(new OverlayImage(x, y, image)); + this->items.append(new OverlayPixmap(x, y, QPixmap::fromImage(image))); return true; } From 7da23759988be15c8abec5aa6d34a758ca1adb83 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 18 Oct 2024 14:22:34 -0400 Subject: [PATCH 061/364] Refactor map list buttons --- forms/mainwindow.ui | 663 ++++++-------------------- forms/maplisttoolbar.ui | 172 +++++++ include/mainwindow.h | 58 +-- include/ui/filterchildrenproxymodel.h | 2 +- include/ui/maplistmodels.h | 22 +- include/ui/maplisttoolbar.h | 49 ++ porymap.pro | 3 + resources/icons/collapse_all.ico | Bin 318 -> 1871 bytes resources/icons/expand_all.ico | Bin 318 -> 2142 bytes resources/icons/folder_add.ico | Bin 0 -> 1572 bytes resources/icons/folder_closed_map.ico | Bin 1150 -> 5558 bytes resources/images.qrc | 1 + src/core/map.cpp | 2 +- src/mainwindow.cpp | 566 +++++++++------------- src/ui/maplistmodels.cpp | 12 +- src/ui/maplisttoolbar.cpp | 121 +++++ 16 files changed, 772 insertions(+), 899 deletions(-) create mode 100644 forms/maplisttoolbar.ui create mode 100644 include/ui/maplisttoolbar.h create mode 100755 resources/icons/folder_add.ico create mode 100644 src/ui/maplisttoolbar.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index bc03f823..44d69d30 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1287 - 936 + 1298 + 963
@@ -30,7 +30,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -45,7 +45,7 @@ 0 - + Groups @@ -66,140 +66,7 @@ 0 - - - 0 - - - 3 - - - 3 - - - 3 - - - - - <html><head/><body><p>Toggle hide all empty map folders</p></body></html> - - - - - - - :/icons/folder_eye_open.ico - :/icons/folder_eye_closed.ico:/icons/folder_eye_open.ico - - - true - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Expand all map folders</p></body></html> - - - - - - - :/icons/expand_all.ico:/icons/expand_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Collapse all map list folders</p></body></html> - - - - - - - :/icons/collapse_all.ico:/icons/collapse_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Toggle editability of group folders</p></body></html> - - - - - - - :/icons/lock_edit.ico - :/icons/unlock_edit.ico:/icons/lock_edit.ico - - - true - - - QToolButton::InstantPopup - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 12 - 20 - - - - - - - - true - - - - - - Filter... - - - true - - - - + @@ -216,10 +83,10 @@
- QAbstractItemView::SingleSelection + QAbstractItemView::SelectionMode::SingleSelection - QAbstractItemView::SelectItems + QAbstractItemView::SelectionBehavior::SelectItems false @@ -228,7 +95,7 @@
- + Areas @@ -249,116 +116,7 @@ 0
- - - 0 - - - 3 - - - 3 - - - 3 - - - - - <html><head/><body><p>Toggle hide all empty mapsection folders</p></body></html> - - - - - - - :/icons/folder_eye_open.ico - :/icons/folder_eye_closed.ico:/icons/folder_eye_open.ico - - - true - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Expand all map folders</p></body></html> - - - - - - - :/icons/expand_all.ico:/icons/expand_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Collapse all map list folders</p></body></html> - - - - - - - :/icons/collapse_all.ico:/icons/collapse_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 12 - 20 - - - - - - - - true - - - - - - Filter... - - - true - - - - + @@ -375,10 +133,10 @@
- QAbstractItemView::SingleSelection + QAbstractItemView::SelectionMode::SingleSelection - QAbstractItemView::SelectItems + QAbstractItemView::SelectionBehavior::SelectItems false @@ -387,7 +145,7 @@
- + Layouts @@ -408,116 +166,7 @@ 0 - - - 0 - - - 3 - - - 3 - - - 3 - - - - - <html><head/><body><p>Toggle hide all unused layouts</p></body></html> - - - - - - - :/icons/folder_eye_open.ico - :/icons/folder_eye_closed.ico:/icons/folder_eye_open.ico - - - true - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Expand all layout folders</p></body></html> - - - - - - - :/icons/expand_all.ico:/icons/expand_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Collapse all layout folders</p></body></html> - - - - - - - :/icons/collapse_all.ico:/icons/collapse_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 12 - 20 - - - - - - - - true - - - - - - Filter... - - - true - - - - + @@ -534,10 +183,10 @@ - QAbstractItemView::SingleSelection + QAbstractItemView::SelectionMode::SingleSelection - QAbstractItemView::SelectItems + QAbstractItemView::SelectionBehavior::SelectItems false @@ -581,7 +230,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -633,7 +282,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -643,10 +292,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -670,10 +319,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -722,10 +371,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -746,10 +395,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -930,7 +579,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -953,10 +602,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1004,7 +653,7 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint 3 @@ -1033,17 +682,17 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -1067,7 +716,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1104,10 +753,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain true @@ -1117,8 +766,8 @@ 0 0 - 420 - 77 + 424 + 79 @@ -1140,7 +789,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1162,17 +811,17 @@ <html><head/><body><p>The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1223,10 +872,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain true @@ -1236,8 +885,8 @@ 0 0 - 420 - 78 + 424 + 79 @@ -1259,7 +908,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1284,17 +933,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1318,19 +967,19 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -1338,10 +987,10 @@ - 0 + 8 0 - 409 - 440 + 412 + 446 @@ -1369,7 +1018,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1391,20 +1040,20 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1417,7 +1066,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1444,10 +1093,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1460,7 +1109,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> @@ -1480,7 +1129,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> @@ -1493,10 +1142,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -1538,7 +1187,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 3 @@ -1569,8 +1218,8 @@ 0 0 - 424 - 627 + 428 + 633 @@ -1592,7 +1241,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1617,17 +1266,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1640,7 +1289,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1666,7 +1315,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1682,17 +1331,17 @@ 50 - Qt::Horizontal + Qt::Orientation::Horizontal - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1718,7 +1367,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1779,7 +1428,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -1789,8 +1438,8 @@ 0 0 - 379 - 732 + 383 + 744 @@ -1812,10 +1461,10 @@ <html><head/><body><p>No prefabs have been created for the currently-used tilesets. Create some by using the button above!</p><p>Prefabs are &quot;prefabricated&quot; metatile selections that are used for easy selecting of complicated map structures. For example, a useful prefab could be a building or tree formation, which would otherwise be annoying to paint with the regular metatile picker.</p></body></html> - Qt::RichText + Qt::TextFormat::RichText - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop true @@ -1888,10 +1537,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised 0 @@ -1928,7 +1577,7 @@ :/icons/add.ico:/icons/add.ico - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -1951,7 +1600,7 @@ :/icons/delete.ico:/icons/delete.ico - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon false @@ -1961,7 +1610,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1996,7 +1645,7 @@ There are no events on the current map. - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter
@@ -2062,7 +1711,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2077,13 +1726,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2091,7 +1740,7 @@ 0 0 100 - 30 + 16 @@ -2156,7 +1805,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2171,13 +1820,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2185,7 +1834,7 @@ 0 0 100 - 30 + 16 @@ -2250,7 +1899,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2265,13 +1914,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2279,7 +1928,7 @@ 0 0 100 - 30 + 16 @@ -2350,7 +1999,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2365,13 +2014,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2379,7 +2028,7 @@ 0 0 100 - 30 + 16 @@ -2444,7 +2093,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2459,13 +2108,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2473,7 +2122,7 @@ 0 0 100 - 30 + 16 @@ -2513,13 +2162,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2582,14 +2231,14 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - QFormLayout::FieldsStayAtSizeHint + QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint 12 @@ -2793,10 +2442,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2809,10 +2458,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -2844,7 +2493,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2925,10 +2574,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2940,10 +2589,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -3008,7 +2657,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3093,7 +2742,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3109,7 +2758,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3141,7 +2790,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3172,7 +2821,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3188,22 +2837,22 @@ false - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - QGraphicsView::AnchorUnderMouse + QGraphicsView::ViewportAnchor::AnchorUnderMouse - QGraphicsView::AnchorUnderMouse + QGraphicsView::ViewportAnchor::AnchorUnderMouse @@ -3214,10 +2863,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -3234,10 +2883,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff true @@ -3247,8 +2896,8 @@ 0 0 - 365 - 651 + 204 + 16 @@ -3270,7 +2919,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -3298,19 +2947,19 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -3323,7 +2972,7 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContents @@ -3364,7 +3013,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3416,8 +3065,8 @@ 0 0 - 1287 - 22 + 1298 + 37 @@ -3788,7 +3437,7 @@ Check for Updates... - QAction::ApplicationSpecificRole + QAction::MenuRole::ApplicationSpecificRole @@ -3868,6 +3517,12 @@ QGraphicsView
mapview.h
+ + MapListToolBar + QFrame +
maplisttoolbar.h
+ 1 +
diff --git a/forms/maplisttoolbar.ui b/forms/maplisttoolbar.ui new file mode 100644 index 00000000..6f753ea8 --- /dev/null +++ b/forms/maplisttoolbar.ui @@ -0,0 +1,172 @@ + + + MapListToolBar + + + + 0 + 0 + 274 + 32 + + + + Form + + + + 0 + + + 3 + + + 3 + + + 0 + + + 3 + + + + + Add a folder to the list. + + + + :/icons/folder_add.ico:/icons/folder_add.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Hide empty folders in the list. + + + + :/icons/folder_eye_open.ico + :/icons/folder_eye_closed.ico:/icons/folder_eye_open.ico + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Expand all folders in the list. + + + + + + + :/icons/expand_all.ico:/icons/expand_all.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Collapse all folders in the list. + + + + + + + :/icons/collapse_all.ico:/icons/collapse_all.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Toggle editability of folders in the list. + + + + + + + :/icons/lock_edit.ico + :/icons/unlock_edit.ico:/icons/lock_edit.ico + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Preferred + + + + 12 + 19 + + + + + + + + true + + + + + + Filter... + + + true + + + + + + + + + + diff --git a/include/mainwindow.h b/include/mainwindow.h index 06b9bf78..e0bf7177 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -173,11 +173,6 @@ private slots: void on_action_Open_Project_triggered(); void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); - - void on_mapList_activated(const QModelIndex &index); - void on_areaList_activated(const QModelIndex &index); - void on_layoutList_activated(const QModelIndex &index); - void on_action_Save_Project_triggered(); void openWarpMap(QString map_name, int event_id, Event::Group event_group); @@ -275,10 +270,6 @@ private slots: void on_actionTileset_Editor_triggered(); - void on_lineEdit_filterBox_textChanged(const QString &arg1); - void on_lineEdit_filterBox_Areas_textChanged(const QString &arg1); - void on_lineEdit_filterBox_Layouts_textChanged(const QString &arg1); - void moveEvent(QMoveEvent *event); void closeEvent(QCloseEvent *); @@ -292,19 +283,9 @@ private slots: void on_slider_EmergeMapOpacity_valueChanged(int value); void on_horizontalSlider_CollisionTransparency_valueChanged(int value); - void do_HideShow(); - void do_ExpandAll(); - void do_CollapseAll(); - void on_toolButton_HideShow_Groups_clicked(); - void on_toolButton_ExpandAll_Groups_clicked(); - void on_toolButton_CollapseAll_Groups_clicked(); - void on_toolButton_EnableDisable_EditGroups_clicked(); - void on_toolButton_HideShow_Areas_clicked(); - void on_toolButton_ExpandAll_Areas_clicked(); - void on_toolButton_CollapseAll_Areas_clicked(); - void on_toolButton_HideShow_Layouts_clicked(); - void on_toolButton_ExpandAll_Layouts_clicked(); - void on_toolButton_CollapseAll_Layouts_clicked(); + void mapListShortcut_ToggleEmptyFolders(); + void mapListShortcut_ExpandAll(); + void mapListShortcut_CollapseAll(); void on_actionAbout_Porymap_triggered(); void on_actionOpen_Log_File_triggered(); @@ -347,14 +328,12 @@ private: QPointer gridSettingsDialog = nullptr; QPointer customScriptsEditor = nullptr; - FilterChildrenProxyModel *groupListProxyModel; - MapGroupModel *mapGroupModel; - - FilterChildrenProxyModel *areaListProxyModel; - MapAreaModel *mapAreaModel; - - FilterChildrenProxyModel *layoutListProxyModel; - LayoutTreeModel *layoutTreeModel; + QPointer groupListProxyModel = nullptr; + QPointer mapGroupModel = nullptr; + QPointer areaListProxyModel = nullptr; + QPointer mapAreaModel = nullptr; + QPointer layoutListProxyModel = nullptr; + QPointer layoutTreeModel = nullptr; QPointer updatePromoter = nullptr; QPointer networkAccessManager = nullptr; @@ -375,9 +354,10 @@ private: bool tilesetNeedsRedraw = false; bool setLayout(QString layoutId); - bool setMap(QString, bool scroll = false); + bool setMap(QString); void unsetMap(); - bool userSetMap(QString, bool scrollTreeView = false); + bool userSetLayout(QString layoutId); + bool userSetMap(QString); void redrawMapScene(); void redrawLayoutScene(); void refreshMapScene(); @@ -389,7 +369,10 @@ private: void clearProjectUI(); void openSubWindow(QWidget * window); - void scrollTreeView(QString itemName); + void scrollMapList(MapTree *list, QString itemName); + void scrollMapListToCurrentMap(MapTree *list); + void scrollMapListToCurrentLayout(MapTree *list); + void resetMapListFilters(); QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); @@ -403,31 +386,29 @@ private: void refreshRecentProjectsMenu(); void updateMapList(); - void mapListAddItem(); - void mapListRemoveItem(); void mapListAddGroup(); void mapListAddLayout(); void mapListAddArea(); void mapListRemoveGroup(); void mapListRemoveArea(); void mapListRemoveLayout(); + void openMapListItem(const QModelIndex &index); void displayMapProperties(); void checkToolButtons(); void clickToolButtonFromEditAction(Editor::EditAction editAction); - void showWindowTitle(); + void updateWindowTitle(); void initWindow(); void initCustomUI(); void initExtraSignals(); void initEditor(); void initMiscHeapObjects(); - void initMapSortOrder(); + void initMapList(); void initShortcuts(); void initExtraShortcuts(); void loadUserSettings(); - void applyMapListFilter(QString filterText); void restoreWindowState(); void setTheme(QString); void updateTilesetEditor(); @@ -447,6 +428,7 @@ private: double getMetatilesZoomScale(); void redrawMetatileSelection(); void scrollMetatileSelectorToSelection(); + MapListToolBar* getCurrentMapListToolBar(); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); diff --git a/include/ui/filterchildrenproxymodel.h b/include/ui/filterchildrenproxymodel.h index 5853d625..d9eed7af 100644 --- a/include/ui/filterchildrenproxymodel.h +++ b/include/ui/filterchildrenproxymodel.h @@ -9,7 +9,7 @@ class FilterChildrenProxyModel : public QSortFilterProxyModel public: explicit FilterChildrenProxyModel(QObject *parent = nullptr); - void toggleHideEmpty() { this->hideEmpty = !this->hideEmpty; } + bool toggleHideEmpty() { return this->hideEmpty = !this->hideEmpty; } protected: bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; private: diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index d99c8815..8d00c492 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -53,7 +53,17 @@ private: class QRegularExpressionValidator; -class MapGroupModel : public QStandardItemModel { +class MapListModel : public QStandardItemModel { + Q_OBJECT + +public: + MapListModel(QObject *parent = nullptr) : QStandardItemModel(parent) {}; + ~MapListModel() { } + + virtual QModelIndex indexOf(QString id) const = 0; +}; + +class MapGroupModel : public MapListModel { Q_OBJECT public: @@ -80,7 +90,7 @@ public: void removeGroup(int groupIndex); QStandardItem *getItem(const QModelIndex &index) const; - QModelIndex indexOfMap(QString mapName); + virtual QModelIndex indexOf(QString mapName) const override; void initialize(); @@ -103,7 +113,7 @@ signals: -class MapAreaModel : public QStandardItemModel { +class MapAreaModel : public MapListModel { Q_OBJECT public: @@ -123,7 +133,7 @@ public: void removeArea(int groupIndex); QStandardItem *getItem(const QModelIndex &index) const; - QModelIndex indexOfMap(QString mapName); + virtual QModelIndex indexOf(QString mapName) const override; void initialize(); @@ -143,7 +153,7 @@ signals: -class LayoutTreeModel : public QStandardItemModel { +class LayoutTreeModel : public MapListModel { Q_OBJECT public: @@ -162,7 +172,7 @@ public: QStandardItem *insertMapItem(QString mapName, QString layoutId); QStandardItem *getItem(const QModelIndex &index) const; - QModelIndex indexOfLayout(QString layoutName); + virtual QModelIndex indexOf(QString layoutName) const override; void initialize(); diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h new file mode 100644 index 00000000..c66b0d80 --- /dev/null +++ b/include/ui/maplisttoolbar.h @@ -0,0 +1,49 @@ +#ifndef MAPLISTTOOLBAR_H +#define MAPLISTTOOLBAR_H + +#include "maplistmodels.h" +#include "filterchildrenproxymodel.h" + +#include +#include + +namespace Ui { +class MapListToolBar; +} + +class MapListToolBar : public QFrame +{ + Q_OBJECT + +public: + explicit MapListToolBar(QWidget *parent = nullptr); + ~MapListToolBar(); + + MapTree* list() const { return m_list; } + void setList(MapTree *list); + + void setEditsAllowedButtonHidden(bool hidden); + + void toggleEmptyFolders(); + void expandList(); + void collapseList(); + void toggleEditsAllowed(); + + void applyFilter(const QString &filterText); + void clearFilter(); + void setFilterLocked(bool locked) { m_filterLocked = locked; } + bool isFilterLocked() const { return m_filterLocked; } + +signals: + void filterCleared(MapTree*); + void addFolderClicked(); + +private: + Ui::MapListToolBar *ui; + QPointer m_list; + bool m_filterLocked = false; + + void setEditsAllowed(bool allowed); +}; + +#endif // MAPLISTTOOLBAR_H diff --git a/porymap.pro b/porymap.pro index d65bdf07..1b1c693e 100644 --- a/porymap.pro +++ b/porymap.pro @@ -75,6 +75,7 @@ SOURCES += src/core/block.cpp \ src/ui/eventfilters.cpp \ src/ui/filterchildrenproxymodel.cpp \ src/ui/maplistmodels.cpp \ + src/ui/maplisttoolbar.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ src/ui/layoutpixmapitem.cpp \ @@ -174,6 +175,7 @@ HEADERS += include/core/block.h \ include/ui/eventfilters.h \ include/ui/filterchildrenproxymodel.h \ include/ui/maplistmodels.h \ + include/ui/maplisttoolbar.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ include/ui/layoutpixmapitem.h \ @@ -229,6 +231,7 @@ FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ forms/connectionslistitem.ui \ forms/gridsettingsdialog.ui \ + forms/maplisttoolbar.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ diff --git a/resources/icons/collapse_all.ico b/resources/icons/collapse_all.ico index f6c7f315826bdea6dc03fe387720105362368e68..806f2435f3d3cd6ecec97362dc3eaba5627bd310 100644 GIT binary patch literal 1871 zcmZ`)3p|r+7=O2^u_el-a=NT_a*JIkmo>{}h1xQdE#_p_uwv6T4HZKU(d{G^DnjWV zDXExsq1Gu;x~NdUh7hG9iFUrxDZkU{{eIu~KHu~I|DWf1pZER!-mQ$~?z$)w6aYZi z(}U^*%^1zAJs$dsV`D!;lUAq;-35S>o#-LXI1R}4@Sy_`YYKpD3jl+VOZFOoC;|YT zK>(2U1284>z-4bT1Tc6k&k#BtSVFcofWsyO1Z2UW2c+|| zl}d3^Tbxi7iYGccI^qd7cpDpQh+!>`5lA@E)&lXI(IEfEq4LCB5kFGG7YZ<%xSU{N zl*9>()g<~pM(gB7^M7O#h`%ihS|DD7;E6Z_{{LhWe#kFm8e~*9vaZo|WKA#WYac_hnEz>hk|2wA@pjsgBFbQ6XPMZS?7E{{U|mTQb_3|mt*$xFoN zK@HWkO+)|5_l-}+YkE4S$D=$NPB)VtzG9`} z*PfimSr(ZXocYy4RxrE4!^mS`>%!|xxpn2Qmz;J%B$d}2S+OU_u>m@}#`EEmsSRPN z^+xC@F2f+mD%CjIE)2UzLVIfJaPZTi7HLaGZC4j5 zW2d0kva)LmUxNguztI3?QIZLTNZ5hMxNW>q9J)|R2uM19WbVEqQelPktv&iYq>^?v(?Mdn!j{B#iwZ_w%^K32bg=8yERQ;4+itQ zru$RkdZo1Ha;1^!y3_2|<$?Lg>1Ig#QdcXnu^(9>j8zn}+kzG#4Rxvmj0kZr?K}I5 zdFqs*)yk(<@MOi}mrak`IOjnj7-~QIVE(C27|eX%=550S6bTks+2w8Gw4~|1vlvVe zSJ&fF&XDF8)UyxeZ_G?v9Mj1u>I}L$0lTk@l6l=s`@*}VT>Q-$xF17EvuPkT6PC4Lb8}8l#b-N zhE|s%D$N`FfPTW-CI=}@wnw~WcNSL%b<#QgO`;Sni&dUFYkV(+i3YY>4DIPhNc=`u zeg9dpvNBTo7TMi{cK%R%jD+=_aLF)8_r(&j=JFiR| zv=qx&4ioU(s*xFB{Qr-VC!{>L9}a124*NiHiNl z(Mi|manEHR>dYr?x@iBk0yC=y8&Gt5P`m;N{mg}{& zKOVkrin~$VUeYnm_U=QPs=&d$^n>4fccO)ey?P=z-L~^gO;$Y2+#uj|dl4%_bKiI_ LUQRvZ8kG1CNBinz literal 318 zcmah@$qm3D40C7)2%o+yUW~!$9jO~oheYLtFmfys=KzFHRT)oI0WJWQRPrV*zd~yb yz4ujln=i#JAt>BG5Gqq8XWqzTL)|v(i+?txuq8GV`mcN0^#sR&U9dsM!}$R)WC;%d diff --git a/resources/icons/expand_all.ico b/resources/icons/expand_all.ico index 0707936c59da83addeb3911acd285a97cf4119dd..ca913a1322074b24f0642be85334412725e89c4b 100644 GIT binary patch literal 2142 zcmZ`)3p|s1AODZ#vdAUg$|Y;nQrI&KWo=3p>dZAK>*VA*&SIOkITp&w%4I}}crzTS zQ=Q1<_L5vma$Jf|F_)AghQq;oToUg_Z~46EJ^$zPJpbSG`+mQ_+yD7|o>*r`J5_`Z z0sx?T(4OQf-I20eNkRH#hlNc^H+X=xgEau6#MSfOt7M?By{iKNVQ2t&d;sR8F5Vyj zAy@z;UI5_J09Y5CQSL&J0?t&*L4OAautRDq0Xdi^ke6C8=>?)`0KQ}cAdMtl^4YMj zXtvxJ6qzmm)s_(-9wUzeAcv*7Q8*L_GTxU-$9VfOeW(~NJy=En1TJ1`(y1J8B$pn< zVB@(&)DiH=9)0ro0o(9Hx7x)_GQt6IW#5% zDU0jv!wlgNQ7BoWFKf9@Dwp;(6NCM^Eop<041r8BSm^)BI5hvik;#x{*-~H2=?Jo5 zcxM`y8g!IIqf;4dX=zqFafGETe*wPMxk5H0CW3|+Rpph*z1Ju_$CA zhLLBhc-%l7&L)-yh_(Sekuv#1UTRIQ7D8~*Z*Y&-45_o@-~F|c&JYa!nmFB%^5@2k zh5ehfkM=i$YOS^H%DliM%6^`QHsgA79sL{vqVREgku;-Nv8bq5e3J$^-dh_$)O$F# zs}#|cY-);2{Z=GSMH8ZWidx5AUTiiWmZZ9^w`!)mWKGz0c48Dhl|s>HAJkw);8hd~87$UTSTrzu8^kU{1XWxA>+SA6Q73Xs*}N5Cv2eFpE>T$* z?^+X8u-o)TIZ61cXH?*OeEhZhMbWTg>pXROR|F~_+#<-b30mJ^<9^u~CCu0-8PL!k zw}=_K{PdFBX;x`AgIG~-c;1rQ_sVMwYmQU9iBlPDK_V0xIU}(lk1a@LxDD)tfsu#7 z(G8(_>oDj~d*8L~_^0T zwbY3@v~TY(BU@GOPtIzr$*55yzP0Ig77Lzihru2*;G%*Dm+k5XXzAn^5PEiNO5yn1 zd?czB?v$4vpflBaX-(c>`k(Z!SHbJ&P=?NG zF>(j)_PmEcS4XF${T1feA9Ja;9tL{l!uS?V$Rtups6VO%SJ*PMrc<%3Rx=4YtKLGD zG`Yk1zZFGH4m2A;CZ6SUFB4Q_wq^M5ikH(PMy$7~`RRpFC>(2ZJeSBypKM5b)~^NI zWPHTTf+0TY;X2X%&9JA-h5CzU1om54cPcAS7tl1iSW)n{m} zq4z#(FYBfFNC*n=KcET?$fuGsJ<@FGzwc|YZ+iyeG?5}rp6Yw<=FAnOtYIG+H|GFW CWC>FM diff --git a/resources/icons/folder_add.ico b/resources/icons/folder_add.ico new file mode 100755 index 0000000000000000000000000000000000000000..d881adf8e9202901244af1bafbc1c39dc939a9a7 GIT binary patch literal 1572 zcmV+<2HW|GP)4k@LZHNm0kuIuj3$~G9|=D&_$DD3O;BSn$`7eQK@(63(eMuu8kGhRsSlvl zMZ?1a-FE51w%gY(ZMVDk;mkSPeUxptC5_%>a_^mc?wy%$zL_&uv27dw=b@13jXRbd z&|s{zEsJjLjLQ^Ng$eyo)r-g0lERoQ2XvAgHa>s-9{MbO*H%VxQ-f#c^N;X)lJI0Y z;1-dS@#y)j6u%a?n0lCSl|KVxD1xTi#UVveCI+*rF?xpyy+=o)rzfmg25i+ycMg&i z1Dp}tgf$dI)rPNc_ARS*`he~r^qyUr2|Yb&h#XVtR?*WD5SJ=q!Q(*Z(Ft0&>2(v% z%8iixf_roa5T(FX#w$Y#Sg)l(lJG1GAhtx@3JtGRGI2oatr*$SMsXlhUa2w^MV!Y* zfWB!Bq^C7A0){9x5p5O+*r=+h@HRE0yY(6qXmkMB38jIus}X^h1fyZ^WW2TUA`lzGf|vFR0%NR(ri}2)L8RQZPLML00y`%1tgvXBL@YzvI0Emf z3&dFyW%DZ#y|@jdH?Wb11ud@}D6BzmIC`2CPCd2ORYE}39PpkwhhR%Pna=c46@_Ib zD8FY8EXx+C3EE_Hz?PM!PMM3~<P}q$N-GMfpk(1r-VyV z$;cAPkvLZp3v8wc1>-7(88y*;wYlSuOMOAE{UidaA>a%Jf|z;la`LU6?w#@aSH@yh z;?O7;oW?{s50{>8$wrqq3td+{IPdMTjpS2KM_=eE58)Rjb^g#j3bb3 zOQmY(R1km81)A~IU-dXb<*6pY$BOb|tgC(<1^zZ1_jK1W{ONr_HPL%DfP#uUgfj|v zFICUp6Zln*Sa;#$-@d_jEoZT2!BePxU@>x>xnj*=bO0XjFL=3bGnOc&`10tre@H!q zM0hf3=xOt#a84y|A3D;cM2NY<(XaYTiQe^t|Wp+lVEIH-3S@jcb17&n_1o&1Qax2(>Xq zp{jwyg*~3{@z|oLarXKb=pOJvSJPWRU$d@@p8e_Hjhd<_;V#5hdgPD@5P`b9tpxu( zy6Y#~1g#^xfW_%T{S_a|O7hVjJ`aORZirYHP1zp!nsxll7e0s5>3O{CS0`FAr=Zu{ zhKh%&Jr5E2^t|zoI^;x-h^`}YMzDR`gEx1?ovV_%&GiZbgQth7EnBf^D`CyynCkg# z7qwSw+~WqZ0C}pLs}KEA34PXgXw8jiTcWk`xqxKuhSl?`BT;igGrCEWS}1J9I@VtO zyk;rYJ3~!-T8z#S>O0#XlAn9^2L*0jCfwzaeE7KmsM~}RT_hdwesb_~=eWAg&0EaW z>g8zc>PFR!=`upoO5Y-KsZw(t@)>P6=pakf>6`d{c7S|F4@uvxI?&}oZA&rs&MKCz z!{D}`vWTN*zOr$iQ7^Xu?Ty&Z{G8~Fo9WctCX7%@sg%#;hLy9)=H zrN7b?(BZ|w;~(M`()hJWaDZ8Bc^lAFbSKI>L%2PM!q1;Zu+t3ecFLC55dfMzG+4Ld z@K4+DCaJD-m!1p+WI>Qkl3A52u(WIu);p)+?$iM~8p7GO-?6Lt2u_lwJ)UxNvK$z8 zf*VL7NfF7ExK55+RDC2o19ZoUZ)P9sZ|!H-5&r{nB5fq09}hVL#Kt*5{x`?J0t^7^ WWe&kFMnG%;0000e6r<%oD&DDM{_ScOGMf<*{M zN4qF$WsW7KX5p<81a@IrWMM%r3R;?(DSJEr{?A@)S4a>>RkFlDC^t;{YoE z0=AC<=~sOxeBTPTf5iDh*$p76!VC(J(oZy+VEhyK@G4O90Hn{h-yYCU+BMka1QMvF zrC)j-C>jC`m(>SSe8tyS`iEj*UE#1cpNvqyTwA~Rcc9>ssuFA&^hAUL&Qr+w43~ZD z{EYhXVe}2Omwr%H3GxR#k%9Vp*$?%L2OQXQTU$SgXHr0$=Jk72f)D!K5h0KBy8*X7v0G1^I&uD2)??dz5irm3vvg6jXiFaV1wNi zF*f$Nu&GyY;v2q~@#QV|JpU=w-_);SZMREZ*X_dEE@wnp*UkCJ(Qx_|Em+^HVwKIM zuC}?bs?#}UR6njxh1(rC`rsag9&Yy-!fMGMwyFw6~9FR%IPzWj46zaQ@kbbr7qAe~<&e*N#Se*l&E@Oc0L delta 230 zcmdm{{f|SKfq@YS1q48}0z(E11B2LPZ$ar0VQvNn5Qc&O|Nk>kf`R53Os*4Do;+7j ylErb_#gCJB30i;|o4*O}VPbQhacP6|jLAPl?3^gE2BLSOqZtR|e+Hm~85jT*rh9q- diff --git a/resources/images.qrc b/resources/images.qrc index 15650a65..a89535a9 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -10,6 +10,7 @@ icons/file_put.ico icons/fill_color_cursor.ico icons/fill_color.ico + icons/folder_add.ico icons/folder_closed_map.ico icons/folder_closed.ico icons/folder_eye_closed.ico diff --git a/src/core/map.cpp b/src/core/map.cpp index 01ab95b0..8067b9a7 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -257,7 +257,7 @@ void Map::clean() { } bool Map::hasUnsavedChanges() { - return !editHistory.isClean() || !this->layout->editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile; + return !editHistory.isClean() || this->layout->hasUnsavedChanges() || hasUnsavedDataChanges || !isPersistedToFile; } void Map::pruneEditHistory() { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 08cdaf8c..14a255c2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -119,7 +119,7 @@ void MainWindow::initWindow() { this->initExtraSignals(); this->initEditor(); this->initMiscHeapObjects(); - this->initMapSortOrder(); + this->initMapList(); this->initShortcuts(); this->restoreWindowState(); @@ -166,15 +166,15 @@ void MainWindow::initExtraShortcuts() { shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths"); shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths"); - auto *shortcutHide_Show = new Shortcut(QKeySequence(), this, SLOT(do_HideShow())); + auto *shortcutHide_Show = new Shortcut(QKeySequence(), this, SLOT(mapListShortcut_ToggleEmptyFolders())); shortcutHide_Show->setObjectName("shortcutHide_Show"); shortcutHide_Show->setWhatsThis("Map List: Hide/Show Empty Folders"); - auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(do_ExpandAll())); + auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(mapListShortcut_ExpandAll())); shortcutExpand_All->setObjectName("shortcutExpand_All"); shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); - auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(do_CollapseAll())); + auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(mapListShortcut_CollapseAll())); shortcutCollapse_All->setObjectName("shortcutCollapse_All"); shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); @@ -240,44 +240,9 @@ void MainWindow::initCustomUI() { ui->mainTabBar->addTab(mainTabNames.value(i)); ui->mainTabBar->setTabIcon(i, mainTabIcons.value(i)); } - - WheelFilter *wheelFilter = new WheelFilter(this); - ui->mainTabBar->installEventFilter(wheelFilter); - this->ui->mapListContainer->tabBar()->installEventFilter(wheelFilter); - - // Create buttons for adding and removing items from the mapList - QFrame *frame = new QFrame(this->ui->mapListContainer); - frame->setFrameShape(QFrame::NoFrame); - QHBoxLayout *layout = new QHBoxLayout(frame); - - QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); - connect(buttonAdd, &QPushButton::clicked, [this]() { this->mapListAddItem(); }); - QPushButton *buttonRemove = new QPushButton(QIcon(":/icons/delete.ico"), ""); - connect(buttonRemove, &QPushButton::clicked, [this]() { this->mapListRemoveItem(); }); - - layout->addWidget(buttonAdd); - layout->addWidget(buttonRemove); - - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); - - this->ui->mapListContainer->setCornerWidget(frame, Qt::TopRightCorner); } void MainWindow::initExtraSignals() { - // Right-clicking on items in the map list tree view brings up a context menu. - ui->mapList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->mapList, &QTreeView::customContextMenuRequested, - this, &MainWindow::onOpenMapListContextMenu); - - ui->areaList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->areaList, &QTreeView::customContextMenuRequested, - this, &MainWindow::onOpenMapListContextMenu); - - ui->layoutList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->layoutList, &QTreeView::customContextMenuRequested, - this, &MainWindow::onOpenMapListContextMenu); - // other signals connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this, &MainWindow::addNewEvent); connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged); @@ -381,7 +346,7 @@ void MainWindow::initEditor() { ui->menuEdit->addAction(showHistory); // Toggle an asterisk in the window title when the undo state is changed - connect(&editor->editGroup, &QUndoGroup::indexChanged, this, &MainWindow::showWindowTitle); + connect(&editor->editGroup, &QUndoGroup::indexChanged, this, &MainWindow::updateWindowTitle); // selecting objects from the spinners connect(this->ui->spinner_ObjectID, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -405,37 +370,104 @@ void MainWindow::initMiscHeapObjects() { ui->tabWidget_EventType->clear(); } -void MainWindow::initMapSortOrder() { - this->ui->mapListContainer->setCurrentIndex(static_cast(porymapConfig.mapSortOrder)); +void MainWindow::initMapList() { + ui->mapListContainer->setCurrentIndex(static_cast(porymapConfig.mapSortOrder)); + + WheelFilter *wheelFilter = new WheelFilter(this); + ui->mainTabBar->installEventFilter(wheelFilter); + ui->mapListContainer->tabBar()->installEventFilter(wheelFilter); + + // Create buttons for adding and removing items from the mapList + QFrame *buttonFrame = new QFrame(this->ui->mapListContainer); + buttonFrame->setFrameShape(QFrame::NoFrame); + + QHBoxLayout *layout = new QHBoxLayout(buttonFrame); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + + // Create add map/layout button + QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); + connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::on_action_NewMap_triggered); + layout->addWidget(buttonAdd); + + /* TODO: Remove button disabled, no current support for deleting maps/layouts + // Create remove map/layout button + QPushButton *buttonRemove = new QPushButton(QIcon(":/icons/delete.ico"), ""); + connect(buttonRemove, &QPushButton::clicked, this, &MainWindow::deleteCurrentMapOrLayout); + layout->addWidget(buttonRemove); + */ + + ui->mapListContainer->setCornerWidget(buttonFrame, Qt::TopRightCorner); + + // Connect tool bars to lists + ui->mapListToolBar_Groups->setList(ui->mapList); + ui->mapListToolBar_Areas->setList(ui->areaList); + ui->mapListToolBar_Layouts->setList(ui->layoutList); + + // Left-clicking on items in the map list opens the corresponding map/layout. + connect(ui->mapList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->areaList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + + // Right-clicking on items in the map list brings up a context menu. + ui->mapList->setContextMenuPolicy(Qt::CustomContextMenu); + ui->areaList->setContextMenuPolicy(Qt::CustomContextMenu); + ui->layoutList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->areaList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + + // Only the groups list allows reorganizing folder contents, editing folder names, etc. + ui->mapListToolBar_Areas->setEditsAllowedButtonHidden(true); + ui->mapListToolBar_Layouts->setEditsAllowedButtonHidden(true); + + // When map list search filter is cleared we want the current map/layout in the editor to be visible in the list. + connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Areas, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); + + // 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); } -void MainWindow::showWindowTitle() { +void MainWindow::updateWindowTitle() { + if (!editor || !editor->project) { + setWindowTitle(QCoreApplication::applicationName()); + return; + } + + const QString projectName = editor->project->getProjectTitle(); + if (!editor->layout) { + setWindowTitle(projectName); + return; + } + if (editor->map) { setWindowTitle(QString("%1%2 - %3") .arg(editor->map->hasUnsavedChanges() ? "* " : "") .arg(editor->map->name) - .arg(editor->project->getProjectTitle()) + .arg(projectName) ); - } - else if (editor->layout) { + } else { setWindowTitle(QString("%1%2 - %3") .arg(editor->layout->hasUnsavedChanges() ? "* " : "") .arg(editor->layout->name) - .arg(editor->project->getProjectTitle()) + .arg(projectName) ); } - if (editor && editor->layout) { - // For some reason (perhaps on Qt < 6?) we had to clear the icon first here or mainTabBar wouldn't display correctly. - ui->mainTabBar->setTabIcon(MainTab::Map, QIcon()); - QPixmap pixmap = editor->layout->pixmap; - if (!pixmap.isNull()) { - ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(pixmap)); - } else { - ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(QStringLiteral(":/icons/map.ico"))); - } + // For some reason (perhaps on Qt < 6?) we had to clear the icon first here or mainTabBar wouldn't display correctly. + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon()); + + QPixmap pixmap = editor->layout->pixmap; + if (!pixmap.isNull()) { + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(pixmap)); + } else { + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(QStringLiteral(":/icons/map.ico"))); } - updateMapList(); + updateMapList(); // TODO: Why is this function responsible for this } void MainWindow::markMapEdited() { @@ -448,52 +480,7 @@ void MainWindow::markSpecificMapEdited(Map* map) { map->hasUnsavedDataChanges = true; if (editor && editor->map == map) - showWindowTitle(); -} - -void MainWindow::on_lineEdit_filterBox_textChanged(const QString &text) { - this->applyMapListFilter(text); -} - -void MainWindow::on_lineEdit_filterBox_Areas_textChanged(const QString &text) { - this->applyMapListFilter(text); -} - -void MainWindow::on_lineEdit_filterBox_Layouts_textChanged(const QString &text) { - this->applyMapListFilter(text); -} - -void MainWindow::applyMapListFilter(QString filterText) { - FilterChildrenProxyModel *proxy; - QTreeView *list; - QModelIndex sourceIndex; - switch (porymapConfig.mapSortOrder) { - case MapSortOrder::SortByGroup: - proxy = this->groupListProxyModel; - list = this->ui->mapList; - sourceIndex = mapGroupModel->indexOfMap(editor->map->name); - break; - case MapSortOrder::SortByArea: - proxy = this->areaListProxyModel; - list = this->ui->areaList; - sourceIndex = mapAreaModel->indexOfMap(editor->map->name); - break; - case MapSortOrder::SortByLayout: - proxy = this->layoutListProxyModel; - list = this->ui->layoutList; - sourceIndex = layoutTreeModel->indexOfLayout(editor->layout->id); - break; - } - - proxy->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); - if (filterText.isEmpty()) { - list->collapseAll(); - } else { - list->expandToDepth(0); - } - - list->setExpanded(proxy->mapFromSource(sourceIndex), true); - list->scrollTo(proxy->mapFromSource(sourceIndex), QAbstractItemView::PositionAtCenter); + updateWindowTitle(); } void MainWindow::loadUserSettings() { @@ -640,7 +627,7 @@ bool MainWindow::openProject(QString dir, bool initial) { // Only create the config files once the project has opened successfully in case the user selected an invalid directory this->editor->project->saveConfig(); - showWindowTitle(); + updateWindowTitle(); this->statusBar()->showMessage(QString("Opened %1").arg(projectString)); porymapConfig.projectManuallyClosed = false; @@ -702,7 +689,7 @@ bool MainWindow::setInitialMap() { const QString recent = userConfig.recentMapOrLayout; if (editor->project->mapNames.contains(recent)) { // User recently had a map open that still exists. - if (setMap(recent, true)) + if (setMap(recent)) return true; } else if (editor->project->mapLayoutsTable.contains(recent)) { // User recently had a layout open that still exists. @@ -712,7 +699,7 @@ bool MainWindow::setInitialMap() { // Failed to open recent map/layout, or no recent map/layout. Try opening maps then layouts sequentially. for (const auto &name : editor->project->mapNames) { - if (name != recent && setMap(name, true)) + if (name != recent && setMap(name)) return true; } for (const auto &id : editor->project->mapLayoutsTable) { @@ -808,7 +795,7 @@ void MainWindow::unsetMap() { // setMap, but with a visible error message in case of failure. // Use when the user is specifically requesting a map to open. -bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { +bool MainWindow::userSetMap(QString map_name) { if (editor->map && editor->map->name == map_name) return true; // Already set @@ -819,7 +806,7 @@ bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { return false; } - if (!setMap(map_name, scrollTreeView)) { + if (!setMap(map_name)) { QMessageBox msgBox(this); QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3") .arg(map_name) @@ -831,7 +818,7 @@ bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { return true; } -bool MainWindow::setMap(QString map_name, bool scroll) { +bool MainWindow::setMap(QString map_name) { // if map name is empty, clear & disable map ui if (map_name.isEmpty()) { unsetMap(); @@ -851,7 +838,7 @@ bool MainWindow::setMap(QString map_name, bool scroll) { } if (editor->map && !editor->map->name.isNull()) { - ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(map_name)), false); + ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOf(map_name)), false); } setLayoutOnlyMode(false); @@ -859,12 +846,8 @@ bool MainWindow::setMap(QString map_name, bool scroll) { refreshMapScene(); displayMapProperties(); - - if (scroll) { - scrollTreeView(map_name); - } - - showWindowTitle(); + updateWindowTitle(); + resetMapListFilters(); connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing, Qt::UniqueConnection); connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); @@ -892,12 +875,28 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { this->ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled); } +// setLayout, but with a visible error message in case of failure. +// Use when the user is specifically requesting a layout to open. +bool MainWindow::userSetLayout(QString layoutId) { + if (!setLayout(layoutId)) { + QMessageBox msgBox(this); + QString errorMsg = QString("There was an error opening layout %1. Please see %2 for full error details.\n\n%3") + .arg(layoutId) + .arg(getLogPath()) + .arg(getMostRecentError()); + msgBox.critical(nullptr, "Error Opening Layout", errorMsg); + return false; + } + return true; +} + bool MainWindow::setLayout(QString layoutId) { if (this->editor->map) logInfo("Switching to a layout-only editing mode. Disabling map-related edits."); - setMap(QString()); + unsetMap(); + // TODO: Using the 'id' instead of the layout name here is inconsistent with how we treat maps. logInfo(QString("Setting layout to '%1'").arg(layoutId)); if (!this->editor->setLayout(layoutId)) { @@ -907,8 +906,8 @@ bool MainWindow::setLayout(QString layoutId) { layoutTreeModel->setLayout(layoutId); refreshMapScene(); - showWindowTitle(); - updateMapList(); + updateWindowTitle(); + resetMapListFilters(); connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); @@ -969,7 +968,7 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } // Open the destination map. - if (!userSetMap(map_name, true)) + if (!userSetMap(map_name)) return; // Select the target event. @@ -1229,7 +1228,7 @@ bool MainWindow::setProjectUI() { this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); - on_toolButton_EnableDisable_EditGroups_clicked(); + //on_toolButton_EnableDisable_EditGroups_clicked();//TODO return true; } @@ -1245,8 +1244,7 @@ void MainWindow::clearProjectUI() { const QSignalBlocker blocker7(ui->comboBox_Type); const QSignalBlocker blocker8(ui->comboBox_DiveMap); const QSignalBlocker blocker9(ui->comboBox_EmergeMap); - const QSignalBlocker blockerA(ui->lineEdit_filterBox); - const QSignalBlocker blockerB(ui->comboBox_LayoutSelector); + const QSignalBlocker blockerA(ui->comboBox_LayoutSelector); ui->comboBox_Song->clear(); ui->comboBox_Location->clear(); @@ -1257,49 +1255,51 @@ void MainWindow::clearProjectUI() { ui->comboBox_Type->clear(); ui->comboBox_DiveMap->clear(); ui->comboBox_EmergeMap->clear(); - ui->lineEdit_filterBox->clear(); ui->comboBox_LayoutSelector->clear(); // Clear map models - if (this->mapGroupModel) { - delete this->mapGroupModel; - this->mapGroupModel = nullptr; - delete this->groupListProxyModel; - this->groupListProxyModel = nullptr; - } - if (this->mapAreaModel) { - delete this->mapAreaModel; - this->mapAreaModel = nullptr; - delete this->areaListProxyModel; - this->areaListProxyModel = nullptr; - } - if (this->layoutTreeModel) { - delete this->layoutTreeModel; - this->layoutTreeModel = nullptr; - delete this->layoutListProxyModel; - this->layoutListProxyModel = nullptr; - } + delete this->mapGroupModel; + delete this->groupListProxyModel; + delete this->mapAreaModel; + delete this->areaListProxyModel; + delete this->layoutTreeModel; + delete this->layoutListProxyModel; + resetMapListFilters(); Event::clearIcons(); } -void MainWindow::scrollTreeView(QString itemName) { - switch (ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: - groupListProxyModel->setFilterRegularExpression(QString()); - ui->mapList->setCurrentIndex(groupListProxyModel->mapFromSource(mapGroupModel->indexOfMap(itemName))); - ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); - break; - case MapListTab::Areas: - areaListProxyModel->setFilterRegularExpression(QString()); - ui->areaList->setCurrentIndex(areaListProxyModel->mapFromSource(mapAreaModel->indexOfMap(itemName))); - ui->areaList->scrollTo(ui->areaList->currentIndex(), QAbstractItemView::PositionAtCenter); - break; - case MapListTab::Layouts: - layoutListProxyModel->setFilterRegularExpression(QString()); - ui->layoutList->setCurrentIndex(layoutListProxyModel->mapFromSource(layoutTreeModel->indexOfLayout(itemName))); - ui->layoutList->scrollTo(ui->layoutList->currentIndex(), QAbstractItemView::PositionAtCenter); - break; +void MainWindow::scrollMapList(MapTree *list, QString itemName) { + if (!list || itemName.isEmpty()) + return; + auto model = static_cast(list->model()); + if (!model) + return; + auto sourceModel = static_cast(model->sourceModel()); + if (!sourceModel) + return; + QModelIndex sourceIndex = sourceModel->indexOf(itemName); + if (!sourceIndex.isValid()) + return; + QModelIndex index = model->mapFromSource(sourceIndex); + if (!index.isValid()) + return; + + list->setCurrentIndex(index); + list->setExpanded(index, true); + list->scrollTo(index, QAbstractItemView::PositionAtCenter); +} + +void MainWindow::scrollMapListToCurrentMap(MapTree *list) { + if (this->editor->map) { + scrollMapList(list, this->editor->map->name); + } +} + +// TODO: Initial scrolling doesn't center the layout on launch if it's not the current tab. +void MainWindow::scrollMapListToCurrentLayout(MapTree *list) { + if (this->editor->layout) { + scrollMapList(list, this->editor->layout->id); } } @@ -1346,6 +1346,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QStandardItem *selectedItem = model->itemFromIndex(index); if (selectedItem->parent()) { + // TODO: Right-click delete on maps? return; } @@ -1376,6 +1377,7 @@ void MainWindow::mapListAddGroup() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ if (!this->editor->project->groupNames.contains(newNameEdit->text())) dialog.accept(); + // TODO: Else display error? }); QFormLayout form(&dialog); @@ -1390,6 +1392,8 @@ 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) void MainWindow::mapListAddLayout() { if (!editor || !editor->project) return; @@ -1421,6 +1425,7 @@ void MainWindow::mapListAddLayout() { errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); QString errorMessage; + // TODO: Select default tilesets QComboBox *primaryCombo = new QComboBox(&dialog); primaryCombo->addItems(this->editor->project->primaryTilesetLabels); QComboBox *secondaryCombo = new QComboBox(&dialog); @@ -1529,14 +1534,19 @@ void MainWindow::mapListAddArea() { newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - if (!this->editor->project->mapSectionNameToValue.contains(newNameEdit->text())) + if (!this->editor->project->mapSectionNameToValue.contains(newNameDisplay->text())) dialog.accept(); + // TODO: Else display error? }); + QLabel *newNameEditLabel = new QLabel("New Map Section Name", &dialog); + QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); + newNameDisplayLabel->setEnabled(false); + QFormLayout form(&dialog); - form.addRow("New Map Section Name", newNameEdit); - form.addRow("Constant Name", newNameDisplay); + form.addRow(newNameEditLabel, newNameEdit); + form.addRow(newNameDisplayLabel, newNameDisplay); form.addRow(&newItemButtonBox); if (dialog.exec() == QDialog::Accepted) { @@ -1545,22 +1555,7 @@ void MainWindow::mapListAddArea() { } } -void MainWindow::mapListAddItem() { - if (!this->editor || !this->editor->project) return; - - switch (this->ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: - this->mapListAddGroup(); - break; - case MapListTab::Areas: - this->mapListAddArea(); - break; - case MapListTab::Layouts: - this->mapListAddLayout(); - break; - } -} - +// TODO: Connect to right-click on map group folder in list void MainWindow::mapListRemoveGroup() { QItemSelectionModel *selectionModel = this->ui->mapList->selectionModel(); if (selectionModel->hasSelection()) { @@ -1579,6 +1574,7 @@ void MainWindow::mapListRemoveGroup() { } } +// TODO: Decide what to do about this. Currently unused. void MainWindow::mapListRemoveArea() { QItemSelectionModel *selectionModel = this->ui->areaList->selectionModel(); if (selectionModel->hasSelection()) { @@ -1597,27 +1593,11 @@ void MainWindow::mapListRemoveArea() { } } +// TODO: Connect to right-click on layout void MainWindow::mapListRemoveLayout() { // TODO: consider this in the future } -void MainWindow::mapListRemoveItem() { - if (!this->editor || !this->editor->project) return; - - switch (this->ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: - this->mapListRemoveGroup(); - break; - case MapListTab::Areas: - // Disabled - // this->mapListRemoveArea(); - break; - case MapListTab::Layouts: - // Disabled - // this->mapListRemoveLayout(); - break; - } -} void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) { if (!triggeredAction) return; @@ -1661,7 +1641,7 @@ void MainWindow::onNewMapCreated() { this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); - setMap(newMapName, true); + setMap(newMapName); // Refresh any combo box that displays map names and persists between maps // (other combo boxes like for warp destinations are repopulated when the map changes). @@ -1877,51 +1857,42 @@ void MainWindow::currentMetatilesSelectionChanged() { scrollMetatileSelectorToSelection(); } +// TODO: Redundant. Remove void MainWindow::on_mapListContainer_currentChanged(int index) { switch (index) { case MapListTab::Groups: porymapConfig.mapSortOrder = MapSortOrder::SortByGroup; - if (this->editor && this->editor->map) scrollTreeView(this->editor->map->name); break; case MapListTab::Areas: porymapConfig.mapSortOrder = MapSortOrder::SortByArea; - if (this->editor && this->editor->map) scrollTreeView(this->editor->map->name); break; case MapListTab::Layouts: porymapConfig.mapSortOrder = MapSortOrder::SortByLayout; - if (this->editor && this->editor->layout) scrollTreeView(this->editor->layout->id); break; } } -void MainWindow::on_mapList_activated(const QModelIndex &index) { - QVariant data = index.data(Qt::UserRole); - if (index.data(MapListUserRoles::TypeRole) == "map_name" && !data.isNull()) { - QString mapName = data.toString(); - userSetMap(mapName); - } -} - -void MainWindow::on_areaList_activated(const QModelIndex &index) { - on_mapList_activated(index); -} - -void MainWindow::on_layoutList_activated(const QModelIndex &index) { - if (!index.isValid()) return; +void MainWindow::openMapListItem(const QModelIndex &index) { + if (!index.isValid()) + return; QVariant data = index.data(Qt::UserRole); - if (index.data(MapListUserRoles::TypeRole) == "map_layout" && !data.isNull()) { - QString layoutId = data.toString(); + if (data.isNull()) + return; - if (!setLayout(layoutId)) { - QMessageBox msgBox(this); - QString errorMsg = QString("There was an error opening layout %1. Please see %2 for full error details.\n\n%3") - .arg(layoutId) - .arg(getLogPath()) - .arg(getMostRecentError()); - msgBox.critical(nullptr, "Error Opening Layout", errorMsg); - } + // Normally when a new map/layout is opened the search filters are cleared and the lists will scroll to display that map/layout in the list. + // We don't want to do this when the user interacts with a list directly, so we temporarily prevent changes to the search filter. + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->setFilterLocked(true); + + QString type = index.data(MapListUserRoles::TypeRole).toString(); + if (type == "map_name") { + userSetMap(data.toString()); + } else if (type == "map_layout") { + userSetLayout(data.toString()); } + + if (toolbar) toolbar->setFilterLocked(false); } void MainWindow::updateMapList() { @@ -1930,8 +1901,7 @@ void MainWindow::updateMapList() { this->groupListProxyModel->layoutChanged(); this->mapAreaModel->setMap(this->editor->map->name); this->areaListProxyModel->layoutChanged(); - } - else { + } else { this->mapGroupModel->setMap(QString()); this->groupListProxyModel->layoutChanged(); this->ui->mapList->clearSelection(); @@ -1943,8 +1913,7 @@ void MainWindow::updateMapList() { if (this->editor->layout) { this->layoutTreeModel->setLayout(this->editor->layout->id); this->layoutListProxyModel->layoutChanged(); - } - else { + } else { this->layoutTreeModel->setLayout(QString()); this->layoutListProxyModel->layoutChanged(); this->ui->layoutList->clearSelection(); @@ -1953,14 +1922,12 @@ void MainWindow::updateMapList() { void MainWindow::on_action_Save_Project_triggered() { editor->saveProject(); - updateMapList(); - showWindowTitle(); + updateWindowTitle(); } void MainWindow::on_action_Save_triggered() { editor->save(); - updateMapList(); - showWindowTitle(); + updateWindowTitle(); } void MainWindow::duplicate() { @@ -2884,7 +2851,7 @@ void MainWindow::clickToolButtonFromEditAction(Editor::EditAction editAction) { void MainWindow::onOpenConnectedMap(MapConnection *connection) { if (!connection) return; - if (userSetMap(connection->targetMapName(), true)) + if (userSetMap(connection->targetMapName())) editor->setSelectedConnection(connection->findMirror()); } @@ -2904,6 +2871,7 @@ void MainWindow::onMapLoaded(Map *map) { connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(map); }); } +// TODO: editor->layout below? and redrawLayoutScene? void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { // If saved tilesets are currently in-use, update them and redraw // Otherwise overwrite the cache for the saved tileset @@ -3042,13 +3010,13 @@ void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() { void MainWindow::on_button_OpenDiveMap_clicked() { const QString mapName = ui->comboBox_DiveMap->currentText(); if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName, true); + userSetMap(mapName); } void MainWindow::on_button_OpenEmergeMap_clicked() { const QString mapName = ui->comboBox_EmergeMap->currentText(); if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName, true); + userSetMap(mapName); } void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { @@ -3211,124 +3179,36 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } -void MainWindow::do_ExpandAll() { +MapListToolBar* MainWindow::getCurrentMapListToolBar() { switch (ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: - this->on_toolButton_ExpandAll_Groups_clicked(); - break; - case MapListTab::Areas: - this->on_toolButton_ExpandAll_Areas_clicked(); - break; - case MapListTab::Layouts: - this->on_toolButton_ExpandAll_Layouts_clicked(); - break; + case MapListTab::Groups: return ui->mapListToolBar_Groups; + case MapListTab::Areas: return ui->mapListToolBar_Areas; + case MapListTab::Layouts: return ui->mapListToolBar_Layouts; + default: return nullptr; } } -void MainWindow::do_CollapseAll() { - switch (ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: - this->on_toolButton_CollapseAll_Groups_clicked(); - break; - case MapListTab::Areas: - this->on_toolButton_CollapseAll_Areas_clicked(); - break; - case MapListTab::Layouts: - this->on_toolButton_CollapseAll_Layouts_clicked(); - break; - } +// Clear the search filters on all the map lists. +// When the search filter is cleared the map lists will (if possible) display the currently-selected map/layout. +void MainWindow::resetMapListFilters() { + ui->mapListToolBar_Groups->clearFilter(); + ui->mapListToolBar_Areas->clearFilter(); + ui->mapListToolBar_Layouts->clearFilter(); } -// TODO: Save this state in porymapConfig -void MainWindow::do_HideShow() { - switch (ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: - this->on_toolButton_HideShow_Groups_clicked(); - break; - case MapListTab::Areas: - this->on_toolButton_HideShow_Areas_clicked(); - break; - case MapListTab::Layouts: - this->on_toolButton_HideShow_Layouts_clicked(); - break; - } +void MainWindow::mapListShortcut_ExpandAll() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->expandList(); } -void MainWindow::on_toolButton_HideShow_Groups_clicked() { - if (ui->mapList) { - this->groupListProxyModel->toggleHideEmpty(); - this->groupListProxyModel->setFilterRegularExpression(this->ui->lineEdit_filterBox->text()); - } +void MainWindow::mapListShortcut_CollapseAll() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->collapseList(); } -void MainWindow::on_toolButton_ExpandAll_Groups_clicked() { - if (ui->mapList) { - ui->mapList->expandToDepth(0); - } -} - -void MainWindow::on_toolButton_CollapseAll_Groups_clicked() { - if (ui->mapList) { - ui->mapList->collapseAll(); - } -} - -// TODO: Save this state in porymapConfig -void MainWindow::on_toolButton_EnableDisable_EditGroups_clicked() { - this->ui->mapList->clearSelection(); - if (this->ui->toolButton_EnableDisable_EditGroups->isChecked()) { - ui->mapList->setSelectionMode(QAbstractItemView::ExtendedSelection); - ui->mapList->setDragEnabled(true); - ui->mapList->setAcceptDrops(true); - ui->mapList->setDropIndicatorShown(true); - ui->mapList->setDragDropMode(QAbstractItemView::InternalMove); - ui->mapList->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); - } else { - ui->mapList->setSelectionMode(QAbstractItemView::NoSelection); - ui->mapList->setDragEnabled(false); - ui->mapList->setAcceptDrops(false); - ui->mapList->setDropIndicatorShown(false); - ui->mapList->setDragDropMode(QAbstractItemView::NoDragDrop); - ui->mapList->setEditTriggers(QAbstractItemView::NoEditTriggers); - } -} - -void MainWindow::on_toolButton_HideShow_Areas_clicked() { - if (ui->areaList) { - this->areaListProxyModel->toggleHideEmpty(); - this->areaListProxyModel->setFilterRegularExpression(this->ui->lineEdit_filterBox->text()); - } -} - -void MainWindow::on_toolButton_ExpandAll_Areas_clicked() { - if (ui->areaList) { - ui->areaList->expandToDepth(0); - } -} - -void MainWindow::on_toolButton_CollapseAll_Areas_clicked() { - if (ui->areaList) { - ui->areaList->collapseAll(); - } -} - -void MainWindow::on_toolButton_HideShow_Layouts_clicked() { - if (ui->layoutList) { - this->layoutListProxyModel->toggleHideEmpty(); - this->layoutListProxyModel->setFilterRegularExpression(this->ui->lineEdit_filterBox->text()); - } -} - -void MainWindow::on_toolButton_ExpandAll_Layouts_clicked() { - if (ui->layoutList) { - ui->layoutList->expandToDepth(0); - } -} - -void MainWindow::on_toolButton_CollapseAll_Layouts_clicked() { - if (ui->layoutList) { - ui->layoutList->collapseAll(); - } +void MainWindow::mapListShortcut_ToggleEmptyFolders() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->toggleEmptyFolders(); } void MainWindow::on_actionAbout_Porymap_triggered() @@ -3646,7 +3526,7 @@ bool MainWindow::closeProject() { editor->closeProject(); clearProjectUI(); setWindowDisabled(true); - setWindowTitle(QCoreApplication::applicationName()); + updateWindowTitle(); return true; } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 7e0d7e62..e9fbaa1f 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -44,7 +44,7 @@ void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOption -MapGroupModel::MapGroupModel(Project *project, QObject *parent) : QStandardItemModel(parent) { +MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(parent) { this->project = project; this->root = this->invisibleRootItem(); @@ -283,7 +283,7 @@ QStandardItem *MapGroupModel::getItem(const QModelIndex &index) const { return this->root; } -QModelIndex MapGroupModel::indexOfMap(QString mapName) { +QModelIndex MapGroupModel::indexOf(QString mapName) const { if (this->mapItems.contains(mapName)) { return this->mapItems[mapName]->index(); } @@ -366,7 +366,7 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int -MapAreaModel::MapAreaModel(Project *project, QObject *parent) : QStandardItemModel(parent) { +MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) { this->project = project; this->root = this->invisibleRootItem(); @@ -461,7 +461,7 @@ QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const { return this->root; } -QModelIndex MapAreaModel::indexOfMap(QString mapName) { +QModelIndex MapAreaModel::indexOf(QString mapName) const { if (this->mapItems.contains(mapName)) { return this->mapItems[mapName]->index(); } @@ -531,7 +531,7 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { -LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : QStandardItemModel(parent) { +LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(parent) { this->project = project; this->root = this->invisibleRootItem(); @@ -609,7 +609,7 @@ QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { return this->root; } -QModelIndex LayoutTreeModel::indexOfLayout(QString layoutName) { +QModelIndex LayoutTreeModel::indexOf(QString layoutName) const { if (this->layoutItems.contains(layoutName)) { return this->layoutItems[layoutName]->index(); } diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp new file mode 100644 index 00000000..4dd26ecd --- /dev/null +++ b/src/ui/maplisttoolbar.cpp @@ -0,0 +1,121 @@ +#include "maplisttoolbar.h" +#include "ui_maplisttoolbar.h" +#include "editor.h" + +#include + +MapListToolBar::MapListToolBar(QWidget *parent) + : QFrame(parent) + , ui(new Ui::MapListToolBar) +{ + ui->setupUi(this); + + connect(ui->button_ToggleEmptyFolders, &QAbstractButton::clicked, this, &MapListToolBar::toggleEmptyFolders); + connect(ui->button_AddFolder, &QAbstractButton::clicked, this, &MapListToolBar::addFolderClicked); // TODO: Tool tip + connect(ui->button_ExpandAll, &QAbstractButton::clicked, this, &MapListToolBar::expandList); + connect(ui->button_CollapseAll, &QAbstractButton::clicked, this, &MapListToolBar::collapseList); + connect(ui->button_ToggleEdit, &QAbstractButton::clicked, this, &MapListToolBar::toggleEditsAllowed); + connect(ui->lineEdit_filterBox, &QLineEdit::textChanged, this, &MapListToolBar::applyFilter); +} + +MapListToolBar::~MapListToolBar() +{ + delete ui; +} + +void MapListToolBar::setList(MapTree *list) { + m_list = list; + + // Sync list with current button states + setEditsAllowed(ui->button_ToggleEdit->isChecked()); + // TODO: Empty folders +} + +void MapListToolBar::setEditsAllowedButtonHidden(bool hidden) { + ui->button_ToggleEdit->setVisible(!hidden); +} + +void MapListToolBar::setEditsAllowed(bool allowed) { + if (!m_list) + return; + + if (allowed) { + m_list->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_list->setDragEnabled(true); + m_list->setAcceptDrops(true); + m_list->setDropIndicatorShown(true); + m_list->setDragDropMode(QAbstractItemView::InternalMove); + m_list->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + } else { + m_list->setSelectionMode(QAbstractItemView::NoSelection); + m_list->setDragEnabled(false); + m_list->setAcceptDrops(false); + m_list->setDropIndicatorShown(false); + m_list->setDragDropMode(QAbstractItemView::NoDragDrop); + m_list->setEditTriggers(QAbstractItemView::NoEditTriggers); + } +} + +// TODO: Sync the UI in each of these + +void MapListToolBar::toggleEmptyFolders() { + if (!m_list) + return; + + auto model = static_cast(m_list->model()); + if (!model) + return; + + bool hidden = model->toggleHideEmpty(); + model->setFilterRegularExpression(ui->lineEdit_filterBox->text()); + + // Update tool tip to reflect what will happen if the button is pressed. + const QString toolTip = QString("%1 empty folders in the list.").arg(hidden ? "Show" : "Hide"); + ui->button_ToggleEmptyFolders->setToolTip(toolTip); + + // Display message to let user know what just happened (if there are no empty folders visible it's not obvious). + const QString message = QString("%1 empty folders!").arg(hidden ? "Hiding" : "Showing"); + QToolTip::showText(ui->button_ToggleEmptyFolders->mapToGlobal(QPoint(0, 0)), message); +} + +void MapListToolBar::expandList() { + if (m_list) + m_list->expandToDepth(0); +} + +void MapListToolBar::collapseList() { + if (m_list) { + m_list->collapseAll(); + } +} + +// TODO: Save this state in porymapConfig? +// TODO: This isn't actually toggling anything, it's just updating based on the button +void MapListToolBar::toggleEditsAllowed() { + if (m_list) { + m_list->clearSelection(); + } + setEditsAllowed(ui->button_ToggleEdit->isChecked()); +} + +void MapListToolBar::applyFilter(const QString &filterText) { + if (!m_list || m_filterLocked) + return; + + const QSignalBlocker b(ui->lineEdit_filterBox); + ui->lineEdit_filterBox->setText(filterText); + + auto model = static_cast(m_list->model()); + if (model) model->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); + + if (filterText.isEmpty()) { + m_list->collapseAll(); + emit filterCleared(m_list); + } else { + m_list->expandToDepth(0); + } +} + +void MapListToolBar::clearFilter() { + applyFilter(""); +} From a18b2c960bf1a65f579c7ac66b462ef527320261 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 28 Oct 2024 15:43:13 -0400 Subject: [PATCH 062/364] Stop unnecessary work/leaks from extra setProjectUI calls --- src/mainwindow.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 14a255c2..8ea0d5df 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1634,25 +1634,32 @@ void MainWindow::onNewMapCreated() { editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); - setProjectUI(); // need to maybe repopulate layout combo - // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); - setMap(newMapName); - // Refresh any combo box that displays map names and persists between maps // (other combo boxes like for warp destinations are repopulated when the map changes). - int index = this->editor->project->mapNames.indexOf(newMapName); - if (index >= 0) { - const QSignalBlocker blocker1(ui->comboBox_DiveMap); - const QSignalBlocker blocker2(ui->comboBox_EmergeMap); - ui->comboBox_DiveMap->insertItem(index, newMapName); - ui->comboBox_EmergeMap->insertItem(index, newMapName); + int mapIndex = this->editor->project->mapNames.indexOf(newMapName); + if (mapIndex >= 0) { + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); + ui->comboBox_DiveMap->insertItem(mapIndex, newMapName); + ui->comboBox_EmergeMap->insertItem(mapIndex, newMapName); } + // Refresh layout combo box (if a new one was created) + if (!existingLayout) { + int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout->id); + if (layoutIndex >= 0) { + const QSignalBlocker b_Layouts(ui->comboBox_LayoutSelector); + ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout->id); + } + } + + setMap(newMapName); + if (newMap->needsHealLocation) { addNewEvent(Event::Type::HealLocation); editor->project->saveHealLocations(newMap); @@ -1787,8 +1794,6 @@ void MainWindow::on_actionNew_Tileset_triggered() { } insertTilesetLabel(&editor->project->tilesetLabelsOrdered, createTilesetDialog->fullSymbolName); - setProjectUI(); // need to reload tileset combos - QMessageBox msgBox(this); msgBox.setText("Successfully created tileset."); QString message = QString("Tileset \"%1\" was created successfully.").arg(createTilesetDialog->friendlyName); From 3bd5ddbf2f1c227356f8eed9fbac64ee499b8f8d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 28 Oct 2024 16:02:17 -0400 Subject: [PATCH 063/364] Simplify saving the map list tab --- include/config.h | 10 ++----- include/mainwindow.h | 5 +--- include/ui/newmappopup.h | 2 +- src/config.cpp | 18 ++--------- src/mainwindow.cpp | 65 ++++++++++++---------------------------- src/ui/newmappopup.cpp | 10 +++---- 6 files changed, 31 insertions(+), 79 deletions(-) diff --git a/include/config.h b/include/config.h index 01a7b09c..31f7fa7f 100644 --- a/include/config.h +++ b/include/config.h @@ -22,12 +22,6 @@ static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_ #define CONFIG_BACKWARDS_COMPATABILITY -enum MapSortOrder { - SortByGroup = 0, - SortByArea = 1, - SortByLayout = 2, -}; - class KeyValueConfigBase { public: @@ -56,7 +50,7 @@ public: this->recentProjects.clear(); this->projectManuallyClosed = false; this->reopenOnLaunch = true; - this->mapSortOrder = MapSortOrder::SortByGroup; + this->mapListTab = 0; this->prettyCursors = true; this->mirrorConnectingMaps = true; this->showDiveEmergeMaps = false; @@ -107,7 +101,7 @@ public: bool reopenOnLaunch; bool projectManuallyClosed; - MapSortOrder mapSortOrder; + int mapListTab; bool prettyCursors; bool mirrorConnectingMaps; bool showDiveEmergeMaps; diff --git a/include/mainwindow.h b/include/mainwindow.h index e0bf7177..2abc151d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -242,11 +242,7 @@ private slots: void on_toolButton_Move_clicked(); void on_toolButton_Shift_clicked(); - void on_mapListContainer_currentChanged(int index); void onOpenMapListContextMenu(const QPoint &point); - void onAddNewMapToGroupClick(QAction* triggeredAction); - void onAddNewMapToAreaClick(QAction* triggeredAction); - void onAddNewMapToLayoutClick(QAction* triggeredAction); void currentMetatilesSelectionChanged(); void on_action_Export_Map_Image_triggered(); @@ -393,6 +389,7 @@ private: void mapListRemoveArea(); void mapListRemoveLayout(); void openMapListItem(const QModelIndex &index); + void saveMapListTab(int index); void displayMapProperties(); void checkToolButtons(); diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h index 3d24715d..66a15a91 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmappopup.h @@ -24,7 +24,7 @@ public: QString layoutId; void init(); void initUi(); - void init(MapSortOrder type, QVariant data); + void init(int tabIndex, QVariant data); void init(Layout *); static void setDefaultSettings(Project *project); diff --git a/src/config.cpp b/src/config.cpp index 0d72542d..ca6473b1 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -278,12 +278,6 @@ uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_ return qMin(max, qMax(min, result)); } -const QMap mapSortOrderMap = { - {"group", MapSortOrder::SortByGroup}, - {"layout", MapSortOrder::SortByLayout}, - {"area", MapSortOrder::SortByArea}, -}; - PorymapConfig porymapConfig; QString PorymapConfig::getConfigFilepath() { @@ -308,14 +302,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->reopenOnLaunch = getConfigBool(key, value); } else if (key == "pretty_cursors") { this->prettyCursors = getConfigBool(key, value); - } else if (key == "map_sort_order") { - QString sortOrder = value.toLower(); - if (mapSortOrderMap.contains(sortOrder)) { - this->mapSortOrder = mapSortOrderMap.value(sortOrder); - } else { - this->mapSortOrder = MapSortOrder::SortByGroup; - logWarn(QString("Invalid config value for map_sort_order: '%1'. Must be 'group', 'area', or 'layout'.").arg(value)); - } + } else if (key == "map_list_tab") { + this->mapListTab = getConfigInteger(key, value, 0, 2, 0); } else if (key == "main_window_geometry") { this->mainWindowGeometry = bytesFromString(value); } else if (key == "main_window_state") { @@ -432,7 +420,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("project_manually_closed", this->projectManuallyClosed ? "1" : "0"); map.insert("reopen_on_launch", this->reopenOnLaunch ? "1" : "0"); map.insert("pretty_cursors", this->prettyCursors ? "1" : "0"); - map.insert("map_sort_order", mapSortOrderMap.key(this->mapSortOrder)); + map.insert("map_list_tab", QString::number(this->mapListTab)); map.insert("main_window_geometry", stringFromByteArray(this->mainWindowGeometry)); map.insert("main_window_state", stringFromByteArray(this->mainWindowState)); map.insert("map_splitter_state", stringFromByteArray(this->mapSplitterState)); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ea0d5df..db7cf6b1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -371,7 +371,7 @@ void MainWindow::initMiscHeapObjects() { } void MainWindow::initMapList() { - ui->mapListContainer->setCurrentIndex(static_cast(porymapConfig.mapSortOrder)); + ui->mapListContainer->setCurrentIndex(porymapConfig.mapListTab); WheelFilter *wheelFilter = new WheelFilter(this); ui->mainTabBar->installEventFilter(wheelFilter); @@ -430,6 +430,8 @@ void MainWindow::initMapList() { 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->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); } void MainWindow::updateWindowTitle() { @@ -1209,7 +1211,6 @@ bool MainWindow::setProjectUI() { ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); // map models - // !TODO: delete these on close this->mapGroupModel = new MapGroupModel(editor->project); this->groupListProxyModel = new FilterChildrenProxyModel(); groupListProxyModel->setSourceModel(this->mapGroupModel); @@ -1308,32 +1309,30 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { int dataRole; FilterChildrenProxyModel *proxy; QTreeView *list; - void (MainWindow::*addFunction)(QAction *); QString actionText; - switch (porymapConfig.mapSortOrder) { - case MapSortOrder::SortByGroup: + int currentTab = ui->mapListContainer->currentIndex(); + + switch (currentTab) { + case MapListTab::Groups: model = this->mapGroupModel; dataRole = MapListUserRoles::GroupRole; proxy = this->groupListProxyModel; list = this->ui->mapList; - addFunction = &MainWindow::onAddNewMapToGroupClick; actionText = "Add New Map to Group"; break; - case MapSortOrder::SortByArea: + case MapListTab::Areas: model = this->mapAreaModel; dataRole = Qt::UserRole; proxy = this->areaListProxyModel; list = this->ui->areaList; - addFunction = &MainWindow::onAddNewMapToAreaClick; actionText = "Add New Map to Area"; break; - case MapSortOrder::SortByLayout: + case MapListTab::Layouts: model = this->layoutTreeModel; dataRole = Qt::UserRole; proxy = this->layoutListProxyModel; list = this->ui->layoutList; - addFunction = &MainWindow::onAddNewMapToLayoutClick; actionText = "Add New Map with Layout"; break; } @@ -1358,7 +1357,14 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QMenu menu(this); QActionGroup actions(&menu); actions.addAction(menu.addAction(actionText))->setData(itemData); - (this->*addFunction)(menu.exec(QCursor::pos())); + + auto triggeredAction = menu.exec(QCursor::pos()); + if (!triggeredAction) + return; + + // At the moment all the actions do the same thing (add new map/layout). + openNewMapPopupWindow(); + this->newMapPrompt->init(currentTab, triggeredAction->data()); } void MainWindow::mapListAddGroup() { @@ -1598,28 +1604,6 @@ void MainWindow::mapListRemoveLayout() { // TODO: consider this in the future } - -void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) { - if (!triggeredAction) return; - - openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::SortByGroup, triggeredAction->data()); -} - -void MainWindow::onAddNewMapToAreaClick(QAction* triggeredAction) { - if (!triggeredAction) return; - - openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::SortByArea, triggeredAction->data()); -} - -void MainWindow::onAddNewMapToLayoutClick(QAction* triggeredAction) { - if (!triggeredAction) return; - - openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::SortByLayout, triggeredAction->data()); -} - void MainWindow::onNewMapCreated() { QString newMapName = this->newMapPrompt->map->name; int newMapGroup = this->newMapPrompt->group; @@ -1862,19 +1846,8 @@ void MainWindow::currentMetatilesSelectionChanged() { scrollMetatileSelectorToSelection(); } -// TODO: Redundant. Remove -void MainWindow::on_mapListContainer_currentChanged(int index) { - switch (index) { - case MapListTab::Groups: - porymapConfig.mapSortOrder = MapSortOrder::SortByGroup; - break; - case MapListTab::Areas: - porymapConfig.mapSortOrder = MapSortOrder::SortByArea; - break; - case MapListTab::Layouts: - porymapConfig.mapSortOrder = MapSortOrder::SortByLayout; - break; - } +void MainWindow::saveMapListTab(int index) { + porymapConfig.mapListTab = index; } void MainWindow::openMapListItem(const QModelIndex &index) { diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index 17c1ed29..a9d661da 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -102,17 +102,17 @@ void NewMapPopup::init() { } // Creating new map by right-clicking in the map list -void NewMapPopup::init(MapSortOrder type, QVariant data) { +void NewMapPopup::init(int tabIndex, QVariant data) { initUi(); - switch (type) + switch (tabIndex) { - case MapSortOrder::SortByGroup: + case MapListTab::Groups: settings.group = project->groupNames.at(data.toInt()); break; - case MapSortOrder::SortByArea: + case MapListTab::Areas: settings.location = data.toString(); break; - case MapSortOrder::SortByLayout: + case MapListTab::Layouts: this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked); useLayout(data.toString()); break; From 2ce5c3fcc59ce62512592d0533242844897a8dfe Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Oct 2024 15:56:02 -0400 Subject: [PATCH 064/364] Fix crash on tileset save, bugs with map resizing --- include/core/map.h | 1 - include/mainwindow.h | 3 --- src/core/editcommands.cpp | 1 + src/editor.cpp | 13 ++++++++----- src/mainwindow.cpp | 39 +++++++++++---------------------------- src/scriptapi/apimap.cpp | 12 ++++++------ 6 files changed, 26 insertions(+), 43 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 03a8931d..66aa0008 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -114,7 +114,6 @@ private: signals: void modified(); void mapDimensionsChanged(const QSize &size); - void mapNeedsRedrawing(); void openScriptRequested(QString label); void connectionAdded(MapConnection*); void connectionRemoved(MapConnection*); diff --git a/include/mainwindow.h b/include/mainwindow.h index 2abc151d..986036d6 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -185,8 +185,6 @@ private slots: void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); - void onMapNeedsRedrawing(); - void onLayoutNeedsRedrawing(); void onTilesetsSaved(QString, QString); void openNewMapPopupWindow(); void onNewMapCreated(); @@ -355,7 +353,6 @@ private: bool userSetLayout(QString layoutId); bool userSetMap(QString); void redrawMapScene(); - void redrawLayoutScene(); void refreshMapScene(); void setLayoutOnlyMode(bool layoutOnly); diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 224aad21..0843b2c0 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -486,6 +486,7 @@ int EventPaste::id() const { ************************************************************************ ******************************************************************************/ +// TODO: Undo/redo for script edits to layout dimensions doesn't render correctly. ScriptEditLayout::ScriptEditLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, diff --git a/src/editor.cpp b/src/editor.cpp index 68169432..ac0f1011 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1280,6 +1280,7 @@ bool Editor::setMap(QString map_name) { if (!displayMap()) { return false; } + displayWildMonTables(); connect(map, &Map::openScriptRequested, this, &Editor::openScript); connect(map, &Map::connectionAdded, this, &Editor::displayConnection); @@ -1551,10 +1552,11 @@ void Editor::clearMap() { } bool Editor::displayMap() { + if (!this->map) + return false; displayMapEvents(); displayMapConnections(); - displayWildMonTables(); maskNonVisibleConnectionTiles(); if (events_group) { @@ -1564,6 +1566,9 @@ bool Editor::displayMap() { } bool Editor::displayLayout() { + if (!this->layout) + return false; + if (!scene) { scene = new QGraphicsScene; MapSceneEventFilter *filter = new MapSceneEventFilter(scene); @@ -1826,10 +1831,8 @@ void Editor::clearMapConnections() { void Editor::displayMapConnections() { clearMapConnections(); - if (map) { - for (auto connection : map->getConnections()) - displayConnection(connection); - } + for (auto connection : map->getConnections()) + displayConnection(connection); if (!connection_items.isEmpty()) setSelectedConnectionItem(connection_items.first()); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index db7cf6b1..bd69d464 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -839,6 +839,7 @@ bool MainWindow::setMap(QString map_name) { return false; } + // TODO: Redundant? if (editor->map && !editor->map->name.isNull()) { ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOf(map_name)), false); } @@ -851,11 +852,10 @@ bool MainWindow::setMap(QString map_name) { updateWindowTitle(); resetMapListFilters(); - connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing, Qt::UniqueConnection); connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); connect(editor->layout, &Layout::layoutChanged, this, &MainWindow::onLayoutChanged, Qt::UniqueConnection); - connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); userConfig.recentMapOrLayout = map_name; @@ -911,7 +911,7 @@ bool MainWindow::setLayout(QString layoutId) { updateWindowTitle(); resetMapListFilters(); - connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::onLayoutNeedsRedrawing, Qt::UniqueConnection); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); updateTilesetEditor(); @@ -921,17 +921,9 @@ bool MainWindow::setLayout(QString layoutId) { } void MainWindow::redrawMapScene() { - if (!editor->displayMap()) - return; - - this->refreshMapScene(); -} - -void MainWindow::redrawLayoutScene() { - if (!editor->displayLayout()) - return; - - this->refreshMapScene(); + editor->displayMap(); + editor->displayLayout(); + refreshMapScene(); } void MainWindow::refreshMapScene() { @@ -2837,31 +2829,22 @@ void MainWindow::onLayoutChanged(Layout *) { updateMapList(); } -void MainWindow::onMapNeedsRedrawing() { - redrawMapScene(); -} - -void MainWindow::onLayoutNeedsRedrawing() { - redrawLayoutScene(); -} - void MainWindow::onMapLoaded(Map *map) { connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(map); }); } -// TODO: editor->layout below? and redrawLayoutScene? void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { // If saved tilesets are currently in-use, update them and redraw // Otherwise overwrite the cache for the saved tileset bool updated = false; - if (primaryTilesetLabel == this->editor->map->layout->tileset_primary_label) { + if (primaryTilesetLabel == this->editor->layout->tileset_primary_label) { this->editor->updatePrimaryTileset(primaryTilesetLabel, true); Scripting::cb_TilesetUpdated(primaryTilesetLabel); updated = true; } else { this->editor->project->getTileset(primaryTilesetLabel, true); } - if (secondaryTilesetLabel == this->editor->map->layout->tileset_secondary_label) { + if (secondaryTilesetLabel == this->editor->layout->tileset_secondary_label) { this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); Scripting::cb_TilesetUpdated(secondaryTilesetLabel); updated = true; @@ -3012,7 +2995,7 @@ void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &ti { if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updatePrimaryTileset(tilesetLabel); - redrawLayoutScene(); + redrawMapScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); @@ -3024,7 +3007,7 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & { if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updateSecondaryTileset(tilesetLabel); - redrawLayoutScene(); + redrawMapScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); @@ -3289,7 +3272,7 @@ void MainWindow::reloadScriptEngine() { // Lying to the scripts here, simulating a project reload Scripting::cb_ProjectOpened(projectConfig.projectDir); if (editor && editor->map) - Scripting::cb_MapOpened(editor->map->name); + Scripting::cb_MapOpened(editor->map->name); // TODO: API should have equivalent for layout } void MainWindow::on_pushButton_AddCustomHeaderField_clicked() diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 5df7fc58..ef260390 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -231,7 +231,7 @@ void MainWindow::setDimensions(int width, int height) { return; this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setWidth(int width) { @@ -241,7 +241,7 @@ void MainWindow::setWidth(int width) { return; this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setHeight(int height) { @@ -251,7 +251,7 @@ void MainWindow::setHeight(int height) { return; this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } //===================== @@ -301,7 +301,7 @@ void MainWindow::setBorderDimensions(int width, int height) { return; this->editor->layout->setBorderDimensions(width, height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setBorderWidth(int width) { @@ -311,7 +311,7 @@ void MainWindow::setBorderWidth(int width) { return; this->editor->layout->setBorderDimensions(width, this->editor->layout->getBorderHeight()); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setBorderHeight(int height) { @@ -321,7 +321,7 @@ void MainWindow::setBorderHeight(int height) { return; this->editor->layout->setBorderDimensions(this->editor->layout->getBorderWidth(), height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } //====================== From 785ac958a519706bbd373b0b98b0ad17b58e9a08 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Oct 2024 20:09:01 -0400 Subject: [PATCH 065/364] Fix crash when file watcher message triggers --- include/mainwindow.h | 1 + include/project.h | 5 +---- src/mainwindow.cpp | 47 ++++++++++++++++++++++++++++++++++++++------ src/project.cpp | 41 +------------------------------------- 4 files changed, 44 insertions(+), 50 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 986036d6..160c138a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -366,6 +366,7 @@ private: void scrollMapListToCurrentMap(MapTree *list); void scrollMapListToCurrentLayout(MapTree *list); void resetMapListFilters(); + void showFileWatcherWarning(QString filepath); QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); diff --git a/include/project.h b/include/project.h index 6782769b..07d6dcab 100644 --- a/include/project.h +++ b/include/project.h @@ -88,8 +88,6 @@ public: void set_root(QString); - void initSignals(); - void clearMapCache(); void clearTilesetCache(); void clearMapLayouts(); @@ -267,8 +265,7 @@ private: static int max_object_events; signals: - void reloadProject(); - void uncheckMonitorFilesAction(); + void fileChanged(QString filepath); void mapLoaded(Map *map); }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bd69d464..6e0f9185 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -602,13 +602,8 @@ bool MainWindow::openProject(QString dir, bool initial) { // Create the project auto project = new Project(editor); project->set_root(dir); - QObject::connect(project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered); + QObject::connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); QObject::connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - QObject::connect(project, &Project::uncheckMonitorFilesAction, [this]() { - porymapConfig.monitorFiles = false; - if (this->preferenceEditor) - this->preferenceEditor->updateFields(); - }); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -761,6 +756,46 @@ void MainWindow::openSubWindow(QWidget * window) { } } +void MainWindow::showFileWatcherWarning(QString filepath) { + if (!porymapConfig.monitorFiles || !isProjectOpen()) + return; + + Project *project = this->editor->project; + if (project->modifiedFileTimestamps.contains(filepath)) { + if (QDateTime::currentMSecsSinceEpoch() < project->modifiedFileTimestamps[filepath]) { + return; + } + project->modifiedFileTimestamps.remove(filepath); + } + + static bool showing = false; + if (showing) return; + + QMessageBox notice(this); + notice.setText("File Changed"); + notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?") + .arg(filepath.remove(project->root + "/"))); + notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes); + notice.setDefaultButton(QMessageBox::No); + notice.setIcon(QMessageBox::Question); + + QCheckBox showAgainCheck("Do not ask again."); + notice.setCheckBox(&showAgainCheck); + + showing = true; + int choice = notice.exec(); + if (choice == QMessageBox::Yes) { + on_action_Reload_Project_triggered(); + } else if (choice == QMessageBox::No) { + if (showAgainCheck.isChecked()) { + porymapConfig.monitorFiles = false; + if (this->preferenceEditor) + this->preferenceEditor->updateFields(); + } + } + showing = false; +} + QString MainWindow::getExistingDirectory(QString dir) { return FileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly); } diff --git a/src/project.cpp b/src/project.cpp index f3c02ecb..314503f4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -38,7 +38,7 @@ int Project::max_object_events = 64; Project::Project(QObject *parent) : QObject(parent) { - initSignals(); + QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::fileChanged); } Project::~Project() @@ -49,45 +49,6 @@ Project::~Project() clearEventGraphics(); } -void Project::initSignals() { - // detect changes to specific filepaths being monitored - QObject::connect(&fileWatcher, &QFileSystemWatcher::fileChanged, [this](QString changed){ - if (!porymapConfig.monitorFiles) return; - if (modifiedFileTimestamps.contains(changed)) { - if (QDateTime::currentMSecsSinceEpoch() < modifiedFileTimestamps[changed]) { - return; - } - modifiedFileTimestamps.remove(changed); - } - - static bool showing = false; - if (showing) return; - - QMessageBox notice(this->parentWidget()); - notice.setText("File Changed"); - notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?") - .arg(changed.remove(this->root + "/"))); - notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes); - notice.setDefaultButton(QMessageBox::No); - notice.setIcon(QMessageBox::Question); - - QCheckBox showAgainCheck("Do not ask again."); - notice.setCheckBox(&showAgainCheck); - - showing = true; - int choice = notice.exec(); - if (choice == QMessageBox::Yes) { - emit reloadProject(); - } else if (choice == QMessageBox::No) { - if (showAgainCheck.isChecked()) { - porymapConfig.monitorFiles = false; - emit uncheckMonitorFilesAction(); - } - } - showing = false; - }); -} - void Project::set_root(QString dir) { this->root = dir; FileDialog::setDirectory(dir); From b89c1ddc80f013ac9ee99f38b8add3d74b33d405 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Oct 2024 21:51:05 -0400 Subject: [PATCH 066/364] Show unsaved changes warning for map list and layout-only edits --- include/core/map.h | 2 +- include/core/maplayout.h | 2 +- include/project.h | 3 +++ src/core/map.cpp | 2 +- src/core/maplayout.cpp | 2 +- src/mainwindow.cpp | 16 ++++------------ src/project.cpp | 25 +++++++++++++++++++++++-- src/ui/maplistmodels.cpp | 1 + 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 66aa0008..acc52d90 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -101,7 +101,7 @@ public: QUndoStack editHistory; void modify(); void clean(); - bool hasUnsavedChanges(); + bool hasUnsavedChanges() const; void pruneEditHistory(); private: diff --git a/include/core/maplayout.h b/include/core/maplayout.h index cdd3b5d6..b617002f 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -104,7 +104,7 @@ public: void clearBorderCache(); void cacheBorder(); - bool hasUnsavedChanges(); + bool hasUnsavedChanges() const; bool layoutBlockChanged(int i, const Blockdata &cache); diff --git a/include/project.h b/include/project.h index 07d6dcab..39c492bb 100644 --- a/include/project.h +++ b/include/project.h @@ -144,6 +144,9 @@ public: int appendMapsec(QString name); + bool hasUnsavedChanges(); + bool hasUnsavedDataChanges = false; + QSet getTopLevelMapFields(); bool loadMapData(Map*); bool readMapLayouts(); diff --git a/src/core/map.cpp b/src/core/map.cpp index 8067b9a7..2a7d96dc 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -256,7 +256,7 @@ void Map::clean() { this->hasUnsavedDataChanges = false; } -bool Map::hasUnsavedChanges() { +bool Map::hasUnsavedChanges() const { return !editHistory.isClean() || this->layout->hasUnsavedChanges() || hasUnsavedDataChanges || !isPersistedToFile; } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 9e283e26..34033ac5 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -418,6 +418,6 @@ QPixmap Layout::getLayoutItemPixmap() { return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); } -bool Layout::hasUnsavedChanges() { +bool Layout::hasUnsavedChanges() const { return !this->editHistory.isClean(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e0f9185..9bac433a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1427,6 +1427,8 @@ 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) void MainWindow::mapListAddLayout() { if (!editor || !editor->project) return; @@ -1458,7 +1460,6 @@ void MainWindow::mapListAddLayout() { errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); QString errorMessage; - // TODO: Select default tilesets QComboBox *primaryCombo = new QComboBox(&dialog); primaryCombo->addItems(this->editor->project->primaryTilesetLabels); QComboBox *secondaryCombo = new QComboBox(&dialog); @@ -1642,6 +1643,7 @@ void MainWindow::onNewMapCreated() { logInfo(QString("Created a new map named %1.").arg(newMapName)); + // TODO: Creating a new map shouldn't be automatically saved editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); @@ -3496,17 +3498,7 @@ bool MainWindow::closeProject() { if (!isProjectOpen()) return true; - // Check loaded maps for unsaved changes - // TODO: This needs to check for unsaved changes in layouts too. - bool unsavedChanges = false; - for (auto map : editor->project->mapCache.values()) { - if (map && map->hasUnsavedChanges()) { - unsavedChanges = true; - break; - } - } - - if (unsavedChanges) { + if (this->editor->project->hasUnsavedChanges()) { QMessageBox::StandardButton result = QMessageBox::question( this, "porymap", "The project has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); diff --git a/src/project.cpp b/src/project.cpp index 314503f4..e7a9beeb 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -484,7 +484,7 @@ bool Project::loadMapLayout(Map* map) { return false; } - if (map->hasUnsavedChanges() || map->layout->hasUnsavedChanges()) { + if (map->hasUnsavedChanges()) { return true; } else { return loadLayout(map->layout); @@ -1463,6 +1463,7 @@ void Project::saveAllDataStructures() { saveMapConstantsHeader(); saveWildMonData(); saveConfig(); + this->hasUnsavedDataChanges = false; } void Project::saveConfig() { @@ -2293,7 +2294,6 @@ QString Project::getEmptyMapsecName() { // This function assumes a valid and unique name. // Will return the new index. -// TODO: We're not currently tracking map/layout agonstic changes like this as unsaved, so there's no warning if you close the project after doing this. int Project::appendMapsec(QString name) { const QString emptyMapsecName = getEmptyMapsecName(); int newMapsecValue = mapSectionValueToName.isEmpty() ? 0 : mapSectionValueToName.lastKey(); @@ -2308,6 +2308,7 @@ int Project::appendMapsec(QString name) { this->mapSectionNameToValue[name] = newMapsecValue; this->mapSectionValueToName[newMapsecValue] = name; + this->hasUnsavedDataChanges = true; return newMapsecValue; } @@ -2993,3 +2994,23 @@ void Project::applyParsedLimits() { projectConfig.collisionSheetHeight = qMin(projectConfig.collisionSheetHeight, Block::getMaxElevation() + 1); projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1); } + +bool Project::hasUnsavedChanges() { + if (this->hasUnsavedDataChanges) + return true; + + // Check layouts for unsaved changes + for (auto i = this->mapLayouts.constBegin(); i != this->mapLayouts.constEnd(); i++) { + auto map = i.value(); + if (map && map->hasUnsavedChanges()) + return true; + } + + // Check loaded maps for unsaved changes + for (auto i = this->mapCache.constBegin(); i != this->mapCache.constEnd(); i++) { + auto layout = i.value(); + if (layout && layout->hasUnsavedChanges()) + return true; + } + return false; +} diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index e9fbaa1f..46297892 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -214,6 +214,7 @@ void MapGroupModel::updateProject() { this->project->mapGroups = mapGroups; this->project->groupedMapNames = groupedMapNames; this->project->mapNames = mapNames; + this->project->hasUnsavedDataChanges = true; } QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *group) { From 23e094d850ba875fee0de4a8b2a70e1c846aae4f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Oct 2024 22:18:16 -0400 Subject: [PATCH 067/364] Update map list delete functionality --- include/mainwindow.h | 4 +- include/ui/maplistmodels.h | 18 ++-- include/ui/newmappopup.h | 2 +- src/mainwindow.cpp | 163 ++++++++++++------------------------- src/project.cpp | 8 +- src/ui/maplistmodels.cpp | 56 +++++++++++-- src/ui/newmappopup.cpp | 8 +- 7 files changed, 127 insertions(+), 132 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 160c138a..ea1c62de 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -383,9 +383,6 @@ private: void mapListAddGroup(); void mapListAddLayout(); void mapListAddArea(); - void mapListRemoveGroup(); - void mapListRemoveArea(); - void mapListRemoveLayout(); void openMapListItem(const QModelIndex &index); void saveMapListTab(int index); @@ -424,6 +421,7 @@ private: void redrawMetatileSelection(); void scrollMetatileSelectorToSelection(); MapListToolBar* getCurrentMapListToolBar(); + MapTree* getCurrentMapList(); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 8d00c492..fbcd9710 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -26,8 +26,12 @@ public: MapTree(QWidget *parent) : QTreeView(parent) { this->setDropIndicatorShown(true); this->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + this->setFocusPolicy(Qt::StrongFocus); } +protected: + virtual void keyPressEvent(QKeyEvent *event) override; + public slots: void removeSelected(); }; @@ -61,6 +65,9 @@ public: ~MapListModel() { } virtual QModelIndex indexOf(QString id) const = 0; + virtual void removeFolder(int index) = 0; + virtual void removeItem(const QModelIndex &index); + virtual QStandardItem *getItem(const QModelIndex &index) const = 0; }; class MapGroupModel : public MapListModel { @@ -87,9 +94,9 @@ public: QStandardItem *insertGroupItem(QString groupName); QStandardItem *insertMapItem(QString mapName, QString groupName); - void removeGroup(int groupIndex); + virtual void removeFolder(int index) override; - QStandardItem *getItem(const QModelIndex &index) const; + virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString mapName) const override; void initialize(); @@ -130,9 +137,9 @@ public: QStandardItem *insertAreaItem(QString areaName); QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); - void removeArea(int groupIndex); + virtual void removeFolder(int index) override; - QStandardItem *getItem(const QModelIndex &index) const; + virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString mapName) const override; void initialize(); @@ -170,8 +177,9 @@ public: QStandardItem *insertLayoutItem(QString layoutId); QStandardItem *insertMapItem(QString mapName, QString layoutId); + virtual void removeFolder(int index) override; - QStandardItem *getItem(const QModelIndex &index) const; + virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString layoutName) const override; void initialize(); diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h index 66a15a91..f160876a 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmappopup.h @@ -24,7 +24,7 @@ public: QString layoutId; void init(); void initUi(); - void init(int tabIndex, QVariant data); + void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9bac433a..df62341a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -153,10 +153,11 @@ void MainWindow::initExtraShortcuts() { shortcutDuplicate_Events->setObjectName("shortcutDuplicate_Events"); shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)"); - auto *shortcutDelete_Object = new Shortcut( + // TODO: Reimplement this using keyPressEvent on the relevant widgets. Otherwise it steals the key event from anything else trying to use delete. + /*auto *shortcutDelete_Object = new Shortcut( {QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(onDeleteKeyPressed())); shortcutDelete_Object->setObjectName("shortcutDelete_Object"); - shortcutDelete_Object->setWhatsThis("Delete Selected Item(s)"); + shortcutDelete_Object->setWhatsThis("Delete Selected Item(s)");*/ auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle())); shortcutToggle_Border->setObjectName("shortcutToggle_Border"); @@ -874,11 +875,6 @@ bool MainWindow::setMap(QString map_name) { return false; } - // TODO: Redundant? - if (editor->map && !editor->map->name.isNull()) { - ui->mapList->setExpanded(groupListProxyModel->mapFromSource(mapGroupModel->indexOf(map_name)), false); - } - setLayoutOnlyMode(false); this->lastSelectedEvent.clear(); @@ -1301,11 +1297,7 @@ void MainWindow::scrollMapList(MapTree *list, QString itemName) { if (!list || itemName.isEmpty()) return; auto model = static_cast(list->model()); - if (!model) - return; auto sourceModel = static_cast(model->sourceModel()); - if (!sourceModel) - return; QModelIndex sourceIndex = sourceModel->indexOf(itemName); if (!sourceIndex.isValid()) return; @@ -1332,66 +1324,53 @@ void MainWindow::scrollMapListToCurrentLayout(MapTree *list) { } void MainWindow::onOpenMapListContextMenu(const QPoint &point) { - QStandardItemModel *model; - int dataRole; - FilterChildrenProxyModel *proxy; - QTreeView *list; - QString actionText; - - int currentTab = ui->mapListContainer->currentIndex(); - - switch (currentTab) { - case MapListTab::Groups: - model = this->mapGroupModel; - dataRole = MapListUserRoles::GroupRole; - proxy = this->groupListProxyModel; - list = this->ui->mapList; - actionText = "Add New Map to Group"; - break; - case MapListTab::Areas: - model = this->mapAreaModel; - dataRole = Qt::UserRole; - proxy = this->areaListProxyModel; - list = this->ui->areaList; - actionText = "Add New Map to Area"; - break; - case MapListTab::Layouts: - model = this->layoutTreeModel; - dataRole = Qt::UserRole; - proxy = this->layoutListProxyModel; - list = this->ui->layoutList; - actionText = "Add New Map with Layout"; - break; - } - - QModelIndex index = proxy->mapToSource(list->indexAt(point)); - if (!index.isValid()) { - return; - } - - QStandardItem *selectedItem = model->itemFromIndex(index); - - if (selectedItem->parent()) { - // TODO: Right-click delete on maps? - return; - } - - QVariant itemData = selectedItem->data(dataRole); - if (!itemData.isValid()) { - return; - } + // Get selected item from list + auto list = getCurrentMapList(); + if (!list) return; + auto model = static_cast(list->model()); + QModelIndex index = model->mapToSource(list->indexAt(point)); + if (!index.isValid()) return; + auto sourceModel = static_cast(model->sourceModel()); + QStandardItem *selectedItem = sourceModel->itemFromIndex(index); + const QString itemType = selectedItem->data(MapListUserRoles::TypeRole).toString(); + const QString itemName = selectedItem->data(Qt::UserRole).toString(); QMenu menu(this); - QActionGroup actions(&menu); - actions.addAction(menu.addAction(actionText))->setData(itemData); + QAction* addToFolderAction = nullptr; + QAction* deleteFolderAction = nullptr; + if (itemType == "map_name") { + // Right-clicking on a map. + // TODO: Add action to delete map once deleting maps is supported + } else if (itemType == "map_group") { + // Right-clicking on a map group folder + addToFolderAction = menu.addAction("Add New Map to Group"); + deleteFolderAction = menu.addAction("Delete Map Group"); + } else if (itemType == "map_section") { + // Right-clicking on an MAPSEC folder + addToFolderAction = menu.addAction("Add New Map to Area"); + } else if (itemType == "map_layout") { + // Right-clicking on a map layout + addToFolderAction = menu.addAction("Add New Map with Layout"); + } - auto triggeredAction = menu.exec(QCursor::pos()); - if (!triggeredAction) - return; + if (addToFolderAction) { + connect(addToFolderAction, &QAction::triggered, [this, itemName] { + openNewMapPopupWindow(); + this->newMapPrompt->init(ui->mapListContainer->currentIndex(), itemName); + }); + } + if (deleteFolderAction) { + connect(deleteFolderAction, &QAction::triggered, [sourceModel, index] { + sourceModel->removeFolder(index.row()); + }); + if (selectedItem->hasChildren()){ + // TODO: No support for deleting maps, so you may only delete folders if they don't contain any maps. + deleteFolderAction->setEnabled(false); + } + } - // At the moment all the actions do the same thing (add new map/layout). - openNewMapPopupWindow(); - this->newMapPrompt->init(currentTab, triggeredAction->data()); + if (menu.actions().length() != 0) + menu.exec(QCursor::pos()); } void MainWindow::mapListAddGroup() { @@ -1589,49 +1568,6 @@ void MainWindow::mapListAddArea() { } } -// TODO: Connect to right-click on map group folder in list -void MainWindow::mapListRemoveGroup() { - QItemSelectionModel *selectionModel = this->ui->mapList->selectionModel(); - if (selectionModel->hasSelection()) { - QModelIndexList selectedIndexes = selectionModel->selectedRows(); - for (QModelIndex proxyIndex : selectedIndexes) { - QModelIndex index = this->groupListProxyModel->mapToSource(proxyIndex); - QStandardItem *item = this->mapGroupModel->getItem(index)->child(index.row(), index.column()); - if (!item) continue; - QString type = item->data(MapListUserRoles::TypeRole).toString(); - if (type == "map_group" && !item->hasChildren()) { - QString groupName = item->data(Qt::UserRole).toString(); - // delete empty group - this->mapGroupModel->removeGroup(index.row()); - } - } - } -} - -// TODO: Decide what to do about this. Currently unused. -void MainWindow::mapListRemoveArea() { - QItemSelectionModel *selectionModel = this->ui->areaList->selectionModel(); - if (selectionModel->hasSelection()) { - QModelIndexList selectedIndexes = selectionModel->selectedRows(); - for (QModelIndex proxyIndex : selectedIndexes) { - QModelIndex index = this->areaListProxyModel->mapToSource(proxyIndex); - QStandardItem *item = this->mapAreaModel->getItem(index)->child(index.row(), index.column()); - if (!item) continue; - QString type = item->data(MapListUserRoles::TypeRole).toString(); - if (type == "map_section" && !item->hasChildren()) { - QString groupName = item->data(Qt::UserRole).toString(); - // delete empty section - this->mapAreaModel->removeArea(index.row()); - } - } - } -} - -// TODO: Connect to right-click on layout -void MainWindow::mapListRemoveLayout() { - // TODO: consider this in the future -} - void MainWindow::onNewMapCreated() { QString newMapName = this->newMapPrompt->map->name; int newMapGroup = this->newMapPrompt->group; @@ -3186,6 +3122,13 @@ MapListToolBar* MainWindow::getCurrentMapListToolBar() { } } +MapTree* MainWindow::getCurrentMapList() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) + return toolbar->list(); + return nullptr; +} + // Clear the search filters on all the map lists. // When the search filter is cleared the map lists will (if possible) display the currently-selected map/layout. void MainWindow::resetMapListFilters() { diff --git a/src/project.cpp b/src/project.cpp index e7a9beeb..657bd89c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3001,15 +3001,15 @@ bool Project::hasUnsavedChanges() { // Check layouts for unsaved changes for (auto i = this->mapLayouts.constBegin(); i != this->mapLayouts.constEnd(); i++) { - auto map = i.value(); - if (map && map->hasUnsavedChanges()) + auto layout = i.value(); + if (layout && layout->hasUnsavedChanges()) return true; } // Check loaded maps for unsaved changes for (auto i = this->mapCache.constBegin(); i != this->mapCache.constEnd(); i++) { - auto layout = i.value(); - if (layout && layout->hasUnsavedChanges()) + auto map = i.value(); + if (map && map->hasUnsavedChanges()) return true; } return false; diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 46297892..af8149d8 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -15,6 +15,45 @@ void MapTree::removeSelected() { } } +void MapTree::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + // Delete selected items in the tree + auto selectionModel = this->selectionModel(); + if (!selectionModel->hasSelection()) + return; + + auto model = static_cast(this->model()); + auto sourceModel = static_cast(model->sourceModel()); + + QModelIndexList selectedIndexes = selectionModel->selectedRows(); + QList persistentIndexes; + for (const auto &index : selectedIndexes) { + persistentIndexes.append(model->mapToSource(index)); + } + for (const auto &index : persistentIndexes) { + sourceModel->removeItem(index); + } + } else { + QWidget::keyPressEvent(event); + } +} + +void MapListModel::removeItem(const QModelIndex &index) { + QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); + if (!item) + return; + + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + if (type == "map_name") { + // TODO: No support for deleting maps + } else { + // TODO: Because there's no support for deleting maps we can only delete empty folders + if (!item->hasChildren()) { + this->removeFolder(index.row()); + } + } +} + QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { @@ -244,8 +283,8 @@ QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { return group; } -void MapGroupModel::removeGroup(int groupIndex) { - this->removeRow(groupIndex); +void MapGroupModel::removeFolder(int index) { + this->removeRow(index); this->updateProject(); } @@ -365,6 +404,8 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int } } +// TODO: Deleting MAPSEC support? Currently it has no limits on drag/drop etc, so editing is disabled (so delete key from the map list is ignored) +// and it has no delete action in the context menu. MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) { @@ -422,9 +463,9 @@ QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, in return map; } -void MapAreaModel::removeArea(int areaIndex) { - this->removeRow(areaIndex); - this->project->mapSectionNameToValue.remove(this->project->mapSectionValueToName.take(areaIndex)); +void MapAreaModel::removeFolder(int index) { + this->removeRow(index); + this->project->mapSectionNameToValue.remove(this->project->mapSectionValueToName.take(index)); } void MapAreaModel::initialize() { @@ -583,6 +624,11 @@ QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) return map; } +void LayoutTreeModel::removeFolder(int) { + // TODO: Deleting layouts not supported +} + + void LayoutTreeModel::initialize() { this->layoutItems.clear(); this->mapItems.clear(); diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index a9d661da..a9e50c93 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -102,19 +102,19 @@ void NewMapPopup::init() { } // Creating new map by right-clicking in the map list -void NewMapPopup::init(int tabIndex, QVariant data) { +void NewMapPopup::init(int tabIndex, QString fieldName) { initUi(); switch (tabIndex) { case MapListTab::Groups: - settings.group = project->groupNames.at(data.toInt()); + settings.group = fieldName; break; case MapListTab::Areas: - settings.location = data.toString(); + settings.location = fieldName; break; case MapListTab::Layouts: this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked); - useLayout(data.toString()); + useLayout(fieldName); break; } init(); From ab8eb7c7e4cb0370a20adf1258bf71509dc2d4df Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 31 Oct 2024 14:55:32 -0400 Subject: [PATCH 068/364] Reimplement disabled Delete key features --- include/editor.h | 2 +- include/mainwindow.h | 3 - include/ui/connectionpixmapitem.h | 12 ++-- include/ui/connectionslistitem.h | 5 +- include/ui/graphicsview.h | 9 +-- include/ui/mapview.h | 3 +- src/editor.cpp | 102 +++++++++++++++++++++--------- src/mainwindow.cpp | 69 +------------------- src/ui/connectionpixmapitem.cpp | 25 +++++++- src/ui/connectionslistitem.cpp | 14 ++++ src/ui/graphicsview.cpp | 8 +++ src/ui/maplistmodels.cpp | 2 - 12 files changed, 137 insertions(+), 117 deletions(-) diff --git a/include/editor.h b/include/editor.h index ad2ae672..f5e94d28 100644 --- a/include/editor.h +++ b/include/editor.h @@ -96,7 +96,6 @@ public: void renderDivingConnections(); void addConnection(MapConnection* connection); void removeConnection(MapConnection* connection); - void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); void configureEncounterJSON(QWidget *); @@ -187,6 +186,7 @@ public: bool selectingEvent = false; + void deleteSelectedEvents(); void shouldReselectEvents(); void scaleMapView(int); static void openInTextEditor(const QString &path, int lineNum = 0); diff --git a/include/mainwindow.h b/include/mainwindow.h index ea1c62de..d77eaadc 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -224,9 +224,6 @@ private slots: void on_actionMove_triggered(); void on_actionMap_Shift_triggered(); - void onDeleteKeyPressed(); - void on_toolButton_deleteObject_clicked(); - void addNewEvent(Event::Type type); void tryAddEventTab(QWidget * tab); void displayEventTabs(); diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 62eda6fe..183f2d79 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -5,6 +5,7 @@ #include #include #include +#include class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT @@ -36,14 +37,17 @@ private: static const int mHeight = 16; protected: - QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; - void mousePressEvent(QGraphicsSceneMouseEvent*) override; - void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual void keyPressEvent(QKeyEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; signals: void connectionItemDoubleClicked(MapConnection*); void selectionChanged(bool selected); + void deleteRequested(MapConnection*); }; #endif // CONNECTIONPIXMAPITEM_H diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index bbe0f2d3..7ba6a9d8 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -34,11 +34,12 @@ private: unsigned actionId = 0; protected: - void mousePressEvent(QMouseEvent*) override; + virtual void mousePressEvent(QMouseEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; + virtual void keyPressEvent(QKeyEvent*) override; signals: void selected(); - void removed(MapConnection*); void openMapClicked(MapConnection*); private slots: diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index c0d1592c..92771cf7 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -34,6 +34,7 @@ signals: class Editor; +// TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView. class GraphicsView : public QGraphicsView { public: @@ -44,10 +45,10 @@ public: // GraphicsView_Object object; Editor *editor; protected: - void mousePressEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void moveEvent(QMoveEvent *event); + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void moveEvent(QMoveEvent *event) override; }; //Q_DECLARE_METATYPE(GraphicsView) diff --git a/include/ui/mapview.h b/include/ui/mapview.h index 9176c825..7355da9d 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -73,7 +73,8 @@ public: private: QMap overlayMap; protected: - void drawForeground(QPainter *painter, const QRectF &rect); + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + virtual void keyPressEvent(QKeyEvent*) override; }; #endif // GRAPHICSVIEW_H diff --git a/src/editor.cpp b/src/editor.cpp index ac0f1011..86cedd5b 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -811,6 +811,9 @@ void Editor::displayConnection(MapConnection *connection) { connect(listItem, &ConnectionsListItem::openMapClicked, this, &Editor::openConnectedMap); connect(pixmapItem, &ConnectionPixmapItem::connectionItemDoubleClicked, this, &Editor::openConnectedMap); + // Pressing the delete key on a selected connection's pixmap deletes it + connect(pixmapItem, &ConnectionPixmapItem::deleteRequested, this, &Editor::removeConnection); + // Sync the selection highlight between the list UI and the pixmap connect(pixmapItem, &ConnectionPixmapItem::selectionChanged, [=](bool selected) { listItem->setSelected(selected); @@ -869,11 +872,6 @@ void Editor::removeConnection(MapConnection *connection) { this->map->editHistory.push(new MapConnectionRemove(this->map, connection)); } -void Editor::removeSelectedConnection() { - if (selected_connection_item) - removeConnection(selected_connection_item->connection); -} - void Editor::removeConnectionPixmap(MapConnection *connection) { if (!connection) return; @@ -1257,42 +1255,42 @@ void Editor::unsetMap() { } bool Editor::setMap(QString map_name) { - if (map_name.isEmpty()) { + if (!project || map_name.isEmpty()) { return false; } unsetMap(); - if (project) { - Map *loadedMap = project->loadMap(map_name); - if (!loadedMap) { - return false; - } - - this->map = loadedMap; - - setLayout(map->layout->id); - - editGroup.addStack(&map->editHistory); - editGroup.setActiveStack(&map->editHistory); - - selected_events->clear(); - if (!displayMap()) { - return false; - } - displayWildMonTables(); - - connect(map, &Map::openScriptRequested, this, &Editor::openScript); - connect(map, &Map::connectionAdded, this, &Editor::displayConnection); - connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap); - updateSelectedEvents(); + Map *loadedMap = project->loadMap(map_name); + if (!loadedMap) { + return false; } + this->map = loadedMap; + + setLayout(map->layout->id); + + editGroup.addStack(&map->editHistory); + editGroup.setActiveStack(&map->editHistory); + + selected_events->clear(); + if (!displayMap()) { + return false; + } + displayWildMonTables(); + + connect(map, &Map::openScriptRequested, this, &Editor::openScript); + connect(map, &Map::connectionAdded, this, &Editor::displayConnection); + connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap); + updateSelectedEvents(); + return true; } bool Editor::setLayout(QString layoutId) { - if (layoutId.isEmpty()) return false; + if (!project || layoutId.isEmpty()) { + return false; + } this->layout = this->project->loadLayout(layoutId); @@ -2224,6 +2222,50 @@ bool Editor::eventLimitReached(Event::Type event_type) { return false; } +void Editor::deleteSelectedEvents() { + if (!this->selected_events || this->selected_events->length() == 0 || !this->map || this->editMode != EditMode::Events) + return; + + DraggablePixmapItem *nextSelectedEvent = nullptr; + QList selectedEvents; + int numDeleted = 0; + for (DraggablePixmapItem *item : *this->selected_events) { + Event::Group event_group = item->event->getEventGroup(); + if (event_group != Event::Group::Heal) { + numDeleted++; + item->event->setPixmapItem(item); + selectedEvents.append(item->event); + } + else { // don't allow deletion of heal locations + logWarn(QString("Cannot delete event of type '%1'").arg(Event::eventTypeToString(item->event->getEventType()))); + } + } + if (numDeleted) { + // Get the index for the event that should be selected after this event has been deleted. + // Select event at next smallest index when deleting a single event. + // If deleting multiple events, just let editor work out next selected. + if (numDeleted == 1) { + Event::Group event_group = selectedEvents[0]->getEventGroup(); + int index = this->map->events.value(event_group).indexOf(selectedEvents[0]); + if (index != this->map->events.value(event_group).size() - 1) + index++; + else + index--; + Event *event = nullptr; + if (index >= 0) + event = this->map->events.value(event_group).at(index); + for (QGraphicsItem *child : this->events_group->childItems()) { + DraggablePixmapItem *event_item = static_cast(child); + if (event_item->event == event) { + nextSelectedEvent = event_item; + break; + } + } + } + this->map->editHistory.push(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); + } +} + void Editor::openMapScripts() const { openInTextEditor(map->getScriptsFilePath()); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index df62341a..8a1b7859 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -153,12 +153,6 @@ void MainWindow::initExtraShortcuts() { shortcutDuplicate_Events->setObjectName("shortcutDuplicate_Events"); shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)"); - // TODO: Reimplement this using keyPressEvent on the relevant widgets. Otherwise it steals the key event from anything else trying to use delete. - /*auto *shortcutDelete_Object = new Shortcut( - {QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(onDeleteKeyPressed())); - shortcutDelete_Object->setObjectName("shortcutDelete_Object"); - shortcutDelete_Object->setWhatsThis("Delete Selected Item(s)");*/ - auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle())); shortcutToggle_Border->setObjectName("shortcutToggle_Border"); shortcutToggle_Border->setWhatsThis("Toggle Border"); @@ -320,6 +314,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); + connect(ui->toolButton_deleteObject, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); this->loadUserSettings(); @@ -857,13 +852,7 @@ bool MainWindow::userSetMap(QString map_name) { } bool MainWindow::setMap(QString map_name) { - // if map name is empty, clear & disable map ui - if (map_name.isEmpty()) { - unsetMap(); - return false; - } - - if (map_name == DYNAMIC_MAP_NAME) { + if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { logInfo(QString("Cannot set map to '%1'").arg(DYNAMIC_MAP_NAME)); return false; } @@ -2585,60 +2574,6 @@ void MainWindow::on_horizontalSlider_CollisionTransparency_valueChanged(int valu this->editor->collision_item->draw(true); } -void MainWindow::onDeleteKeyPressed() { - auto tab = ui->mainTabBar->currentIndex(); - if (tab == MainTab::Events) { - on_toolButton_deleteObject_clicked(); - } else if (tab == MainTab::Connections) { - if (editor) editor->removeSelectedConnection(); - } -} - -void MainWindow::on_toolButton_deleteObject_clicked() { - if (editor && editor->selected_events) { - if (editor->selected_events->length()) { - DraggablePixmapItem *nextSelectedEvent = nullptr; - QList selectedEvents; - int numDeleted = 0; - for (DraggablePixmapItem *item : *editor->selected_events) { - Event::Group event_group = item->event->getEventGroup(); - if (event_group != Event::Group::Heal) { - numDeleted++; - item->event->setPixmapItem(item); - selectedEvents.append(item->event); - } - else { // don't allow deletion of heal locations - logWarn(QString("Cannot delete event of type '%1'").arg(Event::eventTypeToString(item->event->getEventType()))); - } - } - if (numDeleted) { - // Get the index for the event that should be selected after this event has been deleted. - // Select event at next smallest index when deleting a single event. - // If deleting multiple events, just let editor work out next selected. - if (numDeleted == 1) { - Event::Group event_group = selectedEvents[0]->getEventGroup(); - int index = editor->map->events.value(event_group).indexOf(selectedEvents[0]); - if (index != editor->map->events.value(event_group).size() - 1) - index++; - else - index--; - Event *event = nullptr; - if (index >= 0) - event = editor->map->events.value(event_group).at(index); - for (QGraphicsItem *child : editor->events_group->childItems()) { - DraggablePixmapItem *event_item = static_cast(child); - if (event_item->event == event) { - nextSelectedEvent = event_item; - break; - } - } - } - editor->map->editHistory.push(new EventDelete(editor, editor->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); - } - } - } -} - void MainWindow::on_toolButton_Paint_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index f8412012..35e07a15 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -9,6 +9,7 @@ ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection, int x, int connection(connection) { this->setEditable(true); + setFlag(ItemIsFocusable, true); this->basePixmap = pixmap(); this->setOrigin(x, y); } @@ -110,17 +111,20 @@ bool ConnectionPixmapItem::getEditable() { } void ConnectionPixmapItem::setSelected(bool selected) { + if (selected && !hasFocus()) { + setFocus(Qt::OtherFocusReason); + } + if (this->selected == selected) return; this->selected = selected; + this->render(); emit selectionChanged(selected); } void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { - if (!this->getEditable()) - return; - this->setSelected(true); + setFocus(Qt::MouseFocusReason); } void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { @@ -131,3 +135,18 @@ void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { emit connectionItemDoubleClicked(this->connection); } + +void ConnectionPixmapItem::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + emit deleteRequested(this->connection); + } else { + QGraphicsPixmapItem::keyPressEvent(event); + } +} + +void ConnectionPixmapItem::focusInEvent(QFocusEvent* event) { + if (!this->getEditable()) + return; + this->setSelected(true); + QGraphicsPixmapItem::focusInEvent(event); +} diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index a5b8759a..ccdf7e6c 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -10,6 +10,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui(new Ui::ConnectionsListItem) { ui->setupUi(this); + setFocusPolicy(Qt::StrongFocus); const QSignalBlocker blocker1(ui->comboBox_Direction); const QSignalBlocker blocker2(ui->comboBox_Map); @@ -101,3 +102,16 @@ void ConnectionsListItem::on_button_Delete_clicked() { void ConnectionsListItem::on_button_OpenMap_clicked() { emit openMapClicked(this->connection); } + +void ConnectionsListItem::focusInEvent(QFocusEvent* event) { + this->setSelected(true); + QFrame::focusInEvent(event); +} + +void ConnectionsListItem::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + on_button_Delete_clicked(); + } else { + QFrame::keyPressEvent(event); + } +} diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index fa04c0f7..a9761139 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -24,6 +24,14 @@ void GraphicsView::moveEvent(QMoveEvent *event) { label_MapRulerStatus->move(mapToGlobal(QPoint(6, 6))); } +void MapView::keyPressEvent(QKeyEvent *event) { + if (editor && (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)) { + editor->deleteSelectedEvents(); + } else { + QGraphicsView::keyPressEvent(event); + } +} + void MapView::drawForeground(QPainter *painter, const QRectF&) { for (auto i = this->overlayMap.constBegin(); i != this->overlayMap.constEnd(); i++) { i.value()->renderItems(painter); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index af8149d8..b375beb1 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -404,8 +404,6 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int } } -// TODO: Deleting MAPSEC support? Currently it has no limits on drag/drop etc, so editing is disabled (so delete key from the map list is ignored) -// and it has no delete action in the context menu. MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) { From f90dae0da00e6621094d960a83a39b23a41ccf13 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 31 Oct 2024 16:36:16 -0400 Subject: [PATCH 069/364] Set up map list tool bar to record setting states --- include/ui/filterchildrenproxymodel.h | 2 +- include/ui/maplisttoolbar.h | 11 ++-- src/mainwindow.cpp | 4 +- src/ui/maplisttoolbar.cpp | 74 +++++++++++++++++---------- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/include/ui/filterchildrenproxymodel.h b/include/ui/filterchildrenproxymodel.h index d9eed7af..507693b3 100644 --- a/include/ui/filterchildrenproxymodel.h +++ b/include/ui/filterchildrenproxymodel.h @@ -9,7 +9,7 @@ class FilterChildrenProxyModel : public QSortFilterProxyModel public: explicit FilterChildrenProxyModel(QObject *parent = nullptr); - bool toggleHideEmpty() { return this->hideEmpty = !this->hideEmpty; } + void setHideEmpty(bool hidden) { this->hideEmpty = hidden; } protected: bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; private: diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h index c66b0d80..bf749a3e 100644 --- a/include/ui/maplisttoolbar.h +++ b/include/ui/maplisttoolbar.h @@ -22,12 +22,15 @@ public: MapTree* list() const { return m_list; } void setList(MapTree *list); - void setEditsAllowedButtonHidden(bool hidden); + void setEditsAllowedButtonVisible(bool visible); + void setEditsAllowed(bool allowed); + void toggleEditsAllowed(); + void setEmptyFoldersVisible(bool visible); void toggleEmptyFolders(); + void expandList(); void collapseList(); - void toggleEditsAllowed(); void applyFilter(const QString &filterText); void clearFilter(); @@ -42,8 +45,8 @@ private: Ui::MapListToolBar *ui; QPointer m_list; bool m_filterLocked = false; - - void setEditsAllowed(bool allowed); + bool m_editsAllowed = false; + bool m_emptyFoldersVisible = true; }; #endif // MAPLISTTOOLBAR_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8a1b7859..77391c3e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -414,8 +414,8 @@ void MainWindow::initMapList() { connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); // Only the groups list allows reorganizing folder contents, editing folder names, etc. - ui->mapListToolBar_Areas->setEditsAllowedButtonHidden(true); - ui->mapListToolBar_Layouts->setEditsAllowedButtonHidden(true); + ui->mapListToolBar_Areas->setEditsAllowedButtonVisible(false); + ui->mapListToolBar_Layouts->setEditsAllowedButtonVisible(false); // When map list search filter is cleared we want the current map/layout in the editor to be visible in the list. connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 4dd26ecd..2468c849 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -4,18 +4,32 @@ #include +/* + TODO: The button states for each tool bar (just the two toggleable buttons, hide empty folders and allow editing) + should be saved in the config. This will be cleaner/easier once the config is JSON, so holding off on that for now. +*/ + MapListToolBar::MapListToolBar(QWidget *parent) : QFrame(parent) , ui(new Ui::MapListToolBar) { ui->setupUi(this); - connect(ui->button_ToggleEmptyFolders, &QAbstractButton::clicked, this, &MapListToolBar::toggleEmptyFolders); + ui->button_ToggleEmptyFolders->setChecked(!m_emptyFoldersVisible); + ui->button_ToggleEdit->setChecked(m_editsAllowed); + connect(ui->button_AddFolder, &QAbstractButton::clicked, this, &MapListToolBar::addFolderClicked); // TODO: Tool tip connect(ui->button_ExpandAll, &QAbstractButton::clicked, this, &MapListToolBar::expandList); connect(ui->button_CollapseAll, &QAbstractButton::clicked, this, &MapListToolBar::collapseList); connect(ui->button_ToggleEdit, &QAbstractButton::clicked, this, &MapListToolBar::toggleEditsAllowed); connect(ui->lineEdit_filterBox, &QLineEdit::textChanged, this, &MapListToolBar::applyFilter); + connect(ui->button_ToggleEmptyFolders, &QAbstractButton::clicked, [this] { + toggleEmptyFolders(); + + // Display message to let user know what just happened (if there are no empty folders visible it's not obvious). + const QString message = QString("%1 empty folders!").arg(m_emptyFoldersVisible ? "Showing" : "Hiding"); + QToolTip::showText(ui->button_ToggleEmptyFolders->mapToGlobal(QPoint(0, 0)), message); + }); } MapListToolBar::~MapListToolBar() @@ -26,16 +40,28 @@ MapListToolBar::~MapListToolBar() void MapListToolBar::setList(MapTree *list) { m_list = list; - // Sync list with current button states - setEditsAllowed(ui->button_ToggleEdit->isChecked()); - // TODO: Empty folders + // Sync list with current settings + setEditsAllowed(m_editsAllowed); + setEmptyFoldersVisible(m_emptyFoldersVisible); } -void MapListToolBar::setEditsAllowedButtonHidden(bool hidden) { - ui->button_ToggleEdit->setVisible(!hidden); +void MapListToolBar::setEditsAllowedButtonVisible(bool visible) { + ui->button_ToggleEdit->setVisible(visible); +} + +void MapListToolBar::toggleEditsAllowed() { + if (m_list) { + m_list->clearSelection(); + } + setEditsAllowed(!m_editsAllowed); } void MapListToolBar::setEditsAllowed(bool allowed) { + m_editsAllowed = allowed; + + const QSignalBlocker b(ui->button_ToggleEdit); + ui->button_ToggleEdit->setChecked(allowed); + if (!m_list) return; @@ -56,26 +82,27 @@ void MapListToolBar::setEditsAllowed(bool allowed) { } } -// TODO: Sync the UI in each of these - void MapListToolBar::toggleEmptyFolders() { - if (!m_list) - return; + setEmptyFoldersVisible(!m_emptyFoldersVisible); +} - auto model = static_cast(m_list->model()); - if (!model) - return; +void MapListToolBar::setEmptyFoldersVisible(bool visible) { + m_emptyFoldersVisible = visible; - bool hidden = model->toggleHideEmpty(); - model->setFilterRegularExpression(ui->lineEdit_filterBox->text()); + if (m_list) { + auto model = static_cast(m_list->model()); + if (model) { + model->setHideEmpty(!visible); + model->setFilterRegularExpression(ui->lineEdit_filterBox->text()); + } + } // Update tool tip to reflect what will happen if the button is pressed. - const QString toolTip = QString("%1 empty folders in the list.").arg(hidden ? "Show" : "Hide"); + const QString toolTip = QString("%1 empty folders in the list.").arg(visible ? "Hide" : "Show"); ui->button_ToggleEmptyFolders->setToolTip(toolTip); - // Display message to let user know what just happened (if there are no empty folders visible it's not obvious). - const QString message = QString("%1 empty folders!").arg(hidden ? "Hiding" : "Showing"); - QToolTip::showText(ui->button_ToggleEmptyFolders->mapToGlobal(QPoint(0, 0)), message); + const QSignalBlocker b(ui->button_ToggleEmptyFolders); + ui->button_ToggleEmptyFolders->setChecked(!visible); } void MapListToolBar::expandList() { @@ -89,15 +116,6 @@ void MapListToolBar::collapseList() { } } -// TODO: Save this state in porymapConfig? -// TODO: This isn't actually toggling anything, it's just updating based on the button -void MapListToolBar::toggleEditsAllowed() { - if (m_list) { - m_list->clearSelection(); - } - setEditsAllowed(ui->button_ToggleEdit->isChecked()); -} - void MapListToolBar::applyFilter(const QString &filterText) { if (!m_list || m_filterLocked) return; From 7d890312733547605e450a604b8e27b8e258d032 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 4 Nov 2024 20:55:31 -0500 Subject: [PATCH 070/364] Resolve warnings and low-hanging TODO items --- forms/maplisttoolbar.ui | 7 ++++-- include/mainwindow.h | 2 -- include/ui/maplistmodels.h | 1 - src/mainwindow.cpp | 50 +++++++++++++++++++++++++------------- src/ui/maplistmodels.cpp | 7 +++--- src/ui/maplisttoolbar.cpp | 18 ++++++++------ 6 files changed, 52 insertions(+), 33 deletions(-) diff --git a/forms/maplisttoolbar.ui b/forms/maplisttoolbar.ui index 6f753ea8..54eb48d0 100644 --- a/forms/maplisttoolbar.ui +++ b/forms/maplisttoolbar.ui @@ -31,8 +31,11 @@
+ + Add a new folder to the list. + - Add a folder to the list. + @@ -110,7 +113,7 @@ - Toggle editability of folders in the list. + If enabled, folders may be renamed and items in the list may be rearranged. diff --git a/include/mainwindow.h b/include/mainwindow.h index d77eaadc..8e999b24 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -369,8 +369,6 @@ private: bool closeProject(); void showProjectOpenFailure(); - QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); - bool setInitialMap(); void saveGlobalConfigs(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index fbcd9710..20eb24a9 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -150,7 +150,6 @@ private: QMap areaItems; QMap mapItems; - // TODO: if reordering, will the item be the same? QString openMap; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 77391c3e..0ace9fb9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -465,7 +465,6 @@ void MainWindow::updateWindowTitle() { } else { ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(QStringLiteral(":/icons/map.ico"))); } - updateMapList(); // TODO: Why is this function responsible for this } void MainWindow::markMapEdited() { @@ -479,6 +478,7 @@ void MainWindow::markSpecificMapEdited(Map* map) { if (editor && editor->map == map) updateWindowTitle(); + updateMapList(); } void MainWindow::loadUserSettings() { @@ -870,6 +870,7 @@ bool MainWindow::setMap(QString map_name) { refreshMapScene(); displayMapProperties(); updateWindowTitle(); + updateMapList(); resetMapListFilters(); connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); @@ -918,8 +919,9 @@ bool MainWindow::setLayout(QString layoutId) { unsetMap(); - // TODO: Using the 'id' instead of the layout name here is inconsistent with how we treat maps. - logInfo(QString("Setting layout to '%1'").arg(layoutId)); + // Prefer logging the name of the layout as displayed in the map list. + const QString layoutName = this->editor->project ? this->editor->project->layoutIdsToNames.value(layoutId, layoutId) : layoutId; + logInfo(QString("Setting layout to '%1'").arg(layoutName)); if (!this->editor->setLayout(layoutId)) { return false; @@ -929,6 +931,7 @@ bool MainWindow::setLayout(QString layoutId) { refreshMapScene(); updateWindowTitle(); + updateMapList(); resetMapListFilters(); connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); @@ -1241,8 +1244,6 @@ bool MainWindow::setProjectUI() { this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); - //on_toolButton_EnableDisable_EditGroups_clicked();//TODO - return true; } @@ -1366,7 +1367,6 @@ void MainWindow::mapListAddGroup() { QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::ApplicationModal); QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - connect(&newItemButtonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); QLineEdit *newNameEdit = new QLineEdit(&dialog); @@ -1375,15 +1375,24 @@ void MainWindow::mapListAddGroup() { static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); + QLabel *errorMessageLabel = new QLabel(&dialog); + errorMessageLabel->setVisible(false); + errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - if (!this->editor->project->groupNames.contains(newNameEdit->text())) + 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)); + errorMessageLabel->setVisible(true); + } else { dialog.accept(); - // TODO: Else display error? + } }); QFormLayout form(&dialog); form.addRow("New Group Name", newNameEdit); + form.addRow("", errorMessageLabel); form.addRow(&newItemButtonBox); if (dialog.exec() == QDialog::Accepted) { @@ -1426,7 +1435,6 @@ void MainWindow::mapListAddLayout() { QLabel *errorMessageLabel = new QLabel(&dialog); errorMessageLabel->setVisible(false); errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - QString errorMessage; QComboBox *primaryCombo = new QComboBox(&dialog); primaryCombo->addItems(this->editor->project->primaryTilesetLabels); @@ -1463,28 +1471,25 @@ void MainWindow::mapListAddLayout() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ // verify some things - bool issue = false; + QString errorMessage; QString tryLayoutName = newNameEdit->text(); // name not empty if (tryLayoutName.isEmpty()) { errorMessage = "Name cannot be empty"; - issue = true; } // unique layout name & id else if (this->editor->project->mapLayoutsTable.contains(newId->text()) || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { errorMessage = "Layout Name / ID is not unique"; - issue = true; } // from id is existing value else if (useExistingCheck->isChecked()) { if (!this->editor->project->mapLayoutsTable.contains(useExistingCombo->currentText())) { errorMessage = "Existing layout ID is not valid"; - issue = true; } } - if (issue) { + if (!errorMessage.isEmpty()) { // show error errorMessageLabel->setText(errorMessage); errorMessageLabel->setVisible(true); @@ -1532,16 +1537,24 @@ void MainWindow::mapListAddArea() { newNameDisplay->setText(prefix + text); }); + QLabel *errorMessageLabel = new QLabel(&dialog); + errorMessageLabel->setVisible(false); + errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); + static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - if (!this->editor->project->mapSectionNameToValue.contains(newNameDisplay->text())) + const QString newAreaName = newNameDisplay->text(); + if (this->editor->project->mapSectionNameToValue.contains(newAreaName)){ + errorMessageLabel->setText(QString("An area with the name '%1' already exists").arg(newAreaName)); + errorMessageLabel->setVisible(true); + } else { dialog.accept(); - // TODO: Else display error? + } }); - QLabel *newNameEditLabel = new QLabel("New Map Section Name", &dialog); + QLabel *newNameEditLabel = new QLabel("New Area Name", &dialog); QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); newNameDisplayLabel->setEnabled(false); @@ -1549,6 +1562,7 @@ void MainWindow::mapListAddArea() { form.addRow(newNameEditLabel, newNameEdit); form.addRow(newNameDisplayLabel, newNameDisplay); + form.addRow("", errorMessageLabel); form.addRow(&newItemButtonBox); if (dialog.exec() == QDialog::Accepted) { @@ -1855,11 +1869,13 @@ void MainWindow::updateMapList() { void MainWindow::on_action_Save_Project_triggered() { editor->saveProject(); updateWindowTitle(); + updateMapList(); } void MainWindow::on_action_Save_triggered() { editor->save(); updateWindowTitle(); + updateMapList(); } void MainWindow::duplicate() { diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index b375beb1..e4ebd82c 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -131,7 +131,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { return mimeData; } -bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parentIndex) { +bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex &parentIndex) { if (action == Qt::IgnoreAction) return true; @@ -154,7 +154,6 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group"); QDataStream stream(&encodedData, QIODevice::ReadOnly); QString groupName; - int rowCount = 1; while (!stream.atEnd()) { stream >> groupName; @@ -402,6 +401,7 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int if (QStandardItemModel::setData(index, value, role)) { this->updateProject(); } + return true; } @@ -425,7 +425,7 @@ QStandardItem *MapAreaModel::createAreaItem(QString mapsecName, int areaIndex) { return area; } -QStandardItem *MapAreaModel::createMapItem(QString mapName, int groupIndex, int mapIndex) { +QStandardItem *MapAreaModel::createMapItem(QString mapName, int, int) { QStandardItem *map = new QStandardItem; map->setText(mapName); map->setEditable(false); @@ -603,6 +603,7 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { QStandardItem *layoutItem = this->createLayoutItem(layoutId); this->root->appendRow(layoutItem); + return layoutItem; } QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 2468c849..d03aa838 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -117,20 +117,22 @@ void MapListToolBar::collapseList() { } void MapListToolBar::applyFilter(const QString &filterText) { - if (!m_list || m_filterLocked) + if (m_filterLocked) return; const QSignalBlocker b(ui->lineEdit_filterBox); ui->lineEdit_filterBox->setText(filterText); - auto model = static_cast(m_list->model()); - if (model) model->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); + if (m_list) { + auto model = static_cast(m_list->model()); + if (model) model->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); - if (filterText.isEmpty()) { - m_list->collapseAll(); - emit filterCleared(m_list); - } else { - m_list->expandToDepth(0); + if (filterText.isEmpty()) { + m_list->collapseAll(); + emit filterCleared(m_list); + } else { + m_list->expandToDepth(0); + } } } From d448765d631940ba094f15293cac53dc97b4ca2e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 4 Nov 2024 21:49:49 -0500 Subject: [PATCH 071/364] Read/write MAPSEC values using the region map json --- docsrc/manual/project-files.rst | 2 - include/config.h | 2 - include/core/regionmap.h | 10 +- include/core/regionmapeditcommands.h | 4 +- include/core/wildmoninfo.h | 2 +- include/project.h | 13 +-- include/ui/maplistmodels.h | 2 +- include/ui/regionmapeditor.h | 3 +- src/config.cpp | 2 - src/core/regionmap.cpp | 37 +----- src/core/regionmapeditcommands.cpp | 2 +- src/mainwindow.cpp | 4 +- src/project.cpp | 169 +++++++++++++++------------ src/scriptapi/apimap.cpp | 2 +- src/scriptapi/apiutility.cpp | 2 +- src/ui/maplistmodels.cpp | 34 ++---- src/ui/newmappopup.cpp | 4 +- src/ui/regionmapeditor.cpp | 90 ++------------ 18 files changed, 143 insertions(+), 241 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 30f0acc8..e4f0479b 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -59,7 +59,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files include/constants/event_object_movement.h, yes, no, ``constants_obj_event_movement``, include/constants/event_objects.h, yes, no, ``constants_obj_events``, include/constants/event_bg.h, yes, no, ``constants_event_bg``, - include/constants/region_map_sections.h, yes, no, ``constants_region_map_sections``, include/constants/metatile_labels.h, yes, yes, ``constants_metatile_labels``, include/constants/metatile_behaviors.h, yes, no, ``constants_metatile_behaviors``, include/constants/species.h, yes, no, ``constants_metatile_behaviors``, for the Wild Pokémon tab @@ -122,7 +121,6 @@ In addition to these files, there are some specific symbol and macro names that ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps ``define_map_section_prefix``, ``MAPSEC_``, expected prefix for location macro names ``define_map_section_empty``, ``NONE``, macro name after prefix for empty region map sections - ``define_map_section_count``, ``COUNT``, macro name after prefix for total number of region map sections ``define_species_prefix``, ``SPECIES_``, expected prefix for species macro names ``regex_behaviors``, ``\bMB_``, regex to find metatile behavior macro names ``regex_obj_event_gfx``, ``\bOBJ_EVENT_GFX_``, regex to find Object Event graphics ID macro names diff --git a/include/config.h b/include/config.h index 31f7fa7f..6b64b612 100644 --- a/include/config.h +++ b/include/config.h @@ -213,7 +213,6 @@ enum ProjectIdentifier { define_map_empty, define_map_section_prefix, define_map_section_empty, - define_map_section_count, define_species_prefix, regex_behaviors, regex_obj_event_gfx, @@ -269,7 +268,6 @@ enum ProjectFilePath { constants_obj_event_movement, constants_obj_events, constants_event_bg, - constants_region_map_sections, constants_metatile_labels, constants_metatile_behaviors, constants_species, diff --git a/include/core/regionmap.h b/include/core/regionmap.h index bc05a84f..822df79b 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -57,8 +57,8 @@ public: bool loadLayout(poryjson::Json); bool loadEntries(); - void setEntries(tsl::ordered_map *entries) { this->region_map_entries = entries; } - void setEntries(tsl::ordered_map entries) { *(this->region_map_entries) = entries; } + void setEntries(QMap *entries) { this->region_map_entries = entries; } + void setEntries(const QMap &entries) { *(this->region_map_entries) = entries; } void clearEntries() { this->region_map_entries->clear(); } MapSectionEntry getEntry(QString section); void setEntry(QString section, MapSectionEntry entry); @@ -114,8 +114,6 @@ public: void setLayer(QString layer) { this->current_layer = layer; } QString getLayer() { return this->current_layer; } - QString fixCase(QString); - int padLeft() { return this->offset_left; } int padTop() { return this->offset_top; } int padRight() { return this->tilemap_width - this->layout_width - this->offset_left; } @@ -149,14 +147,12 @@ public: const QString section_prefix; const QString default_map_section; - const QString count_map_section; signals: void mapNeedsDisplaying(); private: - // TODO: defaults needed? - tsl::ordered_map *region_map_entries = nullptr; + QMap *region_map_entries = nullptr; QString alias = ""; diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index e142c5cc..05b12bc3 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -153,7 +153,7 @@ private: /// ClearEntries class ClearEntries : public QUndoCommand { public: - ClearEntries(RegionMap *map, tsl::ordered_map, QUndoCommand *parent = nullptr); + ClearEntries(RegionMap *map, QMap, QUndoCommand *parent = nullptr); void undo() override; void redo() override; @@ -163,7 +163,7 @@ public: private: RegionMap *map; - tsl::ordered_map entries; + QMap entries; }; #endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index 6c93d0d2..d0aa29dc 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -8,7 +8,7 @@ struct WildPokemon { int minLevel = 5; int maxLevel = 5; - QString species = "SPECIES_NONE"; + QString species = "SPECIES_NONE"; // TODO: Use define_species_prefix }; struct WildMonInfo { diff --git a/include/project.h b/include/project.h index 39c492bb..c99bd05b 100644 --- a/include/project.h +++ b/include/project.h @@ -49,9 +49,6 @@ public: QMap layoutIdsToNames; QMap mapLayouts; QMap mapLayoutsMaster; - QMap mapSecToMapHoverName; - QMap mapSectionNameToValue; - QMap mapSectionValueToName; QMap eventGraphicsMap; QMap gfxDefines; QString defaultSong; @@ -68,6 +65,8 @@ public: QStringList bgEventFacingDirections; QStringList trainerTypes; QStringList globalScriptLabels; + QStringList mapSectionIdNames; + QMap regionMapEntries; QMap> metatileLabelsMap; QMap unusedMetatileLabels; QMap metatileBehaviorMap; @@ -82,9 +81,7 @@ public: int pokemonMaxLevel; int maxEncounterRate; bool wildEncountersLoaded; - - // For files that are read and could contain extra text - QMap extraFileText; + bool saveEmptyMapsec; void set_root(QString); @@ -142,7 +139,7 @@ public: bool readSpeciesIconPaths(); QMap speciesToIconPath; - int appendMapsec(QString name); + void addNewMapsec(QString name); bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; @@ -172,7 +169,7 @@ public: void saveConfig(); void saveMapLayouts(); void saveMapGroups(); - void saveMapSections(); + void saveRegionMapSections(); void saveWildMonData(); void saveMapConstantsHeader(); void saveHealLocations(Map*); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 20eb24a9..3e6e95d1 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -132,7 +132,7 @@ public: public: void setMap(QString mapName) { this->openMap = mapName; } - QStandardItem *createAreaItem(QString areaName, int areaIndex); + QStandardItem *createAreaItem(QString areaName); QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); QStandardItem *insertAreaItem(QString areaName); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 3d889f88..c1941651 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -57,7 +57,6 @@ private: tsl::ordered_map region_maps; QString configFilepath; - QString mapSectionFilepath; poryjson::Json rmConfigJson; @@ -96,7 +95,7 @@ private: void saveConfig(); bool loadRegionMapEntries(); bool saveRegionMapEntries(); - tsl::ordered_map region_map_entries; + QMap region_map_entries; bool buildConfigDialog(); poryjson::Json configRegionMapDialog(); diff --git a/src/config.cpp b/src/config.cpp index ca6473b1..3d604da6 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -110,7 +110,6 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, {ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}}, {ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}}, - {ProjectIdentifier::define_map_section_count, {"define_map_section_count", "COUNT"}}, {ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}}, // Regex {ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}}, @@ -167,7 +166,6 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::constants_obj_event_movement, { "constants_obj_event_movement", "include/constants/event_object_movement.h"}}, {ProjectFilePath::constants_obj_events, { "constants_obj_events", "include/constants/event_objects.h"}}, {ProjectFilePath::constants_event_bg, { "constants_event_bg", "include/constants/event_bg.h"}}, - {ProjectFilePath::constants_region_map_sections, { "constants_region_map_sections", "include/constants/region_map_sections.h"}}, {ProjectFilePath::constants_metatile_labels, { "constants_metatile_labels", "include/constants/metatile_labels.h"}}, {ProjectFilePath::constants_metatile_behaviors, { "constants_metatile_behaviors", "include/constants/metatile_behaviors.h"}}, {ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}}, diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index 7dbd4c5f..94650ccd 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -19,8 +19,7 @@ using std::make_shared; RegionMap::RegionMap(Project *project) : section_prefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)), - default_map_section(section_prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty)), - count_map_section(section_prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_count)) + default_map_section(project->getEmptyMapsecName()) { this->project = project; } @@ -157,7 +156,7 @@ bool RegionMap::loadLayout(poryjson::Json layoutJson) { for (int x = 0; x < this->layout_width; x++) { int bin_index = x + y * this->layout_width; uint8_t square_section_id = mapBinData.at(bin_index); - QString square_section_name = project->mapSectionValueToName.value(square_section_id); + QString square_section_name = project->mapSectionIdNames.value(square_section_id, this->default_map_section); LayoutSquare square; square.map_section = square_section_name; @@ -401,7 +400,7 @@ void RegionMap::saveLayout() { for (int m = 0; m < this->layout_height; m++) { for (int n = 0; n < this->layout_width; n++) { int i = n + this->layout_width * m; - data.append(this->project->mapSectionNameToValue.value(this->layouts["main"][i].map_section)); + data.append(this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section)); } } QFile bfile(fullPath(this->layout_path)); @@ -760,18 +759,15 @@ bool RegionMap::squareInLayout(int x, int y) { } MapSectionEntry RegionMap::getEntry(QString section) { - if (this->region_map_entries->contains(section)) - return this->region_map_entries->operator[](section); - else - return MapSectionEntry(); + return this->region_map_entries->value(section, MapSectionEntry()); } void RegionMap::setEntry(QString section, MapSectionEntry entry) { - this->region_map_entries->operator[](section) = entry; + this->region_map_entries->insert(section, entry); } void RegionMap::removeEntry(QString section) { - this->region_map_entries->erase(section); + this->region_map_entries->remove(section); } QString RegionMap::palPath() { @@ -788,27 +784,6 @@ int RegionMap::getMapSquareIndex(int x, int y) { return ((index < tilemap.length()) && (index >= 0)) ? index : 0; } -// For turning a MAPSEC_NAME into a unique identifier sMapName-style variable. -// CAPS_WITH_UNDERSCORE to CamelCase -QString RegionMap::fixCase(QString caps) { - bool big = true; - QString camel; - - static const QRegularExpression re_braced("({.*})"); - for (auto ch : caps.remove(re_braced).remove(this->section_prefix)) { - if (ch == '_' || ch == ' ') { - big = true; - continue; - } - if (big) { - camel += ch.toUpper(); - big = false; - } - else camel += ch.toLower(); - } - return camel; -} - QString RegionMap::fullPath(QString local) { return this->project->root + "/" + local; } diff --git a/src/core/regionmapeditcommands.cpp b/src/core/regionmapeditcommands.cpp index 1be247b0..7a12cbc6 100644 --- a/src/core/regionmapeditcommands.cpp +++ b/src/core/regionmapeditcommands.cpp @@ -260,7 +260,7 @@ void ResizeTilemap::undo() { /// -ClearEntries::ClearEntries(RegionMap *map, tsl::ordered_map entries, QUndoCommand *parent) +ClearEntries::ClearEntries(RegionMap *map, QMap entries, QUndoCommand *parent) : QUndoCommand(parent) { setText("Clear Entries"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0ace9fb9..90b2a80c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1177,7 +1177,7 @@ bool MainWindow::setProjectUI() { ui->comboBox_Song->clear(); ui->comboBox_Song->addItems(project->songNames); ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(project->mapSectionValueToName.values()); + ui->comboBox_Location->addItems(project->mapSectionIdNames); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_SecondaryTileset->clear(); @@ -1546,7 +1546,7 @@ void MainWindow::mapListAddArea() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ const QString newAreaName = newNameDisplay->text(); - if (this->editor->project->mapSectionNameToValue.contains(newAreaName)){ + if (this->editor->project->mapSectionIdNames.contains(newAreaName)){ errorMessageLabel->setText(QString("An area with the name '%1' already exists").arg(newAreaName)); errorMessageLabel->setVisible(true); } else { diff --git a/src/project.cpp b/src/project.cpp index 657bd89c..9d87c6f4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -708,36 +708,45 @@ void Project::saveMapGroups() { mapGroupsFile.close(); } -void Project::saveMapSections() { - QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections); - - QString text = QString("#ifndef GUARD_REGIONMAPSEC_H\n"); - text += QString("#define GUARD_REGIONMAPSEC_H\n\n"); - - int longestLength = 0; - for (QString label : this->mapSectionNameToValue.keys()) { - if (label.size() > longestLength) - longestLength = label.size(); +void Project::saveRegionMapSections() { + const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); + QFile file(filepath); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing").arg(filepath)); + return; } - longestLength += 1; + const QString emptyMapsecName = getEmptyMapsecName(); + OrderedJson::array mapSectionArray; + for (const auto &idName : this->mapSectionIdNames) { + // The 'empty' map section (MAPSEC_NONE) isn't normally present in the region map sections data file. + // We append this name to mapSectionIdNames ourselves if it isn't present, in which case we don't want to output data for it here. + if (!this->saveEmptyMapsec && idName == emptyMapsecName) + continue; - // TODO: Maybe print as an enum now that we can? - for (int value : this->mapSectionValueToName.keys()) { - QString line = QString("#define %1 0x%2\n") - .arg(this->mapSectionValueToName[value], -1 * longestLength) - .arg(QString("%1").arg(value, 2, 16, QLatin1Char('0')).toUpper()); - text += line; + OrderedJson::object mapSectionObj; + mapSectionObj["id"] = idName; + + if (this->regionMapEntries.contains(idName)) { + MapSectionEntry entry = this->regionMapEntries.value(idName); + mapSectionObj["name"] = entry.name; + mapSectionObj["x"] = entry.x; + mapSectionObj["y"] = entry.y; + mapSectionObj["width"] = entry.width; + mapSectionObj["height"] = entry.height; + } + + mapSectionArray.append(mapSectionObj); } - // TODO: We should maybe consider another way to update MAPSEC values in this file, in case we break anything by relocating it to the bottom of the file. - // (or alternatively keep separate strings for text before/after the MAPSEC values) - text += "\n" + this->extraFileText[projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections)] + "\n"; - - text += QString("#endif // GUARD_REGIONMAPSEC_H\n"); + OrderedJson::object object; + object["map_sections"] = mapSectionArray; ignoreWatchedFileTemporarily(filepath); - saveTextFile(filepath, text); + OrderedJson json(object); + OrderedJsonDoc jsonDoc(&json); + jsonDoc.dump(&file); + file.close(); } void Project::saveWildMonData() { @@ -1459,7 +1468,7 @@ void Project::updateLayout(Layout *layout) { void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); - saveMapSections(); + saveRegionMapSections(); saveMapConstantsHeader(); saveWildMonData(); saveConfig(); @@ -2242,49 +2251,66 @@ bool Project::readFieldmapMasks() { } bool Project::readRegionMapSections() { - this->mapSectionNameToValue.clear(); - this->mapSectionValueToName.clear(); + this->mapSectionIdNames.clear(); + this->regionMapEntries.clear(); + this->saveEmptyMapsec = false; + const QString defaultName = getEmptyMapsecName(); + const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); - const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))}; - QString filename = projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections); - fileWatcher.addPath(root + "/" + filename); - this->mapSectionNameToValue = parser.readCDefinesByRegex(filename, regexList); - if (this->mapSectionNameToValue.isEmpty()) { - logError(QString("Failed to read region map sections from %1.").arg(filename)); + QJsonDocument doc; + const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); + if (!parser.tryParseJsonFile(&doc, filepath)) { + logError(QString("Failed to read region map sections from '%1'").arg(filepath)); return false; } + fileWatcher.addPath(filepath); - for (QString defineName : this->mapSectionNameToValue.keys()) { - this->mapSectionValueToName.insert(this->mapSectionNameToValue[defineName], defineName); + QJsonArray mapSections = doc.object()["map_sections"].toArray(); + for (const auto &mapSection : mapSections) { + // For each map section, "id" is the only required field. This is the field we use + // to display the location names in various drop-downs. + QJsonObject mapSectionObj = mapSection.toObject(); + const QString idName = ParseUtil::jsonToQString(mapSectionObj["id"]); + if (!idName.startsWith(requiredPrefix)) { + logWarn(QString("Ignoring data for map section '%1'. IDs must start with the prefix '%2'").arg(idName).arg(requiredPrefix)); + continue; + } + + this->mapSectionIdNames.append(idName); + if (idName == defaultName) { + // If the user has data for the 'empty' MAPSEC we need to know to output it later, + // because we will otherwise add a dummy entry for this value. + this->saveEmptyMapsec = true; + } + + // Map sections may have additional data indicating their position on the region map. + // If they have this data, we can add them to the region map entry list. + bool hasRegionMapData = true; + static const QSet regionMapFieldNames = { "name", "x", "y", "width", "height" }; + for (auto fieldName : regionMapFieldNames) { + if (!mapSectionObj.contains(fieldName)) { + hasRegionMapData = false; + break; + } + } + if (!hasRegionMapData) + continue; + + MapSectionEntry entry; + entry.name = ParseUtil::jsonToQString(mapSectionObj["name"]); + entry.x = ParseUtil::jsonToInt(mapSectionObj["x"]); + entry.y = ParseUtil::jsonToInt(mapSectionObj["y"]); + entry.width = ParseUtil::jsonToInt(mapSectionObj["width"]); + entry.height = ParseUtil::jsonToInt(mapSectionObj["height"]); + entry.valid = true; + this->regionMapEntries[idName] = entry; } - // extra text - QString extraText; - QString fileText = ParseUtil::readTextFile(root + "/" + filename); - QTextStream stream(&fileText); - QString currentLine; - while (stream.readLineInto(¤tLine)) { - // is this line something that porymap will output again? - if (currentLine.isEmpty()) { - continue; - } - // include guards - // TODO: Assuming guard name is the same across projects (it isn't) - else if (currentLine.contains("GUARD_REGIONMAPSEC_H")) { - continue; - } - // defines captured - // TODO: Regex to consider comments/extra space - else if (currentLine.contains("#define " + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))) { - continue; - } - // everything else should be kept here - else { - extraText += currentLine + "\n"; - } + // Make sure the default name is present in the list. + if (!this->mapSectionIdNames.contains(defaultName)) { + this->mapSectionIdNames.append(defaultName); } - stream.seek(0); - this->extraFileText[filename] = extraText; + return true; } @@ -2292,24 +2318,15 @@ QString Project::getEmptyMapsecName() { return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); } -// This function assumes a valid and unique name. -// Will return the new index. -int Project::appendMapsec(QString name) { - const QString emptyMapsecName = getEmptyMapsecName(); - int newMapsecValue = mapSectionValueToName.isEmpty() ? 0 : mapSectionValueToName.lastKey(); - - // If the user has the 'empty' MAPSEC value defined last in the list we'll shift it so that it stays last in the list. - if (this->mapSectionNameToValue.contains(emptyMapsecName) && this->mapSectionNameToValue.value(emptyMapsecName) == newMapsecValue) { - this->mapSectionNameToValue.insert(emptyMapsecName, newMapsecValue + 1); - this->mapSectionValueToName.insert(newMapsecValue + 1, emptyMapsecName); +// This function assumes a valid and unique name +void Project::addNewMapsec(QString name) { + if (!this->mapSectionIdNames.isEmpty() && this->mapSectionIdNames.last() == getEmptyMapsecName()) { + // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. + this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); + } else { + this->mapSectionIdNames.append(name); } - - // TODO: Update 'define_map_section_count'? - - this->mapSectionNameToValue[name] = newMapsecValue; - this->mapSectionValueToName[newMapsecValue] = name; this->hasUnsavedDataChanges = true; - return newMapsecValue; } // Read the constants to preserve any "unused" heal locations when writing the file later diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index ef260390..98478f94 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -836,7 +836,7 @@ QString MainWindow::getLocation() { void MainWindow::setLocation(QString location) { if (!this->ui || !this->editor || !this->editor->project) return; - if (!this->editor->project->mapSectionNameToValue.contains(location)) { + if (!this->editor->project->mapSectionIdNames.contains(location)) { logError(QString("Unknown location '%1'").arg(location)); return; } diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index 27bd5677..e5cebc54 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -282,7 +282,7 @@ QList ScriptUtility::getSongNames() { QList ScriptUtility::getLocationNames() { if (!window || !window->editor || !window->editor->project) return QList(); - return window->editor->project->mapSectionNameToValue.keys(); + return window->editor->project->mapSectionIdNames; } QList ScriptUtility::getWeatherNames() { diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index e4ebd82c..616e608c 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -413,13 +413,12 @@ MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(par initialize(); } -QStandardItem *MapAreaModel::createAreaItem(QString mapsecName, int areaIndex) { +QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) { QStandardItem *area = new QStandardItem; area->setText(mapsecName); area->setEditable(false); area->setData(mapsecName, Qt::UserRole); area->setData("map_section", MapListUserRoles::TypeRole); - area->setData(areaIndex, MapListUserRoles::GroupRole); // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->areaItems.insert(mapsecName, area); return area; @@ -437,20 +436,14 @@ QStandardItem *MapAreaModel::createMapItem(QString mapName, int, int) { } QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { - int newAreaIndex = this->project->appendMapsec(areaName); - QStandardItem *item = createAreaItem(areaName, newAreaIndex); - this->root->insertRow(newAreaIndex, item); - - // MAPSEC_NONE may have shifted to accomodate the new item, update it in the list. - const QString emptyMapsecName = Project::getEmptyMapsecName(); - if (this->areaItems.contains(emptyMapsecName)) - this->areaItems[emptyMapsecName]->setData(this->project->mapSectionNameToValue.value(emptyMapsecName), MapListUserRoles::GroupRole); - + this->project->addNewMapsec(areaName); + QStandardItem *item = createAreaItem(areaName); + this->root->appendRow(item); + this->sort(0, Qt::AscendingOrder); return item; } QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) { - // int areaIndex = this->project->mapSectionNameToValue[areaName]; QStandardItem *area = this->areaItems[areaName]; if (!area) { return nullptr; @@ -461,21 +454,18 @@ QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, in return map; } +// Note: Not actually supported in the interface at the moment. void MapAreaModel::removeFolder(int index) { this->removeRow(index); - this->project->mapSectionNameToValue.remove(this->project->mapSectionValueToName.take(index)); + this->project->mapSectionIdNames.removeAt(index); } void MapAreaModel::initialize() { this->areaItems.clear(); this->mapItems.clear(); - this->setSortRole(MapListUserRoles::GroupRole); - // TODO: Ignore 'define_map_section_count' and/or 'define_map_section_empty'? - for (int i : this->project->mapSectionNameToValue) { - QString mapsecName = project->mapSectionValueToName.value(i); - QStandardItem *areaItem = createAreaItem(mapsecName, i); - this->root->appendRow(areaItem); + for (const auto &idName : this->project->mapSectionIdNames) { + this->root->appendRow(createAreaItem(idName)); } for (int i = 0; i < this->project->groupNames.length(); i++) { @@ -560,9 +550,7 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_section") { - return QString("[0x%1] %2") - .arg(QString("%1").arg(item->data(MapListUserRoles::GroupRole).toInt(), 2, 16, QLatin1Char('0')).toUpper()) - .arg(item->data(Qt::UserRole).toString()); + return item->data(Qt::UserRole).toString(); } } @@ -603,6 +591,7 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { QStandardItem *layoutItem = this->createLayoutItem(layoutId); this->root->appendRow(layoutItem); + this->sort(0, Qt::AscendingOrder); return layoutItem; } @@ -644,6 +633,7 @@ void LayoutTreeModel::initialize() { this->layoutItems[layoutId]->appendRow(map); } } + this->sort(0, Qt::AscendingOrder); } QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index a9e50c93..261544f2 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -34,7 +34,7 @@ void NewMapPopup::initUi() { 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->mapSectionNameToValue.keys()); + ui->comboBox_NewMap_Location->addItems(project->mapSectionIdNames); const QSignalBlocker b(ui->comboBox_Layout); ui->comboBox_Layout->addItems(project->mapLayoutsTable); @@ -186,7 +186,7 @@ void NewMapPopup::setDefaultSettings(Project *project) { settings.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); settings.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); settings.type = project->mapTypes.value(0, "0"); - settings.location = project->mapSectionNameToValue.keys().value(0, "0"); + settings.location = project->mapSectionIdNames.value(0, "0"); settings.song = project->defaultSong; settings.canFlyTo = false; settings.showLocationName = true; diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 2a23b454..c6485d51 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -29,7 +29,6 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->ui->setupUi(this); this->project = project; this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); - this->mapSectionFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); this->initShortcuts(); this->restoreWindowState(); } @@ -110,67 +109,13 @@ void RegionMapEditor::applyUserShortcuts() { } bool RegionMapEditor::loadRegionMapEntries() { - this->region_map_entries.clear(); - - ParseUtil parser; - QJsonDocument sectionsDoc; - if (!parser.tryParseJsonFile(§ionsDoc, this->mapSectionFilepath)) { - logError(QString("Failed to read map data from %1").arg(this->mapSectionFilepath)); - return false; - } - - // for some unknown reason, the OrderedJson class would not parse this properly - // perhaps updating nlohmann/json here would fix it, but that also requires using C++17 - QJsonObject object = sectionsDoc.object(); - - for (auto entryRef : object["map_sections"].toArray()) { - QJsonObject entryObject = entryRef.toObject(); - QString entryMapSection = ParseUtil::jsonToQString(entryObject["map_section"]); - MapSectionEntry entry; - entry.name = ParseUtil::jsonToQString(entryObject["name"]); - entry.x = ParseUtil::jsonToInt(entryObject["x"]); - entry.y = ParseUtil::jsonToInt(entryObject["y"]); - entry.width = ParseUtil::jsonToInt(entryObject["width"]); - entry.height = ParseUtil::jsonToInt(entryObject["height"]); - entry.valid = true; - this->region_map_entries[entryMapSection] = entry; - } - + this->region_map_entries = this->project->regionMapEntries; return true; } bool RegionMapEditor::saveRegionMapEntries() { - QFile sectionsFile(this->mapSectionFilepath); - if (!sectionsFile.open(QIODevice::WriteOnly)) { - logError(QString("Could not open %1 for writing").arg(this->mapSectionFilepath)); - return false; - } - - OrderedJson::object object; - OrderedJson::array mapSectionArray; - - for (auto pair : this->region_map_entries) { - QString section = pair.first; - MapSectionEntry entry = pair.second; - - OrderedJson::object entryObject; - entryObject["map_section"] = section; - entryObject["name"] = entry.name; - entryObject["x"] = entry.x; - entryObject["y"] = entry.y; - entryObject["width"] = entry.width; - entryObject["height"] = entry.height; - - mapSectionArray.append(entryObject); - } - - object["map_sections"] = mapSectionArray; - - OrderedJson sectionsJson(object); - OrderedJsonDoc jsonDoc(§ionsJson); - jsonDoc.dump(§ionsFile); - sectionsFile.close(); - + this->project->regionMapEntries = this->region_map_entries; + this->project->saveRegionMapSections(); return true; } @@ -708,7 +653,7 @@ void RegionMapEditor::displayRegionMapLayoutOptions() { this->ui->comboBox_RM_ConnectedMap->blockSignals(true); this->ui->comboBox_RM_ConnectedMap->clear(); - this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionValueToName.values()); + this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionIdNames); this->ui->comboBox_RM_ConnectedMap->blockSignals(false); this->ui->frame_RM_Options->setEnabled(true); @@ -775,7 +720,7 @@ void RegionMapEditor::displayRegionMapEntryOptions() { if (!this->region_map->layoutEnabled()) return; this->ui->comboBox_RM_Entry_MapSection->clear(); - this->ui->comboBox_RM_Entry_MapSection->addItems(this->project->mapSectionValueToName.values()); + this->ui->comboBox_RM_Entry_MapSection->addItems(this->project->mapSectionIdNames); this->ui->spinBox_RM_Entry_x->setMaximum(128); this->ui->spinBox_RM_Entry_y->setMaximum(128); this->ui->spinBox_RM_Entry_width->setMinimum(1); @@ -787,17 +732,13 @@ void RegionMapEditor::displayRegionMapEntryOptions() { void RegionMapEditor::updateRegionMapEntryOptions(QString section) { if (!this->region_map->layoutEnabled()) return; - bool isSpecialSection = (section == this->region_map->default_map_section - || section == this->region_map->count_map_section); - - bool enabled = (!isSpecialSection && this->region_map_entries.contains(section)); - + bool enabled = (section != this->region_map->default_map_section) && this->region_map_entries.contains(section); this->ui->lineEdit_RM_MapName->setEnabled(enabled); this->ui->spinBox_RM_Entry_x->setEnabled(enabled); this->ui->spinBox_RM_Entry_y->setEnabled(enabled); this->ui->spinBox_RM_Entry_width->setEnabled(enabled); this->ui->spinBox_RM_Entry_height->setEnabled(enabled); - this->ui->pushButton_entryActivate->setEnabled(!isSpecialSection); + this->ui->pushButton_entryActivate->setEnabled(section != this->region_map->default_map_section); this->ui->pushButton_entryActivate->setText(enabled ? "Remove" : "Add"); this->ui->lineEdit_RM_MapName->blockSignals(true); @@ -902,14 +843,8 @@ void RegionMapEditor::onRegionMapEntryDragged(int new_x, int new_y) { } void RegionMapEditor::onRegionMapLayoutSelectedTileChanged(int index) { - QString message = QString(); this->currIndex = index; this->region_map_layout_item->highlightedTile = index; - if (this->region_map->squareHasMap(index)) { - message = QString("\t %1").arg(this->project->mapSecToMapHoverName.value( - this->region_map->squareMapSection(index))).remove("{NAME_END}"); - } - this->ui->statusbar->showMessage(message); updateRegionMapLayoutOptions(index); this->region_map_layout_item->draw(); @@ -922,8 +857,7 @@ void RegionMapEditor::onRegionMapLayoutHoveredTileChanged(int index) { if (x >= 0 && y >= 0) { message = QString("(%1, %2)").arg(x).arg(y); if (this->region_map->squareHasMap(index)) { - message += QString("\t %1").arg(this->project->mapSecToMapHoverName.value( - this->region_map->squareMapSection(index))).remove("{NAME_END}"); + message += QString("\t %1").arg(this->region_map->squareMapSection(index)); } } this->ui->statusbar->showMessage(message); @@ -1203,10 +1137,10 @@ void RegionMapEditor::on_action_Swap_triggered() { QFormLayout form(&popup); QComboBox *oldSecBox = new QComboBox(); - oldSecBox->addItems(this->project->mapSectionValueToName.values()); + oldSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("Map Section 1:"), oldSecBox); QComboBox *newSecBox = new QComboBox(); - newSecBox->addItems(this->project->mapSectionValueToName.values()); + newSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("Map Section 2:"), newSecBox); QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &popup); @@ -1242,10 +1176,10 @@ void RegionMapEditor::on_action_Replace_triggered() { QFormLayout form(&popup); QComboBox *oldSecBox = new QComboBox(); - oldSecBox->addItems(this->project->mapSectionValueToName.values()); + oldSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("Old Map Section:"), oldSecBox); QComboBox *newSecBox = new QComboBox(); - newSecBox->addItems(this->project->mapSectionValueToName.values()); + newSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("New Map Section:"), newSecBox); QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &popup); From 06ece16b9351677b19ea29e596f5afc2683c7678 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Nov 2024 13:55:50 -0500 Subject: [PATCH 072/364] Finish support for deleting MAPSEC values --- include/mainwindow.h | 1 + include/project.h | 4 +++- include/ui/maplistmodels.h | 18 +++++++++++++----- src/mainwindow.cpp | 38 +++++++++++++++++++++++++++++++------- src/project.cpp | 12 +++++++++++- src/ui/maplistmodels.cpp | 19 +++++++++---------- src/ui/newmappopup.cpp | 2 ++ 7 files changed, 70 insertions(+), 24 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 8e999b24..397a1d72 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -417,6 +417,7 @@ private: void scrollMetatileSelectorToSelection(); MapListToolBar* getCurrentMapListToolBar(); MapTree* getCurrentMapList(); + void refreshLocationsComboBox(); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); diff --git a/include/project.h b/include/project.h index c99bd05b..efb7db7c 100644 --- a/include/project.h +++ b/include/project.h @@ -139,7 +139,8 @@ public: bool readSpeciesIconPaths(); QMap speciesToIconPath; - void addNewMapsec(QString name); + void addNewMapsec(const QString &name); + void removeMapsec(const QString &name); bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; @@ -266,6 +267,7 @@ private: signals: void fileChanged(QString filepath); + void mapSectionIdNamesChanged(); void mapLoaded(Map *map); }; diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 3e6e95d1..80a5423b 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -65,9 +65,11 @@ public: ~MapListModel() { } virtual QModelIndex indexOf(QString id) const = 0; - virtual void removeFolder(int index) = 0; - virtual void removeItem(const QModelIndex &index); + virtual void removeItemAt(const QModelIndex &index); virtual QStandardItem *getItem(const QModelIndex &index) const = 0; + +protected: + virtual void removeItem(QStandardItem *item) = 0; }; class MapGroupModel : public MapListModel { @@ -94,13 +96,15 @@ public: QStandardItem *insertGroupItem(QString groupName); QStandardItem *insertMapItem(QString mapName, QString groupName); - virtual void removeFolder(int index) override; virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString mapName) const override; void initialize(); +protected: + virtual void removeItem(QStandardItem *item) override; + private: friend class MapTree; void updateProject(); @@ -137,13 +141,15 @@ public: QStandardItem *insertAreaItem(QString areaName); QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); - virtual void removeFolder(int index) override; virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString mapName) const override; void initialize(); +protected: + virtual void removeItem(QStandardItem *item) override; + private: Project *project; QStandardItem *root = nullptr; @@ -176,13 +182,15 @@ public: QStandardItem *insertLayoutItem(QString layoutId); QStandardItem *insertMapItem(QString mapName, QString layoutId); - virtual void removeFolder(int index) override; virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString layoutName) const override; void initialize(); +protected: + virtual void removeItem(QStandardItem *item) override; + private: Project *project; QStandardItem *root = nullptr; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 90b2a80c..9cb9d130 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -598,8 +598,9 @@ bool MainWindow::openProject(QString dir, bool initial) { // Create the project auto project = new Project(editor); project->set_root(dir); - QObject::connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); - QObject::connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); + connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); + connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); + connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::refreshLocationsComboBox); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -1163,7 +1164,6 @@ bool MainWindow::setProjectUI() { // Block signals to the comboboxes while they are being modified const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker2(ui->comboBox_Location); const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); const QSignalBlocker blocker5(ui->comboBox_Weather); @@ -1176,8 +1176,6 @@ bool MainWindow::setProjectUI() { // Set up project comboboxes ui->comboBox_Song->clear(); ui->comboBox_Song->addItems(project->songNames); - ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(project->mapSectionIdNames); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_SecondaryTileset->clear(); @@ -1198,6 +1196,7 @@ bool MainWindow::setProjectUI() { ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->setClearButtonEnabled(true); ui->comboBox_EmergeMap->setFocusedScrollingEnabled(false); + refreshLocationsComboBox(); // Show/hide parts of the UI that are dependent on the user's project settings @@ -1247,6 +1246,17 @@ bool MainWindow::setProjectUI() { return true; } +void MainWindow::refreshLocationsComboBox() { + QStringList locations = this->editor->project->mapSectionIdNames; + locations.sort(); + + const QSignalBlocker b(ui->comboBox_Location); + ui->comboBox_Location->clear(); + ui->comboBox_Location->addItems(locations); + if (this->editor->map) + ui->comboBox_Location->setCurrentText(this->editor->map->location); +} + void MainWindow::clearProjectUI() { // Block signals to the comboboxes while they are being modified const QSignalBlocker blocker1(ui->comboBox_Song); @@ -1328,19 +1338,30 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QMenu menu(this); QAction* addToFolderAction = nullptr; QAction* deleteFolderAction = nullptr; + QAction* openItemAction = nullptr; if (itemType == "map_name") { // Right-clicking on a map. - // TODO: Add action to delete map once deleting maps is supported + openItemAction = menu.addAction("Open Map"); + //menu.addSeparator(); + //connect(menu.addAction("Delete Map"), &QAction::triggered, [this, index] { deleteMapListItem(index); }); // TODO: No support for deleting maps } else if (itemType == "map_group") { // Right-clicking on a map group folder addToFolderAction = menu.addAction("Add New Map to Group"); + menu.addSeparator(); deleteFolderAction = menu.addAction("Delete Map Group"); } else if (itemType == "map_section") { // Right-clicking on an MAPSEC folder addToFolderAction = menu.addAction("Add New Map to Area"); + menu.addSeparator(); + deleteFolderAction = menu.addAction("Delete Area"); + if (itemName == this->editor->project->getEmptyMapsecName()) + deleteFolderAction->setEnabled(false); // Disallow deleting the default name } else if (itemType == "map_layout") { // Right-clicking on a map layout + openItemAction = menu.addAction("Open Layout"); addToFolderAction = menu.addAction("Add New Map with Layout"); + //menu.addSeparator(); + //deleteFolderAction = menu.addAction("Delete Layout"); // TODO: No support for deleting layouts } if (addToFolderAction) { @@ -1351,13 +1372,16 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } if (deleteFolderAction) { connect(deleteFolderAction, &QAction::triggered, [sourceModel, index] { - sourceModel->removeFolder(index.row()); + sourceModel->removeItemAt(index); }); if (selectedItem->hasChildren()){ // TODO: No support for deleting maps, so you may only delete folders if they don't contain any maps. deleteFolderAction->setEnabled(false); } } + if (openItemAction) { + connect(openItemAction, &QAction::triggered, [this, index] { openMapListItem(index); }); + } if (menu.actions().length() != 0) menu.exec(QCursor::pos()); diff --git a/src/project.cpp b/src/project.cpp index 9d87c6f4..9c9fcf14 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2319,7 +2319,7 @@ QString Project::getEmptyMapsecName() { } // This function assumes a valid and unique name -void Project::addNewMapsec(QString name) { +void Project::addNewMapsec(const QString &name) { if (!this->mapSectionIdNames.isEmpty() && this->mapSectionIdNames.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); @@ -2327,6 +2327,16 @@ void Project::addNewMapsec(QString name) { this->mapSectionIdNames.append(name); } this->hasUnsavedDataChanges = true; + emit mapSectionIdNamesChanged(); +} + +void Project::removeMapsec(const QString &name) { + if (!this->mapSectionIdNames.contains(name) || name == getEmptyMapsecName()) + return; + + this->mapSectionIdNames.removeOne(name); + this->hasUnsavedDataChanges = true; + emit mapSectionIdNamesChanged(); } // Read the constants to preserve any "unused" heal locations when writing the file later diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 616e608c..a270736a 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -31,14 +31,14 @@ void MapTree::keyPressEvent(QKeyEvent *event) { persistentIndexes.append(model->mapToSource(index)); } for (const auto &index : persistentIndexes) { - sourceModel->removeItem(index); + sourceModel->removeItemAt(index); } } else { QWidget::keyPressEvent(event); } } -void MapListModel::removeItem(const QModelIndex &index) { +void MapListModel::removeItemAt(const QModelIndex &index) { QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); if (!item) return; @@ -49,7 +49,7 @@ void MapListModel::removeItem(const QModelIndex &index) { } else { // TODO: Because there's no support for deleting maps we can only delete empty folders if (!item->hasChildren()) { - this->removeFolder(index.row()); + this->removeItem(item); } } } @@ -282,8 +282,8 @@ QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { return group; } -void MapGroupModel::removeFolder(int index) { - this->removeRow(index); +void MapGroupModel::removeItem(QStandardItem *item) { + this->removeRow(item->row()); this->updateProject(); } @@ -454,10 +454,9 @@ QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, in return map; } -// Note: Not actually supported in the interface at the moment. -void MapAreaModel::removeFolder(int index) { - this->removeRow(index); - this->project->mapSectionIdNames.removeAt(index); +void MapAreaModel::removeItem(QStandardItem *item) { + this->project->removeMapsec(item->data(Qt::UserRole).toString()); + this->removeRow(item->row()); } void MapAreaModel::initialize() { @@ -612,7 +611,7 @@ QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) return map; } -void LayoutTreeModel::removeFolder(int) { +void LayoutTreeModel::removeItem(QStandardItem *) { // TODO: Deleting layouts not supported } diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index 261544f2..def485c2 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -8,6 +8,8 @@ #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) + struct NewMapPopup::Settings NewMapPopup::settings = {}; NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : From e278d48380c198d0589164e7b12edd4c7d9c3a0c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Nov 2024 14:59:41 -0500 Subject: [PATCH 073/364] Fix script API undo/redo for layouts, final TODO items --- include/ui/maplistmodels.h | 1 + src/core/editcommands.cpp | 5 ++--- src/mainwindow.cpp | 4 ---- src/project.cpp | 15 ++++++++++----- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 80a5423b..17810c7e 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -27,6 +27,7 @@ public: this->setDropIndicatorShown(true); this->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); this->setFocusPolicy(Qt::StrongFocus); + this->setContextMenuPolicy(Qt::CustomContextMenu); } protected: diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 0843b2c0..c500c8c0 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -486,7 +486,6 @@ int EventPaste::id() const { ************************************************************************ ******************************************************************************/ -// TODO: Undo/redo for script edits to layout dimensions doesn't render correctly. ScriptEditLayout::ScriptEditLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, @@ -538,7 +537,7 @@ void ScriptEditLayout::redo() { layout->lastCommitBlocks.border = newBorder; layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); - renderBlocks(layout); + renderBlocks(layout, true); layout->borderItem->draw(); } @@ -564,7 +563,7 @@ void ScriptEditLayout::undo() { layout->lastCommitBlocks.border = oldBorder; layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); - renderBlocks(layout); + renderBlocks(layout, true); layout->borderItem->draw(); QUndoCommand::undo(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9cb9d130..2b83e0b0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -406,9 +406,6 @@ void MainWindow::initMapList() { connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); // Right-clicking on items in the map list brings up a context menu. - ui->mapList->setContextMenuPolicy(Qt::CustomContextMenu); - ui->areaList->setContextMenuPolicy(Qt::CustomContextMenu); - ui->layoutList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); connect(ui->areaList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); @@ -1316,7 +1313,6 @@ void MainWindow::scrollMapListToCurrentMap(MapTree *list) { } } -// TODO: Initial scrolling doesn't center the layout on launch if it's not the current tab. void MainWindow::scrollMapListToCurrentLayout(MapTree *list) { if (this->editor->layout) { scrollMapList(list, this->editor->layout->id); diff --git a/src/project.cpp b/src/project.cpp index 9c9fcf14..88e3d9d1 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2266,11 +2266,16 @@ bool Project::readRegionMapSections() { fileWatcher.addPath(filepath); QJsonArray mapSections = doc.object()["map_sections"].toArray(); - for (const auto &mapSection : mapSections) { - // For each map section, "id" is the only required field. This is the field we use - // to display the location names in various drop-downs. - QJsonObject mapSectionObj = mapSection.toObject(); - const QString idName = ParseUtil::jsonToQString(mapSectionObj["id"]); + for (int i = 0; i < mapSections.size(); i++) { + QJsonObject mapSectionObj = mapSections.at(i).toObject(); + + // For each map section, "id" is the only required field. This is the field we use to display the location names in various drop-downs. + const QString idField = "id"; + if (!mapSectionObj.contains(idField)) { + logWarn(QString("Ignoring data for map section %1. Missing required field \"%2\"").arg(i).arg(idField)); + continue; + } + const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); if (!idName.startsWith(requiredPrefix)) { logWarn(QString("Ignoring data for map section '%1'. IDs must start with the prefix '%2'").arg(idName).arg(requiredPrefix)); continue; From 43c45f7d98f39131db3843c200330dd4ea53a8a4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Nov 2024 19:06:56 -0500 Subject: [PATCH 074/364] Fix some typos --- src/mainwindow.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2b83e0b0..d9e65eb3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -851,7 +851,7 @@ bool MainWindow::userSetMap(QString map_name) { bool MainWindow::setMap(QString map_name) { if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { - logInfo(QString("Cannot set map to '%1'").arg(DYNAMIC_MAP_NAME)); + logInfo(QString("Cannot set map to '%1'").arg(map_name)); return false; } @@ -1346,7 +1346,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { menu.addSeparator(); deleteFolderAction = menu.addAction("Delete Map Group"); } else if (itemType == "map_section") { - // Right-clicking on an MAPSEC folder + // Right-clicking on a MAPSEC folder addToFolderAction = menu.addAction("Add New Map to Area"); menu.addSeparator(); deleteFolderAction = menu.addAction("Delete Area"); @@ -1541,7 +1541,6 @@ void MainWindow::mapListAddLayout() { } void MainWindow::mapListAddArea() { - // Note: there is no checking here for the limits on map section count QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::ApplicationModal); QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); @@ -2824,7 +2823,7 @@ void MainWindow::on_actionExport_Stitched_Map_Image_triggered() { if (!this->editor->map) { QMessageBox warning(this); warning.setText("Notice"); - warning.setInformativeText("Map stich images are not possible without a map selected."); + warning.setInformativeText("Map stitch images are not possible without a map selected."); warning.setStandardButtons(QMessageBox::Ok); warning.setDefaultButton(QMessageBox::Cancel); warning.setIcon(QMessageBox::Warning); From 18308fa9dedb0348c2471cd58e9358d54c8699f8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 11 Nov 2024 18:42:26 -0500 Subject: [PATCH 075/364] Stop writing map_groups.h --- docsrc/manual/project-files.rst | 1 - include/config.h | 1 - include/project.h | 1 - src/config.cpp | 1 - src/project.cpp | 38 --------------------------------- 5 files changed, 42 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index e4f0479b..8e0dfa33 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -45,7 +45,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, include/constants/global.h, yes, no, ``constants_global``, reads ``define_obj_event_count`` - include/constants/map_groups.h, no, yes, ``constants_map_groups``, include/constants/items.h, yes, no, ``constants_items``, for Hidden Item events include/constants/flags.h, yes, no, ``constants_flags``, for Object and Hidden Item events include/constants/vars.h, yes, no, ``constants_vars``, for Trigger events diff --git a/include/config.h b/include/config.h index 6b64b612..2a93a793 100644 --- a/include/config.h +++ b/include/config.h @@ -254,7 +254,6 @@ enum ProjectFilePath { data_pokemon_gfx, data_heal_locations, constants_global, - constants_map_groups, constants_items, constants_flags, constants_vars, diff --git a/include/project.h b/include/project.h index efb7db7c..f35fc403 100644 --- a/include/project.h +++ b/include/project.h @@ -172,7 +172,6 @@ public: void saveMapGroups(); void saveRegionMapSections(); void saveWildMonData(); - void saveMapConstantsHeader(); void saveHealLocations(Map*); void saveTilesets(Tileset*, Tileset*); void saveTilesetMetatileLabels(Tileset*, Tileset*); diff --git a/src/config.cpp b/src/config.cpp index 3d604da6..a1987203 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -152,7 +152,6 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}}, {ProjectFilePath::data_heal_locations, { "data_heal_locations", "src/data/heal_locations.h"}}, {ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}}, - {ProjectFilePath::constants_map_groups, { "constants_map_groups", "include/constants/map_groups.h"}}, {ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}}, {ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}}, {ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}}, diff --git a/src/project.cpp b/src/project.cpp index 88e3d9d1..22487a13 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -840,43 +840,6 @@ void Project::saveWildMonData() { wildEncountersFile.close(); } -void Project::saveMapConstantsHeader() { - QString text = QString("#ifndef GUARD_CONSTANTS_MAP_GROUPS_H\n"); - text += QString("#define GUARD_CONSTANTS_MAP_GROUPS_H\n"); - text += QString("\n//\n// DO NOT MODIFY THIS FILE! It is auto-generated from %1\n//\n\n") - .arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups)); - - int groupNum = 0; - for (QStringList mapNames : groupedMapNames) { - text += "// " + groupNames.at(groupNum) + "\n"; - int maxLength = 0; - for (QString mapName : mapNames) { - QString mapConstantName = mapNamesToMapConstants.value(mapName); - if (mapConstantName.length() > maxLength) - maxLength = mapConstantName.length(); - } - int groupIndex = 0; - for (QString mapName : mapNames) { - QString mapConstantName = mapNamesToMapConstants.value(mapName); - text += QString("#define %1%2(%3 | (%4 << 8))\n") - .arg(mapConstantName) - .arg(QString(" ").repeated(maxLength - mapConstantName.length() + 1)) - .arg(groupIndex) - .arg(groupNum); - groupIndex++; - } - text += QString("\n"); - groupNum++; - } - - text += QString("#define MAP_GROUPS_COUNT %1\n\n").arg(groupNum); - text += QString("#endif // GUARD_CONSTANTS_MAP_GROUPS_H\n"); - - QString mapGroupFilepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::constants_map_groups); - ignoreWatchedFileTemporarily(mapGroupFilepath); - saveTextFile(mapGroupFilepath, text); -} - void Project::saveHealLocations(Map *map) { this->saveHealLocationsData(map); this->saveHealLocationsConstants(); @@ -1469,7 +1432,6 @@ void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); saveRegionMapSections(); - saveMapConstantsHeader(); saveWildMonData(); saveConfig(); this->hasUnsavedDataChanges = false; From b7d78b0263203eb0347b770e0e08c134fc767e05 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 11 Nov 2024 22:28:53 -0500 Subject: [PATCH 076/364] Make Map members private --- include/core/map.h | 152 ++++++++++++++++-------- include/core/maplayout.h | 8 +- include/project.h | 2 - src/core/events.cpp | 12 +- src/core/map.cpp | 191 +++++++++++++++++++++--------- src/core/mapconnection.cpp | 6 +- src/core/maplayout.cpp | 16 --- src/editor.cpp | 68 +++++------ src/mainwindow.cpp | 111 ++++++++--------- src/project.cpp | 177 +++++++++++++-------------- src/scriptapi/apimap.cpp | 24 ++-- src/ui/connectionpixmapitem.cpp | 2 +- src/ui/connectionslistitem.cpp | 8 +- src/ui/draggablepixmapitem.cpp | 2 +- src/ui/eventframes.cpp | 4 +- src/ui/mapimageexporter.cpp | 62 ++++------ src/ui/newmapconnectiondialog.cpp | 2 +- src/ui/newmappopup.cpp | 45 ++++--- 18 files changed, 494 insertions(+), 398 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index acc52d90..22a2ef29 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -37,58 +37,74 @@ public: ~Map(); public: - QString name; - QString constantName; - - QString song; - QString layoutId; - QString location; - bool requiresFlash; - QString weather; - QString type; - bool show_location; - bool allowRunning; - bool allowBiking; - bool allowEscaping; - int floorNumber = 0; - QString battle_scene; - - QString sharedEventsMap = ""; - QString sharedScriptsMap = ""; - - QStringList scriptsFileLabels; - QMap customHeaders; - - Layout *layout = nullptr; - void setLayout(Layout *layout); - - bool isPersistedToFile = true; - bool hasUnsavedDataChanges = false; - - bool needsLayoutDir = true; - bool needsHealLocation = false; - bool scriptsLoaded = false; - - QMap> events; - QList ownedEvents; // for memory management - - QList metatileLayerOrder; - QList metatileLayerOpacity; - void setName(QString mapName); + QString name() const { return m_name; } + QString constantName() const { return m_constantName; } + static QString mapConstantFromName(QString mapName, bool includePrefix = true); - int getWidth(); - int getHeight(); - int getBorderWidth(); - int getBorderHeight(); + void setLayout(Layout *layout); + Layout* layout() const { return m_layout; } - QList getAllEvents() const; + void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; } + QString layoutId() const { return m_layoutId; } + + int getWidth() const; + int getHeight() const; + int getBorderWidth() const; + int getBorderHeight() const; + + // TODO: Combine these into a separate MapHeader class? + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setShowsLocation(bool showsLocation); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + void setBattleScene(const QString &battleScene); + + QString song() const { return m_song; } + QString location() const { return m_location; } + bool requiresFlash() const { return m_requiresFlash; } + QString weather() const { return m_weather; } + QString type() const { return m_type; } + bool showsLocation() const { return m_showsLocation; } + bool allowsRunning() const { return m_allowsRunning; } + bool allowsBiking() const { return m_allowsBiking; } + bool allowsEscaping() const { return m_allowsEscaping; } + int floorNumber() const { return m_floorNumber; } + QString battleScene() const { return m_battleScene; } + + void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } + void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; } + + QString sharedEventsMap() const { return m_sharedEventsMap; } + QString sharedScriptsMap() const { return m_sharedScriptsMap; } + + void setNeedsLayoutDir(bool needsLayoutDir) { m_needsLayoutDir = needsLayoutDir; } + void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } + void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } + void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } + + bool needsLayoutDir() const { return m_needsLayoutDir; } + bool needsHealLocation() const { return m_needsHealLocation; } + bool isPersistedToFile() const { return m_isPersistedToFile; } + bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } + + void resetEvents(); + QList getEvents(Event::Group group = Event::Group::None) const; + Event* getEvent(Event::Group group, int index) const; + int getNumEvents(Event::Group group = Event::Group::None) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilePath() const; void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); + int getIndexOfEvent(Event *) const; void deleteConnections(); QList getConnections() const; @@ -98,18 +114,60 @@ public: QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr); QPixmap renderConnection(const QString &direction, Layout *fromLayout = nullptr); - QUndoStack editHistory; + QUndoStack* editHistory() const { return m_editHistory; } + void commit(QUndoCommand*); void modify(); - void clean(); + void setClean(); bool hasUnsavedChanges() const; void pruneEditHistory(); + void setCustomAttributes(const QMap &attributes) { m_customAttributes = attributes; } + QMap customAttributes() const { return m_customAttributes; } + private: + QString m_name; + QString m_constantName; + QString m_layoutId; // TODO: Why do we do half this->layout()->id and half this->layoutId. Should these ever be different? + + QString m_song; + QString m_location; + bool m_requiresFlash; + QString m_weather; + QString m_type; + bool m_showsLocation; + bool m_allowsRunning; + bool m_allowsBiking; + bool m_allowsEscaping; + int m_floorNumber = 0; + QString m_battleScene; + + QString m_sharedEventsMap = ""; + QString m_sharedScriptsMap = ""; + + QStringList m_scriptsFileLabels; + QMap m_customAttributes; + + Layout *m_layout = nullptr; + + bool m_isPersistedToFile = true; + bool m_hasUnsavedDataChanges = false; + bool m_needsLayoutDir = true; + bool m_needsHealLocation = false; + bool m_scriptsLoaded = false; + + QMap> m_events; + QList m_ownedEvents; // for memory management + + QList m_metatileLayerOrder; + QList m_metatileLayerOpacity; + void trackConnection(MapConnection*); // MapConnections in 'ownedConnections' but not 'connections' persist in the edit history. - QList connections; - QSet ownedConnections; + QList m_connections; + QSet m_ownedConnections; + + QUndoStack *m_editHistory = nullptr; signals: void modified(); diff --git a/include/core/maplayout.h b/include/core/maplayout.h index b617002f..0cffcefa 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -84,10 +84,10 @@ public: Layout *copy(); void copyFrom(Layout *other); - int getWidth(); - int getHeight(); - int getBorderWidth(); - int getBorderHeight(); + int getWidth() const { return width; } + int getHeight() const { return height; } + int getBorderWidth() const { return border_width; } + int getBorderHeight() const { return border_height; } bool isWithinBounds(int x, int y); bool isWithinBorderBounds(int x, int y); diff --git a/include/project.h b/include/project.h index f35fc403..0b8b9978 100644 --- a/include/project.h +++ b/include/project.h @@ -247,8 +247,6 @@ private: void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); - void setNewMapEvents(Map *map); - void setNewMapConnections(Map *map); void saveHealLocationsData(Map *map); void saveHealLocationsConstants(); diff --git a/src/core/events.cpp b/src/core/events.cpp index 89416eae..6cd92589 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -44,7 +44,7 @@ void Event::setPixmapItem(DraggablePixmapItem *item) { } int Event::getEventIndex() { - return this->map->events.value(this->getEventGroup()).indexOf(this); + return this->map->getIndexOfEvent(this); } void Event::setDefaultValues(Project *) { @@ -424,7 +424,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { void CloneObjectEvent::setDefaultValues(Project *project) { this->setGfx(project->gfxDefines.keys().value(0, "0")); this->setTargetID(1); - if (this->getMap()) this->setTargetMap(this->getMap()->name); + if (this->getMap()) this->setTargetMap(this->getMap()->name()); } const QSet expectedCloneObjectFields = { @@ -445,7 +445,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone int eventIndex = this->targetID - 1; Map *clonedMap = project->getMap(this->targetMap); - Event *clonedEvent = clonedMap ? clonedMap->events[Event::Group::Object].value(eventIndex, nullptr) : nullptr; + Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { // Get graphics data from cloned object @@ -534,7 +534,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { } void WarpEvent::setDefaultValues(Project *) { - if (this->getMap()) this->setDestinationMap(this->getMap()->name); + if (this->getMap()) this->setDestinationMap(this->getMap()->name()); this->setDestinationWarpID("0"); this->setElevation(0); } @@ -952,13 +952,13 @@ void HealLocationEvent::setDefaultValues(Project *) { if (!this->getMap()) return; bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString mapConstant = Map::mapConstantFromName(this->getMap()->name, false); + const QString mapConstant = Map::mapConstantFromName(this->getMap()->name(), false); const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix : ProjectIdentifier::define_heal_locations_prefix); this->setLocationName(mapConstant); this->setIdName(prefix + mapConstant); if (respawnEnabled) { - this->setRespawnMap(this->getMap()->name); + this->setRespawnMap(this->getMap()->name()); this->setRespawnNPC(1); } } diff --git a/src/core/map.cpp b/src/core/map.cpp index 2a7d96dc..496e645f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -13,25 +13,27 @@ Map::Map(QObject *parent) : QObject(parent) { - editHistory.setClean(); + m_editHistory = new QUndoStack(this); + resetEvents(); } Map::~Map() { - qDeleteAll(ownedEvents); - ownedEvents.clear(); + qDeleteAll(m_ownedEvents); + m_ownedEvents.clear(); deleteConnections(); } void Map::setName(QString mapName) { - name = mapName; - constantName = mapConstantFromName(mapName); - scriptsLoaded = false; + m_name = mapName; + m_constantName = mapConstantFromName(mapName); + m_scriptsLoaded = false; } +// Note: Map does not take ownership of layout void Map::setLayout(Layout *layout) { - this->layout = layout; + m_layout = layout; if (layout) { - this->layoutId = layout->id; + m_layoutId = layout->id; } } @@ -51,20 +53,20 @@ QString Map::mapConstantFromName(QString mapName, bool includePrefix) { return constantName; } -int Map::getWidth() { - return layout->getWidth(); +int Map::getWidth() const { + return m_layout->getWidth(); } -int Map::getHeight() { - return layout->getHeight(); +int Map::getHeight() const { + return m_layout->getHeight(); } -int Map::getBorderWidth() { - return layout->getBorderWidth(); +int Map::getBorderWidth() const { + return m_layout->getBorderWidth(); } -int Map::getBorderHeight() { - return layout->getBorderHeight(); +int Map::getBorderHeight() const { + return m_layout->getBorderHeight(); } // Get the portion of the map that can be rendered when rendered as a map connection. @@ -106,7 +108,7 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { if (MapConnection::isDiving(direction)) fromLayout = nullptr; - QPixmap connectionPixmap = this->layout->render(true, fromLayout, bounds); + QPixmap connectionPixmap = m_layout->render(true, fromLayout, bounds); return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); } @@ -114,18 +116,10 @@ void Map::openScript(QString label) { emit openScriptRequested(label); } -QList Map::getAllEvents() const { - QList all_events; - for (const auto &event_list : events) { - all_events << event_list; - } - return all_events; -} - QStringList Map::getScriptLabels(Event::Group group) { - if (!this->scriptsLoaded) { - this->scriptsFileLabels = ParseUtil::getGlobalScriptLabels(this->getScriptsFilePath()); - this->scriptsLoaded = true; + if (!m_scriptsLoaded) { + m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath()); + m_scriptsLoaded = true; } QStringList scriptLabels; @@ -133,20 +127,20 @@ QStringList Map::getScriptLabels(Event::Group group) { // Get script labels currently in-use by the map's events if (group == Event::Group::None) { ScriptTracker scriptTracker; - for (Event *event : this->getAllEvents()) { + for (const auto &event : getEvents()) { event->accept(&scriptTracker); } scriptLabels = scriptTracker.getScripts(); } else { ScriptTracker scriptTracker; - for (Event *event : events.value(group)) { + for (const auto &event : m_events.value(group)) { event->accept(&scriptTracker); } scriptLabels = scriptTracker.getScripts(); } // Add scripts from map's scripts file, and empty names. - scriptLabels.append(this->scriptsFileLabels); + scriptLabels.append(m_scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); scriptLabels.prepend("0x0"); scriptLabels.prepend("NULL"); @@ -162,7 +156,7 @@ QString Map::getScriptsFilePath() const { auto path = QDir::cleanPath(QString("%1/%2/%3/scripts") .arg(projectConfig.projectDir) .arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)) - .arg(this->name)); + .arg(m_name)); auto extension = Project::getScriptFileExtension(usePoryscript); if (usePoryscript && !QFile::exists(path + extension)) extension = Project::getScriptFileExtension(false); @@ -170,37 +164,77 @@ QString Map::getScriptsFilePath() const { return path; } +void Map::resetEvents() { + m_events[Event::Group::Object].clear(); + m_events[Event::Group::Warp].clear(); + m_events[Event::Group::Coord].clear(); + m_events[Event::Group::Bg].clear(); + m_events[Event::Group::Heal].clear(); +} + +QList Map::getEvents(Event::Group group) const { + if (group == Event::Group::None) { + // Get all events + QList all_events; + for (const auto &event_list : m_events) { + all_events << event_list; + } + return all_events; + } + return m_events[group]; +} + +Event* Map::getEvent(Event::Group group, int index) const { + return m_events[group].value(index, nullptr); +} + +int Map::getNumEvents(Event::Group group) const { + if (group == Event::Group::None) { + // Total number of events + int numEvents = 0; + for (auto i = m_events.constBegin(); i != m_events.constEnd(); i++) { + numEvents += i.value().length(); + } + return numEvents; + } + return m_events[group].length(); +} + void Map::removeEvent(Event *event) { - for (Event::Group key : events.keys()) { - events[key].removeAll(event); + for (auto i = m_events.begin(); i != m_events.end(); i++) { + i.value().removeAll(event); } } void Map::addEvent(Event *event) { event->setMap(this); - events[event->getEventGroup()].append(event); - if (!ownedEvents.contains(event)) ownedEvents.append(event); + m_events[event->getEventGroup()].append(event); + if (!m_ownedEvents.contains(event)) m_ownedEvents.append(event); +} + +int Map::getIndexOfEvent(Event *event) const { + return m_events.value(event->getEventGroup()).indexOf(event); } void Map::deleteConnections() { - qDeleteAll(this->ownedConnections); - this->ownedConnections.clear(); - this->connections.clear(); + qDeleteAll(m_ownedConnections); + m_ownedConnections.clear(); + m_connections.clear(); } QList Map::getConnections() const { - return this->connections; + return m_connections; } void Map::addConnection(MapConnection *connection) { - if (!connection || this->connections.contains(connection)) + if (!connection || m_connections.contains(connection)) return; // Maps should only have one Dive/Emerge connection at a time. // (Users can technically have more by editing their data manually, but we will only display one at a time) // Any additional connections being added (this can happen via mirroring) are tracked for deleting but otherwise ignored. if (MapConnection::isDiving(connection->direction())) { - for (auto i : this->connections) { + for (const auto &i : m_connections) { if (i->direction() == connection->direction()) { trackConnection(connection); return; @@ -218,8 +252,8 @@ void Map::loadConnection(MapConnection *connection) { if (!connection) return; - if (!this->connections.contains(connection)) - this->connections.append(connection); + if (!m_connections.contains(connection)) + m_connections.append(connection); trackConnection(connection); } @@ -227,12 +261,12 @@ void Map::loadConnection(MapConnection *connection) { void Map::trackConnection(MapConnection *connection) { connection->setParentMap(this, false); - if (!this->ownedConnections.contains(connection)) { - this->ownedConnections.insert(connection); + if (!m_ownedConnections.contains(connection)) { + m_ownedConnections.insert(connection); connect(connection, &MapConnection::parentMapChanged, [=](Map *, Map *after) { if (after != this && after != nullptr) { // MapConnection's parent has been reassigned, it's no longer our responsibility - this->ownedConnections.remove(connection); + m_ownedConnections.remove(connection); QObject::disconnect(connection, &MapConnection::parentMapChanged, this, nullptr); } }); @@ -241,23 +275,29 @@ void Map::trackConnection(MapConnection *connection) { // We retain ownership of this MapConnection until it's assigned to a new parent map. void Map::removeConnection(MapConnection *connection) { - if (!this->connections.removeOne(connection)) + if (!m_connections.removeOne(connection)) return; connection->setParentMap(nullptr, false); modify(); emit connectionRemoved(connection); } +void Map::commit(QUndoCommand *cmd) { + m_editHistory->push(cmd); +} + void Map::modify() { emit modified(); } -void Map::clean() { - this->hasUnsavedDataChanges = false; +void Map::setClean() { + m_editHistory->setClean(); + m_hasUnsavedDataChanges = false; + m_isPersistedToFile = true; } bool Map::hasUnsavedChanges() const { - return !editHistory.isClean() || this->layout->hasUnsavedChanges() || hasUnsavedDataChanges || !isPersistedToFile; + return !m_editHistory->isClean() || m_layout->hasUnsavedChanges() || m_hasUnsavedDataChanges || !m_isPersistedToFile; } void Map::pruneEditHistory() { @@ -271,12 +311,57 @@ void Map::pruneEditHistory() { ID_MapConnectionAdd, ID_MapConnectionRemove }; - for (int i = 0; i < this->editHistory.count(); i++) { + for (int i = 0; i < m_editHistory->count(); i++) { // Qt really doesn't expect editing commands in the stack to be valid (fair). // A better future design might be to have separate edit histories per map tab, // and dumping the entire Connections tab history with QUndoStack::clear. - auto command = const_cast(this->editHistory.command(i)); + auto command = const_cast(m_editHistory->command(i)); if (mapConnectionIds.contains(command->id())) command->setObsolete(true); } } + +void Map::setSong(const QString &song) { + m_song = song; +} + +void Map::setLocation(const QString &location) { + m_location = location; +} + +void Map::setRequiresFlash(bool requiresFlash) { + m_requiresFlash = requiresFlash; +} + +void Map::setWeather(const QString &weather) { + m_weather = weather; +} + +void Map::setType(const QString &type) { + m_type = type; +} + +void Map::setShowsLocation(bool showsLocation) { + m_showsLocation = showsLocation; +} + +void Map::setAllowsRunning(bool allowsRunning) { + m_allowsRunning = allowsRunning; +} + +void Map::setAllowsBiking(bool allowsBiking) { + m_allowsBiking = allowsBiking; +} + +void Map::setAllowsEscaping(bool allowsEscaping) { + m_allowsEscaping = allowsEscaping; +} + +void Map::setFloorNumber(int floorNumber) { + m_floorNumber = floorNumber; +} + +void Map::setBattleScene(const QString &battleScene) { + m_battleScene = battleScene; +} + diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index b80b8d09..db2755e9 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -65,7 +65,7 @@ QPixmap MapConnection::getPixmap() { if (!map) return QPixmap(); - return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout : nullptr); + return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout() : nullptr); } void MapConnection::setParentMap(Map* map, bool mirror) { @@ -75,7 +75,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) { if (mirror) { auto connection = findMirror(); if (connection) - connection->setTargetMapName(map ? map->name : QString(), false); + connection->setTargetMapName(map ? map->name() : QString(), false); } if (m_parentMap) @@ -91,7 +91,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) { } QString MapConnection::parentMapName() const { - return m_parentMap ? m_parentMap->name : QString(); + return m_parentMap ? m_parentMap->name() : QString(); } void MapConnection::setTargetMapName(const QString &targetMapName, bool mirror) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 34033ac5..11e9943d 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -45,22 +45,6 @@ QString Layout::layoutConstantFromName(QString mapName) { return constantName; } -int Layout::getWidth() { - return width; -} - -int Layout::getHeight() { - return height; -} - -int Layout::getBorderWidth() { - return border_width; -} - -int Layout::getBorderHeight() { - return border_height; -} - bool Layout::isWithinBounds(int x, int y) { return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); } diff --git a/src/editor.cpp b/src/editor.cpp index 86cedd5b..e13f76eb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -152,7 +152,7 @@ void Editor::setEditorView() { map_item->setEditsEnabled(false); case EditMode::Events: if (this->map) { - this->editGroup.setActiveStack(&this->map->editHistory); + this->editGroup.setActiveStack(this->map->editHistory()); } break; case EditMode::Header: @@ -240,23 +240,23 @@ void Editor::displayWildMonTables() { clearWildMonTables(); // Don't try to read encounter data if it doesn't exist on disk for this map. - if (!project->wildMonData.contains(map->constantName)) { + if (!project->wildMonData.contains(map->constantName())) { return; } QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel; - for (auto groupPair : project->wildMonData[map->constantName]) + for (auto groupPair : project->wildMonData[map->constantName()]) labelCombo->addItem(groupPair.first); labelCombo->setCurrentText(labelCombo->itemText(0)); QStackedWidget *stack = ui->stackedWidget_WildMons; int labelIndex = 0; - for (auto labelPair : project->wildMonData[map->constantName]) { + for (auto labelPair : project->wildMonData[map->constantName()]) { QString label = labelPair.first; - WildPokemonHeader header = project->wildMonData[map->constantName][label]; + WildPokemonHeader header = project->wildMonData[map->constantName()][label]; MonTabWidget *tabWidget = new MonTabWidget(this); stack->insertWidget(labelIndex++, tabWidget); @@ -267,7 +267,7 @@ void Editor::displayWildMonTables() { tabWidget->clearTableAt(tabIndex); - if (project->wildMonData.contains(map->constantName) && header.wildMons[fieldName].active) { + if (project->wildMonData.contains(map->constantName()) && header.wildMons[fieldName].active) { tabWidget->populateTab(tabIndex, header.wildMons[fieldName]); } else { tabWidget->setTabActive(tabIndex, false); @@ -312,7 +312,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { } }); // Give a default value to the label. - lineEdit->setText(QString("g%1%2").arg(map->name).arg(stack->count())); + lineEdit->setText(QString("g%1%2").arg(map->name()).arg(stack->count())); // Fields [x] copy from existing QLabel *fieldsLabel = new QLabel("Fields:"); @@ -415,9 +415,9 @@ void Editor::deleteWildMonGroup() { msgBox.exec(); if (msgBox.clickedButton() == deleteButton) { - auto it = project->wildMonData.find(map->constantName); + auto it = project->wildMonData.find(map->constantName()); if (it == project->wildMonData.end()) { - logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName)); + logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName())); return; } @@ -698,7 +698,7 @@ void Editor::saveEncounterTabData() { if (!stack->count()) return; - tsl::ordered_map &encounterMap = project->wildMonData[map->constantName]; + tsl::ordered_map &encounterMap = project->wildMonData[map->constantName()]; for (int groupIndex = 0; groupIndex < stack->count(); groupIndex++) { MonTabWidget *tabWidget = static_cast(stack->widget(groupIndex)); @@ -863,13 +863,13 @@ void Editor::addConnection(MapConnection *connection) { // It's possible this is a Dive/Emerge connection, but that's ok (no selection will occur). connection_to_select = connection; - this->map->editHistory.push(new MapConnectionAdd(this->map, connection)); + this->map->commit(new MapConnectionAdd(this->map, connection)); } void Editor::removeConnection(MapConnection *connection) { if (!connection) return; - this->map->editHistory.push(new MapConnectionRemove(this->map, connection)); + this->map->commit(new MapConnectionRemove(this->map, connection)); } void Editor::removeConnectionPixmap(MapConnection *connection) { @@ -986,7 +986,7 @@ void Editor::setDivingMapName(QString mapName, QString direction) { if (mapName.isEmpty()) { removeConnection(connection); } else { - map->editHistory.push(new MapConnectionChangeMap(connection, mapName)); + map->commit(new MapConnectionChangeMap(connection, mapName)); } } else if (!mapName.isEmpty()) { // Create new connection @@ -1268,10 +1268,10 @@ bool Editor::setMap(QString map_name) { this->map = loadedMap; - setLayout(map->layout->id); + setLayout(map->layout()->id); - editGroup.addStack(&map->editHistory); - editGroup.setActiveStack(&map->editHistory); + editGroup.addStack(map->editHistory()); + editGroup.setActiveStack(map->editHistory()); selected_events->clear(); if (!displayMap()) { @@ -1469,7 +1469,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } selection_origin = QPoint(pos.x(), pos.y()); - map->editHistory.push(new EventShift(selectedEvents, xDelta, yDelta, actionId)); + map->commit(new EventShift(selectedEvents, xDelta, yDelta, actionId)); } } } @@ -1784,8 +1784,7 @@ void Editor::displayMapEvents() { events_group = new QGraphicsItemGroup; scene->addItem(events_group); - QList events = map->getAllEvents(); - for (Event *event : events) { + for (const auto &event : map->getEvents()) { project->setEventPixmap(event); addMapEvent(event); } @@ -2032,14 +2031,14 @@ void Editor::updateBorderVisibility() { // When connecting a map to itself we don't bother to re-render the map connections in real-time, // i.e. if the user paints a new metatile on the map this isn't immediately reflected in the connection. // We're rendering them now, so we take the opportunity to do a full re-render for self-connections. - bool fullRender = (this->map && item->connection && this->map->name == item->connection->targetMapName()); + bool fullRender = (this->map && item->connection && this->map->name() == item->connection->targetMapName()); item->render(fullRender); } } void Editor::updateCustomMapHeaderValues(QTableWidget *table) { - map->customHeaders = CustomAttributesTable::getAttributes(table); + map->setCustomAttributes(CustomAttributesTable::getAttributes(table)); map->modify(); } @@ -2080,13 +2079,13 @@ void Editor::redrawObject(DraggablePixmapItem *item) { void Editor::updateWarpEventWarning(Event *event) { if (porymapConfig.warpBehaviorWarningDisabled) return; - if (!project || !map || !map->layout || !event || event->getEventType() != Event::Type::Warp) + if (!project || !map || !map->layout() || !event || event->getEventType() != Event::Type::Warp) return; Block block; Metatile * metatile = nullptr; WarpEvent * warpEvent = static_cast(event); - if (map->layout->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) { - metatile = Tileset::getMetatile(block.metatileId(), map->layout->tileset_primary, map->layout->tileset_secondary); + if (map->layout()->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) { + metatile = Tileset::getMetatile(block.metatileId(), map->layout()->tileset_primary, map->layout()->tileset_secondary); } // metatile may be null if the warp is in the map border. Display the warning in this case bool validWarpBehavior = metatile && projectConfig.warpBehaviors.contains(metatile->behavior()); @@ -2144,10 +2143,7 @@ void Editor::selectMapEvent(DraggablePixmapItem *object, bool toggle) { void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { int event_offs = Event::getIndexOffset(eventGroup); index = index - event_offs; - Event *event = nullptr; - if (index < this->map->events.value(eventGroup).length()) { - event = this->map->events.value(eventGroup).at(index); - } + Event *event = this->map->getEvent(eventGroup, index); DraggablePixmapItem *selectedEvent = nullptr; for (QGraphicsItem *child : this->events_group->childItems()) { DraggablePixmapItem *item = static_cast(child); @@ -2189,7 +2185,7 @@ void Editor::duplicateSelectedEvents() { duplicate->setY(duplicate->getY() + 1); selectedEvents.append(duplicate); } - map->editHistory.push(new EventDuplicate(this, map, selectedEvents)); + map->commit(new EventDuplicate(this, map, selectedEvents)); } DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { @@ -2209,7 +2205,7 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { ((HealLocationEvent *)event)->setIndex(project->healLocations.length()); } - map->editHistory.push(new EventCreate(this, map, event)); + map->commit(new EventCreate(this, map, event)); return event->getPixmapItem(); } @@ -2217,7 +2213,7 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { bool Editor::eventLimitReached(Event::Type event_type) { if (project && map) { if (Event::typeToGroup(event_type) == Event::Group::Object) - return map->events.value(Event::Group::Object).length() >= project->getMaxObjectEvents(); + return map->getNumEvents(Event::Group::Object) >= project->getMaxObjectEvents(); } return false; } @@ -2246,14 +2242,12 @@ void Editor::deleteSelectedEvents() { // If deleting multiple events, just let editor work out next selected. if (numDeleted == 1) { Event::Group event_group = selectedEvents[0]->getEventGroup(); - int index = this->map->events.value(event_group).indexOf(selectedEvents[0]); - if (index != this->map->events.value(event_group).size() - 1) + int index = this->map->getIndexOfEvent(selectedEvents[0]); + if (index != this->map->getNumEvents(event_group) - 1) index++; else index--; - Event *event = nullptr; - if (index >= 0) - event = this->map->events.value(event_group).at(index); + Event *event = this->map->getEvent(event_group, index); for (QGraphicsItem *child : this->events_group->childItems()) { DraggablePixmapItem *event_item = static_cast(child); if (event_item->event == event) { @@ -2262,7 +2256,7 @@ void Editor::deleteSelectedEvents() { } } } - this->map->editHistory.push(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); + this->map->commit(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d9e65eb3..c8885f62 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -442,7 +442,7 @@ void MainWindow::updateWindowTitle() { if (editor->map) { setWindowTitle(QString("%1%2 - %3") .arg(editor->map->hasUnsavedChanges() ? "* " : "") - .arg(editor->map->name) + .arg(editor->map->name()) .arg(projectName) ); } else { @@ -471,7 +471,7 @@ void MainWindow::markMapEdited() { void MainWindow::markSpecificMapEdited(Map* map) { if (!map) return; - map->hasUnsavedDataChanges = true; + map->setHasUnsavedDataChanges(true); if (editor && editor->map == map) updateWindowTitle(); @@ -827,7 +827,7 @@ void MainWindow::unsetMap() { // setMap, but with a visible error message in case of failure. // Use when the user is specifically requesting a map to open. bool MainWindow::userSetMap(QString map_name) { - if (editor->map && editor->map->name == map_name) + if (editor->map && editor->map->name() == map_name) return true; // Already set if (map_name == DYNAMIC_MAP_NAME) { @@ -988,20 +988,19 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ // Select the target event. int index = event_id - Event::getIndexOffset(event_group); - QList events = editor->map->events[event_group]; - if (index < events.length() && index >= 0) { - Event *event = events.at(index); + Event* event = editor->map->getEvent(event_group, index); + if (event) { for (DraggablePixmapItem *item : editor->getObjects()) { if (item->event == event) { editor->selected_events->clear(); editor->selected_events->append(item); editor->updateSelectedEvents(); + return; } } - } else { - // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::eventGroupToString(event_group)).arg(event_id).arg(map_name)); } + // Can still warp to this map, but can't select the specified event + logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::eventGroupToString(event_group)).arg(event_id).arg(map_name)); } void MainWindow::displayMapProperties() { @@ -1033,35 +1032,37 @@ void MainWindow::displayMapProperties() { ui->frame_3->setEnabled(true); Map *map = editor->map; - ui->comboBox_PrimaryTileset->setCurrentText(map->layout->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(map->layout->tileset_secondary_label); + ui->comboBox_PrimaryTileset->setCurrentText(map->layout()->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(map->layout()->tileset_secondary_label); - ui->comboBox_Song->setCurrentText(map->song); - ui->comboBox_Location->setCurrentText(map->location); - ui->checkBox_Visibility->setChecked(map->requiresFlash); - ui->comboBox_Weather->setCurrentText(map->weather); - ui->comboBox_Type->setCurrentText(map->type); - ui->comboBox_BattleScene->setCurrentText(map->battle_scene); - ui->checkBox_ShowLocation->setChecked(map->show_location); - ui->checkBox_AllowRunning->setChecked(map->allowRunning); - ui->checkBox_AllowBiking->setChecked(map->allowBiking); - ui->checkBox_AllowEscaping->setChecked(map->allowEscaping); - ui->spinBox_FloorNumber->setValue(map->floorNumber); + ui->comboBox_Song->setCurrentText(map->song()); + ui->comboBox_Location->setCurrentText(map->location()); + ui->checkBox_Visibility->setChecked(map->requiresFlash()); + ui->comboBox_Weather->setCurrentText(map->weather()); + ui->comboBox_Type->setCurrentText(map->type()); + ui->comboBox_BattleScene->setCurrentText(map->battleScene()); + ui->checkBox_ShowLocation->setChecked(map->showsLocation()); + ui->checkBox_AllowRunning->setChecked(map->allowsRunning()); + ui->checkBox_AllowBiking->setChecked(map->allowsBiking()); + ui->checkBox_AllowEscaping->setChecked(map->allowsEscaping()); + ui->spinBox_FloorNumber->setValue(map->floorNumber()); // Custom fields table. +/* // TODO: Re-enable ui->tableWidget_CustomHeaderFields->blockSignals(true); ui->tableWidget_CustomHeaderFields->setRowCount(0); for (auto it = map->customHeaders.begin(); it != map->customHeaders.end(); it++) CustomAttributesTable::addAttribute(ui->tableWidget_CustomHeaderFields, it.key(), it.value()); ui->tableWidget_CustomHeaderFields->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); ui->tableWidget_CustomHeaderFields->blockSignals(false); +*/ } void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { if (editor && editor->project && editor->map) { if (editor->project->mapLayouts.contains(text)) { editor->map->setLayout(editor->project->loadLayout(text)); - setMap(editor->map->name); + setMap(editor->map->name()); markMapEdited(); } } @@ -1070,7 +1071,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) { if (editor && editor->map) { - editor->map->song = song; + editor->map->setSong(song); markMapEdited(); } } @@ -1078,7 +1079,7 @@ void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) void MainWindow::on_comboBox_Location_currentTextChanged(const QString &location) { if (editor && editor->map) { - editor->map->location = location; + editor->map->setLocation(location); markMapEdited(); } } @@ -1086,7 +1087,7 @@ void MainWindow::on_comboBox_Location_currentTextChanged(const QString &location void MainWindow::on_comboBox_Weather_currentTextChanged(const QString &weather) { if (editor && editor->map) { - editor->map->weather = weather; + editor->map->setWeather(weather); markMapEdited(); } } @@ -1094,7 +1095,7 @@ void MainWindow::on_comboBox_Weather_currentTextChanged(const QString &weather) void MainWindow::on_comboBox_Type_currentTextChanged(const QString &type) { if (editor && editor->map) { - editor->map->type = type; + editor->map->setType(type); markMapEdited(); } } @@ -1102,7 +1103,7 @@ void MainWindow::on_comboBox_Type_currentTextChanged(const QString &type) void MainWindow::on_comboBox_BattleScene_currentTextChanged(const QString &battle_scene) { if (editor && editor->map) { - editor->map->battle_scene = battle_scene; + editor->map->setBattleScene(battle_scene); markMapEdited(); } } @@ -1110,7 +1111,7 @@ void MainWindow::on_comboBox_BattleScene_currentTextChanged(const QString &battl void MainWindow::on_checkBox_Visibility_stateChanged(int selected) { if (editor && editor->map) { - editor->map->requiresFlash = (selected == Qt::Checked); + editor->map->setRequiresFlash(selected == Qt::Checked); markMapEdited(); } } @@ -1118,7 +1119,7 @@ void MainWindow::on_checkBox_Visibility_stateChanged(int selected) void MainWindow::on_checkBox_ShowLocation_stateChanged(int selected) { if (editor && editor->map) { - editor->map->show_location = (selected == Qt::Checked); + editor->map->setShowsLocation(selected == Qt::Checked); markMapEdited(); } } @@ -1126,7 +1127,7 @@ void MainWindow::on_checkBox_ShowLocation_stateChanged(int selected) void MainWindow::on_checkBox_AllowRunning_stateChanged(int selected) { if (editor && editor->map) { - editor->map->allowRunning = (selected == Qt::Checked); + editor->map->setAllowsRunning(selected == Qt::Checked); markMapEdited(); } } @@ -1134,7 +1135,7 @@ void MainWindow::on_checkBox_AllowRunning_stateChanged(int selected) void MainWindow::on_checkBox_AllowBiking_stateChanged(int selected) { if (editor && editor->map) { - editor->map->allowBiking = (selected == Qt::Checked); + editor->map->setAllowsBiking(selected == Qt::Checked); markMapEdited(); } } @@ -1142,7 +1143,7 @@ void MainWindow::on_checkBox_AllowBiking_stateChanged(int selected) void MainWindow::on_checkBox_AllowEscaping_stateChanged(int selected) { if (editor && editor->map) { - editor->map->allowEscaping = (selected == Qt::Checked); + editor->map->setAllowsEscaping(selected == Qt::Checked); markMapEdited(); } } @@ -1150,7 +1151,7 @@ void MainWindow::on_checkBox_AllowEscaping_stateChanged(int selected) void MainWindow::on_spinBox_FloorNumber_valueChanged(int offset) { if (editor && editor->map) { - editor->map->floorNumber = offset; + editor->map->setFloorNumber(offset); markMapEdited(); } } @@ -1251,7 +1252,7 @@ void MainWindow::refreshLocationsComboBox() { ui->comboBox_Location->clear(); ui->comboBox_Location->addItems(locations); if (this->editor->map) - ui->comboBox_Location->setCurrentText(this->editor->map->location); + ui->comboBox_Location->setCurrentText(this->editor->map->location()); } void MainWindow::clearProjectUI() { @@ -1309,7 +1310,7 @@ void MainWindow::scrollMapList(MapTree *list, QString itemName) { void MainWindow::scrollMapListToCurrentMap(MapTree *list) { if (this->editor->map) { - scrollMapList(list, this->editor->map->name); + scrollMapList(list, this->editor->map->name()); } } @@ -1591,7 +1592,7 @@ void MainWindow::mapListAddArea() { } void MainWindow::onNewMapCreated() { - QString newMapName = this->newMapPrompt->map->name; + QString newMapName = this->newMapPrompt->map->name(); int newMapGroup = this->newMapPrompt->group; Map *newMap = this->newMapPrompt->map; bool existingLayout = this->newMapPrompt->existingLayout; @@ -1607,8 +1608,8 @@ void MainWindow::onNewMapCreated() { // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); - this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); - this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); + this->mapAreaModel->insertMapItem(newMapName, newMap->location(), newMapGroup); + this->layoutTreeModel->insertMapItem(newMapName, newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps // (other combo boxes like for warp destinations are repopulated when the map changes). @@ -1622,16 +1623,16 @@ void MainWindow::onNewMapCreated() { // Refresh layout combo box (if a new one was created) if (!existingLayout) { - int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout->id); + int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout()->id); if (layoutIndex >= 0) { const QSignalBlocker b_Layouts(ui->comboBox_LayoutSelector); - ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout->id); + ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout()->id); } } setMap(newMapName); - if (newMap->needsHealLocation) { + if (newMap->needsHealLocation()) { addNewEvent(Event::Type::HealLocation); editor->project->saveHealLocations(newMap); editor->save(); @@ -1862,9 +1863,9 @@ void MainWindow::openMapListItem(const QModelIndex &index) { void MainWindow::updateMapList() { if (this->editor->map) { - this->mapGroupModel->setMap(this->editor->map->name); + this->mapGroupModel->setMap(this->editor->map->name()); this->groupListProxyModel->layoutChanged(); - this->mapAreaModel->setMap(this->editor->map->name); + this->mapAreaModel->setMap(this->editor->map->name()); this->areaListProxyModel->layoutChanged(); } else { this->mapGroupModel->setMap(QString()); @@ -2107,7 +2108,7 @@ void MainWindow::paste() { } if (!newEvents.empty()) { - editor->map->editHistory.push(new EventPaste(this->editor, editor->map, newEvents)); + editor->map->commit(new EventPaste(this->editor, editor->map, newEvents)); updateObjects(); } @@ -2329,7 +2330,7 @@ void MainWindow::addNewEvent(Event::Type type) { void MainWindow::tryAddEventTab(QWidget * tab) { auto group = getEventGroupFromTabWidget(tab); - if (editor->map->events.value(group).length()) + if (editor->map->getNumEvents(group)) ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::eventGroupToString(group))); } @@ -2363,7 +2364,7 @@ void MainWindow::updateSelectedObjects() { else { QList all_events; if (editor->map) { - all_events = editor->map->getAllEvents(); + all_events = editor->map->getEvents(); } if (all_events.length()) { DraggablePixmapItem *selectedEvent = all_events.first()->getPixmapItem(); @@ -2397,7 +2398,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_ObjectID); this->ui->spinner_ObjectID->setMinimum(event_offs); - this->ui->spinner_ObjectID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_ObjectID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_ObjectID->setValue(current->getEventIndex() + event_offs); break; } @@ -2408,7 +2409,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_WarpID); this->ui->spinner_WarpID->setMinimum(event_offs); - this->ui->spinner_WarpID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_WarpID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_WarpID->setValue(current->getEventIndex() + event_offs); break; } @@ -2419,7 +2420,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_TriggerID); this->ui->spinner_TriggerID->setMinimum(event_offs); - this->ui->spinner_TriggerID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_TriggerID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_TriggerID->setValue(current->getEventIndex() + event_offs); break; } @@ -2430,7 +2431,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_BgID); this->ui->spinner_BgID->setMinimum(event_offs); - this->ui->spinner_BgID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_BgID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_BgID->setValue(current->getEventIndex() + event_offs); break; } @@ -2441,7 +2442,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_HealID); this->ui->spinner_HealID->setMinimum(event_offs); - this->ui->spinner_HealID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_HealID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_HealID->setValue(current->getEventIndex() + event_offs); break; } @@ -2534,8 +2535,8 @@ void MainWindow::eventTabChanged(int index) { } if (!isProgrammaticEventTabChange) { - if (!selectedEvent && editor->map->events.value(group).count()) { - Event *event = editor->map->events.value(group).at(0); + if (!selectedEvent && editor->map->getNumEvents(group)) { + Event *event = editor->map->getEvent(group, 0); for (QGraphicsItem *child : editor->events_group->childItems()) { DraggablePixmapItem *item = static_cast(child); if (item->event == event) { @@ -3222,7 +3223,7 @@ void MainWindow::reloadScriptEngine() { // Lying to the scripts here, simulating a project reload Scripting::cb_ProjectOpened(projectConfig.projectDir); if (editor && editor->map) - Scripting::cb_MapOpened(editor->map->name); // TODO: API should have equivalent for layout + Scripting::cb_MapOpened(editor->map->name()); // TODO: API should have equivalent for layout } void MainWindow::on_pushButton_AddCustomHeaderField_clicked() diff --git a/src/project.cpp b/src/project.cpp index 22487a13..1a9abffd 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -195,11 +195,11 @@ QSet Project::getTopLevelMapFields() { } bool Project::loadMapData(Map* map) { - if (!map->isPersistedToFile) { + if (!map->isPersistedToFile()) { return true; } - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map->name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); + QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map->name()).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); QJsonDocument mapDoc; if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { logError(QString("Failed to read map data from %1").arg(mapFilepath)); @@ -208,28 +208,28 @@ bool Project::loadMapData(Map* map) { QJsonObject mapObj = mapDoc.object(); - map->song = ParseUtil::jsonToQString(mapObj["music"]); - map->layoutId = ParseUtil::jsonToQString(mapObj["layout"]); - map->location = ParseUtil::jsonToQString(mapObj["region_map_section"]); - map->requiresFlash = ParseUtil::jsonToBool(mapObj["requires_flash"]); - map->weather = ParseUtil::jsonToQString(mapObj["weather"]); - map->type = ParseUtil::jsonToQString(mapObj["map_type"]); - map->show_location = ParseUtil::jsonToBool(mapObj["show_map_name"]); - map->battle_scene = ParseUtil::jsonToQString(mapObj["battle_scene"]); + map->setSong(ParseUtil::jsonToQString(mapObj["music"])); + map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); + map->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); + map->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); + map->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); + map->setType(ParseUtil::jsonToQString(mapObj["map_type"])); + map->setShowsLocation(ParseUtil::jsonToBool(mapObj["show_map_name"])); + map->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); if (projectConfig.mapAllowFlagsEnabled) { - map->allowBiking = ParseUtil::jsonToBool(mapObj["allow_cycling"]); - map->allowEscaping = ParseUtil::jsonToBool(mapObj["allow_escaping"]); - map->allowRunning = ParseUtil::jsonToBool(mapObj["allow_running"]); + map->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); + map->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); + map->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); } if (projectConfig.floorNumberEnabled) { - map->floorNumber = ParseUtil::jsonToInt(mapObj["floor_number"]); + map->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); } - map->sharedEventsMap = ParseUtil::jsonToQString(mapObj["shared_events_map"]); - map->sharedScriptsMap = ParseUtil::jsonToQString(mapObj["shared_scripts_map"]); + map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); + map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); // Events - map->events[Event::Group::Object].clear(); + map->resetEvents(); QJsonArray objectEventsArr = mapObj["object_events"].toArray(); for (int i = 0; i < objectEventsArr.size(); i++) { QJsonObject event = objectEventsArr[i].toObject(); @@ -248,11 +248,10 @@ bool Project::loadMapData(Map* map) { delete clone; } } else { - logError(QString("Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'.").arg(map->name).arg(i).arg(type)); + logError(QString("Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'.").arg(map->name()).arg(i).arg(type)); } } - map->events[Event::Group::Warp].clear(); QJsonArray warpEventsArr = mapObj["warp_events"].toArray(); for (int i = 0; i < warpEventsArr.size(); i++) { QJsonObject event = warpEventsArr[i].toObject(); @@ -265,7 +264,6 @@ bool Project::loadMapData(Map* map) { } } - map->events[Event::Group::Coord].clear(); QJsonArray coordEventsArr = mapObj["coord_events"].toArray(); for (int i = 0; i < coordEventsArr.size(); i++) { QJsonObject event = coordEventsArr[i].toObject(); @@ -279,11 +277,10 @@ bool Project::loadMapData(Map* map) { coord->loadFromJson(event, this); map->addEvent(coord); } else { - logError(QString("Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'.").arg(map->name).arg(i).arg(type)); + logError(QString("Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'.").arg(map->name()).arg(i).arg(type)); } } - map->events[Event::Group::Bg].clear(); QJsonArray bgEventsArr = mapObj["bg_events"].toArray(); for (int i = 0; i < bgEventsArr.size(); i++) { QJsonObject event = bgEventsArr[i].toObject(); @@ -301,12 +298,12 @@ bool Project::loadMapData(Map* map) { bg->loadFromJson(event, this); map->addEvent(bg); } else { - logError(QString("Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'.").arg(map->name).arg(i).arg(type)); + logError(QString("Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'.").arg(map->name()).arg(i).arg(type)); } } - map->events[Event::Group::Heal].clear(); - + +/* TODO: Re-enable const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); for (auto it = healLocations.begin(); it != healLocations.end(); it++) { HealLocation loc = *it; @@ -328,6 +325,7 @@ bool Project::loadMapData(Map* map) { map->ownedEvents.append(heal); } } +*/ map->deleteConnections(); QJsonArray connectionsArr = mapObj["connections"].toArray(); @@ -347,19 +345,21 @@ bool Project::loadMapData(Map* map) { } // Check for custom fields +/* // TODO: Re-enable QSet baseFields = this->getTopLevelMapFields(); for (QString key : mapObj.keys()) { if (!baseFields.contains(key)) { map->customHeaders.insert(key, mapObj[key]); } } +*/ return true; } QString Project::readMapLayoutId(QString map_name) { if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->layoutId; + return mapCache.value(map_name)->layoutId(); } QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); @@ -375,7 +375,7 @@ QString Project::readMapLayoutId(QString map_name) { QString Project::readMapLocation(QString map_name) { if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->location; + return mapCache.value(map_name)->location(); } QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); @@ -473,21 +473,21 @@ Layout *Project::loadLayout(QString layoutId) { } bool Project::loadMapLayout(Map* map) { - if (!map->isPersistedToFile) { + if (!map->isPersistedToFile()) { return true; } - if (mapLayouts.contains(map->layoutId)) { - map->layout = mapLayouts[map->layoutId]; + if (mapLayouts.contains(map->layoutId())) { + map->setLayout(mapLayouts[map->layoutId()]); } else { - logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name).arg(map->layoutId)); + logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } if (map->hasUnsavedChanges()) { return true; } else { - return loadLayout(map->layout); + return loadLayout(map->layout()); } } @@ -848,12 +848,14 @@ void Project::saveHealLocations(Map *map) { // Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h void Project::saveHealLocationsData(Map *map) { // 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; @@ -1262,14 +1264,14 @@ void Project::saveAllMaps() { void Project::saveMap(Map *map) { // Create/Modify a few collateral files for brand new maps. QString basePath = projectConfig.getFilePath(ProjectFilePath::data_map_folders); - QString mapDataDir = root + "/" + basePath + map->name; - if (!map->isPersistedToFile) { + QString mapDataDir = root + "/" + basePath + map->name(); + if (!map->isPersistedToFile()) { if (!QDir::root().mkdir(mapDataDir)) { logError(QString("Error: failed to create directory for new map: '%1'").arg(mapDataDir)); } // Create file data/maps//scripts.inc - QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name); + QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name()); saveTextFile(mapDataDir + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); if (projectConfig.createMapTextFileEnabled) { @@ -1278,14 +1280,14 @@ void Project::saveMap(Map *map) { } // Simply append to data/event_scripts.s. - text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name); + text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name()); if (projectConfig.createMapTextFileEnabled) { - text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name); + text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name()); } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); - if (map->needsLayoutDir) { - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name); + if (map->needsLayoutDir()) { + QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); if (!QDir::root().mkdir(newLayoutDir)) { logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); } @@ -1302,24 +1304,24 @@ void Project::saveMap(Map *map) { OrderedJson::object mapObj; // Header values. - mapObj["id"] = map->constantName; - mapObj["name"] = map->name; - mapObj["layout"] = map->layout->id; - mapObj["music"] = map->song; - mapObj["region_map_section"] = map->location; - mapObj["requires_flash"] = map->requiresFlash; - mapObj["weather"] = map->weather; - mapObj["map_type"] = map->type; + mapObj["id"] = map->constantName(); + mapObj["name"] = map->name(); + mapObj["layout"] = map->layout()->id; + mapObj["music"] = map->song(); + mapObj["region_map_section"] = map->location(); + mapObj["requires_flash"] = map->requiresFlash(); + mapObj["weather"] = map->weather(); + mapObj["map_type"] = map->type(); if (projectConfig.mapAllowFlagsEnabled) { - mapObj["allow_cycling"] = map->allowBiking; - mapObj["allow_escaping"] = map->allowEscaping; - mapObj["allow_running"] = map->allowRunning; + mapObj["allow_cycling"] = map->allowsBiking(); + mapObj["allow_escaping"] = map->allowsEscaping(); + mapObj["allow_running"] = map->allowsRunning(); } - mapObj["show_map_name"] = map->show_location; + mapObj["show_map_name"] = map->showsLocation(); if (projectConfig.floorNumberEnabled) { - mapObj["floor_number"] = map->floorNumber; + mapObj["floor_number"] = map->floorNumber(); } - mapObj["battle_scene"] = map->battle_scene; + mapObj["battle_scene"] = map->battleScene(); // Connections auto connections = map->getConnections(); @@ -1341,67 +1343,59 @@ void Project::saveMap(Map *map) { mapObj["connections"] = QJsonValue::Null; } - if (map->sharedEventsMap.isEmpty()) { + if (map->sharedEventsMap().isEmpty()) { // Object events OrderedJson::array objectEventsArr; - for (int i = 0; i < map->events[Event::Group::Object].length(); i++) { - Event *event = map->events[Event::Group::Object].value(i); - OrderedJson::object jsonObj = event->buildEventJson(this); - objectEventsArr.push_back(jsonObj); + for (const auto &event : map->getEvents(Event::Group::Object)){ + objectEventsArr.push_back(event->buildEventJson(this)); } mapObj["object_events"] = objectEventsArr; // Warp events OrderedJson::array warpEventsArr; - for (int i = 0; i < map->events[Event::Group::Warp].length(); i++) { - Event *event = map->events[Event::Group::Warp].value(i); - OrderedJson::object warpObj = event->buildEventJson(this); - warpEventsArr.append(warpObj); + for (const auto &event : map->getEvents(Event::Group::Warp)) { + warpEventsArr.push_back(event->buildEventJson(this)); } mapObj["warp_events"] = warpEventsArr; // Coord events OrderedJson::array coordEventsArr; - for (int i = 0; i < map->events[Event::Group::Coord].length(); i++) { - Event *event = map->events[Event::Group::Coord].value(i); - OrderedJson::object triggerObj = event->buildEventJson(this); - coordEventsArr.append(triggerObj); + for (const auto &event : map->getEvents(Event::Group::Coord)) { + coordEventsArr.push_back(event->buildEventJson(this)); } mapObj["coord_events"] = coordEventsArr; // Bg Events OrderedJson::array bgEventsArr; - for (int i = 0; i < map->events[Event::Group::Bg].length(); i++) { - Event *event = map->events[Event::Group::Bg].value(i); - OrderedJson::object bgObj = event->buildEventJson(this); - bgEventsArr.append(bgObj); + for (const auto &event : map->getEvents(Event::Group::Bg)) { + bgEventsArr.push_back(event->buildEventJson(this)); } mapObj["bg_events"] = bgEventsArr; } else { - mapObj["shared_events_map"] = map->sharedEventsMap; + mapObj["shared_events_map"] = map->sharedEventsMap(); } - if (!map->sharedScriptsMap.isEmpty()) { - mapObj["shared_scripts_map"] = map->sharedScriptsMap; + if (!map->sharedScriptsMap().isEmpty()) { + mapObj["shared_scripts_map"] = map->sharedScriptsMap(); } // Custom header fields. +/* // TODO: Re-enable for (QString key : map->customHeaders.keys()) { mapObj[key] = OrderedJson::fromQJsonValue(map->customHeaders[key]); } +*/ OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); jsonDoc.dump(&mapFile); mapFile.close(); - saveLayout(map->layout); + saveLayout(map->layout()); saveHealLocations(map); - map->isPersistedToFile = true; - map->hasUnsavedDataChanges = false; - map->editHistory.setClean(); + map->setClean(); } void Project::saveLayout(Layout *layout) { @@ -1910,25 +1904,24 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool this->mapGroups.insert(mapName, groupNum); this->groupedMapNames[groupNum].append(mapName); - newMap->isPersistedToFile = false; + newMap->setIsPersistedToFile(false); newMap->setName(mapName); - this->mapConstantsToMapNames.insert(newMap->constantName, newMap->name); - this->mapNamesToMapConstants.insert(newMap->name, newMap->constantName); + this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); + this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); if (!existingLayout) { - this->mapLayouts.insert(newMap->layoutId, newMap->layout); - this->mapLayoutsTable.append(newMap->layoutId); - this->layoutIdsToNames.insert(newMap->layout->id, newMap->layout->name); + this->mapLayouts.insert(newMap->layoutId(), newMap->layout()); + this->mapLayoutsTable.append(newMap->layoutId()); + this->layoutIdsToNames.insert(newMap->layout()->id, newMap->layout()->name); if (!importedMap) { - setNewLayoutBlockdata(newMap->layout); + setNewLayoutBlockdata(newMap->layout()); } - if (newMap->layout->border.isEmpty()) { - setNewLayoutBorder(newMap->layout); + if (newMap->layout()->border.isEmpty()) { + setNewLayoutBorder(newMap->layout()); } } - loadLayoutTilesets(newMap->layout); - setNewMapEvents(newMap); + loadLayoutTilesets(newMap->layout()); return newMap; } @@ -2854,14 +2847,6 @@ bool Project::readSpeciesIconPaths() { return true; } -void Project::setNewMapEvents(Map *map) { - map->events[Event::Group::Object].clear(); - map->events[Event::Group::Warp].clear(); - map->events[Event::Group::Heal].clear(); - map->events[Event::Group::Coord].clear(); - map->events[Event::Group::Bg].clear(); -} - int Project::getNumTilesPrimary() { return Project::num_tiles_primary; diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 98478f94..d13d660b 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -811,10 +811,12 @@ QJSValue MainWindow::getTilePixels(int tileId) { // Editing map header //===================== +// TODO: Replace UI setting here with calls to appropriate set functions. Update UI with signals from Map + QString MainWindow::getSong() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->song; + return this->editor->map->song(); } void MainWindow::setSong(QString song) { @@ -830,7 +832,7 @@ void MainWindow::setSong(QString song) { QString MainWindow::getLocation() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->location; + return this->editor->map->location(); } void MainWindow::setLocation(QString location) { @@ -846,7 +848,7 @@ void MainWindow::setLocation(QString location) { bool MainWindow::getRequiresFlash() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->requiresFlash; + return this->editor->map->requiresFlash(); } void MainWindow::setRequiresFlash(bool require) { @@ -858,7 +860,7 @@ void MainWindow::setRequiresFlash(bool require) { QString MainWindow::getWeather() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->weather; + return this->editor->map->weather(); } void MainWindow::setWeather(QString weather) { @@ -874,7 +876,7 @@ void MainWindow::setWeather(QString weather) { QString MainWindow::getType() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->type; + return this->editor->map->type(); } void MainWindow::setType(QString type) { @@ -890,7 +892,7 @@ void MainWindow::setType(QString type) { QString MainWindow::getBattleScene() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->battle_scene; + return this->editor->map->battleScene(); } void MainWindow::setBattleScene(QString battleScene) { @@ -906,7 +908,7 @@ void MainWindow::setBattleScene(QString battleScene) { bool MainWindow::getShowLocationName() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->show_location; + return this->editor->map->showsLocation(); } void MainWindow::setShowLocationName(bool show) { @@ -918,7 +920,7 @@ void MainWindow::setShowLocationName(bool show) { bool MainWindow::getAllowRunning() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowRunning; + return this->editor->map->allowsRunning(); } void MainWindow::setAllowRunning(bool allow) { @@ -930,7 +932,7 @@ void MainWindow::setAllowRunning(bool allow) { bool MainWindow::getAllowBiking() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowBiking; + return this->editor->map->allowsBiking(); } void MainWindow::setAllowBiking(bool allow) { @@ -942,7 +944,7 @@ void MainWindow::setAllowBiking(bool allow) { bool MainWindow::getAllowEscaping() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowEscaping; + return this->editor->map->allowsEscaping(); } void MainWindow::setAllowEscaping(bool allow) { @@ -954,7 +956,7 @@ void MainWindow::setAllowEscaping(bool allow) { int MainWindow::getFloorNumber() { if (!this->editor || !this->editor->map) return 0; - return this->editor->map->floorNumber; + return this->editor->map->floorNumber(); } void MainWindow::setFloorNumber(int floorNumber) { diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index 35e07a15..d3ff13ae 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -66,7 +66,7 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari // This is convoluted because of how our edit history works; this would otherwise just be 'this->connection->setOffset(newOffset);' if (this->connection->parentMap() && newOffset != this->connection->offset()) - this->connection->parentMap()->editHistory.push(new MapConnectionMove(this->connection, newOffset, this->actionId)); + this->connection->parentMap()->commit(new MapConnectionMove(this->connection, newOffset, this->actionId)); return QPointF(x, y); } diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index ccdf7e6c..dcff253c 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -79,24 +79,24 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(QString direction) { this->setSelected(true); if (this->map) - this->map->editHistory.push(new MapConnectionChangeDirection(this->connection, direction)); + this->map->commit(new MapConnectionChangeDirection(this->connection, direction)); } void ConnectionsListItem::on_comboBox_Map_currentTextChanged(QString mapName) { this->setSelected(true); if (this->map && ui->comboBox_Map->findText(mapName) >= 0) - this->map->editHistory.push(new MapConnectionChangeMap(this->connection, mapName)); + this->map->commit(new MapConnectionChangeMap(this->connection, mapName)); } void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) { this->setSelected(true); if (this->map) - this->map->editHistory.push(new MapConnectionMove(this->connection, offset, this->actionId)); + this->map->commit(new MapConnectionMove(this->connection, offset, this->actionId)); } void ConnectionsListItem::on_button_Delete_clicked() { if (this->map) - this->map->editHistory.push(new MapConnectionRemove(this->map, this->connection)); + this->map->commit(new MapConnectionRemove(this->map, this->connection)); } void ConnectionsListItem::on_button_OpenMap_clicked() { diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 59b069dd..78a00bb6 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -91,7 +91,7 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { } else { selectedEvents.append(this->event); } - editor->map->editHistory.push(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId)); + editor->map->commit(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId)); this->releaseSelectionQueued = false; } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index e90658a2..cb06fea1 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -108,7 +108,7 @@ void EventFrame::connectSignals(MainWindow *) { connect(this->spinner_x, QOverload::of(&QSpinBox::valueChanged), [this](int value) { int delta = value - event->getX(); if (delta) { - this->event->getMap()->editHistory.push(new EventMove(QList() << this->event, delta, 0, this->spinner_x->getActionId())); + this->event->getMap()->commit(new EventMove(QList() << this->event, delta, 0, this->spinner_x->getActionId())); } }); @@ -118,7 +118,7 @@ void EventFrame::connectSignals(MainWindow *) { connect(this->spinner_y, QOverload::of(&QSpinBox::valueChanged), [this](int value) { int delta = value - event->getY(); if (delta) { - this->event->getMap()->editHistory.push(new EventMove(QList() << this->event, 0, delta, this->spinner_y->getActionId())); + this->event->getMap()->commit(new EventMove(QList() << this->event, 0, delta, this->spinner_y->getActionId())); } }); connect(this->event->getPixmapItem(), &DraggablePixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index a7a26629..c92b7a4b 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -53,7 +53,7 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor if (this->map) { this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); - this->ui->comboBox_MapSelection->setCurrentText(map->name); + this->ui->comboBox_MapSelection->setCurrentText(map->name()); this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down } @@ -85,18 +85,19 @@ void MapImageExporter::saveImage() { if (this->preview.isNull()) return; - QString title = getTitle(this->mode); + const QString title = getTitle(this->mode); + const QString itemName = this->map ? this->map->name() : this->layout->name; QString defaultFilename; switch (this->mode) { case ImageExporterMode::Normal: - defaultFilename = this->map? this->map->name : this->layout->name; + defaultFilename = itemName; break; case ImageExporterMode::Stitch: - defaultFilename = QString("Stitch_From_%1").arg(this->map? this->map->name : this->layout->name); + defaultFilename = QString("Stitch_From_%1").arg(itemName); break; case ImageExporterMode::Timelapse: - defaultFilename = QString("Timelapse_%1").arg(this->map? this->map->name : this->layout->name); + defaultFilename = QString("Timelapse_%1").arg(itemName); break; } @@ -121,7 +122,7 @@ void MapImageExporter::saveImage() { timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); // lambda to avoid redundancy - auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack &historyStack){ + auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack *historyStack){ QProgressDialog progress(progressText, "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); @@ -137,9 +138,9 @@ void MapImageExporter::saveImage() { } // Rewind to the specified start of the map edit history. int i = 0; - while (historyStack.canUndo()) { + while (historyStack->canUndo()) { progress.setValue(i); - historyStack.undo(); + historyStack->undo(); int width = this->layout->getWidth() * 16; int height = this->layout->getHeight() * 16; if (this->settings.showBorder) { @@ -161,16 +162,16 @@ void MapImageExporter::saveImage() { while (i > 0) { if (progress.wasCanceled()) { progress.close(); - while (i > 0 && historyStack.canRedo()) { + while (i > 0 && historyStack->canRedo()) { i--; - historyStack.redo(); + historyStack->redo(); } return; } - while (historyStack.canRedo() && - !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + while (historyStack->canRedo() && + !historyItemAppliesToFrame(historyStack->command(historyStack->index()))) { i--; - historyStack.redo(); + historyStack->redo(); } progress.setValue(progress.maximum() - i); QPixmap pixmap = this->getFormattedMapPixmap(this->map); @@ -186,11 +187,11 @@ void MapImageExporter::saveImage() { for (int j = 0; j < this->settings.timelapseSkipAmount; j++) { if (i > 0) { i--; - historyStack.redo(); - while (historyStack.canRedo() && - !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + historyStack->redo(); + while (historyStack->canRedo() && + !historyItemAppliesToFrame(historyStack->command(historyStack->index()))) { i--; - historyStack.redo(); + historyStack->redo(); } } } @@ -202,10 +203,10 @@ void MapImageExporter::saveImage() { }; if (this->layout) - generateTimelapseFromHistory("Building layout timelapse...", this->layout->editHistory); + generateTimelapseFromHistory("Building layout timelapse...", &this->layout->editHistory); if (this->map) - generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory); + generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory()); timelapseImg.save(filepath); break; @@ -279,9 +280,9 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu progress->setValue(visited.size()); StitchedMap cur = unvisited.takeFirst(); - if (visited.contains(cur.map->name)) + if (visited.contains(cur.map->name())) continue; - visited.insert(cur.map->name); + visited.insert(cur.map->name()); stitchedMaps.append(cur); for (MapConnection *connection : cur.map->getConnections()) { @@ -420,22 +421,11 @@ void MapImageExporter::scalePreview() { } } -// THIS QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { - QPixmap pixmap; + Layout *layout = this->map ? this->map->layout() : this->layout; + layout->render(true); - Layout *layout; - - // draw background layer / base image - if (!this->map) { - layout = this->layout; - layout->render(true); - pixmap = layout->pixmap; - } else { - layout = map->layout; - map->layout->render(true); - pixmap = map->layout->pixmap; - } + QPixmap pixmap = layout->pixmap; if (this->settings.showCollision) { QPainter collisionPainter(&pixmap); @@ -494,7 +484,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { if (!ignoreBorder && this->settings.showBorder) { pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; } - const QList events = map->getAllEvents(); + const QList events = map->getEvents(); for (const auto &event : events) { Event::Group group = event->getEventGroup(); if ((this->settings.showObjects && group == Event::Group::Object) diff --git a/src/ui/newmapconnectiondialog.cpp b/src/ui/newmapconnectiondialog.cpp index af06789f..def341fd 100644 --- a/src/ui/newmapconnectiondialog.cpp +++ b/src/ui/newmapconnectiondialog.cpp @@ -33,7 +33,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const QString defaultMapName; if (mapNames.isEmpty()) { defaultMapName = QString(); - } else if (mapNames.first() == map->name && mapNames.length() > 1) { + } else if (mapNames.first() == map->name() && mapNames.length() > 1) { // Prefer not to connect the map to itself defaultMapName = mapNames.at(1); } else { diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index def485c2..b5ebf556 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -129,11 +129,11 @@ void NewMapPopup::init(Layout *mapLayout) { useLayoutSettings(mapLayout); this->map = new Map(); - this->map->layout = new Layout(); - this->map->layout->blockdata = mapLayout->blockdata; + this->map->setLayout(new Layout()); + this->map->layout()->blockdata = mapLayout->blockdata; if (!mapLayout->border.isEmpty()) { - this->map->layout->border = mapLayout->border; + this->map->layout()->border = mapLayout->border; } init(); } @@ -299,22 +299,22 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { newMapName = project->getNewMapName(); } - newMap->name = newMapName; - newMap->type = this->ui->comboBox_NewMap_Type->currentText(); - newMap->location = this->ui->comboBox_NewMap_Location->currentText(); - newMap->song = this->ui->comboBox_NewMap_Song->currentText(); - newMap->requiresFlash = false; - newMap->weather = this->project->weatherNames.value(0, "0"); - newMap->show_location = this->ui->checkBox_NewMap_Show_Location->isChecked(); - newMap->battle_scene = this->project->mapBattleScenes.value(0, "0"); + 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")); if (this->existingLayout) { layout = this->project->mapLayouts.value(this->layoutId); - newMap->needsLayoutDir = false; + newMap->setNeedsLayoutDir(false); } else { layout = new Layout; layout->id = Layout::layoutConstantFromName(newMapName); - layout->name = QString("%1_Layout").arg(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(); if (projectConfig.useCustomBorderSize) { @@ -332,26 +332,25 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { } if (this->importedMap) { - layout->blockdata = map->layout->blockdata; - if (!map->layout->border.isEmpty()) - layout->border = map->layout->border; + layout->blockdata = map->layout()->blockdata; + if (!map->layout()->border.isEmpty()) + layout->border = map->layout()->border; } if (this->ui->checkBox_NewMap_Flyable->isChecked()) { - newMap->needsHealLocation = true; + newMap->setNeedsHealLocation(true); } if (projectConfig.mapAllowFlagsEnabled) { - newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked(); - newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked(); - newMap->allowEscaping = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked(); + 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->floorNumber = this->ui->spinBox_NewMap_Floor_Number->value(); + newMap->setFloorNumber(this->ui->spinBox_NewMap_Floor_Number->value()); } - newMap->layout = layout; - newMap->layoutId = layout->id; + newMap->setLayout(layout); if (this->existingLayout) { project->loadMapLayout(newMap); } From 0a87f7b9451b257d3d918616320fd1732facb8fe Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 12 Nov 2024 13:08:46 -0500 Subject: [PATCH 077/364] update changelog after #515 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9fd720..f92f02b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,14 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ## [Unreleased] ### Added - Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access. +- Add the ability to edit layouts with no corresponding map. - Add a `Close Project` option - Add charts to the `Wild Pokémon` tab that show species and level distributions. - Add options for customizing the map grid under `View -> Grid Settings`. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. +- Add button to enable editing map groups including renaming groups and rearranging the maps within them. +- Add buttons to hide and show empty folders in each map tree view. ### Changed - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. @@ -24,6 +27,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. - `Export Map Stitch Image` now shows a preview of the full image, not just the current map. +- Maps and layouts were internally separated. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. @@ -52,6 +56,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Stop sliders in the Palette Editor from creating a bunch of edit history when used. - Fix scrolling on some containers locking up when the mouse stops over a spin box or combo box. - Fix some file dialogs returning to an incorrect window when closed. +- Fix bug where reloading a layout would overwrite all unsaved changes. +- Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. ## [5.4.1] - 2024-03-21 ### Fixed From acaed90d65be16dbd92b28efa3722b16dd07d4f5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:13:09 -0500 Subject: [PATCH 078/364] Read map.json constants on project open --- include/core/map.h | 4 +- include/project.h | 7 ++- src/core/events.cpp | 10 ++-- src/core/heallocation.cpp | 2 + src/core/map.cpp | 7 +-- src/project.cpp | 113 +++++++++++++++++++------------------- src/ui/maplistmodels.cpp | 4 +- 7 files changed, 75 insertions(+), 72 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 22a2ef29..eab3110e 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -37,8 +37,10 @@ public: ~Map(); public: - void setName(QString mapName); + void setName(const QString &mapName) { m_name = mapName; } QString name() const { return m_name; } + + void setConstantName(const QString &constantName) { m_constantName = constantName; } QString constantName() const { return m_constantName; } static QString mapConstantFromName(QString mapName, bool includePrefix = true); diff --git a/include/project.h b/include/project.h index 0b8b9978..a0105bda 100644 --- a/include/project.h +++ b/include/project.h @@ -18,6 +18,7 @@ #include #include +// TODO: Expose to config // The displayed name of the special map value used by warps with multiple potential destinations static QString DYNAMIC_MAP_NAME = "Dynamic"; @@ -43,6 +44,8 @@ public: QMap healLocationNameToValue; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; + QMap mapNameToLayoutId; + QMap mapNameToMapSectionName; QStringList mapLayoutsTable; QStringList mapLayoutsTableMaster; QString layoutsLabel; @@ -126,9 +129,6 @@ public: QString getNewMapName(); QString getProjectTitle(); - QString readMapLayoutId(QString map_name); - QString readMapLocation(QString map_name); - bool readWildMonData(); tsl::ordered_map> wildMonData; @@ -146,6 +146,7 @@ public: bool hasUnsavedDataChanges = false; QSet getTopLevelMapFields(); + bool readMapJson(const QString &mapName, QJsonDocument * out); bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); diff --git a/src/core/events.cpp b/src/core/events.cpp index 6cd92589..94030d03 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -949,16 +949,16 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *) { void HealLocationEvent::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); - if (!this->getMap()) + if (!this->map) return; + bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString mapConstant = Map::mapConstantFromName(this->getMap()->name(), false); const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix : ProjectIdentifier::define_heal_locations_prefix); - this->setLocationName(mapConstant); - this->setIdName(prefix + mapConstant); + this->setLocationName(this->map->constantName()); + this->setIdName(prefix + this->map->constantName()); if (respawnEnabled) { - this->setRespawnMap(this->getMap()->name()); + this->setRespawnMap(this->map->name()); this->setRespawnNPC(1); } } diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp index 7fb424da..d4f9a2c8 100644 --- a/src/core/heallocation.cpp +++ b/src/core/heallocation.cpp @@ -3,6 +3,8 @@ #include "events.h" #include "map.h" +// TODO: Remove + HealLocation::HealLocation(QString id, QString map, int i, int16_t x, int16_t y, QString respawnMap, uint8_t respawnNPC) { diff --git a/src/core/map.cpp b/src/core/map.cpp index 496e645f..33a4a13d 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -13,6 +13,7 @@ Map::Map(QObject *parent) : QObject(parent) { + m_scriptsLoaded = false; m_editHistory = new QUndoStack(this); resetEvents(); } @@ -23,12 +24,6 @@ Map::~Map() { deleteConnections(); } -void Map::setName(QString mapName) { - m_name = mapName; - m_constantName = mapConstantFromName(mapName); - m_scriptsLoaded = false; -} - // Note: Map does not take ownership of layout void Map::setLayout(Layout *layout) { m_layout = layout; diff --git a/src/project.cpp b/src/project.cpp index 1a9abffd..4d4c37a8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -134,20 +134,21 @@ void Project::clearTilesetCache() { tilesetCache.clear(); } -Map* Project::loadMap(QString map_name) { - if (map_name == DYNAMIC_MAP_NAME) +Map* Project::loadMap(QString mapName) { + if (mapName == DYNAMIC_MAP_NAME) return nullptr; Map *map; - if (mapCache.contains(map_name)) { - map = mapCache.value(map_name); + if (mapCache.contains(mapName)) { + map = mapCache.value(mapName); // TODO: uncomment when undo/redo history is fully implemented for all actions. if (true/*map->hasUnsavedChanges()*/) { return map; } } else { map = new Map; - map->setName(map_name); + map->setName(mapName); + map->setConstantName(this->mapNamesToMapConstants.value(mapName)); // TODO: How should we handle if !mapNamesToMapConstants.contains(mapName) here } if (!(loadMapData(map) && loadMapLayout(map))){ @@ -155,7 +156,7 @@ Map* Project::loadMap(QString map_name) { return nullptr; } - mapCache.insert(map_name, map); + mapCache.insert(mapName, map); emit mapLoaded(map); return map; } @@ -194,17 +195,23 @@ QSet Project::getTopLevelMapFields() { return topLevelMapFields; } +bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { + const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName); + if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath))) { + logError(QString("Failed to read map data from %1").arg(mapFilepath)); + return false; + } + return true; +} + bool Project::loadMapData(Map* map) { if (!map->isPersistedToFile()) { return true; } - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map->name()).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); QJsonDocument mapDoc; - if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { - logError(QString("Failed to read map data from %1").arg(mapFilepath)); + if (!readMapJson(map->name(), &mapDoc)) return false; - } QJsonObject mapObj = mapDoc.object(); @@ -333,11 +340,11 @@ bool Project::loadMapData(Map* map) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); - int offset = ParseUtil::jsonToInt(connectionObj["offset"]); + const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - if (mapConstantsToMapNames.contains(mapConstant)) { + if (this->mapConstantsToMapNames.contains(mapConstant)) { // Successully read map connection - map->loadConnection(new MapConnection(mapConstantsToMapNames.value(mapConstant), direction, offset)); + map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant), direction, offset)); } else { logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant)); } @@ -357,38 +364,6 @@ bool Project::loadMapData(Map* map) { return true; } -QString Project::readMapLayoutId(QString map_name) { - if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->layoutId(); - } - - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); - QJsonDocument mapDoc; - if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { - logError(QString("Failed to read map layout id from %1").arg(mapFilepath)); - return QString(); - } - - QJsonObject mapObj = mapDoc.object(); - return ParseUtil::jsonToQString(mapObj["layout"]); -} - -QString Project::readMapLocation(QString map_name) { - if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->location(); - } - - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); - QJsonDocument mapDoc; - if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { - logError(QString("Failed to read map's region map section from %1").arg(mapFilepath)); - return QString(); - } - - QJsonObject mapObj = mapDoc.object(); - return ParseUtil::jsonToQString(mapObj["region_map_section"]); -} - Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); Layout *layout; @@ -1328,7 +1303,7 @@ void Project::saveMap(Map *map) { if (connections.length() > 0) { OrderedJson::array connectionsArr; for (auto connection : connections) { - if (mapNamesToMapConstants.contains(connection->targetMapName())) { + if (this->mapNamesToMapConstants.contains(connection->targetMapName())) { OrderedJson::object connectionObj; connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName()); connectionObj["offset"] = connection->offset(); @@ -1857,24 +1832,52 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { - QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); - QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); + const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); + const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); this->groupedMapNames.append(QStringList()); this->groupNames.append(groupName); for (int j = 0; j < mapNamesJson.size(); j++) { - QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); + const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); if (mapName == DYNAMIC_MAP_NAME) { logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); continue; } - this->mapGroups.insert(mapName, groupIndex); - this->groupedMapNames[groupIndex].append(mapName); - this->mapNames.append(mapName); + if (this->mapNames.contains(mapName)) { + logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); + continue; + } - // Build the mapping and reverse mapping between map constants and map names. - QString mapConstant = Map::mapConstantFromName(mapName); + // Load the map's json file so we can get its ID constant (and two other constants we use for the map list). + QJsonDocument mapDoc; + if (!readMapJson(mapName, &mapDoc)) + continue; // Error message has already been logged + + // Read and validate the map's ID from its JSON data. + const QJsonObject mapObj = mapDoc.object(); + const QString mapConstant = ParseUtil::jsonToQString(mapObj["id"]); + if (mapConstant.isEmpty()) { + logWarn(QString("Map '%1' is missing an \"id\" value and will be ignored.").arg(mapName)); + continue; + } + const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + if (!mapConstant.startsWith(expectedPrefix)) { + logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); + continue; + } + auto it = this->mapConstantsToMapNames.constFind(mapConstant); + if (it != this->mapConstantsToMapNames.constEnd()) { + logWarn(QString("Map '%1' has the same \"id\" value '%2' as map '%3' and will be ignored.").arg(mapName).arg(it.key()).arg(it.value())); + continue; + } + + // Success, save the constants to the project + this->mapNames.append(mapName); + this->groupedMapNames[groupIndex].append(mapName); + this->mapGroups.insert(mapName, groupIndex); this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); + this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); + this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); } } @@ -1905,7 +1908,7 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool this->groupedMapNames[groupNum].append(mapName); newMap->setIsPersistedToFile(false); - newMap->setName(mapName); + newMap->setName(mapName); // TODO: Set map name and map constant before calling this function this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a270736a..a2bcd8a6 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -472,7 +472,7 @@ void MapAreaModel::initialize() { for (int j = 0; j < names.length(); j++) { QString mapName = names.value(j); QStandardItem *map = createMapItem(mapName, i, j); - QString mapsecName = this->project->readMapLocation(mapName); + QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); if (this->areaItems.contains(mapsecName)) { this->areaItems[mapsecName]->appendRow(map); } @@ -627,7 +627,7 @@ void LayoutTreeModel::initialize() { for (auto mapList : this->project->groupedMapNames) { for (auto mapName : mapList) { - QString layoutId = project->readMapLayoutId(mapName); + QString layoutId = project->mapNameToLayoutId.value(mapName); QStandardItem *map = createMapItem(mapName); this->layoutItems[layoutId]->appendRow(map); } From 9d87ece663a4df4b794bfceafebd200809de3e46 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:23:15 -0500 Subject: [PATCH 079/364] New map popup/prompt to dialog --- forms/{newmappopup.ui => newmapdialog.ui} | 4 +- include/mainwindow.h | 6 +-- include/ui/{newmappopup.h => newmapdialog.h} | 18 ++++----- porymap.pro | 6 +-- src/mainwindow.cpp | 42 ++++++++++---------- src/ui/{newmappopup.cpp => newmapdialog.cpp} | 42 ++++++++++---------- 6 files changed, 59 insertions(+), 59 deletions(-) rename forms/{newmappopup.ui => newmapdialog.ui} (99%) rename include/ui/{newmappopup.h => newmapdialog.h} (83%) rename src/ui/{newmappopup.cpp => newmapdialog.cpp} (93%) diff --git a/forms/newmappopup.ui b/forms/newmapdialog.ui similarity index 99% rename from forms/newmappopup.ui rename to forms/newmapdialog.ui index 3a83073f..2414918f 100644 --- a/forms/newmappopup.ui +++ b/forms/newmapdialog.ui @@ -1,7 +1,7 @@ - NewMapPopup - + NewMapDialog + 0 diff --git a/include/mainwindow.h b/include/mainwindow.h index 397a1d72..2efe964d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,7 +22,7 @@ #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" #include "maplistmodels.h" -#include "newmappopup.h" +#include "newmapdialog.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" @@ -186,7 +186,7 @@ private slots: void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); - void openNewMapPopupWindow(); + void openNewMapDialog(); void onNewMapCreated(); void onMapLoaded(Map *map); void importMapFromAdvanceMap1_92(); @@ -313,7 +313,7 @@ private: QPointer regionMapEditor = nullptr; QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; - QPointer newMapPrompt = nullptr; + QPointer newMapDialog = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; diff --git a/include/ui/newmappopup.h b/include/ui/newmapdialog.h similarity index 83% rename from include/ui/newmappopup.h rename to include/ui/newmapdialog.h index f160876a..b87f785e 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmapdialog.h @@ -1,22 +1,22 @@ -#ifndef NEWMAPPOPUP_H -#define NEWMAPPOPUP_H +#ifndef NEWMAPDIALOG_H +#define NEWMAPDIALOG_H -#include +#include #include #include "editor.h" #include "project.h" #include "map.h" namespace Ui { -class NewMapPopup; +class NewMapDialog; } -class NewMapPopup : public QMainWindow +class NewMapDialog : public QDialog { Q_OBJECT public: - explicit NewMapPopup(QWidget *parent = nullptr, Project *project = nullptr); - ~NewMapPopup(); + explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr); + ~NewMapDialog(); Map *map; int group; bool existingLayout; @@ -32,7 +32,7 @@ signals: void applied(); private: - Ui::NewMapPopup *ui; + Ui::NewMapDialog *ui; Project *project; bool checkNewMapDimensions(); bool checkNewMapGroup(); @@ -67,4 +67,4 @@ private slots: void on_lineEdit_NewMap_Name_textChanged(const QString &); }; -#endif // NEWMAPPOPUP_H +#endif // NEWMAPDIALOG_H diff --git a/porymap.pro b/porymap.pro index 1b1c693e..9a2b2c75 100644 --- a/porymap.pro +++ b/porymap.pro @@ -99,7 +99,7 @@ SOURCES += src/core/block.cpp \ src/ui/tileseteditortileselector.cpp \ src/ui/tilemaptileselector.cpp \ src/ui/regionmapeditor.cpp \ - src/ui/newmappopup.cpp \ + src/ui/newmapdialog.cpp \ src/ui/mapimageexporter.cpp \ src/ui/newtilesetdialog.cpp \ src/ui/flowlayout.cpp \ @@ -202,7 +202,7 @@ HEADERS += include/core/block.h \ include/ui/tileseteditortileselector.h \ include/ui/tilemaptileselector.h \ include/ui/regionmapeditor.h \ - include/ui/newmappopup.h \ + include/ui/newmapdialog.h \ include/ui/mapimageexporter.h \ include/ui/newtilesetdialog.h \ include/ui/overlay.h \ @@ -238,7 +238,7 @@ FORMS += forms/mainwindow.ui \ forms/tileseteditor.ui \ forms/paletteeditor.ui \ forms/regionmapeditor.ui \ - forms/newmappopup.ui \ + forms/newmapdialog.ui \ forms/aboutporymap.ui \ forms/newtilesetdialog.ui \ forms/mapimageexporter.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c8885f62..5f42e9c7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1363,8 +1363,8 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (addToFolderAction) { connect(addToFolderAction, &QAction::triggered, [this, itemName] { - openNewMapPopupWindow(); - this->newMapPrompt->init(ui->mapListContainer->currentIndex(), itemName); + openNewMapDialog(); + this->newMapDialog->init(ui->mapListContainer->currentIndex(), itemName); }); } if (deleteFolderAction) { @@ -1592,11 +1592,11 @@ void MainWindow::mapListAddArea() { } void MainWindow::onNewMapCreated() { - QString newMapName = this->newMapPrompt->map->name(); - int newMapGroup = this->newMapPrompt->group; - Map *newMap = this->newMapPrompt->map; - bool existingLayout = this->newMapPrompt->existingLayout; - bool importedMap = this->newMapPrompt->importedMap; + QString newMapName = this->newMapDialog->map->name(); + int newMapGroup = this->newMapDialog->group; + Map *newMap = this->newMapDialog->map; + bool existingLayout = this->newMapDialog->existingLayout; + bool importedMap = this->newMapDialog->importedMap; newMap = editor->project->addNewMapToGroup(newMapName, newMapGroup, newMap, existingLayout, importedMap); @@ -1638,27 +1638,27 @@ void MainWindow::onNewMapCreated() { editor->save(); } - disconnect(this->newMapPrompt, &NewMapPopup::applied, this, &MainWindow::onNewMapCreated); + disconnect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); delete newMap; } -void MainWindow::openNewMapPopupWindow() { +void MainWindow::openNewMapDialog() { if (!this->newMapDefaultsSet) { - NewMapPopup::setDefaultSettings(this->editor->project); + NewMapDialog::setDefaultSettings(this->editor->project); this->newMapDefaultsSet = true; } - if (!this->newMapPrompt) { - this->newMapPrompt = new NewMapPopup(this, this->editor->project); - connect(this->newMapPrompt, &NewMapPopup::applied, this, &MainWindow::onNewMapCreated); + if (!this->newMapDialog) { + this->newMapDialog = new NewMapDialog(this, this->editor->project); + connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); } - openSubWindow(this->newMapPrompt); + openSubWindow(this->newMapDialog); } void MainWindow::on_action_NewMap_triggered() { - openNewMapPopupWindow(); - this->newMapPrompt->initUi(); - this->newMapPrompt->init(); + openNewMapDialog(); + this->newMapDialog->initUi(); + this->newMapDialog->init(); } // Insert label for newly-created tileset into sorted list of existing labels @@ -2864,8 +2864,8 @@ void MainWindow::importMapFromAdvanceMap1_92() return; } - openNewMapPopupWindow(); - this->newMapPrompt->init(mapLayout); + openNewMapDialog(); + this->newMapDialog->init(mapLayout); } void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { @@ -3379,9 +3379,9 @@ bool MainWindow::closeSupplementaryWindows() { return false; this->mapImageExporter = nullptr; - if (this->newMapPrompt && !this->newMapPrompt->close()) + if (this->newMapDialog && !this->newMapDialog->close()) return false; - this->newMapPrompt = nullptr; + this->newMapDialog = nullptr; if (this->shortcutsEditor && !this->shortcutsEditor->close()) return false; diff --git a/src/ui/newmappopup.cpp b/src/ui/newmapdialog.cpp similarity index 93% rename from src/ui/newmappopup.cpp rename to src/ui/newmapdialog.cpp index b5ebf556..ce6737a8 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmapdialog.cpp @@ -1,7 +1,7 @@ -#include "newmappopup.h" +#include "newmapdialog.h" #include "maplayout.h" #include "mainwindow.h" -#include "ui_newmappopup.h" +#include "ui_newmapdialog.h" #include "config.h" #include @@ -10,11 +10,11 @@ // 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) -struct NewMapPopup::Settings NewMapPopup::settings = {}; +struct NewMapDialog::Settings NewMapDialog::settings = {}; -NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : - QMainWindow(parent), - ui(new Ui::NewMapPopup) +NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : + QDialog(parent), + ui(new Ui::NewMapDialog) { this->setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); @@ -23,13 +23,13 @@ NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : this->importedMap = false; } -NewMapPopup::~NewMapPopup() +NewMapDialog::~NewMapDialog() { saveSettings(); delete ui; } -void NewMapPopup::initUi() { +void NewMapDialog::initUi() { // Populate combo boxes ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels); ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels); @@ -76,7 +76,7 @@ void NewMapPopup::initUi() { this->updateGeometry(); } -void NewMapPopup::init() { +void NewMapDialog::init() { // Restore previous settings ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); ui->comboBox_NewMap_Group->setTextItem(settings.group); @@ -104,7 +104,7 @@ void NewMapPopup::init() { } // Creating new map by right-clicking in the map list -void NewMapPopup::init(int tabIndex, QString fieldName) { +void NewMapDialog::init(int tabIndex, QString fieldName) { initUi(); switch (tabIndex) { @@ -123,7 +123,7 @@ void NewMapPopup::init(int tabIndex, QString fieldName) { } // Creating new map from AdvanceMap import -void NewMapPopup::init(Layout *mapLayout) { +void NewMapDialog::init(Layout *mapLayout) { initUi(); this->importedMap = true; useLayoutSettings(mapLayout); @@ -138,7 +138,7 @@ void NewMapPopup::init(Layout *mapLayout) { init(); } -bool NewMapPopup::checkNewMapDimensions() { +bool NewMapDialog::checkNewMapDimensions() { int numMetatiles = project->getMapDataSize(ui->spinBox_NewMap_Width->value(), ui->spinBox_NewMap_Height->value()); int maxMetatiles = project->getMaxMapDataSize(); @@ -162,7 +162,7 @@ bool NewMapPopup::checkNewMapDimensions() { } } -bool NewMapPopup::checkNewMapGroup() { +bool NewMapDialog::checkNewMapGroup() { group = project->groupNames.indexOf(this->ui->comboBox_NewMap_Group->currentText()); if (group < 0) { @@ -179,7 +179,7 @@ bool NewMapPopup::checkNewMapGroup() { } } -void NewMapPopup::setDefaultSettings(Project *project) { +void NewMapDialog::setDefaultSettings(Project *project) { settings.group = project->groupNames.at(0); settings.width = project->getDefaultMapSize(); settings.height = project->getDefaultMapSize(); @@ -198,7 +198,7 @@ void NewMapPopup::setDefaultSettings(Project *project) { settings.floorNumber = 0; } -void NewMapPopup::saveSettings() { +void NewMapDialog::saveSettings() { settings.group = ui->comboBox_NewMap_Group->currentText(); settings.width = ui->spinBox_NewMap_Width->value(); settings.height = ui->spinBox_NewMap_Height->value(); @@ -217,7 +217,7 @@ void NewMapPopup::saveSettings() { settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value(); } -void NewMapPopup::useLayoutSettings(Layout *layout) { +void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; settings.width = layout->width; @@ -239,7 +239,7 @@ void NewMapPopup::useLayoutSettings(Layout *layout) { ui->comboBox_NewMap_Secondary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Secondary_Tileset->findText(layout->tileset_secondary_label)); } -void NewMapPopup::useLayout(QString layoutId) { +void NewMapDialog::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; @@ -248,7 +248,7 @@ void NewMapPopup::useLayout(QString layoutId) { useLayoutSettings(project->mapLayouts.value(this->layoutId)); } -void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) { +void NewMapDialog::on_checkBox_UseExistingLayout_stateChanged(int state) { bool layoutEditsEnabled = (state == Qt::Unchecked); this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled); @@ -267,13 +267,13 @@ void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) { } } -void NewMapPopup::on_comboBox_Layout_currentTextChanged(const QString &text) { +void NewMapDialog::on_comboBox_Layout_currentTextChanged(const QString &text) { if (this->project->mapLayoutsTable.contains(text)) { useLayout(text); } } -void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { +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 { @@ -281,7 +281,7 @@ void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { } } -void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { +void NewMapDialog::on_pushButton_NewMap_Accept_clicked() { if (!checkNewMapDimensions() || !checkNewMapGroup()) { // ignore when map dimensions or map group are invalid return; From d6a796e3b4c1187a317e5b7ce33f7473ca8b6ca2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:44:04 -0500 Subject: [PATCH 080/364] Start new map dialog redesign --- forms/newmapdialog.ui | 958 +++++++++++++++++++++----------------- include/project.h | 6 +- include/ui/newmapdialog.h | 29 +- src/core/map.cpp | 8 +- src/mainwindow.cpp | 4 +- src/project.cpp | 33 +- src/ui/newmapdialog.cpp | 460 +++++++++--------- 7 files changed, 806 insertions(+), 692 deletions(-) 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); } From 9e1ef2c741c6b7796caf5369f4c0c24764034d03 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:48:18 -0500 Subject: [PATCH 081/364] Import collapsible section --- include/lib/collapsiblesection.h | 62 ++++++++++++++++++ porymap.pro | 2 + src/lib/collapsiblesection.cpp | 109 +++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 include/lib/collapsiblesection.h create mode 100644 src/lib/collapsiblesection.cpp diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h new file mode 100644 index 00000000..3a1b3023 --- /dev/null +++ b/include/lib/collapsiblesection.h @@ -0,0 +1,62 @@ +/* + Elypson/qt-collapsible-section + (c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com + + This file is part of Elypson/qt-collapsible section. + + Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Elypson/qt-collapsible-section is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Elypson/qt-collapsible-section. If not, see . +*/ + +#ifndef COLLAPSIBLESECTION_H +#define COLLAPSIBLESECTION_H + +#include +#include +#include +#include +#include +#include + +class CollapsibleSection : public QWidget +{ + Q_OBJECT + +private: + QGridLayout* mainLayout; + QToolButton* toggleButton; + QFrame* headerLine; + QParallelAnimationGroup* toggleAnimation; + QScrollArea* contentArea; + int animationDuration; + int collapsedHeight; + bool isExpanded = false; + +public slots: + void toggle(bool collapsed); + +public: + // initialize section + explicit CollapsibleSection(const QString& title = "", const int animationDuration = 0, QWidget* parent = 0); + + // set layout of content + void setContentLayout(QLayout& contentLayout); + + // set title + void setTitle(QString title); + + // update animations and their heights + void updateHeights(); +}; + +#endif // COLLAPSIBLESECTION_H diff --git a/porymap.pro b/porymap.pro index 9a2b2c75..1bea489b 100644 --- a/porymap.pro +++ b/porymap.pro @@ -45,6 +45,7 @@ SOURCES += src/core/block.cpp \ src/lib/fex/lexer.cpp \ src/lib/fex/parser.cpp \ src/lib/fex/parser_util.cpp \ + src/lib/collapsiblesection.cpp \ src/lib/orderedjson.cpp \ src/core/regionmapeditcommands.cpp \ src/scriptapi/apimap.cpp \ @@ -151,6 +152,7 @@ HEADERS += include/core/block.h \ include/lib/fex/lexer.h \ include/lib/fex/parser.h \ include/lib/fex/parser_util.h \ + include/lib/collapsiblesection.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp new file mode 100644 index 00000000..000e822e --- /dev/null +++ b/src/lib/collapsiblesection.cpp @@ -0,0 +1,109 @@ +/* + Elypson/qt-collapsible-section + (c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com + + This file is part of Elypson/qt-collapsible section. + + Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Elypson/qt-collapsible-section is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Elypson/qt-collapsible-section. If not, see . +*/ + +#include + +#include "collapsiblesection.h" +CollapsibleSection::CollapsibleSection(const QString& title, const int animationDuration, QWidget* parent) + : QWidget(parent), animationDuration(animationDuration) +{ + toggleButton = new QToolButton(this); + headerLine = new QFrame(this); + toggleAnimation = new QParallelAnimationGroup(this); + contentArea = new QScrollArea(this); + mainLayout = new QGridLayout(this); + + toggleButton->setStyleSheet("QToolButton {border: none;}"); + toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toggleButton->setArrowType(Qt::ArrowType::RightArrow); + toggleButton->setText(title); + toggleButton->setCheckable(true); + toggleButton->setChecked(false); + + headerLine->setFrameShape(QFrame::HLine); + headerLine->setFrameShadow(QFrame::Sunken); + headerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // start out collapsed + contentArea->setMaximumHeight(0); + contentArea->setMinimumHeight(0); + + // let the entire widget grow and shrink with its content + toggleAnimation->addAnimation(new QPropertyAnimation(this, "maximumHeight")); + toggleAnimation->addAnimation(new QPropertyAnimation(this, "minimumHeight")); + toggleAnimation->addAnimation(new QPropertyAnimation(contentArea, "maximumHeight")); + + mainLayout->setVerticalSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + + int row = 0; + mainLayout->addWidget(toggleButton, row, 0, 1, 1, Qt::AlignLeft); + mainLayout->addWidget(headerLine, row++, 2, 1, 1); + mainLayout->addWidget(contentArea, row, 0, 1, 3); + setLayout(mainLayout); + + connect(toggleButton, &QToolButton::toggled, this, &CollapsibleSection::toggle); +} + +void CollapsibleSection::toggle(bool expanded) +{ + toggleButton->setArrowType(expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); + toggleAnimation->setDirection(expanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); + toggleAnimation->start(); + + this->isExpanded = expanded; +} + +void CollapsibleSection::setContentLayout(QLayout& contentLayout) +{ + delete contentArea->layout(); + contentArea->setLayout(&contentLayout); + collapsedHeight = sizeHint().height() - contentArea->maximumHeight(); + + updateHeights(); +} + +void CollapsibleSection::setTitle(QString title) +{ + toggleButton->setText(std::move(title)); +} + +void CollapsibleSection::updateHeights() +{ + int contentHeight = contentArea->layout()->sizeHint().height(); + + for (int i = 0; i < toggleAnimation->animationCount() - 1; ++i) + { + QPropertyAnimation* SectionAnimation = static_cast(toggleAnimation->animationAt(i)); + SectionAnimation->setDuration(animationDuration); + SectionAnimation->setStartValue(collapsedHeight); + SectionAnimation->setEndValue(collapsedHeight + contentHeight); + } + + QPropertyAnimation* contentAnimation = static_cast(toggleAnimation->animationAt(toggleAnimation->animationCount() - 1)); + contentAnimation->setDuration(animationDuration); + contentAnimation->setStartValue(0); + contentAnimation->setEndValue(contentHeight); + + toggleAnimation->setDirection(isExpanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); + toggleAnimation->start(); +} From 205bb48c65b35919bdbbfed3bbe1603cee97bc9f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:27:35 -0500 Subject: [PATCH 082/364] Header tab and new map dialog share UI setup --- forms/mainwindow.ui | 202 +------------- forms/mapheaderform.ui | 235 ++++++++++++++++ forms/newmapdialog.ui | 534 ++++++++++++------------------------- include/core/map.h | 6 +- include/mainwindow.h | 16 +- include/ui/mapheaderform.h | 56 ++++ include/ui/newmapdialog.h | 2 + porymap.pro | 3 + src/core/map.cpp | 4 +- src/mainwindow.cpp | 201 ++------------ src/project.cpp | 4 +- src/scriptapi/apimap.cpp | 53 ++-- src/ui/mapheaderform.cpp | 220 +++++++++++++++ src/ui/newmapdialog.cpp | 88 +++--- 14 files changed, 789 insertions(+), 835 deletions(-) create mode 100644 forms/mapheaderform.ui create mode 100644 include/ui/mapheaderform.h create mode 100644 src/ui/mapheaderform.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 44d69d30..eb6c7345 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1155,7 +1155,7 @@ 2
- + Layout @@ -2220,7 +2220,7 @@ 0
- + false @@ -2236,206 +2236,10 @@ QFrame::Shadow::Raised - - - QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint - - - 12 - + 9 - - - - Song - - - - - - - <html><head/><body><p>The default background music for this map.</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 display when the player enters it.</p></body></html> - - - true - - - - - - - Requires Flash - - - - - - - <html><head/><body><p>Whether or not the map is dark and requires Flash to illuminate.</p></body></html> - - - - - - - - - - Weather - - - - - - - <html><head/><body><p>The default weather for this 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 - - - - - - - Battle scene - - - - - - - <html><head/><body><p>Determines the type of battle scene graphics to use.</p></body></html> - - - true - - - - - - - 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> - - - -128 - - - 127 - - - diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui new file mode 100644 index 00000000..06541e2c --- /dev/null +++ b/forms/mapheaderform.ui @@ -0,0 +1,235 @@ + + + MapHeaderForm + + + + 0 + 0 + 407 + 349 + + + + Form + + + + QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint + + + + + Song + + + + + + + <html><head/><body><p>The default background music for this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + 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 + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Requires Flash + + + + + + + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> + + + + + + + + + + Weather + + + + + + + <html><head/><body><p>The default weather on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + 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 + + + + + + + <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 + + + + + + + Show Location Name + + + + + + + <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> + + + + + + + + + + Allow Running + + + + + + + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> + + + + + + + + + + Allow Biking + + + + + + + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> + + + + + + + + + + Allow Dig & Escape Rope + + + + + + + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> + + + + + + + + + + Floor Number + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 9cf4dc18..dcf4f052 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -7,7 +7,7 @@ 0 0 453 - 563 + 588
@@ -25,7 +25,7 @@ 0 0 427 - 841 + 526 @@ -39,7 +39,14 @@
- + + + + 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> @@ -49,7 +56,23 @@ - + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + Border Dimensions @@ -104,357 +127,6 @@ - - - - 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>The name of the group this map will be added to.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - 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> - - - - - - - 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> - - - - - - - - - @@ -462,14 +134,20 @@ - - - - ID + + + + Qt::Orientation::Vertical - + + + 20 + 40 + + +
- + false @@ -485,7 +163,14 @@ - + + + + Can Fly To + + + + Tilesets @@ -550,6 +235,131 @@ + + + + Map Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + 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> + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Header Data + + + + + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + + + + + +
diff --git a/include/core/map.h b/include/core/map.h index eab3110e..314013d7 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -62,7 +62,7 @@ public: void setRequiresFlash(bool requiresFlash); void setWeather(const QString &weather); void setType(const QString &type); - void setShowsLocation(bool showsLocation); + void setShowsLocationName(bool showsLocationName); void setAllowsRunning(bool allowsRunning); void setAllowsBiking(bool allowsBiking); void setAllowsEscaping(bool allowsEscaping); @@ -74,7 +74,7 @@ public: bool requiresFlash() const { return m_requiresFlash; } QString weather() const { return m_weather; } QString type() const { return m_type; } - bool showsLocation() const { return m_showsLocation; } + bool showsLocationName() const { return m_showsLocationName; } bool allowsRunning() const { return m_allowsRunning; } bool allowsBiking() const { return m_allowsBiking; } bool allowsEscaping() const { return m_allowsEscaping; } @@ -136,7 +136,7 @@ private: bool m_requiresFlash; QString m_weather; QString m_type; - bool m_showsLocation; + bool m_showsLocationName; bool m_allowsRunning; bool m_allowsBiking; bool m_allowsEscaping; diff --git a/include/mainwindow.h b/include/mainwindow.h index 2efe964d..ab4e0482 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -32,6 +32,7 @@ #include "wildmonchart.h" #include "updatepromoter.h" #include "aboutporymap.h" +#include "mapheaderform.h" @@ -199,17 +200,7 @@ private slots: void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); void on_action_Exit_triggered(); - void on_comboBox_Song_currentTextChanged(const QString &arg1); - void on_comboBox_Location_currentTextChanged(const QString &arg1); - void on_comboBox_Weather_currentTextChanged(const QString &arg1); - void on_comboBox_Type_currentTextChanged(const QString &arg1); - void on_comboBox_BattleScene_currentTextChanged(const QString &arg1); - void on_comboBox_LayoutSelector_currentTextChanged(const QString &arg1); - void on_checkBox_ShowLocation_stateChanged(int selected); - void on_checkBox_AllowRunning_stateChanged(int selected); - void on_checkBox_AllowBiking_stateChanged(int selected); - void on_checkBox_AllowEscaping_stateChanged(int selected); - void on_spinBox_FloorNumber_valueChanged(int offset); + void on_comboBox_LayoutSelector_currentTextChanged(const QString &text); void on_actionShortcuts_triggered(); void on_actionZoom_In_triggered(); @@ -254,7 +245,6 @@ private slots: void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); void on_checkBox_smartPaths_stateChanged(int selected); - void on_checkBox_Visibility_stateChanged(int selected); void on_checkBox_ToggleBorder_stateChanged(int selected); void resetMapViewScale(); @@ -337,6 +327,8 @@ private: QAction *copyAction = nullptr; QAction *pasteAction = nullptr; + MapHeaderForm *mapHeader = nullptr; + QMap lastSelectedEvent; bool isProgrammaticEventTabChange; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h new file mode 100644 index 00000000..afdee728 --- /dev/null +++ b/include/ui/mapheaderform.h @@ -0,0 +1,56 @@ +#ifndef MAPHEADERFORM_H +#define MAPHEADERFORM_H + +#include "project.h" +#include "map.h" +#include "ui_mapheaderform.h" + +#include + +/* + This is the UI class used to edit the fields in a map's header. + It's intended to be used anywhere the UI needs to present an editor for a map's header, + e.g. for the current map in the main editor or in the new map dialog. +*/ + +namespace Ui { +class MapHeaderForm; +} + +class MapHeaderForm : public QWidget +{ + Q_OBJECT + +public: + explicit MapHeaderForm(QWidget *parent = nullptr); + ~MapHeaderForm(); + + void setProject(Project * project); + void setMap(Map * map); + + void clearDisplay(); + void clear(); + + void refreshLocationsComboBox(); + + Ui::MapHeaderForm *ui; + +private: + QPointer map = nullptr; + QPointer project = nullptr; + +private slots: + void on_comboBox_Song_currentTextChanged(const QString &); + void on_comboBox_Location_currentTextChanged(const QString &); + void on_comboBox_Weather_currentTextChanged(const QString &); + void on_comboBox_Type_currentTextChanged(const QString &); + void on_comboBox_BattleScene_currentTextChanged(const QString &); + void on_checkBox_RequiresFlash_stateChanged(int); + void on_checkBox_ShowLocationName_stateChanged(int); + void on_checkBox_AllowRunning_stateChanged(int); + void on_checkBox_AllowBiking_stateChanged(int); + void on_checkBox_AllowEscaping_stateChanged(int); + void on_spinBox_FloorNumber_valueChanged(int); +}; + +#endif // MAPHEADERFORM_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 4b17f32b..090fcb96 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -6,6 +6,7 @@ #include "editor.h" #include "project.h" #include "map.h" +#include "mapheaderform.h" namespace Ui { class NewMapDialog; @@ -34,6 +35,7 @@ signals: private: Ui::NewMapDialog *ui; Project *project; + MapHeaderForm *headerData; bool validateMapDimensions(); bool validateMapGroup(); diff --git a/porymap.pro b/porymap.pro index 1bea489b..d3d870bc 100644 --- a/porymap.pro +++ b/porymap.pro @@ -83,6 +83,7 @@ SOURCES += src/core/block.cpp \ src/ui/prefabcreationdialog.cpp \ src/ui/regionmappixmapitem.cpp \ src/ui/citymappixmapitem.cpp \ + src/ui/mapheaderform.cpp \ src/ui/metatilelayersitem.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ @@ -166,6 +167,7 @@ HEADERS += include/core/block.h \ include/ui/connectionpixmapitem.h \ include/ui/currentselectedmetatilespixmapitem.h \ include/ui/gridsettings.h \ + include/ui/mapheaderform.h \ include/ui/newmapconnectiondialog.h \ include/ui/prefabframe.h \ include/ui/projectsettingseditor.h \ @@ -233,6 +235,7 @@ FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ forms/connectionslistitem.ui \ forms/gridsettingsdialog.ui \ + forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ diff --git a/src/core/map.cpp b/src/core/map.cpp index 942a4e95..ab31ac86 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -330,8 +330,8 @@ void Map::setType(const QString &type) { m_type = type; } -void Map::setShowsLocation(bool showsLocation) { - m_showsLocation = showsLocation; +void Map::setShowsLocationName(bool showsLocationName) { + m_showsLocationName = showsLocationName; } void Map::setAllowsRunning(bool allowsRunning) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6105a3b4..d47dfef2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -235,6 +235,10 @@ void MainWindow::initCustomUI() { ui->mainTabBar->addTab(mainTabNames.value(i)); ui->mainTabBar->setTabIcon(i, mainTabIcons.value(i)); } + + // Create map header data widget + this->mapHeader = new MapHeaderForm(); + ui->layout_HeaderData->addWidget(this->mapHeader); } void MainWindow::initExtraSignals() { @@ -597,7 +601,7 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::refreshLocationsComboBox); + connect(project, &Project::mapSectionIdNamesChanged, this->mapHeader, &MapHeaderForm::refreshLocationsComboBox); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -1005,47 +1009,22 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ void MainWindow::displayMapProperties() { // Block signals to the comboboxes while they are being modified - const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker2(ui->comboBox_Location); - const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); - const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); - const QSignalBlocker blocker5(ui->comboBox_Weather); - const QSignalBlocker blocker6(ui->comboBox_BattleScene); - const QSignalBlocker blocker7(ui->comboBox_Type); - const QSignalBlocker blocker8(ui->checkBox_Visibility); - const QSignalBlocker blocker9(ui->checkBox_ShowLocation); - const QSignalBlocker blockerA(ui->checkBox_AllowRunning); - const QSignalBlocker blockerB(ui->checkBox_AllowBiking); - const QSignalBlocker blockerC(ui->spinBox_FloorNumber); - const QSignalBlocker blockerD(ui->checkBox_AllowEscaping); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - ui->checkBox_Visibility->setChecked(false); - ui->checkBox_ShowLocation->setChecked(false); - ui->checkBox_AllowRunning->setChecked(false); - ui->checkBox_AllowBiking->setChecked(false); - ui->checkBox_AllowEscaping->setChecked(false); + this->mapHeader->clearDisplay(); if (!editor || !editor->map || !editor->project) { - ui->frame_3->setEnabled(false); + ui->frame_HeaderData->setEnabled(false); return; } - ui->frame_3->setEnabled(true); + ui->frame_HeaderData->setEnabled(true); Map *map = editor->map; ui->comboBox_PrimaryTileset->setCurrentText(map->layout()->tileset_primary_label); ui->comboBox_SecondaryTileset->setCurrentText(map->layout()->tileset_secondary_label); - ui->comboBox_Song->setCurrentText(map->song()); - ui->comboBox_Location->setCurrentText(map->location()); - ui->checkBox_Visibility->setChecked(map->requiresFlash()); - ui->comboBox_Weather->setCurrentText(map->weather()); - ui->comboBox_Type->setCurrentText(map->type()); - ui->comboBox_BattleScene->setCurrentText(map->battleScene()); - ui->checkBox_ShowLocation->setChecked(map->showsLocation()); - ui->checkBox_AllowRunning->setChecked(map->allowsRunning()); - ui->checkBox_AllowBiking->setChecked(map->allowsBiking()); - ui->checkBox_AllowEscaping->setChecked(map->allowsEscaping()); - ui->spinBox_FloorNumber->setValue(map->floorNumber()); + this->mapHeader->setMap(map); // Custom fields table. /* // TODO: Re-enable @@ -1068,122 +1047,24 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te } } -void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) -{ - if (editor && editor->map) { - editor->map->setSong(song); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_Location_currentTextChanged(const QString &location) -{ - if (editor && editor->map) { - editor->map->setLocation(location); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_Weather_currentTextChanged(const QString &weather) -{ - if (editor && editor->map) { - editor->map->setWeather(weather); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_Type_currentTextChanged(const QString &type) -{ - if (editor && editor->map) { - editor->map->setType(type); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_BattleScene_currentTextChanged(const QString &battle_scene) -{ - if (editor && editor->map) { - editor->map->setBattleScene(battle_scene); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_Visibility_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setRequiresFlash(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_ShowLocation_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setShowsLocation(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_AllowRunning_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setAllowsRunning(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_AllowBiking_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setAllowsBiking(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_AllowEscaping_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setAllowsEscaping(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_spinBox_FloorNumber_valueChanged(int offset) -{ - if (editor && editor->map) { - editor->map->setFloorNumber(offset); - markMapEdited(); - } -} - // Update the UI using information we've read from the user's project files. bool MainWindow::setProjectUI() { Project *project = editor->project; + this->mapHeader->setProject(project); + // Block signals to the comboboxes while they are being modified - const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); - const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); - const QSignalBlocker blocker5(ui->comboBox_Weather); - const QSignalBlocker blocker6(ui->comboBox_BattleScene); - const QSignalBlocker blocker7(ui->comboBox_Type); - const QSignalBlocker blocker8(ui->comboBox_DiveMap); - const QSignalBlocker blocker9(ui->comboBox_EmergeMap); - const QSignalBlocker blocker10(ui->comboBox_LayoutSelector); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); // Set up project comboboxes - ui->comboBox_Song->clear(); - ui->comboBox_Song->addItems(project->songNames); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_SecondaryTileset->clear(); ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_Weather->clear(); - ui->comboBox_Weather->addItems(project->weatherNames); - ui->comboBox_BattleScene->clear(); - ui->comboBox_BattleScene->addItems(project->mapBattleScenes); - ui->comboBox_Type->clear(); - ui->comboBox_Type->addItems(project->mapTypes); ui->comboBox_LayoutSelector->clear(); ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); ui->comboBox_DiveMap->clear(); @@ -1194,29 +1075,16 @@ bool MainWindow::setProjectUI() { ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->setClearButtonEnabled(true); ui->comboBox_EmergeMap->setFocusedScrollingEnabled(false); - refreshLocationsComboBox(); // Show/hide parts of the UI that are dependent on the user's project settings // Wild Encounters tab ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, editor->project->wildEncountersLoaded); - bool hasFlags = projectConfig.mapAllowFlagsEnabled; - 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); - ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.eventWeatherTriggerEnabled); ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled); - bool floorNumEnabled = projectConfig.floorNumberEnabled; - ui->spinBox_FloorNumber->setVisible(floorNumEnabled); - ui->label_FloorNumber->setVisible(floorNumEnabled); - Event::setIcons(); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); @@ -1244,41 +1112,22 @@ bool MainWindow::setProjectUI() { return true; } -void MainWindow::refreshLocationsComboBox() { - QStringList locations = this->editor->project->mapSectionIdNames; - locations.sort(); - - const QSignalBlocker b(ui->comboBox_Location); - ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(locations); - if (this->editor->map) - ui->comboBox_Location->setCurrentText(this->editor->map->location()); -} - void MainWindow::clearProjectUI() { // Block signals to the comboboxes while they are being modified - const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker2(ui->comboBox_Location); - const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); - const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); - const QSignalBlocker blocker5(ui->comboBox_Weather); - const QSignalBlocker blocker6(ui->comboBox_BattleScene); - const QSignalBlocker blocker7(ui->comboBox_Type); - const QSignalBlocker blocker8(ui->comboBox_DiveMap); - const QSignalBlocker blocker9(ui->comboBox_EmergeMap); - const QSignalBlocker blockerA(ui->comboBox_LayoutSelector); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); - ui->comboBox_Song->clear(); - ui->comboBox_Location->clear(); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_SecondaryTileset->clear(); - ui->comboBox_Weather->clear(); - ui->comboBox_BattleScene->clear(); - ui->comboBox_Type->clear(); ui->comboBox_DiveMap->clear(); ui->comboBox_EmergeMap->clear(); ui->comboBox_LayoutSelector->clear(); + this->mapHeader->clear(); + // Clear map models delete this->mapGroupModel; delete this->groupListProxyModel; diff --git a/src/project.cpp b/src/project.cpp index f996ff1c..f36f7543 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -221,7 +221,7 @@ bool Project::loadMapData(Map* map) { map->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); map->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); map->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->setShowsLocation(ParseUtil::jsonToBool(mapObj["show_map_name"])); + map->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); map->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); if (projectConfig.mapAllowFlagsEnabled) { @@ -1293,7 +1293,7 @@ void Project::saveMap(Map *map) { mapObj["allow_escaping"] = map->allowsEscaping(); mapObj["allow_running"] = map->allowsRunning(); } - mapObj["show_map_name"] = map->showsLocation(); + mapObj["show_map_name"] = map->showsLocationName(); if (projectConfig.floorNumberEnabled) { mapObj["floor_number"] = map->floorNumber(); } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index d13d660b..5a0fa9ce 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -811,7 +811,8 @@ QJSValue MainWindow::getTilePixels(int tileId) { // Editing map header //===================== -// TODO: Replace UI setting here with calls to appropriate set functions. Update UI with signals from Map +// TODO: Connect signals from new function calls to update UI +// TODO: Is the error-checking for known constant names still reasonable / needed? (you can type anything after all) QString MainWindow::getSong() { if (!this->editor || !this->editor->map) @@ -820,13 +821,13 @@ QString MainWindow::getSong() { } void MainWindow::setSong(QString song) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->songNames.contains(song)) { logError(QString("Unknown song '%1'").arg(song)); return; } - this->ui->comboBox_Song->setCurrentText(song); + this->editor->map->setSong(song); } QString MainWindow::getLocation() { @@ -836,13 +837,13 @@ QString MainWindow::getLocation() { } void MainWindow::setLocation(QString location) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->mapSectionIdNames.contains(location)) { logError(QString("Unknown location '%1'").arg(location)); return; } - this->ui->comboBox_Location->setCurrentText(location); + this->editor->map->setLocation(location); } bool MainWindow::getRequiresFlash() { @@ -852,9 +853,9 @@ bool MainWindow::getRequiresFlash() { } void MainWindow::setRequiresFlash(bool require) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_Visibility->setChecked(require); + this->editor->map->setRequiresFlash(require); } QString MainWindow::getWeather() { @@ -864,13 +865,13 @@ QString MainWindow::getWeather() { } void MainWindow::setWeather(QString weather) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->weatherNames.contains(weather)) { logError(QString("Unknown weather '%1'").arg(weather)); return; } - this->ui->comboBox_Weather->setCurrentText(weather); + this->editor->map->setWeather(weather); } QString MainWindow::getType() { @@ -880,13 +881,13 @@ QString MainWindow::getType() { } void MainWindow::setType(QString type) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->mapTypes.contains(type)) { logError(QString("Unknown map type '%1'").arg(type)); return; } - this->ui->comboBox_Type->setCurrentText(type); + this->editor->map->setType(type); } QString MainWindow::getBattleScene() { @@ -896,25 +897,25 @@ QString MainWindow::getBattleScene() { } void MainWindow::setBattleScene(QString battleScene) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->mapBattleScenes.contains(battleScene)) { logError(QString("Unknown battle scene '%1'").arg(battleScene)); return; } - this->ui->comboBox_BattleScene->setCurrentText(battleScene); + this->editor->map->setBattleScene(battleScene); } bool MainWindow::getShowLocationName() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->showsLocation(); + return this->editor->map->showsLocationName(); } void MainWindow::setShowLocationName(bool show) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_ShowLocation->setChecked(show); + this->editor->map->setShowsLocationName(show); } bool MainWindow::getAllowRunning() { @@ -924,9 +925,9 @@ bool MainWindow::getAllowRunning() { } void MainWindow::setAllowRunning(bool allow) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_AllowRunning->setChecked(allow); + this->editor->map->setAllowsRunning(allow); } bool MainWindow::getAllowBiking() { @@ -936,9 +937,9 @@ bool MainWindow::getAllowBiking() { } void MainWindow::setAllowBiking(bool allow) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_AllowBiking->setChecked(allow); + this->editor->map->setAllowsBiking(allow); } bool MainWindow::getAllowEscaping() { @@ -948,9 +949,9 @@ bool MainWindow::getAllowEscaping() { } void MainWindow::setAllowEscaping(bool allow) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_AllowEscaping->setChecked(allow); + this->editor->map->setAllowsEscaping(allow); } int MainWindow::getFloorNumber() { @@ -960,12 +961,8 @@ int MainWindow::getFloorNumber() { } void MainWindow::setFloorNumber(int floorNumber) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - if (floorNumber < -128 || floorNumber > 127) { - logError(QString("Invalid floor number '%1'").arg(floorNumber)); - return; - } - this->ui->spinBox_FloorNumber->setValue(floorNumber); + this->editor->map->setFloorNumber(floorNumber); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp new file mode 100644 index 00000000..88d7fbf1 --- /dev/null +++ b/src/ui/mapheaderform.cpp @@ -0,0 +1,220 @@ +#include "mapheaderform.h" + +#define BLOCK_SIGNALS \ + const QSignalBlocker b_Song(ui->comboBox_Song); \ + const QSignalBlocker b_Location(ui->comboBox_Location); \ + const QSignalBlocker b_RequiresFlash(ui->checkBox_RequiresFlash); \ + const QSignalBlocker b_Weather(ui->comboBox_Weather); \ + const QSignalBlocker b_Type(ui->comboBox_Type); \ + const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); \ + const QSignalBlocker b_ShowLocationName(ui->checkBox_ShowLocationName); \ + const QSignalBlocker b_AllowRunning(ui->checkBox_AllowRunning); \ + const QSignalBlocker b_AllowBiking(ui->checkBox_AllowBiking); \ + const QSignalBlocker b_AllowEscaping(ui->checkBox_AllowEscaping); \ + const QSignalBlocker b_FloorNumber(ui->spinBox_FloorNumber); + + +MapHeaderForm::MapHeaderForm(QWidget *parent) + : QWidget(parent) + , ui(new Ui::MapHeaderForm) +{ + ui->setupUi(this); + + // This value is an s8 by default, but we don't need to unnecessarily limit users. + ui->spinBox_FloorNumber->setMinimum(INT_MIN); + ui->spinBox_FloorNumber->setMaximum(INT_MAX); +} + +MapHeaderForm::~MapHeaderForm() +{ + delete ui; +} + +void MapHeaderForm::setProject(Project * newProject) { + clear(); + + this->project = newProject; + if (!this->project) + return; + + // Populate combo boxes + BLOCK_SIGNALS + ui->comboBox_Song->addItems(this->project->songNames); + ui->comboBox_Weather->addItems(this->project->weatherNames); + ui->comboBox_Type->addItems(this->project->mapTypes); + ui->comboBox_BattleScene->addItems(this->project->mapBattleScenes); + refreshLocationsComboBox(); + + // Hide config-specific settings + + bool hasFlags = projectConfig.mapAllowFlagsEnabled; + 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 floorNumEnabled = projectConfig.floorNumberEnabled; + ui->spinBox_FloorNumber->setVisible(floorNumEnabled); + ui->label_FloorNumber->setVisible(floorNumEnabled); +} + +void MapHeaderForm::setMap(Map * newMap) { + this->map = newMap; + if (!this->map) { + clearDisplay(); + return; + } + + BLOCK_SIGNALS + ui->comboBox_Song->setCurrentText(this->map->song()); + ui->comboBox_Location->setCurrentText(this->map->location()); + ui->checkBox_RequiresFlash->setChecked(this->map->requiresFlash()); + ui->comboBox_Weather->setCurrentText(this->map->weather()); + ui->comboBox_Type->setCurrentText(this->map->type()); + ui->comboBox_BattleScene->setCurrentText(this->map->battleScene()); + ui->checkBox_ShowLocationName->setChecked(this->map->showsLocationName()); + ui->checkBox_AllowRunning->setChecked(this->map->allowsRunning()); + ui->checkBox_AllowBiking->setChecked(this->map->allowsBiking()); + ui->checkBox_AllowEscaping->setChecked(this->map->allowsEscaping()); + ui->spinBox_FloorNumber->setValue(this->map->floorNumber()); +} + +void MapHeaderForm::clearDisplay() { + BLOCK_SIGNALS + ui->comboBox_Song->clearEditText(); + ui->comboBox_Location->clearEditText(); + ui->comboBox_Weather->clearEditText(); + ui->comboBox_Type->clearEditText(); + ui->comboBox_BattleScene->clearEditText(); + ui->checkBox_ShowLocationName->setChecked(false); + ui->checkBox_RequiresFlash->setChecked(false); + ui->checkBox_AllowRunning->setChecked(false); + ui->checkBox_AllowBiking->setChecked(false); + ui->checkBox_AllowEscaping->setChecked(false); + ui->spinBox_FloorNumber->setValue(0); +} + +// Clear display and depopulate combo boxes +void MapHeaderForm::clear() { + BLOCK_SIGNALS + ui->comboBox_Song->clear(); + ui->comboBox_Location->clear(); + ui->comboBox_Weather->clear(); + ui->comboBox_Type->clear(); + ui->comboBox_BattleScene->clear(); + ui->checkBox_ShowLocationName->setChecked(false); + ui->checkBox_RequiresFlash->setChecked(false); + ui->checkBox_AllowRunning->setChecked(false); + ui->checkBox_AllowBiking->setChecked(false); + ui->checkBox_AllowEscaping->setChecked(false); + ui->spinBox_FloorNumber->setValue(0); +} + +void MapHeaderForm::refreshLocationsComboBox() { + const QSignalBlocker b(ui->comboBox_Location); + ui->comboBox_Location->clear(); + + if (this->project) { + QStringList locations = this->project->mapSectionIdNames; + locations.sort(); + ui->comboBox_Location->addItems(locations); + } + if (this->map) { + ui->comboBox_Location->setCurrentText(this->map->location()); + } +} + +void MapHeaderForm::on_comboBox_Song_currentTextChanged(const QString &song) +{ + if (this->map) { + this->map->setSong(song); + this->map->modify(); + } +} + +void MapHeaderForm::on_comboBox_Location_currentTextChanged(const QString &location) +{ + if (this->map) { + this->map->setLocation(location); + this->map->modify(); + + // Update cached location name in the project + // TODO: This should be handled elsewhere now, connected to the map change signal + if (this->project) + this->project->mapNameToMapSectionName.insert(this->map->name(), this->map->location()); + } +} + +void MapHeaderForm::on_comboBox_Weather_currentTextChanged(const QString &weather) +{ + if (this->map) { + this->map->setWeather(weather); + this->map->modify(); + } +} + +void MapHeaderForm::on_comboBox_Type_currentTextChanged(const QString &type) +{ + if (this->map) { + this->map->setType(type); + this->map->modify(); + } +} + +void MapHeaderForm::on_comboBox_BattleScene_currentTextChanged(const QString &battleScene) +{ + if (this->map) { + this->map->setBattleScene(battleScene); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_RequiresFlash_stateChanged(int selected) +{ + if (this->map) { + this->map->setRequiresFlash(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_ShowLocationName_stateChanged(int selected) +{ + if (this->map) { + this->map->setShowsLocationName(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_AllowRunning_stateChanged(int selected) +{ + if (this->map) { + this->map->setAllowsRunning(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_AllowBiking_stateChanged(int selected) +{ + if (this->map) { + this->map->setAllowsBiking(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_AllowEscaping_stateChanged(int selected) +{ + if (this->map) { + this->map->setAllowsEscaping(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_spinBox_FloorNumber_valueChanged(int offset) +{ + if (this->map) { + this->map->setFloorNumber(offset); + this->map->modify(); + } +} diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 0d6bdd77..d3096a86 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -31,6 +31,9 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->lineEdit_Name->setValidator(validator); ui->lineEdit_ID->setValidator(validator); + this->headerData = new MapHeaderForm(); + ui->layout_HeaderData->addWidget(this->headerData); + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); } @@ -46,11 +49,7 @@ void NewMapDialog::init() { 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); + this->headerData->setProject(project); // Set spin box limits ui->spinBox_MapWidth->setMaximum(project->getMaxMapWidth()); @@ -58,21 +57,8 @@ void NewMapDialog::init() { 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_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); - ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - bool hasFloorNumber = projectConfig.floorNumberEnabled; - ui->spinBox_FloorNumber->setVisible(hasFloorNumber); - ui->label_FloorNumber->setVisible(hasFloorNumber); - // Restore previous settings ui->lineEdit_Name->setText(project->getNewMapName()); ui->comboBox_Group->setTextItem(settings.group); @@ -82,17 +68,17 @@ void NewMapDialog::init() { 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); + this->headerData->ui->comboBox_Song->setTextItem(settings.song); + this->headerData->ui->comboBox_Location->setTextItem(settings.location); + this->headerData->ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); + this->headerData->ui->comboBox_Weather->setTextItem(settings.weather); + this->headerData->ui->comboBox_Type->setTextItem(settings.type); + this->headerData->ui->comboBox_BattleScene->setTextItem(settings.battleScene); + this->headerData->ui->checkBox_ShowLocationName->setChecked(settings.showLocationName); + this->headerData->ui->checkBox_AllowRunning->setChecked(settings.allowRunning); + this->headerData->ui->checkBox_AllowBiking->setChecked(settings.allowBiking); + this->headerData->ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); + this->headerData->ui->spinBox_FloorNumber->setValue(settings.floorNumber); ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); } @@ -164,17 +150,17 @@ void NewMapDialog::saveSettings() { 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.song = this->headerData->ui->comboBox_Song->currentText(); + settings.location = this->headerData->ui->comboBox_Location->currentText(); + settings.requiresFlash = this->headerData->ui->checkBox_RequiresFlash->isChecked(); + settings.weather = this->headerData->ui->comboBox_Weather->currentText(); + settings.type = this->headerData->ui->comboBox_Type->currentText(); + settings.battleScene = this->headerData->ui->comboBox_BattleScene->currentText(); + settings.showLocationName = this->headerData->ui->checkBox_ShowLocationName->isChecked(); + settings.allowRunning = this->headerData->ui->checkBox_AllowRunning->isChecked(); + settings.allowBiking = this->headerData->ui->checkBox_AllowBiking->isChecked(); + settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); + settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); } @@ -334,20 +320,20 @@ void NewMapDialog::on_pushButton_Accept_clicked() { Map *newMap = new Map; 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()); + newMap->setSong(this->headerData->ui->comboBox_Song->currentText()); + newMap->setLocation(this->headerData->ui->comboBox_Location->currentText()); + newMap->setRequiresFlash(this->headerData->ui->checkBox_RequiresFlash->isChecked()); + newMap->setWeather(this->headerData->ui->comboBox_Weather->currentText()); + newMap->setType(this->headerData->ui->comboBox_Type->currentText()); + newMap->setBattleScene(this->headerData->ui->comboBox_BattleScene->currentText()); + newMap->setShowsLocationName(this->headerData->ui->checkBox_ShowLocationName->isChecked()); if (projectConfig.mapAllowFlagsEnabled) { - newMap->setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); - newMap->setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); - newMap->setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); + newMap->setAllowsRunning(this->headerData->ui->checkBox_AllowRunning->isChecked()); + newMap->setAllowsBiking(this->headerData->ui->checkBox_AllowBiking->isChecked()); + newMap->setAllowsEscaping(this->headerData->ui->checkBox_AllowEscaping->isChecked()); } if (projectConfig.floorNumberEnabled) { - newMap->setFloorNumber(ui->spinBox_FloorNumber->value()); + newMap->setFloorNumber(this->headerData->ui->spinBox_FloorNumber->value()); } newMap->setNeedsHealLocation(ui->checkBox_CanFlyTo->isChecked()); From 4f8224359e5eec405f62b5c35a71807c5f96ce90 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:34:10 -0500 Subject: [PATCH 083/364] Use collapsible section for header data in new map dialog --- forms/newmapdialog.ui | 286 ++++++++++++++++--------------- include/config.h | 2 + include/lib/collapsiblesection.h | 36 ++-- include/ui/newmapdialog.h | 4 +- src/config.cpp | 3 + src/lib/collapsiblesection.cpp | 104 +++++++---- src/mainwindow.cpp | 36 ++-- src/ui/newmapdialog.cpp | 12 +- 8 files changed, 273 insertions(+), 210 deletions(-) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index dcf4f052..3ddba1dc 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,7 +25,7 @@ 0 0 427 - 526 + 520
@@ -39,24 +39,23 @@
- - + + + + false + + + color: rgb(255, 0, 0) + - 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 - + false @@ -72,7 +71,7 @@ - + Border Dimensions @@ -127,115 +126,14 @@ - - + + - Group + ID - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - Can Fly To - - - - - - - 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 - - - - - - - + Map Dimensions @@ -306,14 +204,102 @@ - + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - + + + + <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 + + + + + + + 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 + + + + + + + false @@ -329,28 +315,45 @@ - - - - Header Data + + + + Qt::Orientation::Vertical - - + + + 20 + 40 + + + - - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert + + + + Group - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -360,6 +363,13 @@ + + + + Can Fly To + + + diff --git a/include/config.h b/include/config.h index 2a93a793..0dcbfe66 100644 --- a/include/config.h +++ b/include/config.h @@ -70,6 +70,7 @@ public: this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; + this->newMapHeaderSectionExpanded = false; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; @@ -121,6 +122,7 @@ public: bool showTilesetEditorLayerGrid; bool monitorFiles; bool tilesetCheckerboardFill; + bool newMapHeaderSectionExpanded; QString theme; QString wildMonChartTheme; QString textEditorOpenFolder; diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 3a1b3023..584e44cb 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -16,6 +16,10 @@ You should have received a copy of the GNU General Public License along with Elypson/qt-collapsible-section. If not, see . + + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. */ #ifndef COLLAPSIBLESECTION_H @@ -24,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -31,32 +36,33 @@ class CollapsibleSection : public QWidget { Q_OBJECT + +public: + explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0); + + void setContentLayout(QLayout* contentLayout); + void setTitle(QString title); + bool isExpanded() const { return this->expanded; } + +public slots: + void toggle(bool collapsed); private: QGridLayout* mainLayout; QToolButton* toggleButton; QFrame* headerLine; QParallelAnimationGroup* toggleAnimation; + QSet sectionAnimations; + QPropertyAnimation* contentAnimation; QScrollArea* contentArea; int animationDuration; int collapsedHeight; - bool isExpanded = false; - -public slots: - void toggle(bool collapsed); + bool expanded; -public: - // initialize section - explicit CollapsibleSection(const QString& title = "", const int animationDuration = 0, QWidget* parent = 0); + void updateToggleButton(); + void updateAnimationTargets(); + int getContentHeight() const; - // set layout of content - void setContentLayout(QLayout& contentLayout); - - // set title - void setTitle(QString title); - - // update animations and their heights - void updateHeights(); }; #endif // COLLAPSIBLESECTION_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 090fcb96..72b0b5de 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -7,6 +7,7 @@ #include "project.h" #include "map.h" #include "mapheaderform.h" +#include "lib/collapsiblesection.h" namespace Ui { class NewMapDialog; @@ -24,7 +25,7 @@ public: bool importedMap; QString layoutId; void init(); - //void initUi(); + //void initUi();//TODO void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -35,6 +36,7 @@ signals: private: Ui::NewMapDialog *ui; Project *project; + CollapsibleSection *headerSection; MapHeaderForm *headerData; bool validateMapDimensions(); diff --git a/src/config.cpp b/src/config.cpp index a1987203..316c9ea3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -371,6 +371,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->monitorFiles = getConfigBool(key, value); } else if (key == "tileset_checkerboard_fill") { this->tilesetCheckerboardFill = getConfigBool(key, value); + } else if (key == "new_map_header_section_expanded") { + this->newMapHeaderSectionExpanded = getConfigBool(key, value); } else if (key == "theme") { this->theme = value; } else if (key == "wild_mon_chart_theme") { @@ -453,6 +455,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0"); map.insert("monitor_files", this->monitorFiles ? "1" : "0"); map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0"); + map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0"); map.insert("theme", this->theme); map.insert("wild_mon_chart_theme", this->wildMonChartTheme); map.insert("text_editor_open_directory", this->textEditorOpenFolder); diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp index 000e822e..34dfb780 100644 --- a/src/lib/collapsiblesection.cpp +++ b/src/lib/collapsiblesection.cpp @@ -16,13 +16,15 @@ You should have received a copy of the GNU General Public License along with Elypson/qt-collapsible-section. If not, see . + + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. */ -#include - #include "collapsiblesection.h" -CollapsibleSection::CollapsibleSection(const QString& title, const int animationDuration, QWidget* parent) - : QWidget(parent), animationDuration(animationDuration) +CollapsibleSection::CollapsibleSection(const QString& title, const bool expanded, const int animationDuration, QWidget* parent) + : QWidget(parent), animationDuration(animationDuration), expanded(expanded) { toggleButton = new QToolButton(this); headerLine = new QFrame(this); @@ -32,25 +34,24 @@ CollapsibleSection::CollapsibleSection(const QString& title, const int animation toggleButton->setStyleSheet("QToolButton {border: none;}"); toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - toggleButton->setArrowType(Qt::ArrowType::RightArrow); toggleButton->setText(title); toggleButton->setCheckable(true); - toggleButton->setChecked(false); + updateToggleButton(); headerLine->setFrameShape(QFrame::HLine); headerLine->setFrameShadow(QFrame::Sunken); headerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // start out collapsed contentArea->setMaximumHeight(0); contentArea->setMinimumHeight(0); - // let the entire widget grow and shrink with its content - toggleAnimation->addAnimation(new QPropertyAnimation(this, "maximumHeight")); - toggleAnimation->addAnimation(new QPropertyAnimation(this, "minimumHeight")); - toggleAnimation->addAnimation(new QPropertyAnimation(contentArea, "maximumHeight")); + sectionAnimations.insert(new QPropertyAnimation(this, "minimumHeight")); + sectionAnimations.insert(new QPropertyAnimation(this, "maximumHeight")); + for (const auto &anim : sectionAnimations) + toggleAnimation->addAnimation(anim); + contentAnimation = new QPropertyAnimation(contentArea, "maximumHeight"); + toggleAnimation->addAnimation(contentAnimation); mainLayout->setVerticalSpacing(0); mainLayout->setContentsMargins(0, 0, 0, 0); @@ -64,22 +65,58 @@ CollapsibleSection::CollapsibleSection(const QString& title, const int animation connect(toggleButton, &QToolButton::toggled, this, &CollapsibleSection::toggle); } -void CollapsibleSection::toggle(bool expanded) +void CollapsibleSection::updateToggleButton() { - toggleButton->setArrowType(expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); - toggleAnimation->setDirection(expanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); - toggleAnimation->start(); - - this->isExpanded = expanded; + toggleButton->setChecked(this->expanded); + toggleButton->setArrowType(this->expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); } -void CollapsibleSection::setContentLayout(QLayout& contentLayout) +void CollapsibleSection::toggle(bool expand) { + if (toggleAnimation->state() != QAbstractAnimation::Stopped) + return; + if (this->expanded == expand) + return; + this->expanded = expand; + + updateToggleButton(); + + if (expand) { + // Opening animation. Set the contents to their maximum size immediately, + // and they will be revealed slowly by the section animation. + int contentHeight = getContentHeight(); + contentArea->setMinimumHeight(contentHeight); + contentArea->setMaximumHeight(contentHeight); + toggleAnimation->setDirection(QAbstractAnimation::Forward); + } else { + // Closing animation. Keep the contents at their current size, allowing + // them to be hidden slowly by the section animation, then change their size + // once the animation is complete so they aren't visible just below the title. + auto ctx = new QObject(); + connect(toggleAnimation, &QAbstractAnimation::finished, ctx, [this, ctx]() { + // This is a single-shot connection. Qt6 has built-in support for this kind of thing. + contentArea->setMinimumHeight(0); + contentArea->setMaximumHeight(0); + ctx->deleteLater(); + }); + toggleAnimation->setDirection(QAbstractAnimation::Backward); + } + toggleAnimation->start(); +} + +void CollapsibleSection::setContentLayout(QLayout* contentLayout) +{ + if (contentArea->layout() == contentLayout) + return; delete contentArea->layout(); - contentArea->setLayout(&contentLayout); + contentArea->setLayout(contentLayout); collapsedHeight = sizeHint().height() - contentArea->maximumHeight(); - - updateHeights(); + + int contentHeight = this->expanded ? getContentHeight() : 0; + contentArea->setMinimumHeight(contentHeight); + contentArea->setMaximumHeight(contentHeight); + + updateAnimationTargets(); } void CollapsibleSection::setTitle(QString title) @@ -87,23 +124,20 @@ void CollapsibleSection::setTitle(QString title) toggleButton->setText(std::move(title)); } -void CollapsibleSection::updateHeights() +int CollapsibleSection::getContentHeight() const { - int contentHeight = contentArea->layout()->sizeHint().height(); + return contentArea->layout() ? contentArea->layout()->sizeHint().height() : 0; +} - for (int i = 0; i < toggleAnimation->animationCount() - 1; ++i) - { - QPropertyAnimation* SectionAnimation = static_cast(toggleAnimation->animationAt(i)); - SectionAnimation->setDuration(animationDuration); - SectionAnimation->setStartValue(collapsedHeight); - SectionAnimation->setEndValue(collapsedHeight + contentHeight); +void CollapsibleSection::updateAnimationTargets() +{ + const int contentHeight = getContentHeight(); + for (auto anim : sectionAnimations) { + anim->setDuration(animationDuration); + anim->setStartValue(collapsedHeight); + anim->setEndValue(collapsedHeight + contentHeight); } - - QPropertyAnimation* contentAnimation = static_cast(toggleAnimation->animationAt(toggleAnimation->animationCount() - 1)); contentAnimation->setDuration(animationDuration); contentAnimation->setStartValue(0); contentAnimation->setEndValue(contentHeight); - - toggleAnimation->setDirection(isExpanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); - toggleAnimation->start(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d47dfef2..50c4b4d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,11 +86,27 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() { + // Some config settings are updated as subwindows are destroyed (e.g. their geometry), + // so we need to ensure that the configs are saved after this happens. + saveGlobalConfigs(); + delete label_MapRulerStatus; delete editor; delete ui; } +void MainWindow::saveGlobalConfigs() { + porymapConfig.setMainGeometry( + this->saveGeometry(), + this->saveState(), + this->ui->splitter_map->saveState(), + this->ui->splitter_main->saveState(), + this->ui->splitter_Metatiles->saveState() + ); + porymapConfig.save(); + shortcutsConfig.save(); +} + void MainWindow::setWindowDisabled(bool disabled) { for (auto action : findChildren()) action->setDisabled(disabled); @@ -1739,12 +1755,14 @@ void MainWindow::on_action_Save_Project_triggered() { editor->saveProject(); updateWindowTitle(); updateMapList(); + saveGlobalConfigs(); } void MainWindow::on_action_Save_triggered() { editor->save(); updateWindowTitle(); updateMapList(); + saveGlobalConfigs(); } void MainWindow::duplicate() { @@ -3282,24 +3300,9 @@ bool MainWindow::closeProject() { return true; } -void MainWindow::saveGlobalConfigs() { - porymapConfig.setMainGeometry( - this->saveGeometry(), - this->saveState(), - this->ui->splitter_map->saveState(), - this->ui->splitter_main->saveState(), - this->ui->splitter_Metatiles->saveState() - ); - porymapConfig.save(); - shortcutsConfig.save(); -} - void MainWindow::on_action_Exit_triggered() { if (!closeProject()) return; - - saveGlobalConfigs(); - QApplication::quit(); } @@ -3308,8 +3311,5 @@ void MainWindow::closeEvent(QCloseEvent *event) { event->ignore(); return; } - - saveGlobalConfigs(); - QMainWindow::closeEvent(event); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d3096a86..ff4cb209 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -8,8 +8,6 @@ #include #include -// TODO: Make ui->groupBox_HeaderData collapsible - const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; struct NewMapDialog::Settings NewMapDialog::settings = {}; @@ -31,8 +29,15 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->lineEdit_Name->setValidator(validator); ui->lineEdit_ID->setValidator(validator); + // Create a collapsible section that has all the map header data. this->headerData = new MapHeaderForm(); - ui->layout_HeaderData->addWidget(this->headerData); + auto sectionLayout = new QVBoxLayout(); + sectionLayout->addWidget(this->headerData); + + this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); + this->headerSection->setContentLayout(sectionLayout); + ui->layout_HeaderData->addWidget(this->headerSection); + ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); @@ -162,6 +167,7 @@ void NewMapDialog::saveSettings() { settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(Layout *layout) { From 7d7db3857d074f59ca2531693c13cf84d2c49718 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:50:44 -0500 Subject: [PATCH 084/364] Clean up new map dialog redesign --- docsrc/manual/project-files.rst | 1 + include/config.h | 3 +- include/project.h | 10 ++-- src/config.cpp | 23 +++++++-- src/core/events.cpp | 32 ++++-------- src/mainwindow.cpp | 25 +++------- src/project.cpp | 88 +++++++++++++++++++-------------- src/ui/newmapdialog.cpp | 1 - 8 files changed, 93 insertions(+), 90 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 8e0dfa33..67bba635 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -92,6 +92,7 @@ In addition to these files, there are some specific symbol and macro names that ``symbol_spawn_npcs``, ``u8 sWhiteoutRespawnHealerNpcIds``, the type and table name for Heal Location ``Respawn NPC`` values ``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab ``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names + ``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic`` ``define_obj_event_count``, ``OBJECT_EVENT_TEMPLATES_COUNT``, to limit total Object Events ``define_min_level``, ``MIN_LEVEL``, minimum wild encounters level ``define_max_level``, ``MAX_LEVEL``, maximum wild encounters level diff --git a/include/config.h b/include/config.h index 0dcbfe66..0655c0bd 100644 --- a/include/config.h +++ b/include/config.h @@ -187,6 +187,7 @@ enum ProjectIdentifier { symbol_spawn_npcs, symbol_attribute_table, symbol_tilesets_prefix, + symbol_dynamic_map_name, define_obj_event_count, define_min_level, define_max_level, @@ -323,7 +324,7 @@ public: QString getCustomFilePath(ProjectFilePath pathId); QString getCustomFilePath(const QString &pathId); QString getFilePath(ProjectFilePath pathId); - void setIdentifier(ProjectIdentifier id, const QString &text); + void setIdentifier(ProjectIdentifier id, QString text); void setIdentifier(const QString &id, const QString &text); QString getCustomIdentifier(ProjectIdentifier id); QString getCustomIdentifier(const QString &id); diff --git a/include/project.h b/include/project.h index 1632d38f..c76fcd72 100644 --- a/include/project.h +++ b/include/project.h @@ -18,10 +18,6 @@ #include #include -// TODO: Expose to config -// The displayed name of the special map value used by warps with multiple potential destinations -static QString DYNAMIC_MAP_NAME = "Dynamic"; - class Project : public QObject { Q_OBJECT @@ -80,6 +76,7 @@ public: QMap modifiedFileTimestamps; bool usingAsmTilesets; QSet disabledSettingsNames; + QSet topLevelMapFields; int pokemonMinLevel; int pokemonMaxLevel; int maxEncounterRate; @@ -145,7 +142,7 @@ public: bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; - QSet getTopLevelMapFields(); + void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); bool loadMapData(Map*); bool readMapLayouts(); @@ -219,7 +216,6 @@ public: QString getDefaultPrimaryTilesetLabel(); QString getDefaultSecondaryTilesetLabel(); - QString getDynamicMapDefineName(); void updateTilesetMetatileLabels(Tileset *tileset); QString buildMetatileLabelsText(const QMap defines); QString findMetatileLabelsTileset(QString label); @@ -227,6 +223,8 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + static QString getDynamicMapDefineName(); + static QString getDynamicMapName(); static int getNumTilesPrimary(); static int getNumTilesTotal(); static int getNumMetatilesPrimary(); diff --git a/src/config.cpp b/src/config.cpp index 316c9ea3..234a0a33 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -81,6 +81,7 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::symbol_spawn_npcs, {"symbol_spawn_npcs", "u8 sWhiteoutRespawnHealerNpcIds"}}, {ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}}, {ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}}, + {ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}}, // Defines {ProjectIdentifier::define_obj_event_count, {"define_obj_event_count", "OBJECT_EVENT_TEMPLATES_COUNT"}}, {ProjectIdentifier::define_min_level, {"define_min_level", "MIN_LEVEL"}}, @@ -941,13 +942,25 @@ QString ProjectConfig::getFilePath(ProjectFilePath pathId) { } -void ProjectConfig::setIdentifier(ProjectIdentifier id, const QString &text) { - if (!defaultIdentifiers.contains(id)) return; - QString copy(text); - if (copy.isEmpty()) { +void ProjectConfig::setIdentifier(ProjectIdentifier id, QString text) { + if (!defaultIdentifiers.contains(id)) + return; + + if (text.isEmpty()) { this->identifiers.remove(id); } else { - this->identifiers[id] = copy; + const QString idName = defaultIdentifiers.value(id).first; + if (idName.startsWith("define_") || idName.startsWith("symbol_")) { + // Validate the input for the identifier, depending on the type. + static const QRegularExpression re("[A-Za-z_]+[\\w]*"); + auto validator = QRegularExpressionValidator(re); + int temp = 0; + if (validator.validate(text, temp) != QValidator::Acceptable) { + logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName)); + return; + } + } + this->identifiers[id] = text; } } diff --git a/src/core/events.cpp b/src/core/events.cpp index 94030d03..0673a652 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -404,17 +404,11 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); - // Ensure the target map constant is valid before adding it to the events. - const QString dynamicMapConstant = project->getDynamicMapDefineName(); - QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); - if (project->mapConstantsToMapNames.contains(mapConstant)) { - this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant)); - } else if (mapConstant == dynamicMapConstant) { - this->setTargetMap(DYNAMIC_MAP_NAME); - } else { - logWarn(QString("Target Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant)); - this->setTargetMap(DYNAMIC_MAP_NAME); - } + // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. + const QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Target Map constant '%1' is invalid.").arg(mapConstant)); + this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); @@ -516,17 +510,11 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { this->setElevation(ParseUtil::jsonToInt(json["elevation"])); this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"])); - // Ensure the warp destination map constant is valid before adding it to the warps. - const QString dynamicMapConstant = project->getDynamicMapDefineName(); - QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); - if (project->mapConstantsToMapNames.contains(mapConstant)) { - this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant)); - } else if (mapConstant == dynamicMapConstant) { - this->setDestinationMap(DYNAMIC_MAP_NAME); - } else { - logWarn(QString("Destination Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant)); - this->setDestinationMap(DYNAMIC_MAP_NAME); - } + // Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data. + const QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Destination Map constant '%1' is invalid.").arg(mapConstant)); + this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 50c4b4d8..eaa98f11 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -850,10 +850,10 @@ bool MainWindow::userSetMap(QString map_name) { if (editor->map && editor->map->name() == map_name) return true; // Already set - if (map_name == DYNAMIC_MAP_NAME) { + if (map_name == editor->project->getDynamicMapName()) { QMessageBox msgBox(this); QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name); - msgBox.critical(nullptr, "Error Opening Map", errorMsg); + msgBox.warning(nullptr, "Cannot Open Map", errorMsg); return false; } @@ -870,14 +870,13 @@ bool MainWindow::userSetMap(QString map_name) { } bool MainWindow::setMap(QString map_name) { - if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { - logInfo(QString("Cannot set map to '%1'").arg(map_name)); + if (!editor || !editor->project || map_name.isEmpty() || map_name == editor->project->getDynamicMapName()) { + logWarn(QString("Ignored setting map to '%1'").arg(map_name)); return false; } logInfo(QString("Setting map to '%1'").arg(map_name)); - - if (!editor || !editor->setMap(map_name)) { + if (!editor->setMap(map_name)) { logWarn(QString("Failed to set map to '%1'").arg(map_name)); return false; } @@ -996,12 +995,6 @@ void MainWindow::refreshMapScene() { } void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) { - // Ensure valid destination map name. - if (!editor->project->mapNames.contains(map_name)) { - logError(QString("Invalid map name '%1'").arg(map_name)); - return; - } - // Open the destination map. if (!userSetMap(map_name)) return; @@ -2780,15 +2773,11 @@ void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() { } void MainWindow::on_button_OpenDiveMap_clicked() { - const QString mapName = ui->comboBox_DiveMap->currentText(); - if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName); + userSetMap(ui->comboBox_DiveMap->currentText()); } void MainWindow::on_button_OpenEmergeMap_clicked() { - const QString mapName = ui->comboBox_EmergeMap->currentText(); - if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName); + userSetMap(ui->comboBox_EmergeMap->currentText()); } void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { diff --git a/src/project.cpp b/src/project.cpp index f36f7543..03f7b4df 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -135,7 +135,7 @@ void Project::clearTilesetCache() { } Map* Project::loadMap(QString mapName) { - if (mapName == DYNAMIC_MAP_NAME) + if (mapName == getDynamicMapName()) return nullptr; Map *map; @@ -148,7 +148,6 @@ Map* Project::loadMap(QString mapName) { } else { map = new Map; map->setName(mapName); - map->setConstantName(this->mapNamesToMapConstants.value(mapName)); // TODO: How should we handle if !mapNamesToMapConstants.contains(mapName) here } if (!(loadMapData(map) && loadMapLayout(map))){ @@ -161,38 +160,36 @@ Map* Project::loadMap(QString mapName) { return map; } -const QSet defaultTopLevelMapFields = { - "id", - "name", - "layout", - "music", - "region_map_section", - "requires_flash", - "weather", - "map_type", - "show_map_name", - "battle_scene", - "connections", - "object_events", - "warp_events", - "coord_events", - "bg_events", - "shared_events_map", - "shared_scripts_map", -}; - -QSet Project::getTopLevelMapFields() { - QSet topLevelMapFields = defaultTopLevelMapFields; +void Project::initTopLevelMapFields() { + static const QSet defaultTopLevelMapFields = { + "id", + "name", + "layout", + "music", + "region_map_section", + "requires_flash", + "weather", + "map_type", + "show_map_name", + "battle_scene", + "connections", + "object_events", + "warp_events", + "coord_events", + "bg_events", + "heal_locations", + "shared_events_map", + "shared_scripts_map", + }; + this->topLevelMapFields = defaultTopLevelMapFields; if (projectConfig.mapAllowFlagsEnabled) { - topLevelMapFields.insert("allow_cycling"); - topLevelMapFields.insert("allow_escaping"); - topLevelMapFields.insert("allow_running"); + this->topLevelMapFields.insert("allow_cycling"); + this->topLevelMapFields.insert("allow_escaping"); + this->topLevelMapFields.insert("allow_running"); } - if (projectConfig.floorNumberEnabled) { - topLevelMapFields.insert("floor_number"); + this->topLevelMapFields.insert("floor_number"); } - return topLevelMapFields; } bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { @@ -215,6 +212,11 @@ bool Project::loadMapData(Map* map) { QJsonObject mapObj = mapDoc.object(); + // We should already know the map constant ID from the initial project launch, but we'll ensure it's correct here anyway. + map->setConstantName(ParseUtil::jsonToQString(mapObj["id"])); + this->mapNamesToMapConstants.insert(map->name(), map->constantName()); + this->mapConstantsToMapNames.insert(map->constantName(), map->name()); + map->setSong(ParseUtil::jsonToQString(mapObj["music"])); map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); map->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); @@ -353,9 +355,8 @@ bool Project::loadMapData(Map* map) { // Check for custom fields /* // TODO: Re-enable - QSet baseFields = this->getTopLevelMapFields(); for (QString key : mapObj.keys()) { - if (!baseFields.contains(key)) { + if (!this->topLevelMapFields.contains(key)) { map->customHeaders.insert(key, mapObj[key]); } } @@ -1822,6 +1823,8 @@ bool Project::readMapGroups() { this->groupedMapNames.clear(); this->mapNames.clear(); + this->initTopLevelMapFields(); + const QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_map_groups); fileWatcher.addPath(filepath); QJsonDocument mapGroupsDoc; @@ -1832,14 +1835,20 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + + const QString dynamicMapName = getDynamicMapName(); + + // Process the map group lists for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); this->groupedMapNames.append(QStringList()); this->groupNames.append(groupName); + + // Process the names in this map group for (int j = 0; j < mapNamesJson.size(); j++) { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); - if (mapName == DYNAMIC_MAP_NAME) { + if (mapName == dynamicMapName) { logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); continue; } @@ -1877,7 +1886,7 @@ bool Project::readMapGroups() { this->mapGroups.insert(mapName, groupIndex); this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); - // TODO: Keep these updated + // TODO: Either verify that these are known IDs, or make sure nothing breaks when they're unknown. this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); } @@ -1892,10 +1901,11 @@ bool Project::readMapGroups() { return false; } + // Save special "Dynamic" constant const QString defineName = this->getDynamicMapDefineName(); - this->mapConstantsToMapNames.insert(defineName, DYNAMIC_MAP_NAME); - this->mapNamesToMapConstants.insert(DYNAMIC_MAP_NAME, defineName); - this->mapNames.append(DYNAMIC_MAP_NAME); + this->mapConstantsToMapNames.insert(defineName, dynamicMapName); + this->mapNamesToMapConstants.insert(dynamicMapName, defineName); + this->mapNames.append(dynamicMapName); return true; } @@ -2941,6 +2951,10 @@ QString Project::getDynamicMapDefineName() { return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); } +QString Project::getDynamicMapName() { + return projectConfig.getIdentifier(ProjectIdentifier::symbol_dynamic_map_name); +} + // If the provided filepath is an absolute path to an existing file, return filepath. // If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path. // Otherwise return empty string. diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index ff4cb209..da10c26a 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -23,7 +23,6 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : 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); From 8bb01005408235d0a20ef896316089d8ebf77055 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 15:57:27 -0500 Subject: [PATCH 085/364] Separate layout/header features of new map dialog --- forms/newlayoutform.ui | 236 +++++++++++++++++++++ forms/newmapdialog.ui | 405 +++++++++++-------------------------- include/config.h | 2 +- include/core/map.h | 41 +--- include/core/mapheader.h | 83 ++++++++ include/mainwindow.h | 2 +- include/project.h | 2 +- include/ui/mapheaderform.h | 63 +++--- include/ui/newlayoutform.h | 46 +++++ include/ui/newmapdialog.h | 29 +-- porymap.pro | 5 + src/core/map.cpp | 48 +---- src/core/mapheader.cpp | 132 ++++++++++++ src/mainwindow.cpp | 59 +++--- src/project.cpp | 51 ++--- src/scriptapi/apimap.cpp | 44 ++-- src/ui/mapheaderform.cpp | 338 ++++++++++++++++++------------- src/ui/newlayoutform.cpp | 123 +++++++++++ src/ui/newmapdialog.cpp | 245 +++++++--------------- 19 files changed, 1134 insertions(+), 820 deletions(-) create mode 100644 forms/newlayoutform.ui create mode 100644 include/core/mapheader.h create mode 100644 include/ui/newlayoutform.h create mode 100644 src/core/mapheader.cpp create mode 100644 src/ui/newlayoutform.cpp diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui new file mode 100644 index 00000000..65183046 --- /dev/null +++ b/forms/newlayoutform.ui @@ -0,0 +1,236 @@ + + + NewLayoutForm + + + + 0 + 0 + 304 + 344 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Map Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + 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 + + + + + + + + + + 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 + + + + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 3ddba1dc..911050d3 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -32,14 +32,27 @@ 10 - - + + - Name + Map ID - + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + false @@ -55,287 +68,10 @@ - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - + + - - - - 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 - - - - - - - - - - ID - - - - - - - Map Dimensions - - - - - - - 0 - 0 - - - - Width - - - - - - - <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> - - - 1 - - - - - - - - 0 - 0 - - - - Height - - - - - - - <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> - - - 1 - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - - - - <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 - - - - - - - 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 - - - - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - Group - - - - + @@ -353,7 +89,83 @@ - + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true + + + + + + + Can Fly To + + + + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Map Group + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Map Name + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -363,13 +175,23 @@ - - + + - Can Fly To + Layout ID + + + + <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> + + + + + + @@ -394,9 +216,10 @@
noscrollcombobox.h
- NoScrollSpinBox - QSpinBox -
noscrollspinbox.h
+ NewLayoutForm + QWidget +
newlayoutform.h
+ 1
diff --git a/include/config.h b/include/config.h index 0655c0bd..21e2dde7 100644 --- a/include/config.h +++ b/include/config.h @@ -70,7 +70,7 @@ public: this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; - this->newMapHeaderSectionExpanded = false; + this->newMapHeaderSectionExpanded = true; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; diff --git a/include/core/map.h b/include/core/map.h index 314013d7..55eee303 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -7,6 +7,7 @@ #include "maplayout.h" #include "tileset.h" #include "events.h" +#include "mapheader.h" #include #include @@ -56,30 +57,8 @@ public: int getBorderWidth() const; int getBorderHeight() const; - // TODO: Combine these into a separate MapHeader class? - void setSong(const QString &song); - void setLocation(const QString &location); - void setRequiresFlash(bool requiresFlash); - void setWeather(const QString &weather); - void setType(const QString &type); - void setShowsLocationName(bool showsLocationName); - void setAllowsRunning(bool allowsRunning); - void setAllowsBiking(bool allowsBiking); - void setAllowsEscaping(bool allowsEscaping); - void setFloorNumber(int floorNumber); - void setBattleScene(const QString &battleScene); - - QString song() const { return m_song; } - QString location() const { return m_location; } - bool requiresFlash() const { return m_requiresFlash; } - QString weather() const { return m_weather; } - QString type() const { return m_type; } - bool showsLocationName() const { return m_showsLocationName; } - bool allowsRunning() const { return m_allowsRunning; } - bool allowsBiking() const { return m_allowsBiking; } - bool allowsEscaping() const { return m_allowsEscaping; } - int floorNumber() const { return m_floorNumber; } - QString battleScene() const { return m_battleScene; } + void setHeader(const MapHeader &header) { *m_header = header; } + MapHeader* header() const { return m_header; } void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; } @@ -130,25 +109,13 @@ private: QString m_name; QString m_constantName; QString m_layoutId; // TODO: Why do we do half this->layout()->id and half this->layoutId. Should these ever be different? - - QString m_song; - QString m_location; - bool m_requiresFlash; - QString m_weather; - QString m_type; - bool m_showsLocationName; - bool m_allowsRunning; - bool m_allowsBiking; - bool m_allowsEscaping; - int m_floorNumber = 0; - QString m_battleScene; - QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; QStringList m_scriptsFileLabels; QMap m_customAttributes; + MapHeader *m_header = nullptr; Layout *m_layout = nullptr; bool m_isPersistedToFile = true; diff --git a/include/core/mapheader.h b/include/core/mapheader.h new file mode 100644 index 00000000..ee2c354e --- /dev/null +++ b/include/core/mapheader.h @@ -0,0 +1,83 @@ +#ifndef MAPHEADER_H +#define MAPHEADER_H + +#include + +class MapHeader : public QObject +{ + Q_OBJECT +public: + MapHeader(QObject *parent = nullptr) : QObject(parent) {}; + ~MapHeader() {}; + MapHeader(const MapHeader& other); + MapHeader& operator=(const MapHeader& other); + bool operator==(const MapHeader& other) const { + return m_song == other.m_song + && m_location == other.m_location + && m_requiresFlash == other.m_requiresFlash + && m_weather == other.m_weather + && m_type == other.m_type + && m_showsLocationName == other.m_showsLocationName + && m_allowsRunning == other.m_allowsRunning + && m_allowsBiking == other.m_allowsBiking + && m_allowsEscaping == other.m_allowsEscaping + && m_floorNumber == other.m_floorNumber + && m_battleScene == other.m_battleScene; + } + bool operator!=(const MapHeader& other) const { + return !(operator==(other)); + } + + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setShowsLocationName(bool showsLocationName); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + void setBattleScene(const QString &battleScene); + + QString song() const { return m_song; } + QString location() const { return m_location; } + bool requiresFlash() const { return m_requiresFlash; } + QString weather() const { return m_weather; } + QString type() const { return m_type; } + bool showsLocationName() const { return m_showsLocationName; } + bool allowsRunning() const { return m_allowsRunning; } + bool allowsBiking() const { return m_allowsBiking; } + bool allowsEscaping() const { return m_allowsEscaping; } + int floorNumber() const { return m_floorNumber; } + QString battleScene() const { return m_battleScene; } + +signals: + void songChanged(QString, QString); + void locationChanged(QString, QString); + void requiresFlashChanged(bool, bool); + void weatherChanged(QString, QString); + void typeChanged(QString, QString); + void showsLocationNameChanged(bool, bool); + void allowsRunningChanged(bool, bool); + void allowsBikingChanged(bool, bool); + void allowsEscapingChanged(bool, bool); + void floorNumberChanged(int, int); + void battleSceneChanged(QString, QString); + void modified(); + +private: + QString m_song; + QString m_location; + bool m_requiresFlash = false; + QString m_weather; + QString m_type; + bool m_showsLocationName = false; + bool m_allowsRunning = false; + bool m_allowsBiking = false; + bool m_allowsEscaping = false; + int m_floorNumber = 0; + QString m_battleScene; +}; + +#endif // MAPHEADER_H diff --git a/include/mainwindow.h b/include/mainwindow.h index ab4e0482..a506f5e0 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -327,7 +327,7 @@ private: QAction *copyAction = nullptr; QAction *pasteAction = nullptr; - MapHeaderForm *mapHeader = nullptr; + MapHeaderForm *mapHeaderForm = nullptr; QMap lastSelectedEvent; diff --git a/include/project.h b/include/project.h index c76fcd72..f28f9259 100644 --- a/include/project.h +++ b/include/project.h @@ -263,7 +263,7 @@ private: signals: void fileChanged(QString filepath); - void mapSectionIdNamesChanged(); + void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); }; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index afdee728..897b8e9a 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -1,18 +1,18 @@ #ifndef MAPHEADERFORM_H #define MAPHEADERFORM_H -#include "project.h" -#include "map.h" -#include "ui_mapheaderform.h" - -#include - /* This is the UI class used to edit the fields in a map's header. It's intended to be used anywhere the UI needs to present an editor for a map's header, e.g. for the current map in the main editor or in the new map dialog. */ +#include +#include +#include "mapheader.h" + +class Project; + namespace Ui { class MapHeaderForm; } @@ -25,32 +25,43 @@ public: explicit MapHeaderForm(QWidget *parent = nullptr); ~MapHeaderForm(); - void setProject(Project * project); - void setMap(Map * map); - - void clearDisplay(); + void init(const Project * project); void clear(); - void refreshLocationsComboBox(); + void setHeader(MapHeader *header); + MapHeader headerData() const; - Ui::MapHeaderForm *ui; + void setLocations(QStringList locations); + void setLocationsDisabled(bool disabled); private: - QPointer map = nullptr; - QPointer project = nullptr; + Ui::MapHeaderForm *ui; + QPointer m_header = nullptr; -private slots: - void on_comboBox_Song_currentTextChanged(const QString &); - void on_comboBox_Location_currentTextChanged(const QString &); - void on_comboBox_Weather_currentTextChanged(const QString &); - void on_comboBox_Type_currentTextChanged(const QString &); - void on_comboBox_BattleScene_currentTextChanged(const QString &); - void on_checkBox_RequiresFlash_stateChanged(int); - void on_checkBox_ShowLocationName_stateChanged(int); - void on_checkBox_AllowRunning_stateChanged(int); - void on_checkBox_AllowBiking_stateChanged(int); - void on_checkBox_AllowEscaping_stateChanged(int); - void on_spinBox_FloorNumber_valueChanged(int); + void updateUi(); + void updateSong(); + void updateLocation(); + void updateRequiresFlash(); + void updateWeather(); + void updateType(); + void updateBattleScene(); + void updateShowsLocationName(); + void updateAllowsRunning(); + void updateAllowsBiking(); + void updateAllowsEscaping(); + void updateFloorNumber(); + + void onSongUpdated(const QString &song); + void onLocationChanged(const QString &location); + void onWeatherChanged(const QString &weather); + void onTypeChanged(const QString &type); + void onBattleSceneChanged(const QString &battleScene); + void onRequiresFlashChanged(int selected); + void onShowLocationNameChanged(int selected); + void onAllowRunningChanged(int selected); + void onAllowBikingChanged(int selected); + void onAllowEscapingChanged(int selected); + void onFloorNumberChanged(int offset); }; #endif // MAPHEADERFORM_H diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h new file mode 100644 index 00000000..6f63466b --- /dev/null +++ b/include/ui/newlayoutform.h @@ -0,0 +1,46 @@ +#ifndef NEWLAYOUTFORM_H +#define NEWLAYOUTFORM_H + +#include + +class Project; + +namespace Ui { +class NewLayoutForm; +} + +class NewLayoutForm : public QWidget +{ + Q_OBJECT + +public: + explicit NewLayoutForm(QWidget *parent = nullptr); + ~NewLayoutForm(); + + void initUi(Project *project); + + struct Settings { + int width; + int height; + int borderWidth; + int borderHeight; + QString primaryTilesetLabel; + QString secondaryTilesetLabel; + }; + + void setSettings(const Settings &settings); + NewLayoutForm::Settings settings() const; + + void setDisabled(bool disabled); + + bool validate(); + +private: + Ui::NewLayoutForm *ui; + Project *m_project; + + bool validateMapDimensions(); + bool validateTilesets(); +}; + +#endif // NEWLAYOUTFORM_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 72b0b5de..e7401242 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -7,6 +7,7 @@ #include "project.h" #include "map.h" #include "mapheaderform.h" +#include "newlayoutform.h" #include "lib/collapsiblesection.h" namespace Ui { @@ -25,7 +26,6 @@ public: bool importedMap; QString layoutId; void init(); - //void initUi();//TODO void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -37,11 +37,9 @@ private: Ui::NewMapDialog *ui; Project *project; CollapsibleSection *headerSection; - MapHeaderForm *headerData; + MapHeaderForm *headerForm; - bool validateMapDimensions(); bool validateMapGroup(); - bool validateTilesets(); bool validateID(); bool validateName(); @@ -51,33 +49,18 @@ private: struct Settings { QString group; - int width; - int height; - int borderWidth; - int borderHeight; - QString primaryTilesetLabel; - QString secondaryTilesetLabel; - QString song; - QString location; - bool requiresFlash; - QString weather; - QString type; - QString battleScene; - bool showLocationName; - bool allowRunning; - bool allowBiking; - bool allowEscaping; - int floorNumber; bool canFlyTo; + NewLayoutForm::Settings layout; + MapHeader header; }; static struct Settings settings; private slots: - //void on_checkBox_UseExistingLayout_stateChanged(int state); + //void on_checkBox_UseExistingLayout_stateChanged(int state); //TODO //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 &); + void on_lineEdit_MapID_textChanged(const QString &); }; #endif // NEWMAPDIALOG_H diff --git a/porymap.pro b/porymap.pro index d3d870bc..c4db35d0 100644 --- a/porymap.pro +++ b/porymap.pro @@ -30,6 +30,7 @@ SOURCES += src/core/block.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ src/core/mapconnection.cpp \ + src/core/mapheader.cpp \ src/core/maplayout.cpp \ src/core/mapparser.cpp \ src/core/metatile.cpp \ @@ -89,6 +90,7 @@ SOURCES += src/core/block.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ src/ui/neweventtoolbutton.cpp \ + src/ui/newlayoutform.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -134,6 +136,7 @@ HEADERS += include/core/block.h \ include/core/imageexport.h \ include/core/map.h \ include/core/mapconnection.h \ + include/core/mapheader.h \ include/core/maplayout.h \ include/core/mapparser.h \ include/core/metatile.h \ @@ -193,6 +196,7 @@ HEADERS += include/core/block.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ include/ui/neweventtoolbutton.h \ + include/ui/newlayoutform.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ include/ui/montabwidget.h \ @@ -237,6 +241,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newlayoutform.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ diff --git a/src/core/map.cpp b/src/core/map.cpp index ab31ac86..1d7c4555 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -16,6 +16,9 @@ Map::Map(QObject *parent) : QObject(parent) m_scriptsLoaded = false; m_editHistory = new QUndoStack(this); resetEvents(); + + m_header = new MapHeader(this); + connect(m_header, &MapHeader::modified, this, &Map::modified); } Map::~Map() { @@ -309,48 +312,3 @@ void Map::pruneEditHistory() { command->setObsolete(true); } } - -void Map::setSong(const QString &song) { - m_song = song; -} - -void Map::setLocation(const QString &location) { - m_location = location; -} - -void Map::setRequiresFlash(bool requiresFlash) { - m_requiresFlash = requiresFlash; -} - -void Map::setWeather(const QString &weather) { - m_weather = weather; -} - -void Map::setType(const QString &type) { - m_type = type; -} - -void Map::setShowsLocationName(bool showsLocationName) { - m_showsLocationName = showsLocationName; -} - -void Map::setAllowsRunning(bool allowsRunning) { - m_allowsRunning = allowsRunning; -} - -void Map::setAllowsBiking(bool allowsBiking) { - m_allowsBiking = allowsBiking; -} - -void Map::setAllowsEscaping(bool allowsEscaping) { - m_allowsEscaping = allowsEscaping; -} - -void Map::setFloorNumber(int floorNumber) { - m_floorNumber = floorNumber; -} - -void Map::setBattleScene(const QString &battleScene) { - m_battleScene = battleScene; -} - diff --git a/src/core/mapheader.cpp b/src/core/mapheader.cpp new file mode 100644 index 00000000..4c0bc06e --- /dev/null +++ b/src/core/mapheader.cpp @@ -0,0 +1,132 @@ +#include "mapheader.h" + +MapHeader::MapHeader(const MapHeader& other) : MapHeader() { + m_song = other.m_song; + m_location = other.m_location; + m_requiresFlash = other.m_requiresFlash; + m_weather = other.m_weather; + m_type = other.m_type; + m_showsLocationName = other.m_showsLocationName; + m_allowsRunning = other.m_allowsRunning; + m_allowsBiking = other.m_allowsBiking; + m_allowsEscaping = other.m_allowsEscaping; + m_floorNumber = other.m_floorNumber; + m_battleScene = other.m_battleScene; +} + +MapHeader &MapHeader::operator=(const MapHeader &other) { + // We want to call each set function here to ensure any fieldChanged signals + // are sent as necessary. This does also mean the modified signal can be sent + // repeatedly (but for now at least that's not a big issue). + setSong(other.m_song); + setLocation(other.m_location); + setRequiresFlash(other.m_requiresFlash); + setWeather(other.m_weather); + setType(other.m_type); + setShowsLocationName(other.m_showsLocationName); + setAllowsRunning(other.m_allowsRunning); + setAllowsBiking(other.m_allowsBiking); + setAllowsEscaping(other.m_allowsEscaping); + setFloorNumber(other.m_floorNumber); + setBattleScene(other.m_battleScene); + return *this; +} + +void MapHeader::setSong(const QString &song) { + if (m_song == song) + return; + auto before = m_song; + m_song = song; + emit songChanged(before, m_song); + emit modified(); +} + +void MapHeader::setLocation(const QString &location) { + if (m_location == location) + return; + auto before = m_location; + m_location = location; + emit locationChanged(before, m_location); + emit modified(); +} + +void MapHeader::setRequiresFlash(bool requiresFlash) { + if (m_requiresFlash == requiresFlash) + return; + auto before = m_requiresFlash; + m_requiresFlash = requiresFlash; + emit requiresFlashChanged(before, m_requiresFlash); + emit modified(); +} + +void MapHeader::setWeather(const QString &weather) { + if (m_weather == weather) + return; + auto before = m_weather; + m_weather = weather; + emit weatherChanged(before, m_weather); + emit modified(); +} + +void MapHeader::setType(const QString &type) { + if (m_type == type) + return; + auto before = m_type; + m_type = type; + emit typeChanged(before, m_type); + emit modified(); +} + +void MapHeader::setShowsLocationName(bool showsLocationName) { + if (m_showsLocationName == showsLocationName) + return; + auto before = m_showsLocationName; + m_showsLocationName = showsLocationName; + emit showsLocationNameChanged(before, m_showsLocationName); + emit modified(); +} + +void MapHeader::setAllowsRunning(bool allowsRunning) { + if (m_allowsRunning == allowsRunning) + return; + auto before = m_allowsRunning; + m_allowsRunning = allowsRunning; + emit allowsRunningChanged(before, m_allowsRunning); + emit modified(); +} + +void MapHeader::setAllowsBiking(bool allowsBiking) { + if (m_allowsBiking == allowsBiking) + return; + auto before = m_allowsBiking; + m_allowsBiking = allowsBiking; + emit allowsBikingChanged(before, m_allowsBiking); + emit modified(); +} + +void MapHeader::setAllowsEscaping(bool allowsEscaping) { + if (m_allowsEscaping == allowsEscaping) + return; + auto before = m_allowsEscaping; + m_allowsEscaping = allowsEscaping; + emit allowsEscapingChanged(before, m_allowsEscaping); + emit modified(); +} + +void MapHeader::setFloorNumber(int floorNumber) { + if (m_floorNumber == floorNumber) + return; + auto before = m_floorNumber; + m_floorNumber = floorNumber; + emit floorNumberChanged(before, m_floorNumber); + emit modified(); +} + +void MapHeader::setBattleScene(const QString &battleScene) { + if (m_battleScene == battleScene) + return; + auto before = m_battleScene; + m_battleScene = battleScene; + emit battleSceneChanged(before, m_battleScene); + emit modified(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eaa98f11..f4c96393 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -253,8 +253,8 @@ void MainWindow::initCustomUI() { } // Create map header data widget - this->mapHeader = new MapHeaderForm(); - ui->layout_HeaderData->addWidget(this->mapHeader); + this->mapHeaderForm = new MapHeaderForm(); + ui->layout_HeaderData->addWidget(this->mapHeaderForm); } void MainWindow::initExtraSignals() { @@ -617,7 +617,7 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - connect(project, &Project::mapSectionIdNamesChanged, this->mapHeader, &MapHeaderForm::refreshLocationsComboBox); + connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -1017,23 +1017,19 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } void MainWindow::displayMapProperties() { - // Block signals to the comboboxes while they are being modified - const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - - this->mapHeader->clearDisplay(); + this->mapHeaderForm->clear(); if (!editor || !editor->map || !editor->project) { ui->frame_HeaderData->setEnabled(false); return; } - ui->frame_HeaderData->setEnabled(true); - Map *map = editor->map; + this->mapHeaderForm->setHeader(editor->map->header()); - ui->comboBox_PrimaryTileset->setCurrentText(map->layout()->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(map->layout()->tileset_secondary_label); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + ui->comboBox_PrimaryTileset->setCurrentText(editor->map->layout()->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(editor->map->layout()->tileset_secondary_label); - this->mapHeader->setMap(map); // Custom fields table. /* // TODO: Re-enable @@ -1060,26 +1056,28 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te bool MainWindow::setProjectUI() { Project *project = editor->project; - this->mapHeader->setProject(project); - - // Block signals to the comboboxes while they are being modified - const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); - const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); + this->mapHeaderForm->init(project); // Set up project comboboxes + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); + + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); ui->comboBox_SecondaryTileset->clear(); ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); + + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); ui->comboBox_LayoutSelector->clear(); ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); + + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); ui->comboBox_DiveMap->addItems(project->mapNames); ui->comboBox_DiveMap->setClearButtonEnabled(true); ui->comboBox_DiveMap->setFocusedScrollingEnabled(false); + + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_EmergeMap->clear(); ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->setClearButtonEnabled(true); @@ -1122,20 +1120,23 @@ bool MainWindow::setProjectUI() { } void MainWindow::clearProjectUI() { - // Block signals to the comboboxes while they are being modified + // Clear project comboboxes const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); - const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); - ui->comboBox_PrimaryTileset->clear(); + + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); ui->comboBox_SecondaryTileset->clear(); + + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); + + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_EmergeMap->clear(); + + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); ui->comboBox_LayoutSelector->clear(); - this->mapHeader->clear(); + this->mapHeaderForm->clear(); // Clear map models delete this->mapGroupModel; @@ -1466,7 +1467,7 @@ void MainWindow::onNewMapCreated() { // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); - this->mapAreaModel->insertMapItem(newMapName, newMap->location(), newMapGroup); + this->mapAreaModel->insertMapItem(newMapName, newMap->header()->location(), newMapGroup); this->layoutTreeModel->insertMapItem(newMapName, newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps diff --git a/src/project.cpp b/src/project.cpp index 03f7b4df..f168099e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -155,6 +155,9 @@ Map* Project::loadMap(QString mapName) { return nullptr; } + // If the map's MAPSEC value in the header changes, update our global array to keep it in sync. + connect(map->header(), &MapHeader::locationChanged, [this, map] { this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); }); + mapCache.insert(mapName, map); emit mapLoaded(map); return map; @@ -217,22 +220,22 @@ bool Project::loadMapData(Map* map) { this->mapNamesToMapConstants.insert(map->name(), map->constantName()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - map->setSong(ParseUtil::jsonToQString(mapObj["music"])); + map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); - map->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); - map->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); - map->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); - map->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); - map->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); + map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); + map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); + map->header()->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); + map->header()->setType(ParseUtil::jsonToQString(mapObj["map_type"])); + map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); + map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); if (projectConfig.mapAllowFlagsEnabled) { - map->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); - map->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); - map->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); + map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); + map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); + map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); } if (projectConfig.floorNumberEnabled) { - map->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); + map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); } map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); @@ -1284,21 +1287,21 @@ void Project::saveMap(Map *map) { mapObj["id"] = map->constantName(); mapObj["name"] = map->name(); mapObj["layout"] = map->layout()->id; - mapObj["music"] = map->song(); - mapObj["region_map_section"] = map->location(); - mapObj["requires_flash"] = map->requiresFlash(); - mapObj["weather"] = map->weather(); - mapObj["map_type"] = map->type(); + mapObj["music"] = map->header()->song(); + mapObj["region_map_section"] = map->header()->location(); + mapObj["requires_flash"] = map->header()->requiresFlash(); + mapObj["weather"] = map->header()->weather(); + mapObj["map_type"] = map->header()->type(); if (projectConfig.mapAllowFlagsEnabled) { - mapObj["allow_cycling"] = map->allowsBiking(); - mapObj["allow_escaping"] = map->allowsEscaping(); - mapObj["allow_running"] = map->allowsRunning(); + mapObj["allow_cycling"] = map->header()->allowsBiking(); + mapObj["allow_escaping"] = map->header()->allowsEscaping(); + mapObj["allow_running"] = map->header()->allowsRunning(); } - mapObj["show_map_name"] = map->showsLocationName(); + mapObj["show_map_name"] = map->header()->showsLocationName(); if (projectConfig.floorNumberEnabled) { - mapObj["floor_number"] = map->floorNumber(); + mapObj["floor_number"] = map->header()->floorNumber(); } - mapObj["battle_scene"] = map->battleScene(); + mapObj["battle_scene"] = map->header()->battleScene(); // Connections auto connections = map->getConnections(); @@ -2301,7 +2304,7 @@ void Project::addNewMapsec(const QString &name) { this->mapSectionIdNames.append(name); } this->hasUnsavedDataChanges = true; - emit mapSectionIdNamesChanged(); + emit mapSectionIdNamesChanged(this->mapSectionIdNames); } void Project::removeMapsec(const QString &name) { @@ -2310,7 +2313,7 @@ void Project::removeMapsec(const QString &name) { this->mapSectionIdNames.removeOne(name); this->hasUnsavedDataChanges = true; - emit mapSectionIdNamesChanged(); + emit mapSectionIdNamesChanged(this->mapSectionIdNames); } // Read the constants to preserve any "unused" heal locations when writing the file later diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 5a0fa9ce..f041b7b1 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -817,7 +817,7 @@ QJSValue MainWindow::getTilePixels(int tileId) { QString MainWindow::getSong() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->song(); + return this->editor->map->header()->song(); } void MainWindow::setSong(QString song) { @@ -827,13 +827,13 @@ void MainWindow::setSong(QString song) { logError(QString("Unknown song '%1'").arg(song)); return; } - this->editor->map->setSong(song); + this->editor->map->header()->setSong(song); } QString MainWindow::getLocation() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->location(); + return this->editor->map->header()->location(); } void MainWindow::setLocation(QString location) { @@ -843,25 +843,25 @@ void MainWindow::setLocation(QString location) { logError(QString("Unknown location '%1'").arg(location)); return; } - this->editor->map->setLocation(location); + this->editor->map->header()->setLocation(location); } bool MainWindow::getRequiresFlash() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->requiresFlash(); + return this->editor->map->header()->requiresFlash(); } void MainWindow::setRequiresFlash(bool require) { if (!this->editor || !this->editor->map) return; - this->editor->map->setRequiresFlash(require); + this->editor->map->header()->setRequiresFlash(require); } QString MainWindow::getWeather() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->weather(); + return this->editor->map->header()->weather(); } void MainWindow::setWeather(QString weather) { @@ -871,13 +871,13 @@ void MainWindow::setWeather(QString weather) { logError(QString("Unknown weather '%1'").arg(weather)); return; } - this->editor->map->setWeather(weather); + this->editor->map->header()->setWeather(weather); } QString MainWindow::getType() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->type(); + return this->editor->map->header()->type(); } void MainWindow::setType(QString type) { @@ -887,13 +887,13 @@ void MainWindow::setType(QString type) { logError(QString("Unknown map type '%1'").arg(type)); return; } - this->editor->map->setType(type); + this->editor->map->header()->setType(type); } QString MainWindow::getBattleScene() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->battleScene(); + return this->editor->map->header()->battleScene(); } void MainWindow::setBattleScene(QString battleScene) { @@ -903,66 +903,66 @@ void MainWindow::setBattleScene(QString battleScene) { logError(QString("Unknown battle scene '%1'").arg(battleScene)); return; } - this->editor->map->setBattleScene(battleScene); + this->editor->map->header()->setBattleScene(battleScene); } bool MainWindow::getShowLocationName() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->showsLocationName(); + return this->editor->map->header()->showsLocationName(); } void MainWindow::setShowLocationName(bool show) { if (!this->editor || !this->editor->map) return; - this->editor->map->setShowsLocationName(show); + this->editor->map->header()->setShowsLocationName(show); } bool MainWindow::getAllowRunning() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowsRunning(); + return this->editor->map->header()->allowsRunning(); } void MainWindow::setAllowRunning(bool allow) { if (!this->editor || !this->editor->map) return; - this->editor->map->setAllowsRunning(allow); + this->editor->map->header()->setAllowsRunning(allow); } bool MainWindow::getAllowBiking() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowsBiking(); + return this->editor->map->header()->allowsBiking(); } void MainWindow::setAllowBiking(bool allow) { if (!this->editor || !this->editor->map) return; - this->editor->map->setAllowsBiking(allow); + this->editor->map->header()->setAllowsBiking(allow); } bool MainWindow::getAllowEscaping() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowsEscaping(); + return this->editor->map->header()->allowsEscaping(); } void MainWindow::setAllowEscaping(bool allow) { if (!this->editor || !this->editor->map) return; - this->editor->map->setAllowsEscaping(allow); + this->editor->map->header()->setAllowsEscaping(allow); } int MainWindow::getFloorNumber() { if (!this->editor || !this->editor->map) return 0; - return this->editor->map->floorNumber(); + return this->editor->map->header()->floorNumber(); } void MainWindow::setFloorNumber(int floorNumber) { if (!this->editor || !this->editor->map) return; - this->editor->map->setFloorNumber(floorNumber); + this->editor->map->header()->setFloorNumber(floorNumber); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 88d7fbf1..1fd9084c 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -1,18 +1,6 @@ #include "mapheaderform.h" - -#define BLOCK_SIGNALS \ - const QSignalBlocker b_Song(ui->comboBox_Song); \ - const QSignalBlocker b_Location(ui->comboBox_Location); \ - const QSignalBlocker b_RequiresFlash(ui->checkBox_RequiresFlash); \ - const QSignalBlocker b_Weather(ui->comboBox_Weather); \ - const QSignalBlocker b_Type(ui->comboBox_Type); \ - const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); \ - const QSignalBlocker b_ShowLocationName(ui->checkBox_ShowLocationName); \ - const QSignalBlocker b_AllowRunning(ui->checkBox_AllowRunning); \ - const QSignalBlocker b_AllowBiking(ui->checkBox_AllowBiking); \ - const QSignalBlocker b_AllowEscaping(ui->checkBox_AllowEscaping); \ - const QSignalBlocker b_FloorNumber(ui->spinBox_FloorNumber); - +#include "ui_mapheaderform.h" +#include "project.h" MapHeaderForm::MapHeaderForm(QWidget *parent) : QWidget(parent) @@ -23,6 +11,19 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) // This value is an s8 by default, but we don't need to unnecessarily limit users. ui->spinBox_FloorNumber->setMinimum(INT_MIN); ui->spinBox_FloorNumber->setMaximum(INT_MAX); + + // When the UI is updated, sync those changes to the tracked MapHeader (if there is one) + connect(ui->comboBox_Song, &QComboBox::currentTextChanged, this, &MapHeaderForm::onSongUpdated); + connect(ui->comboBox_Location, &QComboBox::currentTextChanged, this, &MapHeaderForm::onLocationChanged); + connect(ui->comboBox_Weather, &QComboBox::currentTextChanged, this, &MapHeaderForm::onWeatherChanged); + connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged); + connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged); + connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged); + connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged); + connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); + connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); + connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); + connect(ui->spinBox_FloorNumber, &QSpinBox::valueChanged, this, &MapHeaderForm::onFloorNumberChanged); } MapHeaderForm::~MapHeaderForm() @@ -30,20 +31,31 @@ MapHeaderForm::~MapHeaderForm() delete ui; } -void MapHeaderForm::setProject(Project * newProject) { +void MapHeaderForm::init(const Project * project) { clear(); - this->project = newProject; - if (!this->project) + if (!project) return; // Populate combo boxes - BLOCK_SIGNALS - ui->comboBox_Song->addItems(this->project->songNames); - ui->comboBox_Weather->addItems(this->project->weatherNames); - ui->comboBox_Type->addItems(this->project->mapTypes); - ui->comboBox_BattleScene->addItems(this->project->mapBattleScenes); - refreshLocationsComboBox(); + + const QSignalBlocker b_Song(ui->comboBox_Song); + ui->comboBox_Song->clear(); + ui->comboBox_Song->addItems(project->songNames); + + const QSignalBlocker b_Weather(ui->comboBox_Weather); + ui->comboBox_Weather->clear(); + ui->comboBox_Weather->addItems(project->weatherNames); + + const QSignalBlocker b_Type(ui->comboBox_Type); + ui->comboBox_Type->clear(); + ui->comboBox_Type->addItems(project->mapTypes); + + const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); + ui->comboBox_BattleScene->clear(); + ui->comboBox_BattleScene->addItems(project->mapBattleScenes); + + setLocations(project->mapSectionIdNames); // Hide config-specific settings @@ -60,161 +72,199 @@ void MapHeaderForm::setProject(Project * newProject) { ui->label_FloorNumber->setVisible(floorNumEnabled); } -void MapHeaderForm::setMap(Map * newMap) { - this->map = newMap; - if (!this->map) { - clearDisplay(); - return; - } +// This combo box is treated specially because (unlike the other combo boxes) +// items that should be in this drop-down can be added or removed externally. +void MapHeaderForm::setLocations(QStringList locations) { + locations.sort(); - BLOCK_SIGNALS - ui->comboBox_Song->setCurrentText(this->map->song()); - ui->comboBox_Location->setCurrentText(this->map->location()); - ui->checkBox_RequiresFlash->setChecked(this->map->requiresFlash()); - ui->comboBox_Weather->setCurrentText(this->map->weather()); - ui->comboBox_Type->setCurrentText(this->map->type()); - ui->comboBox_BattleScene->setCurrentText(this->map->battleScene()); - ui->checkBox_ShowLocationName->setChecked(this->map->showsLocationName()); - ui->checkBox_AllowRunning->setChecked(this->map->allowsRunning()); - ui->checkBox_AllowBiking->setChecked(this->map->allowsBiking()); - ui->checkBox_AllowEscaping->setChecked(this->map->allowsEscaping()); - ui->spinBox_FloorNumber->setValue(this->map->floorNumber()); -} - -void MapHeaderForm::clearDisplay() { - BLOCK_SIGNALS - ui->comboBox_Song->clearEditText(); - ui->comboBox_Location->clearEditText(); - ui->comboBox_Weather->clearEditText(); - ui->comboBox_Type->clearEditText(); - ui->comboBox_BattleScene->clearEditText(); - ui->checkBox_ShowLocationName->setChecked(false); - ui->checkBox_RequiresFlash->setChecked(false); - ui->checkBox_AllowRunning->setChecked(false); - ui->checkBox_AllowBiking->setChecked(false); - ui->checkBox_AllowEscaping->setChecked(false); - ui->spinBox_FloorNumber->setValue(0); -} - -// Clear display and depopulate combo boxes -void MapHeaderForm::clear() { - BLOCK_SIGNALS - ui->comboBox_Song->clear(); - ui->comboBox_Location->clear(); - ui->comboBox_Weather->clear(); - ui->comboBox_Type->clear(); - ui->comboBox_BattleScene->clear(); - ui->checkBox_ShowLocationName->setChecked(false); - ui->checkBox_RequiresFlash->setChecked(false); - ui->checkBox_AllowRunning->setChecked(false); - ui->checkBox_AllowBiking->setChecked(false); - ui->checkBox_AllowEscaping->setChecked(false); - ui->spinBox_FloorNumber->setValue(0); -} - -void MapHeaderForm::refreshLocationsComboBox() { const QSignalBlocker b(ui->comboBox_Location); + const QString before = ui->comboBox_Location->currentText(); ui->comboBox_Location->clear(); - - if (this->project) { - QStringList locations = this->project->mapSectionIdNames; - locations.sort(); - ui->comboBox_Location->addItems(locations); - } - if (this->map) { - ui->comboBox_Location->setCurrentText(this->map->location()); - } + ui->comboBox_Location->addItems(locations); + ui->comboBox_Location->setCurrentText(before); } -void MapHeaderForm::on_comboBox_Song_currentTextChanged(const QString &song) +// Assign a MapHeader that the form will keep in sync with the UI. +void MapHeaderForm::setHeader(MapHeader *header) { + if (m_header == header) + return; + + if (m_header) { + m_header->disconnect(this); + } + + m_header = header; + + if (m_header) { + // If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly + connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::updateSong); + connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::updateLocation); + connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::updateRequiresFlash); + connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::updateWeather); + connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::updateType); + connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::updateBattleScene); + connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::updateShowsLocationName); + connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::updateAllowsRunning); + connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::updateAllowsBiking); + connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::updateAllowsEscaping); + connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::updateFloorNumber); + } + + // Immediately update the UI to reflect the assigned MapHeader + updateUi(); +} + +void MapHeaderForm::clear() { + m_header = nullptr; + updateUi(); +} + +void MapHeaderForm::updateUi() { + updateSong(); + updateLocation(); + updateRequiresFlash(); + updateWeather(); + updateType(); + updateBattleScene(); + updateShowsLocationName(); + updateAllowsRunning(); + updateAllowsBiking(); + updateAllowsEscaping(); + updateFloorNumber(); + +} + +MapHeader MapHeaderForm::headerData() const { + if (m_header) + return *m_header; + + // Build header from UI + MapHeader header; + header.setSong(ui->comboBox_Song->currentText()); + header.setLocation(ui->comboBox_Location->currentText()); + header.setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); + header.setWeather(ui->comboBox_Weather->currentText()); + header.setType(ui->comboBox_Type->currentText()); + header.setBattleScene(ui->comboBox_BattleScene->currentText()); + header.setShowsLocationName(ui->checkBox_ShowLocationName->isChecked()); + header.setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); + header.setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); + header.setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); + header.setFloorNumber(ui->spinBox_FloorNumber->value()); + return header; +} + +void MapHeaderForm::setLocationsDisabled(bool disabled) { + ui->label_Location->setDisabled(disabled); + ui->comboBox_Location->setDisabled(disabled); +} + +void MapHeaderForm::updateSong() { + const QSignalBlocker b(ui->comboBox_Song); + ui->comboBox_Song->setCurrentText(m_header ? m_header->song() : QString()); +} + +void MapHeaderForm::updateLocation() { + const QSignalBlocker b(ui->comboBox_Location); + ui->comboBox_Location->setCurrentText(m_header ? m_header->location() : QString()); +} + +void MapHeaderForm::updateRequiresFlash() { + const QSignalBlocker b(ui->checkBox_RequiresFlash); + ui->checkBox_RequiresFlash->setChecked(m_header ? m_header->requiresFlash() : false); +} + +void MapHeaderForm::updateWeather() { + const QSignalBlocker b(ui->comboBox_Weather); + ui->comboBox_Weather->setCurrentText(m_header ? m_header->weather() : QString()); +} + +void MapHeaderForm::updateType() { + const QSignalBlocker b(ui->comboBox_Type); + ui->comboBox_Type->setCurrentText(m_header ? m_header->type() : QString()); +} + +void MapHeaderForm::updateBattleScene() { + const QSignalBlocker b(ui->comboBox_BattleScene); + ui->comboBox_BattleScene->setCurrentText(m_header ? m_header->battleScene() : QString()); +} + +void MapHeaderForm::updateShowsLocationName() { + const QSignalBlocker b(ui->checkBox_ShowLocationName); + ui->checkBox_ShowLocationName->setChecked(m_header ? m_header->showsLocationName() : false); +} + +void MapHeaderForm::updateAllowsRunning() { + const QSignalBlocker b(ui->checkBox_AllowRunning); + ui->checkBox_AllowRunning->setChecked(m_header ? m_header->allowsRunning() : false); +} + +void MapHeaderForm::updateAllowsBiking() { + const QSignalBlocker b(ui->checkBox_AllowBiking); + ui->checkBox_AllowBiking->setChecked(m_header ? m_header->allowsBiking() : false); +} + +void MapHeaderForm::updateAllowsEscaping() { + const QSignalBlocker b(ui->checkBox_AllowEscaping); + ui->checkBox_AllowEscaping->setChecked(m_header ? m_header->allowsEscaping() : false); +} + +void MapHeaderForm::updateFloorNumber() { + const QSignalBlocker b(ui->spinBox_FloorNumber); + ui->spinBox_FloorNumber->setValue(m_header ? m_header->floorNumber() : 0); +} + +void MapHeaderForm::onSongUpdated(const QString &song) { - if (this->map) { - this->map->setSong(song); - this->map->modify(); - } + if (m_header) m_header->setSong(song); } -void MapHeaderForm::on_comboBox_Location_currentTextChanged(const QString &location) +void MapHeaderForm::onLocationChanged(const QString &location) { - if (this->map) { - this->map->setLocation(location); - this->map->modify(); - - // Update cached location name in the project - // TODO: This should be handled elsewhere now, connected to the map change signal - if (this->project) - this->project->mapNameToMapSectionName.insert(this->map->name(), this->map->location()); - } + if (m_header) m_header->setLocation(location); } -void MapHeaderForm::on_comboBox_Weather_currentTextChanged(const QString &weather) +void MapHeaderForm::onWeatherChanged(const QString &weather) { - if (this->map) { - this->map->setWeather(weather); - this->map->modify(); - } + if (m_header) m_header->setWeather(weather); } -void MapHeaderForm::on_comboBox_Type_currentTextChanged(const QString &type) +void MapHeaderForm::onTypeChanged(const QString &type) { - if (this->map) { - this->map->setType(type); - this->map->modify(); - } + if (m_header) m_header->setType(type); } -void MapHeaderForm::on_comboBox_BattleScene_currentTextChanged(const QString &battleScene) +void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { - if (this->map) { - this->map->setBattleScene(battleScene); - this->map->modify(); - } + if (m_header) m_header->setBattleScene(battleScene); } -void MapHeaderForm::on_checkBox_RequiresFlash_stateChanged(int selected) +void MapHeaderForm::onRequiresFlashChanged(int selected) { - if (this->map) { - this->map->setRequiresFlash(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_ShowLocationName_stateChanged(int selected) +void MapHeaderForm::onShowLocationNameChanged(int selected) { - if (this->map) { - this->map->setShowsLocationName(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_AllowRunning_stateChanged(int selected) +void MapHeaderForm::onAllowRunningChanged(int selected) { - if (this->map) { - this->map->setAllowsRunning(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_AllowBiking_stateChanged(int selected) +void MapHeaderForm::onAllowBikingChanged(int selected) { - if (this->map) { - this->map->setAllowsBiking(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_AllowEscaping_stateChanged(int selected) +void MapHeaderForm::onAllowEscapingChanged(int selected) { - if (this->map) { - this->map->setAllowsEscaping(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } -void MapHeaderForm::on_spinBox_FloorNumber_valueChanged(int offset) +void MapHeaderForm::onFloorNumberChanged(int offset) { - if (this->map) { - this->map->setFloorNumber(offset); - this->map->modify(); - } + if (m_header) m_header->setFloorNumber(offset); } diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp new file mode 100644 index 00000000..ed217fcb --- /dev/null +++ b/src/ui/newlayoutform.cpp @@ -0,0 +1,123 @@ +#include "newlayoutform.h" +#include "ui_newlayoutform.h" +#include "project.h" + +NewLayoutForm::NewLayoutForm(QWidget *parent) + : QWidget(parent) + , ui(new Ui::NewLayoutForm) +{ + ui->setupUi(this); + + // TODO: Read from project? + ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); + ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); + + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); + connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); +} + +NewLayoutForm::~NewLayoutForm() +{ + delete ui; +} + +void NewLayoutForm::initUi(Project *project) { + m_project = project; + + ui->comboBox_PrimaryTileset->clear(); + ui->comboBox_SecondaryTileset->clear(); + + if (m_project) { + ui->comboBox_PrimaryTileset->addItems(m_project->primaryTilesetLabels); + ui->comboBox_SecondaryTileset->addItems(m_project->secondaryTilesetLabels); + + 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) { + ui->groupBox_MapDimensions->setDisabled(disabled); + ui->groupBox_BorderDimensions->setDisabled(disabled); + ui->groupBox_Tilesets->setDisabled(disabled); +} + +void NewLayoutForm::setSettings(const Settings &settings) { + 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); +} + +NewLayoutForm::Settings NewLayoutForm::settings() const { + NewLayoutForm::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(); + settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); + settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); + return settings; +} + +bool NewLayoutForm::validate() { + // Make sure to call each validation function so that all errors are shown at once. + bool valid = true; + if (!validateMapDimensions()) valid = false; + if (!validateTilesets()) valid = false; + return valid; +} + +bool NewLayoutForm::validateMapDimensions() { + int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); + int maxSize = m_project->getMaxMapDataSize(); + + 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); + } + + bool isValid = errorText.isEmpty(); + ui->label_MapDimensionsError->setText(errorText); + ui->label_MapDimensionsError->setVisible(!isValid); + return isValid; +} + +bool NewLayoutForm::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; +} diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index da10c26a..9f0a9f18 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -16,30 +16,33 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : QDialog(parent), ui(new Ui::NewMapDialog) { - this->setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); ui->setupUi(this); this->project = project; - this->existingLayout = false; + this->existingLayout = false; // TODO: Replace, we can determine this from the Layout ID combo box this->importedMap = false; + ui->newLayoutForm->initUi(project); + + ui->comboBox_Group->addItems(project->groupNames); + // Map 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_ID->setValidator(validator); + ui->lineEdit_MapID->setValidator(validator); // Create a collapsible section that has all the map header data. - this->headerData = new MapHeaderForm(); + this->headerForm = new MapHeaderForm(); + this->headerForm->init(project); auto sectionLayout = new QVBoxLayout(); - sectionLayout->addWidget(this->headerData); + sectionLayout->addWidget(this->headerForm); this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); this->headerSection->setContentLayout(sectionLayout); ui->layout_HeaderData->addWidget(this->headerSection); ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); - - connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); - connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); } NewMapDialog::~NewMapDialog() @@ -49,57 +52,25 @@ NewMapDialog::~NewMapDialog() } void NewMapDialog::init() { - // Populate combo boxes - ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); - ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_Group->addItems(project->groupNames); - this->headerData->setProject(project); - - // Set spin box limits - 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); - - ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - - // Restore previous settings - 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); - this->headerData->ui->comboBox_Song->setTextItem(settings.song); - this->headerData->ui->comboBox_Location->setTextItem(settings.location); - this->headerData->ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); - this->headerData->ui->comboBox_Weather->setTextItem(settings.weather); - this->headerData->ui->comboBox_Type->setTextItem(settings.type); - this->headerData->ui->comboBox_BattleScene->setTextItem(settings.battleScene); - this->headerData->ui->checkBox_ShowLocationName->setChecked(settings.showLocationName); - this->headerData->ui->checkBox_AllowRunning->setChecked(settings.allowRunning); - this->headerData->ui->checkBox_AllowBiking->setChecked(settings.allowBiking); - this->headerData->ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); - this->headerData->ui->spinBox_FloorNumber->setValue(settings.floorNumber); ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); + ui->newLayoutForm->setSettings(settings.layout); + this->headerForm->setHeader(&settings.header); + ui->lineEdit_Name->setText(project->getNewMapName()); } // Creating new map by right-clicking in the map list void NewMapDialog::init(int tabIndex, QString fieldName) { - //initUi(); switch (tabIndex) { case MapListTab::Groups: settings.group = fieldName; - //ui->label_Group->setDisabled(true); - //ui->comboBox_Group->setDisabled(true); + 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); + settings.header.setLocation(fieldName); + this->headerForm->setLocationsDisabled(true); break; case MapListTab::Layouts: useLayout(fieldName); @@ -109,6 +80,7 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { } // Creating new map from AdvanceMap import +// TODO: Re-use for a "Duplicate Map/Layout" option? void NewMapDialog::init(Layout *layout) { this->importedMap = true; useLayoutSettings(layout); @@ -126,91 +98,54 @@ void NewMapDialog::init(Layout *layout) { void NewMapDialog::setDefaultSettings(Project *project) { settings.group = project->groupNames.at(0); - 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.song = project->defaultSong; - 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; + 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.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 = this->headerData->ui->comboBox_Song->currentText(); - settings.location = this->headerData->ui->comboBox_Location->currentText(); - settings.requiresFlash = this->headerData->ui->checkBox_RequiresFlash->isChecked(); - settings.weather = this->headerData->ui->comboBox_Weather->currentText(); - settings.type = this->headerData->ui->comboBox_Type->currentText(); - settings.battleScene = this->headerData->ui->comboBox_BattleScene->currentText(); - settings.showLocationName = this->headerData->ui->checkBox_ShowLocationName->isChecked(); - settings.allowRunning = this->headerData->ui->checkBox_AllowRunning->isChecked(); - settings.allowBiking = this->headerData->ui->checkBox_AllowBiking->isChecked(); - settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); - settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + settings.layout = ui->newLayoutForm->settings(); + settings.header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; - settings.width = layout->width; - settings.height = layout->height; - settings.borderWidth = layout->border_width; - settings.borderHeight = layout->border_height; - settings.primaryTilesetLabel = layout->tileset_primary_label; - settings.secondaryTilesetLabel = layout->tileset_secondary_label; + 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; + + // Don't allow changes to the layout settings + ui->newLayoutForm->setDisabled(true); } void NewMapDialog::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = 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); -} - -bool NewMapDialog::validateMapDimensions() { - int size = project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); - int maxSize = project->getMaxMapDataSize(); - - 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); - } - - bool isValid = errorText.isEmpty(); - ui->label_MapDimensionsError->setText(errorText); - ui->label_MapDimensionsError->setVisible(!isValid); - return isValid; + useLayoutSettings(project->mapLayouts.value(this->layoutId)); } +// TODO: Create the map group if it doesn't exist bool NewMapDialog::validateMapGroup() { this->group = project->groupNames.indexOf(ui->comboBox_Group->currentText()); @@ -226,37 +161,8 @@ bool NewMapDialog::validateMapGroup() { 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 id = ui->lineEdit_MapID->text(); QString errorText; QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); @@ -272,13 +178,13 @@ bool NewMapDialog::validateID() { } bool isValid = errorText.isEmpty(); - ui->label_IDError->setText(errorText); - ui->label_IDError->setVisible(!isValid); - ui->lineEdit_ID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + ui->label_MapIDError->setText(errorText); + ui->label_MapIDError->setVisible(!isValid); + ui->lineEdit_MapID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); return isValid; } -void NewMapDialog::on_lineEdit_ID_textChanged(const QString &) { +void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { validateID(); } @@ -299,22 +205,23 @@ bool NewMapDialog::validateName() { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(); - ui->lineEdit_ID->setText(Map::mapConstantFromName(text)); + ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); } void NewMapDialog::on_pushButton_Accept_clicked() { + saveSettings(); + // Make sure to call each validation function so that all errors are shown at once. bool success = true; - if (!validateMapDimensions()) success = false; + if (!ui->newLayoutForm->validate()) 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. + // We check if the map name is empty separately from validateName, because validateName is also used during editing. + // 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); @@ -324,23 +231,9 @@ void NewMapDialog::on_pushButton_Accept_clicked() { Map *newMap = new Map; newMap->setName(ui->lineEdit_Name->text()); - newMap->setConstantName(ui->lineEdit_ID->text()); - newMap->setSong(this->headerData->ui->comboBox_Song->currentText()); - newMap->setLocation(this->headerData->ui->comboBox_Location->currentText()); - newMap->setRequiresFlash(this->headerData->ui->checkBox_RequiresFlash->isChecked()); - newMap->setWeather(this->headerData->ui->comboBox_Weather->currentText()); - newMap->setType(this->headerData->ui->comboBox_Type->currentText()); - newMap->setBattleScene(this->headerData->ui->comboBox_BattleScene->currentText()); - newMap->setShowsLocationName(this->headerData->ui->checkBox_ShowLocationName->isChecked()); - if (projectConfig.mapAllowFlagsEnabled) { - newMap->setAllowsRunning(this->headerData->ui->checkBox_AllowRunning->isChecked()); - newMap->setAllowsBiking(this->headerData->ui->checkBox_AllowBiking->isChecked()); - newMap->setAllowsEscaping(this->headerData->ui->checkBox_AllowEscaping->isChecked()); - } - if (projectConfig.floorNumberEnabled) { - newMap->setFloorNumber(this->headerData->ui->spinBox_FloorNumber->value()); - } - newMap->setNeedsHealLocation(ui->checkBox_CanFlyTo->isChecked()); + newMap->setConstantName(ui->lineEdit_MapID->text()); + newMap->setHeader(this->headerForm->headerData()); + newMap->setNeedsHealLocation(settings.canFlyTo); Layout *layout; if (this->existingLayout) { @@ -350,17 +243,17 @@ void NewMapDialog::on_pushButton_Accept_clicked() { layout = new Layout; layout->id = Layout::layoutConstantFromName(newMap->name()); layout->name = QString("%1_Layout").arg(newMap->name()); - layout->width = ui->spinBox_MapWidth->value(); - layout->height = ui->spinBox_MapHeight->value(); + layout->width = settings.layout.width; + layout->height = settings.layout.height; if (projectConfig.useCustomBorderSize) { - layout->border_width = ui->spinBox_BorderWidth->value(); - layout->border_height = ui->spinBox_BorderHeight->value(); + 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; } - layout->tileset_primary_label = ui->comboBox_PrimaryTileset->currentText(); - layout->tileset_secondary_label = ui->comboBox_SecondaryTileset->currentText(); + 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()); From 724f42019ccb74d16785aff11ab99aa8b33ff03c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 14 Nov 2024 16:01:54 -0500 Subject: [PATCH 086/364] Automatically add new map groups --- forms/newmapdialog.ui | 22 +++--- include/config.h | 2 +- include/mainwindow.h | 4 +- include/project.h | 21 ++--- include/ui/maplistmodels.h | 11 ++- include/ui/newlayoutform.h | 1 + include/ui/newmapdialog.h | 27 ++++--- src/core/events.cpp | 10 ++- src/mainwindow.cpp | 99 ++++++++++++------------ src/project.cpp | 153 ++++++++++++++++++++++--------------- src/ui/maplistmodels.cpp | 127 ++++++++++++++---------------- src/ui/newlayoutform.cpp | 6 +- src/ui/newmapdialog.cpp | 146 ++++++++++++++++++----------------- 13 files changed, 331 insertions(+), 298 deletions(-) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 911050d3..ed52f445 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,7 +25,7 @@ 0 0 427 - 520 + 522 @@ -68,7 +68,7 @@
- + @@ -142,7 +142,7 @@ - + false @@ -175,7 +175,7 @@ - + Layout ID @@ -197,15 +197,11 @@ - - - - - Accept - - - - + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + diff --git a/include/config.h b/include/config.h index 21e2dde7..0655c0bd 100644 --- a/include/config.h +++ b/include/config.h @@ -70,7 +70,7 @@ public: this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; - this->newMapHeaderSectionExpanded = true; + this->newMapHeaderSectionExpanded = false; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; diff --git a/include/mainwindow.h b/include/mainwindow.h index a506f5e0..e96c4cc3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -188,7 +188,9 @@ private slots: void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); void openNewMapDialog(); - void onNewMapCreated(); + void onNewMapCreated(Map *newMap, const QString &groupName); + void onNewMapGroupCreated(const QString &groupName); + void onNewLayoutCreated(Layout *layout); void onMapLoaded(Map *map); void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); diff --git a/include/project.h b/include/project.h index f28f9259..36c8e1e3 100644 --- a/include/project.h +++ b/include/project.h @@ -32,20 +32,18 @@ public: public: QString root; - QStringList groupNames; - QMap mapGroups; - QList groupedMapNames; QStringList mapNames; + QStringList groupNames; + QMap groupNameToMapNames; QList healLocations; QMap healLocationNameToValue; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; QMap mapNameToLayoutId; QMap mapNameToMapSectionName; - QStringList mapLayoutsTable; - QStringList mapLayoutsTableMaster; QString layoutsLabel; - QMap layoutIdsToNames; + QStringList layoutIds; + QStringList layoutIdsMaster; QMap mapLayouts; QMap mapLayoutsMaster; QMap eventGraphicsMap; @@ -122,7 +120,9 @@ public: void deleteFile(QString path); bool readMapGroups(); - Map* addNewMapToGroup(Map*, int, bool, bool); + void addNewMap(Map* newMap, const QString &groupName); + void addNewMapGroup(const QString &groupName); + void addNewLayout(Layout* newLayout); QString getNewMapName(); QString getProjectTitle(); @@ -214,8 +214,8 @@ public: QString getScriptDefaultString(bool usePoryScript, QString mapName) const; QStringList getEventScriptsFilePaths() const; - QString getDefaultPrimaryTilesetLabel(); - QString getDefaultSecondaryTilesetLabel(); + QString getDefaultPrimaryTilesetLabel() const; + QString getDefaultSecondaryTilesetLabel() const; void updateTilesetMetatileLabels(Tileset *tileset); QString buildMetatileLabelsText(const QMap defines); QString findMetatileLabelsTileset(QString label); @@ -265,6 +265,9 @@ signals: void fileChanged(QString filepath); void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); + void mapAdded(Map *newMap, const QString &groupName); + void mapGroupAdded(const QString &groupName); + void layoutAdded(Layout *newLayout); }; #endif // PROJECT_H diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 17810c7e..59b65e50 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -13,9 +13,8 @@ class Project; enum MapListUserRoles { - GroupRole = Qt::UserRole + 1, // Used to hold the map group number. - TypeRole, // Used to differentiate between the different layers of the map list tree view. - TypeRole2, // Used for various extra data needed. + NameRole = Qt::UserRole, // Holds the name of the item in the list + TypeRole, // Used to differentiate between the different layers of the map list tree view. }; @@ -92,7 +91,7 @@ public: public: void setMap(QString mapName) { this->openMap = mapName; } - QStandardItem *createGroupItem(QString groupName, int groupIndex, QStandardItem *fromItem = nullptr); + QStandardItem *createGroupItem(QString groupName, QStandardItem *fromItem = nullptr); QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); QStandardItem *insertGroupItem(QString groupName); @@ -138,10 +137,10 @@ public: void setMap(QString mapName) { this->openMap = mapName; } QStandardItem *createAreaItem(QString areaName); - QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); + QStandardItem *createMapItem(QString mapName); QStandardItem *insertAreaItem(QString areaName); - QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); + QStandardItem *insertMapItem(QString mapName, QString areaName); virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString mapName) const override; diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index 6f63466b..d6230f6f 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -20,6 +20,7 @@ public: void initUi(Project *project); struct Settings { + QString id; // TODO: Support in UI (toggleable line edit) int width; int height; int borderWidth; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index e7401242..2e860cba 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -20,31 +20,30 @@ class NewMapDialog : public QDialog public: explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr); ~NewMapDialog(); - Map *map; - int group; - bool existingLayout; - bool importedMap; - QString layoutId; void init(); void init(int tabIndex, QString data); void init(Layout *); - static void setDefaultSettings(Project *project); + void accept() override; + static void setDefaultSettings(const Project *project); signals: - void applied(); + void applied(const QString &newMapName); private: Ui::NewMapDialog *ui; Project *project; CollapsibleSection *headerSection; MapHeaderForm *headerForm; + Layout *importedLayout = nullptr; - bool validateMapGroup(); - bool validateID(); - bool validateName(); + // 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 validateName(bool allowEmpty = false); + bool validateGroup(bool allowEmpty = false); void saveSettings(); - void useLayout(QString layoutId); + bool isExistingLayout() const; void useLayoutSettings(Layout *mapLayout); struct Settings { @@ -56,11 +55,11 @@ private: static struct Settings settings; private slots: - //void on_checkBox_UseExistingLayout_stateChanged(int state); //TODO - //void on_comboBox_Layout_currentTextChanged(const QString &text); - void on_pushButton_Accept_clicked(); + //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 &); + void on_comboBox_Group_currentTextChanged(const QString &text); }; #endif // NEWMAPDIALOG_H diff --git a/src/core/events.cpp b/src/core/events.cpp index 0673a652..c0cf7b7a 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -392,7 +392,8 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["x"] = this->getX(); cloneJson["y"] = this->getY(); cloneJson["target_local_id"] = this->getTargetID(); - cloneJson["target_map"] = project->mapNamesToMapConstants.value(this->getTargetMap()); + const QString mapName = this->getTargetMap(); + cloneJson["target_map"] = project->mapNamesToMapConstants.value(mapName, mapName); this->addCustomValuesTo(&cloneJson); return cloneJson; @@ -407,7 +408,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. const QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); if (!project->mapConstantsToMapNames.contains(mapConstant)) - logWarn(QString("Target Map constant '%1' is invalid.").arg(mapConstant)); + logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); @@ -496,7 +497,8 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["x"] = this->getX(); warpJson["y"] = this->getY(); warpJson["elevation"] = this->getElevation(); - warpJson["dest_map"] = project->mapNamesToMapConstants.value(this->getDestinationMap()); + const QString mapName = this->getDestinationMap(); + warpJson["dest_map"] = project->mapNamesToMapConstants.value(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); this->addCustomValuesTo(&warpJson); @@ -513,7 +515,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { // Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data. const QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); if (!project->mapConstantsToMapNames.contains(mapConstant)) - logWarn(QString("Destination Map constant '%1' is invalid.").arg(mapConstant)); + logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f4c96393..59b6057f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -617,6 +617,9 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); + connect(project, &Project::mapAdded, this, &MainWindow::onNewMapCreated); + connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); + connect(project, &Project::layoutAdded, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); @@ -702,7 +705,7 @@ bool MainWindow::setInitialMap() { // User recently had a map open that still exists. if (setMap(recent)) return true; - } else if (editor->project->mapLayoutsTable.contains(recent)) { + } else if (editor->project->layoutIds.contains(recent)) { // User recently had a layout open that still exists. if (setLayout(recent)) return true; @@ -713,7 +716,7 @@ bool MainWindow::setInitialMap() { if (name != recent && setMap(name)) return true; } - for (const auto &id : editor->project->mapLayoutsTable) { + for (const auto &id : editor->project->layoutIds) { if (id != recent && setLayout(id)) return true; } @@ -932,13 +935,13 @@ bool MainWindow::userSetLayout(QString layoutId) { bool MainWindow::setLayout(QString layoutId) { if (this->editor->map) - logInfo("Switching to a layout-only editing mode. Disabling map-related edits."); + 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 QString layoutName = this->editor->project ? this->editor->project->layoutIdsToNames.value(layoutId, layoutId) : layoutId; - logInfo(QString("Setting layout to '%1'").arg(layoutName)); + const Layout* layout = this->editor->project ? this->editor->project->mapLayouts.value(layoutId) : nullptr; + logInfo(QString("Setting layout to '%1'").arg(layout ? layout->name : layoutId)); if (!this->editor->setLayout(layoutId)) { return false; @@ -1069,7 +1072,7 @@ bool MainWindow::setProjectUI() { const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); ui->comboBox_LayoutSelector->clear(); - ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); + ui->comboBox_LayoutSelector->addItems(project->layoutIds); const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); @@ -1189,7 +1192,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { auto sourceModel = static_cast(model->sourceModel()); QStandardItem *selectedItem = sourceModel->itemFromIndex(index); const QString itemType = selectedItem->data(MapListUserRoles::TypeRole).toString(); - const QString itemName = selectedItem->data(Qt::UserRole).toString(); + const QString itemName = selectedItem->data(MapListUserRoles::NameRole).toString(); QMenu menu(this); QAction* addToFolderAction = nullptr; @@ -1278,7 +1281,7 @@ void MainWindow::mapListAddGroup() { if (dialog.exec() == QDialog::Accepted) { QString newFieldName = newNameEdit->text(); if (newFieldName.isEmpty()) return; - this->mapGroupModel->insertGroupItem(newFieldName); + this->editor->project->addNewMapGroup(newFieldName); } } @@ -1307,7 +1310,7 @@ void MainWindow::mapListAddLayout() { }); NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog); - useExistingCombo->addItems(this->editor->project->mapLayoutsTable); + useExistingCombo->addItems(this->editor->project->layoutIds); useExistingCombo->setEnabled(false); QCheckBox *useExistingCheck = new QCheckBox(&dialog); @@ -1358,13 +1361,13 @@ void MainWindow::mapListAddLayout() { errorMessage = "Name cannot be empty"; } // unique layout name & id - else if (this->editor->project->mapLayoutsTable.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->mapLayoutsTable.contains(useExistingCombo->currentText())) { + if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) { errorMessage = "Existing layout ID is not valid"; } } @@ -1395,7 +1398,6 @@ void MainWindow::mapListAddLayout() { layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); } Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); - this->layoutTreeModel->insertLayoutItem(newLayout->id); setLayout(newLayout->id); } } @@ -1407,10 +1409,9 @@ void MainWindow::mapListAddArea() { connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); - QLineEdit *newNameEdit = new QLineEdit(&dialog); - QLineEdit *newNameDisplay = new QLineEdit(&dialog); + auto newNameEdit = new QLineEdit(&dialog); + auto newNameDisplay = new QLabel(&dialog); newNameDisplay->setText(prefix); - newNameDisplay->setEnabled(false); connect(newNameEdit, &QLineEdit::textEdited, [newNameDisplay, prefix] (const QString &text) { // As the user types a name, update the label to show the name with the prefix. newNameDisplay->setText(prefix + text); @@ -1450,55 +1451,52 @@ void MainWindow::mapListAddArea() { } } -void MainWindow::onNewMapCreated() { - QString newMapName = this->newMapDialog->map->name(); - int newMapGroup = this->newMapDialog->group; - Map *newMap = this->newMapDialog->map; - bool existingLayout = this->newMapDialog->existingLayout; - bool importedMap = this->newMapDialog->importedMap; - - newMap = editor->project->addNewMapToGroup(newMap, newMapGroup, existingLayout, importedMap); - - logInfo(QString("Created a new map named %1.").arg(newMapName)); +void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { + logInfo(QString("Created a new map named %1.").arg(newMap->name())); // TODO: Creating a new map shouldn't be automatically saved editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); - // Add new Map / Layout to the mapList models - this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); - this->mapAreaModel->insertMapItem(newMapName, newMap->header()->location(), newMapGroup); - this->layoutTreeModel->insertMapItem(newMapName, newMap->layout()->id); + // Add new map to the map lists + this->mapGroupModel->insertMapItem(newMap->name(), groupName); + this->mapAreaModel->insertMapItem(newMap->name(), newMap->header()->location()); + this->layoutTreeModel->insertMapItem(newMap->name(), newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps // (other combo boxes like for warp destinations are repopulated when the map changes). - int mapIndex = this->editor->project->mapNames.indexOf(newMapName); + int mapIndex = this->editor->project->mapNames.indexOf(newMap->name()); if (mapIndex >= 0) { const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); - ui->comboBox_DiveMap->insertItem(mapIndex, newMapName); - ui->comboBox_EmergeMap->insertItem(mapIndex, newMapName); + ui->comboBox_DiveMap->insertItem(mapIndex, newMap->name()); + ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } - // Refresh layout combo box (if a new one was created) - if (!existingLayout) { - int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout()->id); - if (layoutIndex >= 0) { - const QSignalBlocker b_Layouts(ui->comboBox_LayoutSelector); - ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout()->id); - } - } - - setMap(newMapName); - if (newMap->needsHealLocation()) { addNewEvent(Event::Type::HealLocation); editor->project->saveHealLocations(newMap); editor->save(); } +} - disconnect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); - delete newMap; +void MainWindow::onNewLayoutCreated(Layout *layout) { + logInfo(QString("Created a new layout named %1.").arg(layout->name)); + + // Refresh layout combo box + int layoutIndex = this->editor->project->layoutIds.indexOf(layout->id); + if (layoutIndex >= 0) { + const QSignalBlocker b(ui->comboBox_LayoutSelector); + ui->comboBox_LayoutSelector->insertItem(layoutIndex, layout->id); + } + + // Add new layout to the Layouts map list view + this->layoutTreeModel->insertLayoutItem(layout->id); +} + +void MainWindow::onNewMapGroupCreated(const QString &groupName) { + // Add new map group to the Groups map list view + this->mapGroupModel->insertGroupItem(groupName); } void MainWindow::openNewMapDialog() { @@ -1508,7 +1506,7 @@ void MainWindow::openNewMapDialog() { } if (!this->newMapDialog) { this->newMapDialog = new NewMapDialog(this, this->editor->project); - connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); + connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); } openSubWindow(this->newMapDialog); @@ -1701,9 +1699,10 @@ void MainWindow::openMapListItem(const QModelIndex &index) { if (!index.isValid()) return; - QVariant data = index.data(Qt::UserRole); + QVariant data = index.data(MapListUserRoles::NameRole); if (data.isNull()) return; + const QString name = data.toString(); // Normally when a new map/layout is opened the search filters are cleared and the lists will scroll to display that map/layout in the list. // We don't want to do this when the user interacts with a list directly, so we temporarily prevent changes to the search filter. @@ -1712,9 +1711,9 @@ void MainWindow::openMapListItem(const QModelIndex &index) { QString type = index.data(MapListUserRoles::TypeRole).toString(); if (type == "map_name") { - userSetMap(data.toString()); + userSetMap(name); } else if (type == "map_layout") { - userSetLayout(data.toString()); + userSetLayout(name); } if (toolbar) toolbar->setFilterLocked(false); diff --git a/src/project.cpp b/src/project.cpp index f168099e..ccd6c8a2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -368,6 +368,7 @@ 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; @@ -409,15 +410,16 @@ Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { return nullptr; } - mapLayouts.insert(layout->id, layout); - mapLayoutsMaster.insert(layout->id, layout->copy()); - mapLayoutsTable.append(layout->id); - mapLayoutsTableMaster.append(layout->id); - layoutIdsToNames.insert(layout->id, layout->name); + // 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); - this->loadLayout(layout); + loadLayout(layout); + emit layoutAdded(layout); return layout; } @@ -471,12 +473,12 @@ bool Project::loadMapLayout(Map* map) { } void Project::clearMapLayouts() { - qDeleteAll(mapLayouts); - mapLayouts.clear(); - qDeleteAll(mapLayoutsMaster); - mapLayoutsMaster.clear(); - mapLayoutsTable.clear(); - layoutIdsToNames.clear(); + qDeleteAll(this->mapLayouts); + this->mapLayouts.clear(); + qDeleteAll(this->mapLayoutsMaster); + this->mapLayoutsMaster.clear(); + this->layoutIds.clear(); + this->layoutIdsMaster.clear(); } bool Project::readMapLayouts() { @@ -498,9 +500,9 @@ bool Project::readMapLayouts() { return false; } - layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); - if (layoutsLabel.isNull()) { - layoutsLabel = "gMapLayouts"; + this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); + if (this->layoutsLabel.isEmpty()) { + this->layoutsLabel = "gMapLayouts"; logWarn(QString("'layouts_table_label' value is missing from %1. Defaulting to %2") .arg(layoutsFilepath) .arg(layoutsLabel)); @@ -599,11 +601,11 @@ bool Project::readMapLayouts() { delete layout; return false; } - mapLayouts.insert(layout->id, layout); - mapLayoutsMaster.insert(layout->id, layout->copy()); - mapLayoutsTable.append(layout->id); - mapLayoutsTableMaster.append(layout->id); - layoutIdsToNames.insert(layout->id, layout->name); + + this->mapLayouts.insert(layout->id, layout); + this->mapLayoutsMaster.insert(layout->id, layout->copy()); + this->layoutIds.append(layout->id); + this->layoutIdsMaster.append(layout->id); } return true; @@ -618,11 +620,11 @@ void Project::saveMapLayouts() { } OrderedJson::object layoutsObj; - layoutsObj["layouts_table_label"] = layoutsLabel; + layoutsObj["layouts_table_label"] = this->layoutsLabel; OrderedJson::array layoutsArr; - for (QString layoutId : mapLayoutsTableMaster) { - Layout *layout = mapLayoutsMaster.value(layoutId); + for (const QString &layoutId : this->layoutIdsMaster) { + Layout *layout = this->mapLayoutsMaster.value(layoutId); OrderedJson::object layoutObj; layoutObj["id"] = layout->id; layoutObj["name"] = layout->name; @@ -669,14 +671,12 @@ void Project::saveMapGroups() { } mapGroupsObj["group_order"] = groupNamesArr; - int groupNum = 0; - for (QStringList mapNames : groupedMapNames) { + for (const auto &groupName : this->groupNames) { OrderedJson::array groupArr; - for (QString mapName : mapNames) { + for (const auto &mapName : this->groupNameToMapNames.value(groupName)) { groupArr.push_back(mapName); } - mapGroupsObj[this->groupNames.at(groupNum)] = groupArr; - groupNum++; + mapGroupsObj[groupName] = groupArr; } ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -1390,15 +1390,15 @@ void Project::saveLayout(Layout *layout) { } void Project::updateLayout(Layout *layout) { - if (!mapLayoutsTableMaster.contains(layout->id)) { - mapLayoutsTableMaster.append(layout->id); + if (!this->layoutIdsMaster.contains(layout->id)) { + this->layoutIdsMaster.append(layout->id); } - if (mapLayoutsMaster.contains(layout->id)) { - mapLayoutsMaster[layout->id]->copyFrom(layout); + if (this->mapLayoutsMaster.contains(layout->id)) { + this->mapLayoutsMaster[layout->id]->copyFrom(layout); } else { - mapLayoutsMaster.insert(layout->id, layout->copy()); + this->mapLayoutsMaster.insert(layout->id, layout->copy()); } } @@ -1821,10 +1821,9 @@ bool Project::readWildMonData() { bool Project::readMapGroups() { this->mapConstantsToMapNames.clear(); this->mapNamesToMapConstants.clear(); - this->mapGroups.clear(); - this->groupNames.clear(); - this->groupedMapNames.clear(); this->mapNames.clear(); + this->groupNames.clear(); + this->groupNameToMapNames.clear(); this->initTopLevelMapFields(); @@ -1845,7 +1844,6 @@ bool Project::readMapGroups() { for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); - this->groupedMapNames.append(QStringList()); this->groupNames.append(groupName); // Process the names in this map group @@ -1885,8 +1883,8 @@ bool Project::readMapGroups() { // Success, save the constants to the project this->mapNames.append(mapName); - this->groupedMapNames[groupIndex].append(mapName); - this->mapGroups.insert(mapName, groupIndex); + this->groupNameToMapNames[groupName].append(mapName); + // TODO: These are not well-kept in sync (and that's probably a bad design indication. Maybe Maps should have a not-fully-loaded state, but have all their map.json data cached) this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); // TODO: Either verify that these are known IDs, or make sure nothing breaks when they're unknown. @@ -1913,34 +1911,69 @@ bool Project::readMapGroups() { return true; } -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(); +void Project::addNewMap(Map *newMap, const QString &groupName) { + if (!newMap) + return; + + // Make sure we keep the order of the map names the same as in the map group order. + int mapNamePos; + if (this->groupNames.contains(groupName)) { + mapNamePos = 0; + for (const auto &name : this->groupNames) { + mapNamePos += this->groupNameToMapNames[name].length(); + if (name == groupName) + break; + } + } else { + // Adding map to a map group that doesn't exist yet. + // Create the group, and we already know the map will be last in the list. + addNewMapGroup(groupName); + mapNamePos = this->mapNames.length(); + } this->mapNames.insert(mapNamePos, newMap->name()); - this->mapGroups.insert(newMap->name(), groupNum); - this->groupedMapNames[groupNum].append(newMap->name()); + this->groupNameToMapNames[groupName].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()); - this->layoutIdsToNames.insert(newMap->layout()->id, newMap->layout()->name); - if (!importedMap) { - setNewLayoutBlockdata(newMap->layout()); - } - if (newMap->layout()->border.isEmpty()) { - setNewLayoutBorder(newMap->layout()); - } + // If we don't recognize the layout ID (i.e., it's also new) we'll add that too. + if (!this->layoutIds.contains(newMap->layout()->id)) { + addNewLayout(newMap->layout()); } - loadLayoutTilesets(newMap->layout()); + emit mapAdded(newMap, groupName); +} - return newMap; +void Project::addNewLayout(Layout* newLayout) { + if (!newLayout || this->layoutIds.contains(newLayout->id)) + return; + + this->mapLayouts.insert(newLayout->id, newLayout); + this->layoutIds.append(newLayout->id); + + 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); +} + +void Project::addNewMapGroup(const QString &groupName) { + if (this->groupNames.contains(groupName)) + return; + + this->groupNames.append(groupName); + this->groupNameToMapNames.insert(groupName, QStringList()); + this->hasUnsavedDataChanges = true; + + emit mapGroupAdded(groupName); } QString Project::getNewMapName() { @@ -1949,7 +1982,7 @@ QString Project::getNewMapName() { QString newMapName; do { newMapName = QString("NewMap%1").arg(++i); - } while (mapNames.contains(newMapName)); + } while (this->mapNames.contains(newMapName)); return newMapName; } @@ -1966,7 +1999,7 @@ Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) return qualifiers; } -QString Project::getDefaultPrimaryTilesetLabel() { +QString Project::getDefaultPrimaryTilesetLabel() const { QString defaultLabel = projectConfig.defaultPrimaryTileset; if (!this->primaryTilesetLabels.contains(defaultLabel)) { QString firstLabel = this->primaryTilesetLabels.first(); @@ -1976,7 +2009,7 @@ QString Project::getDefaultPrimaryTilesetLabel() { return defaultLabel; } -QString Project::getDefaultSecondaryTilesetLabel() { +QString Project::getDefaultSecondaryTilesetLabel() const { QString defaultLabel = projectConfig.defaultSecondaryTileset; if (!this->secondaryTilesetLabels.contains(defaultLabel)) { QString firstLabel = this->secondaryTilesetLabels.first(); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a2bcd8a6..0fd6fea9 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -66,7 +66,7 @@ QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionView } void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { - QString groupName = index.data(Qt::UserRole).toString(); + QString groupName = index.data(MapListUserRoles::NameRole).toString(); QLineEdit *le = static_cast(editor); le->setText(groupName); } @@ -74,7 +74,7 @@ void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) void GroupNameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *le = static_cast(editor); QString groupName = le->text(); - model->setData(index, groupName, Qt::UserRole); + model->setData(index, groupName, MapListUserRoles::NameRole); } void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const { @@ -112,7 +112,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { // if dropping a selection containing a group(s) and map(s), clear all selection but first group. for (const QModelIndex &index : indexes) { if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { - QString groupName = data(index, Qt::UserRole).toString(); + QString groupName = data(index, MapListUserRoles::NameRole).toString(); stream << groupName; mimeData->setData("application/porymap.mapgroupmodel.group", encodedData); mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row())); @@ -122,7 +122,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { for (const QModelIndex &index : indexes) { if (index.isValid()) { - QString mapName = data(index, Qt::UserRole).toString(); + QString mapName = data(index, MapListUserRoles::NameRole).toString(); stream << mapName; } } @@ -168,12 +168,12 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i QStringList mapsToMove; for (int i = 0; i < this->rowCount(originIndex); ++i ) { children << this->index( i, 0, originIndex); - mapsToMove << this->index( i, 0 , originIndex).data(Qt::UserRole).toString(); + mapsToMove << this->index( i, 0 , originIndex).data(MapListUserRoles::NameRole).toString(); } QModelIndex groupIndex = index(row, 0, parentIndex); QStandardItem *groupItem = this->itemFromIndex(groupIndex); - createGroupItem(groupName, row, groupItem); + createGroupItem(groupName, groupItem); for (QString mapName : mapsToMove) { QStandardItem *mapItem = createMapItem(mapName); @@ -224,43 +224,38 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i void MapGroupModel::updateProject() { if (!this->project) return; - QStringList groupNames; - QMap mapGroups; - QList groupedMapNames; + // Temporary objects in case of failure, so we won't modify the project unless it succeeds. QStringList mapNames; + QStringList groupNames; + QMap groupNameToMapNames; for (int g = 0; g < this->root->rowCount(); g++) { - QStandardItem *groupItem = this->item(g); - QString groupName = groupItem->data(Qt::UserRole).toString(); + const QStandardItem *groupItem = this->item(g); + QString groupName = groupItem->data(MapListUserRoles::NameRole).toString(); groupNames.append(groupName); - mapGroups[groupName] = g; - QStringList mapsInGroup; for (int m = 0; m < groupItem->rowCount(); m++) { - QStandardItem *mapItem = groupItem->child(m); + const QStandardItem *mapItem = groupItem->child(m); if (!mapItem) { logError("An error occured while trying to apply updates to map group structure."); return; } - QString mapName = mapItem->data(Qt::UserRole).toString(); - mapsInGroup.append(mapName); + QString mapName = mapItem->data(MapListUserRoles::NameRole).toString(); + groupNameToMapNames[groupName].append(mapName); mapNames.append(mapName); } - groupedMapNames.append(mapsInGroup); } - this->project->groupNames = groupNames; - this->project->mapGroups = mapGroups; - this->project->groupedMapNames = groupedMapNames; this->project->mapNames = mapNames; + this->project->groupNames = groupNames; + this->project->groupNameToMapNames = groupNameToMapNames; this->project->hasUnsavedDataChanges = true; } -QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *group) { +QStandardItem *MapGroupModel::createGroupItem(QString groupName, QStandardItem *group) { if (!group) group = new QStandardItem; group->setText(groupName); - group->setData(groupName, Qt::UserRole); + group->setData(groupName, MapListUserRoles::NameRole); group->setData("map_group", MapListUserRoles::TypeRole); - group->setData(groupIndex, MapListUserRoles::GroupRole); group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); this->groupItems.insert(groupName, group); return group; @@ -268,7 +263,7 @@ QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { if (!map) map = new QStandardItem; - map->setData(mapName, Qt::UserRole); + map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->mapItems[mapName] = map; @@ -276,9 +271,8 @@ QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) } QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { - QStandardItem *group = createGroupItem(groupName, this->groupItems.size()); + QStandardItem *group = createGroupItem(groupName); this->root->appendRow(group); - this->updateProject(); return group; } @@ -300,15 +294,13 @@ QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) void MapGroupModel::initialize() { this->groupItems.clear(); this->mapItems.clear(); - for (int i = 0; i < this->project->groupNames.length(); i++) { - QString group_name = this->project->groupNames.value(i); - QStandardItem *group = createGroupItem(group_name, i); + + + for (const auto &groupName : this->project->groupNames) { + QStandardItem *group = createGroupItem(groupName); root->appendRow(group); - QStringList names = this->project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name); - group->appendRow(map); + for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { + group->appendRow(createMapItem(mapName)); } } } @@ -361,7 +353,7 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { } return mapFolderIcon; } else if (type == "map_name") { - QString mapName = item->data(Qt::UserRole).toString(); + QString mapName = item->data(MapListUserRoles::NameRole).toString(); if (mapName == this->openMap) { return mapOpenedIcon; } @@ -381,10 +373,10 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_name") { - return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(Qt::UserRole).toString(); + return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(MapListUserRoles::NameRole).toString(); } else if (type == "map_group") { - return item->data(Qt::UserRole).toString(); + return item->data(MapListUserRoles::NameRole).toString(); } } @@ -392,8 +384,9 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { } bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (role == Qt::UserRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { + if (role == MapListUserRoles::NameRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { // verify uniqueness of new group name + // TODO: Check that the name is a valid symbol name (i.e. only word characters, not starting with a number) if (this->project->groupNames.contains(value.toString())) { return false; } @@ -417,18 +410,18 @@ QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) { QStandardItem *area = new QStandardItem; area->setText(mapsecName); area->setEditable(false); - area->setData(mapsecName, Qt::UserRole); + area->setData(mapsecName, MapListUserRoles::NameRole); area->setData("map_section", MapListUserRoles::TypeRole); // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->areaItems.insert(mapsecName, area); return area; } -QStandardItem *MapAreaModel::createMapItem(QString mapName, int, int) { +QStandardItem *MapAreaModel::createMapItem(QString mapName) { QStandardItem *map = new QStandardItem; map->setText(mapName); map->setEditable(false); - map->setData(mapName, Qt::UserRole); + map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->mapItems.insert(mapName, map); @@ -443,19 +436,18 @@ QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { return item; } -QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) { +QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName) { QStandardItem *area = this->areaItems[areaName]; if (!area) { return nullptr; } - int mapIndex = area->rowCount(); - QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex); + QStandardItem *map = createMapItem(mapName); area->appendRow(map); return map; } void MapAreaModel::removeItem(QStandardItem *item) { - this->project->removeMapsec(item->data(Qt::UserRole).toString()); + this->project->removeMapsec(item->data(MapListUserRoles::NameRole).toString()); this->removeRow(item->row()); } @@ -467,17 +459,12 @@ void MapAreaModel::initialize() { this->root->appendRow(createAreaItem(idName)); } - for (int i = 0; i < this->project->groupNames.length(); i++) { - QStringList names = this->project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString mapName = names.value(j); - QStandardItem *map = createMapItem(mapName, i, j); - QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); - if (this->areaItems.contains(mapsecName)) { - this->areaItems[mapsecName]->appendRow(map); - } - } + for (const auto &mapName : this->project->mapNames) { + const QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); + if (this->areaItems.contains(mapsecName)) + this->areaItems[mapsecName]->appendRow(createMapItem(mapName)); } + this->sort(0, Qt::AscendingOrder); } @@ -529,7 +516,7 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { } return folderIcon; } else if (type == "map_name") { - QString mapName = item->data(Qt::UserRole).toString(); + QString mapName = item->data(MapListUserRoles::NameRole).toString(); if (mapName == this->openMap) { return mapOpenedIcon; } @@ -549,7 +536,7 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_section") { - return item->data(Qt::UserRole).toString(); + return item->data(MapListUserRoles::NameRole).toString(); } } @@ -567,9 +554,9 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListMod QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { QStandardItem *layout = new QStandardItem; - layout->setText(this->project->layoutIdsToNames[layoutId]); + layout->setText(this->project->mapLayouts[layoutId]->name); layout->setEditable(false); - layout->setData(layoutId, Qt::UserRole); + layout->setData(layoutId, MapListUserRoles::NameRole); layout->setData("map_layout", MapListUserRoles::TypeRole); // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->layoutItems.insert(layoutId, layout); @@ -580,7 +567,7 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { QStandardItem *map = new QStandardItem; map->setText(mapName); map->setEditable(false); - map->setData(mapName, Qt::UserRole); + map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren); this->mapItems.insert(mapName, map); @@ -619,19 +606,17 @@ void LayoutTreeModel::removeItem(QStandardItem *) { void LayoutTreeModel::initialize() { this->layoutItems.clear(); this->mapItems.clear(); - for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { - QString layoutId = project->mapLayoutsTable.value(i); - QStandardItem *layoutItem = createLayoutItem(layoutId); - this->root->appendRow(layoutItem); + + for (const auto &layoutId : this->project->layoutIds) { + this->root->appendRow(createLayoutItem(layoutId)); } - for (auto mapList : this->project->groupedMapNames) { - for (auto mapName : mapList) { - QString layoutId = project->mapNameToLayoutId.value(mapName); - QStandardItem *map = createMapItem(mapName); - this->layoutItems[layoutId]->appendRow(map); - } + for (const auto &mapName : this->project->mapNames) { + QString layoutId = project->mapNameToLayoutId.value(mapName); + if (this->layoutItems.contains(layoutId)) + this->layoutItems[layoutId]->appendRow(createMapItem(mapName)); } + this->sort(0, Qt::AscendingOrder); } @@ -667,7 +652,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_layout") { - QString layoutId = item->data(Qt::UserRole).toString(); + QString layoutId = item->data(MapListUserRoles::NameRole).toString(); if (layoutId == this->openLayout) { return mapOpenedIcon; } diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index ed217fcb..2f972a52 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -2,6 +2,8 @@ #include "ui_newlayoutform.h" #include "project.h" +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + NewLayoutForm::NewLayoutForm(QWidget *parent) : QWidget(parent) , ui(new Ui::NewLayoutForm) @@ -10,7 +12,7 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) // TODO: Read from project? ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); - ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); + ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); @@ -119,5 +121,7 @@ bool NewLayoutForm::validateTilesets() { bool isValid = errorText.isEmpty(); ui->label_TilesetsError->setText(errorText); ui->label_TilesetsError->setVisible(!isValid); + ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!primaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); + ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!secondaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); return isValid; } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 9f0a9f18..0a14e552 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -20,8 +20,6 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : setModal(true); ui->setupUi(this); this->project = project; - this->existingLayout = false; // TODO: Replace, we can determine this from the Layout ID combo box - this->importedMap = false; ui->newLayoutForm->initUi(project); @@ -32,6 +30,7 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_MapID->setValidator(validator); + ui->comboBox_Group->setValidator(validator); // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); @@ -43,11 +42,14 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : this->headerSection->setContentLayout(sectionLayout); ui->layout_HeaderData->addWidget(this->headerSection); ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); } NewMapDialog::~NewMapDialog() { saveSettings(); + delete this->importedLayout; delete ui; } @@ -73,7 +75,7 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { this->headerForm->setLocationsDisabled(true); break; case MapListTab::Layouts: - useLayout(fieldName); + useLayoutSettings(project->mapLayouts.value(fieldName)); break; } init(); @@ -81,24 +83,23 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { // Creating new map from AdvanceMap import // TODO: Re-use for a "Duplicate Map/Layout" option? -void NewMapDialog::init(Layout *layout) { - this->importedMap = true; - useLayoutSettings(layout); +void NewMapDialog::init(Layout *layoutToCopy) { + if (this->importedLayout) + delete this->importedLayout; - // TODO: These are probably leaking - this->map = new Map(); - this->map->setLayout(new Layout()); - this->map->layout()->blockdata = layout->blockdata; + this->importedLayout = new Layout(); + this->importedLayout->blockdata = layoutToCopy->blockdata; + if (!layoutToCopy->border.isEmpty()) + this->importedLayout->border = layoutToCopy->border; - if (!layout->border.isEmpty()) { - this->map->layout()->border = layout->border; - } + useLayoutSettings(this->importedLayout); init(); } -void NewMapDialog::setDefaultSettings(Project *project) { +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; @@ -128,6 +129,7 @@ void NewMapDialog::saveSettings() { 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; @@ -139,39 +141,24 @@ void NewMapDialog::useLayoutSettings(Layout *layout) { ui->newLayoutForm->setDisabled(true); } -void NewMapDialog::useLayout(QString layoutId) { - this->existingLayout = true; - this->layoutId = layoutId; - useLayoutSettings(project->mapLayouts.value(this->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); } -// TODO: Create the map group if it doesn't exist -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::validateID() { +bool NewMapDialog::validateID(bool allowEmpty) { QString id = ui->lineEdit_MapID->text(); + const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); 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); + if (id.isEmpty()) { + 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 = project->mapNamesToMapConstants.constBegin(), end = project->mapNamesToMapConstants.constEnd(); i != end; i++) { + for (auto i = this->project->mapNamesToMapConstants.constBegin(), end = this->project->mapNamesToMapConstants.constEnd(); i != end; i++) { if (id == i.value()) { - errorText = QString("The specified ID name '%1' is already in use.").arg(id); + errorText = QString("%1 '%2' is already in use.").arg(ui->label_MapID->text()).arg(id); break; } } @@ -185,15 +172,17 @@ bool NewMapDialog::validateID() { } void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { - validateID(); + validateID(true); } -bool NewMapDialog::validateName() { +bool NewMapDialog::validateName(bool allowEmpty) { 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); + 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); } bool isValid = errorText.isEmpty(); @@ -204,31 +193,53 @@ bool NewMapDialog::validateName() { } void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { - validateName(); + validateName(true); ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); } -void NewMapDialog::on_pushButton_Accept_clicked() { +bool NewMapDialog::validateGroup(bool allowEmpty) { + QString groupName = ui->comboBox_Group->currentText(); + + QString errorText; + if (groupName.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Group->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_GroupError->setText(errorText); + ui->label_GroupError->setVisible(!isValid); + ui->comboBox_Group->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewMapDialog::on_comboBox_Group_currentTextChanged(const QString &) { + validateGroup(true); +} + +void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::ResetRole) { + setDefaultSettings(this->project); // TODO: Don't allow this to change locked settings + init(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +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 (!validateMapGroup()) success = false; if (!validateID()) success = false; if (!validateName()) success = false; + if (!validateGroup()) success = false; if (!success) return; - // We check if the map name is empty separately from validateName, because validateName is also used during editing. - // 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; newMap->setName(ui->lineEdit_Name->text()); newMap->setConstantName(ui->lineEdit_MapID->text()); @@ -236,8 +247,9 @@ void NewMapDialog::on_pushButton_Accept_clicked() { newMap->setNeedsHealLocation(settings.canFlyTo); Layout *layout; - if (this->existingLayout) { - layout = this->project->mapLayouts.value(this->layoutId); + const bool existingLayout = isExistingLayout(); + if (existingLayout) { + layout = this->project->mapLayouts.value(settings.layout.id); newMap->setNeedsLayoutDir(false); } else { layout = new Layout; @@ -258,17 +270,15 @@ void NewMapDialog::on_pushButton_Accept_clicked() { 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->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; } newMap->setLayout(layout); - if (this->existingLayout) { - project->loadMapLayout(newMap); - } - map = newMap; - emit applied(); - this->close(); + this->project->addNewMap(newMap, settings.group); + emit applied(newMap->name()); + QDialog::accept(); } From d3a34cf5fc448eab23a13b5268eb88db276bf4a5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 19 Nov 2024 21:18:55 -0500 Subject: [PATCH 087/364] Fix scrolling over UIntSpinBox --- src/ui/uintspinbox.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp index 789a1662..53f6df78 100644 --- a/src/ui/uintspinbox.cpp +++ b/src/ui/uintspinbox.cpp @@ -1,4 +1,5 @@ #include "uintspinbox.h" +#include UIntSpinBox::UIntSpinBox(QWidget *parent) : QAbstractSpinBox(parent) @@ -178,8 +179,11 @@ QAbstractSpinBox::StepEnabled UIntSpinBox::stepEnabled() const { void UIntSpinBox::wheelEvent(QWheelEvent *event) { // Only allow scrolling to modify contents when it explicitly has focus. - if (hasFocus()) + if (hasFocus()) { QAbstractSpinBox::wheelEvent(event); + } else { + event->ignore(); + } } void UIntSpinBox::focusOutEvent(QFocusEvent *event) { From bd39bcfdd297222050bdecac425d95ebd095e51a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 19 Nov 2024 14:52:47 -0500 Subject: [PATCH 088/364] 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(); } From e7df8298434f0fc4198291e96c53547d177eb05c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 21 Nov 2024 13:24:12 -0500 Subject: [PATCH 089/364] Group AdvanceMap parsing together, fix its tileset defaults --- forms/mainwindow.ui | 6 +- include/core/advancemapparser.h | 18 +++ include/core/mapparser.h | 16 --- include/core/metatileparser.h | 12 -- include/core/parseutil.h | 3 +- include/lib/fex/parser.h | 5 +- include/mainwindow.h | 3 +- include/project.h | 2 +- include/ui/newlayoutdialog.h | 3 +- porymap.pro | 10 +- src/core/advancemapparser.cpp | 217 ++++++++++++++++++++++++++++++++ src/core/mapparser.cpp | 97 -------------- src/core/metatileparser.cpp | 99 --------------- src/core/paletteutil.cpp | 36 +----- src/core/parseutil.cpp | 12 +- src/lib/fex/parser.cpp | 8 +- src/lib/fex/parser_util.cpp | 4 +- src/mainwindow.cpp | 22 ++-- src/project.cpp | 27 ++-- src/ui/newlayoutdialog.cpp | 35 +++--- src/ui/tileseteditor.cpp | 4 +- 21 files changed, 304 insertions(+), 335 deletions(-) create mode 100644 include/core/advancemapparser.h delete mode 100644 include/core/mapparser.h delete mode 100644 include/core/metatileparser.h create mode 100644 src/core/advancemapparser.cpp delete mode 100644 src/core/mapparser.cpp delete mode 100644 src/core/metatileparser.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index d7c4abe1..50e03e88 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2931,7 +2931,7 @@ - +
@@ -3222,9 +3222,9 @@ Open Config Folder - + - Import Map from Advance Map 1.92... + Import Layout from Advance Map 1.92... diff --git a/include/core/advancemapparser.h b/include/core/advancemapparser.h new file mode 100644 index 00000000..c3cb76cb --- /dev/null +++ b/include/core/advancemapparser.h @@ -0,0 +1,18 @@ +#ifndef ADVANCEMAPPARSER_H +#define ADVANCEMAPPARSER_H + +#include +#include +#include + +class Project; +class Layout; +class Metatile; + +namespace AdvanceMapParser { + Layout *parseLayout(const QString &filepath, bool *error, const Project *project); + QList parseMetatiles(const QString &filepath, bool *error, bool primaryTileset); + QList parsePalette(const QString &filepath, bool *error); +}; + +#endif // ADVANCEMAPPARSER_H diff --git a/include/core/mapparser.h b/include/core/mapparser.h deleted file mode 100644 index 4032154a..00000000 --- a/include/core/mapparser.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MAPPARSER_H -#define MAPPARSER_H - -#include "maplayout.h" -#include "project.h" -#include -#include - -class MapParser -{ -public: - MapParser(); - Layout *parse(QString filepath, bool *error, Project *project); -}; - -#endif // MAPPARSER_H diff --git a/include/core/metatileparser.h b/include/core/metatileparser.h deleted file mode 100644 index b85e5b36..00000000 --- a/include/core/metatileparser.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#ifndef METATILEPARSER_H -#define METATILEPARSER_H - -#include "metatile.h" -#include - -namespace MetatileParser { - QList parse(QString filepath, bool *error, bool primaryTileset); -} - -#endif // METATILEPARSER_H diff --git a/include/core/parseutil.h b/include/core/parseutil.h index e3df8375..d184646b 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -5,6 +5,7 @@ #include "heallocation.h" #include "log.h" #include "orderedjson.h" +#include "orderedmap.h" #include #include @@ -57,7 +58,7 @@ public: QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList); QMap readCDefinesByName(const QString &filename, const QStringList &names); QStringList readCDefineNames(const QString &filename, const QStringList ®exList); - QMap> readCStructs(const QString &, const QString & = "", const QHash = { }); + tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); bool tryParseJsonFile(QJsonDocument *out, const QString &filepath); diff --git a/include/lib/fex/parser.h b/include/lib/fex/parser.h index 6a6b9e43..c79b34a2 100644 --- a/include/lib/fex/parser.h +++ b/include/lib/fex/parser.h @@ -9,6 +9,7 @@ #include "array_value.h" #include "define_statement.h" #include "lexer.h" +#include "orderedmap.h" namespace fex { @@ -19,9 +20,9 @@ namespace fex std::vector Parse(std::vector tokens); std::vector ParseTopLevelArrays(std::vector tokens); - std::map ParseTopLevelObjects(std::vector tokens); + tsl::ordered_map ParseTopLevelObjects(std::vector tokens); - std::map ReadDefines(const std::string &filename, std::vector matching); + tsl::ordered_map ReadDefines(const std::string &filename, std::vector matching); private: int EvaluateExpression(std::vector tokens); diff --git a/include/mainwindow.h b/include/mainwindow.h index a8dbca1f..53889dff 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -194,7 +194,6 @@ private slots: void onNewMapGroupCreated(const QString &groupName); void onNewLayoutCreated(Layout *layout); void onMapLoaded(Map *map); - void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); void markMapEdited(); @@ -238,7 +237,7 @@ private slots: void on_action_Export_Map_Image_triggered(); void on_actionExport_Stitched_Map_Image_triggered(); void on_actionExport_Map_Timelapse_Image_triggered(); - void on_actionImport_Map_from_Advance_Map_1_92_triggered(); + void on_actionImport_Layout_from_Advance_Map_1_92_triggered(); void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); diff --git a/include/project.h b/include/project.h index 19b757db..c1a36a80 100644 --- a/include/project.h +++ b/include/project.h @@ -189,7 +189,7 @@ public: void saveTilesetMetatiles(Tileset*); void saveTilesetTilesImage(Tileset*); void saveTilesetPalettes(Tileset*); - void appendTilesetLabel(QString label, QString isSecondaryStr); + void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); bool readRegionMapSections(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index 49047cc7..c90a408b 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -20,7 +20,7 @@ class NewLayoutDialog : public QDialog public: explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr); ~NewLayoutDialog(); - void init(Layout *); + void copyFrom(const Layout &); void accept() override; signals: @@ -39,7 +39,6 @@ private: void saveSettings(); bool isExistingLayout() const; - void useLayoutSettings(Layout *mapLayout); private slots: //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO diff --git a/porymap.pro b/porymap.pro index 1cc9497c..1dfaf4b1 100644 --- a/porymap.pro +++ b/porymap.pro @@ -21,7 +21,8 @@ QMAKE_TARGET_BUNDLE_PREFIX = com.pret VERSION = 5.4.1 DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" -SOURCES += src/core/block.cpp \ +SOURCES += src/core/advancemapparser.cpp \ + src/core/block.cpp \ src/core/bitpacker.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ @@ -32,9 +33,7 @@ SOURCES += src/core/block.cpp \ src/core/mapconnection.cpp \ src/core/mapheader.cpp \ src/core/maplayout.cpp \ - src/core/mapparser.cpp \ src/core/metatile.cpp \ - src/core/metatileparser.cpp \ src/core/network.cpp \ src/core/paletteutil.cpp \ src/core/parseutil.cpp \ @@ -127,7 +126,8 @@ SOURCES += src/core/block.cpp \ src/ui/updatepromoter.cpp \ src/ui/wildmonchart.cpp -HEADERS += include/core/block.h \ +HEADERS += include/core/advancemapparser.h \ + include/core/block.h \ include/core/bitpacker.h \ include/core/blockdata.h \ include/core/events.h \ @@ -139,9 +139,7 @@ HEADERS += include/core/block.h \ include/core/mapconnection.h \ include/core/mapheader.h \ include/core/maplayout.h \ - include/core/mapparser.h \ include/core/metatile.h \ - include/core/metatileparser.h \ include/core/network.h \ include/core/paletteutil.h \ include/core/parseutil.h \ diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp new file mode 100644 index 00000000..6af7476e --- /dev/null +++ b/src/core/advancemapparser.cpp @@ -0,0 +1,217 @@ +#include "advancemapparser.h" +#include "log.h" +#include "project.h" +#include "maplayout.h" + +Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, const Project *project) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); + return nullptr; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 20 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); + return nullptr; + } + + int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files + int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files + int numBorderTiles = borderWidth * borderHeight; // 0 if RSE + + int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header + int mapWidth = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + int mapHeight = static_cast(in.at(4)) | + (static_cast(in.at(5)) << 8) | + (static_cast(in.at(6)) << 16) | + (static_cast(in.at(7)) << 24); + int mapPrimaryTilesetNum = static_cast(in.at(8)) | + (static_cast(in.at(9)) << 8) | + (static_cast(in.at(10)) << 16) | + (static_cast(in.at(11)) << 24); + int mapSecondaryTilesetNum = static_cast(in.at(12)) | + (static_cast(in.at(13)) << 8) | + (static_cast(in.at(14)) << 16) | + (static_cast(in.at(15)) << 24); + + int numMetatiles = mapWidth * mapHeight; + int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return nullptr; + } + + Blockdata blockdata; + for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + blockdata.append(word); + } + + Blockdata border; + if (numBorderTiles != 0) { + for (int i = 20; (i + 1) < mapDataOffset; i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + border.append(word); + } + } + + Layout *mapLayout = new Layout(); + mapLayout->width = mapWidth; + mapLayout->height = mapHeight; + mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; + mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight; + + const QList tilesets = project->tilesetLabelsOrdered; + + if (mapPrimaryTilesetNum > tilesets.size()) + mapLayout->tileset_primary_label = project->getDefaultPrimaryTilesetLabel(); + else + mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); + + if (mapSecondaryTilesetNum > tilesets.size()) + mapLayout->tileset_secondary_label = project->getDefaultSecondaryTilesetLabel(); + else + mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); + + mapLayout->blockdata = blockdata; + + if (!border.isEmpty()) { + mapLayout->border = border; + } + + return mapLayout; +} + +QList AdvanceMapParser::parseMetatiles(const QString &filepath, bool *error, bool primaryTileset) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString()); + return { }; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 9 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath)); + return { }; + } + + int projIdOffset = in.length() - 4; + int metatileSize = 16; + BaseGameVersion version; + if (in.at(projIdOffset + 0) == 'R' + && in.at(projIdOffset + 1) == 'S' + && in.at(projIdOffset + 2) == 'E' + && in.at(projIdOffset + 3) == ' ') { + // ruby and emerald are handled equally here. + version = BaseGameVersion::pokeemerald; + } else if (in.at(projIdOffset + 0) == 'F' + && in.at(projIdOffset + 1) == 'R' + && in.at(projIdOffset + 2) == 'L' + && in.at(projIdOffset + 3) == 'G') { + version = BaseGameVersion::pokefirered; + } else { + *error = true; + logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); + return { }; + } + + int attrSize = Metatile::getDefaultAttributesSize(version); + int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); + int numMetatiles = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + if (numMetatiles > maxMetatiles) { + *error = true; + logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles)); + return { }; + } + if (numMetatiles < 1) { + *error = true; + logError(QString(".bvd file contains no data for metatiles.")); + return { }; + } + + int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4; + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return { }; + } + + QList metatiles; + for (int i = 0; i < numMetatiles; i++) { + Metatile *metatile = new Metatile(); + QList tiles; + for (int j = 0; j < 8; j++) { + int metatileOffset = 4 + i * metatileSize + j * 2; + Tile tile(static_cast( + static_cast(in.at(metatileOffset)) | + (static_cast(in.at(metatileOffset + 1)) << 8))); + tiles.append(tile); + } + + // AdvanceMap .bvd files only contain 8 tiles of data per metatile. + // If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves. + if (projectConfig.tripleLayerMetatilesEnabled) { + Tile tile = Tile(); + for (int j = 0; j < 4; j++) + tiles.append(tile); + } + + int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); + uint32_t attributes = 0; + for (int j = 0; j < attrSize; j++) + attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); + metatile->setAttributes(attributes, version); + metatile->tiles = tiles; + metatiles.append(metatile); + } + + return metatiles; +} + +QList AdvanceMapParser::parsePalette(const QString &filepath, bool *error) { + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); + return QList(); + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() % 4 != 0) { + *error = true; + logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); + return QList(); + } + + QList palette; + int i = 0; + while (i < in.length()) { + unsigned char red = qMin(qMax(static_cast(in.at(i + 0)), 0u), 255u); + unsigned char green = qMin(qMax(static_cast(in.at(i + 1)), 0u), 255u); + unsigned char blue = qMin(qMax(static_cast(in.at(i + 2)), 0u), 255u); + palette.append(qRgb(red, green, blue)); + i += 4; + } + + return palette; +} diff --git a/src/core/mapparser.cpp b/src/core/mapparser.cpp deleted file mode 100644 index 3d4258bd..00000000 --- a/src/core/mapparser.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "mapparser.h" -#include "config.h" -#include "log.h" -#include "project.h" - -MapParser::MapParser() -{ -} - -Layout *MapParser::parse(QString filepath, bool *error, Project *project) -{ - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); - return nullptr; - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() < 20 || in.length() % 2 != 0) { - *error = true; - logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); - return nullptr; - } - - int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files - int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files - int numBorderTiles = borderWidth * borderHeight; // 0 if RSE - - int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header - int mapWidth = static_cast(in.at(0)) | - (static_cast(in.at(1)) << 8) | - (static_cast(in.at(2)) << 16) | - (static_cast(in.at(3)) << 24); - int mapHeight = static_cast(in.at(4)) | - (static_cast(in.at(5)) << 8) | - (static_cast(in.at(6)) << 16) | - (static_cast(in.at(7)) << 24); - int mapPrimaryTilesetNum = static_cast(in.at(8)) | - (static_cast(in.at(9)) << 8) | - (static_cast(in.at(10)) << 16) | - (static_cast(in.at(11)) << 24); - int mapSecondaryTilesetNum = static_cast(in.at(12)) | - (static_cast(in.at(13)) << 8) | - (static_cast(in.at(14)) << 16) | - (static_cast(in.at(15)) << 24); - - int numMetatiles = mapWidth * mapHeight; - int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); - if (in.length() != expectedFileSize) { - *error = true; - logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); - return nullptr; - } - - Blockdata blockdata; - for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { - uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); - blockdata.append(word); - } - - Blockdata border; - if (numBorderTiles != 0) { - for (int i = 20; (i + 1) < mapDataOffset; i += 2) { - uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); - border.append(word); - } - } - - Layout *mapLayout = new Layout(); - mapLayout->width = mapWidth; - mapLayout->height = mapHeight; - mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; - mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight; - - QList tilesets = project->tilesetLabelsOrdered; - - if (mapPrimaryTilesetNum > tilesets.size()) - mapLayout->tileset_primary_label = tilesets.at(0); - else - mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); - - if (mapSecondaryTilesetNum > tilesets.size()) - mapLayout->tileset_secondary_label = tilesets.at(1); - else - mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); - - mapLayout->blockdata = blockdata; - - if (!border.isEmpty()) { - mapLayout->border = border; - } - - return mapLayout; -} diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp deleted file mode 100644 index 104b757a..00000000 --- a/src/core/metatileparser.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "metatileparser.h" -#include "config.h" -#include "log.h" -#include "project.h" -#include - -QList MetatileParser::parse(QString filepath, bool *error, bool primaryTileset) -{ - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString()); - return { }; - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() < 9 || in.length() % 2 != 0) { - *error = true; - logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath)); - return { }; - } - - int projIdOffset = in.length() - 4; - int metatileSize = 16; - BaseGameVersion version; - if (in.at(projIdOffset + 0) == 'R' - && in.at(projIdOffset + 1) == 'S' - && in.at(projIdOffset + 2) == 'E' - && in.at(projIdOffset + 3) == ' ') { - // ruby and emerald are handled equally here. - version = BaseGameVersion::pokeemerald; - } else if (in.at(projIdOffset + 0) == 'F' - && in.at(projIdOffset + 1) == 'R' - && in.at(projIdOffset + 2) == 'L' - && in.at(projIdOffset + 3) == 'G') { - version = BaseGameVersion::pokefirered; - } else { - *error = true; - logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); - return { }; - } - - int attrSize = Metatile::getDefaultAttributesSize(version); - int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); - int numMetatiles = static_cast(in.at(0)) | - (static_cast(in.at(1)) << 8) | - (static_cast(in.at(2)) << 16) | - (static_cast(in.at(3)) << 24); - if (numMetatiles > maxMetatiles) { - *error = true; - logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles)); - return { }; - } - if (numMetatiles < 1) { - *error = true; - logError(QString(".bvd file contains no data for metatiles.")); - return { }; - } - - int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4; - if (in.length() != expectedFileSize) { - *error = true; - logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); - return { }; - } - - QList metatiles; - for (int i = 0; i < numMetatiles; i++) { - Metatile *metatile = new Metatile(); - QList tiles; - for (int j = 0; j < 8; j++) { - int metatileOffset = 4 + i * metatileSize + j * 2; - Tile tile(static_cast( - static_cast(in.at(metatileOffset)) | - (static_cast(in.at(metatileOffset + 1)) << 8))); - tiles.append(tile); - } - - // AdvanceMap .bvd files only contain 8 tiles of data per metatile. - // If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves. - if (projectConfig.tripleLayerMetatilesEnabled) { - Tile tile = Tile(); - for (int j = 0; j < 4; j++) - tiles.append(tile); - } - - int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); - uint32_t attributes = 0; - for (int j = 0; j < attrSize; j++) - attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); - metatile->setAttributes(attributes, version); - metatile->tiles = tiles; - metatiles.append(metatile); - } - - return metatiles; -} diff --git a/src/core/paletteutil.cpp b/src/core/paletteutil.cpp index da281ce6..929336b2 100644 --- a/src/core/paletteutil.cpp +++ b/src/core/paletteutil.cpp @@ -1,4 +1,5 @@ #include "paletteutil.h" +#include "advancemapparser.h" #include "log.h" #include #include @@ -6,7 +7,6 @@ QList parsePal(QString filepath, bool *error); QList parseJASC(QString filepath, bool *error); -QList parseAdvanceMapPal(QString filepath, bool *error); QList parseAdobeColorTable(QString filepath, bool *error); QList parseTileLayerPro(QString filepath, bool *error); QList parseAdvancePaletteEditor(QString filepath, bool *error); @@ -81,7 +81,7 @@ QList parsePal(QString filepath, bool *error) { return parseJASC(filepath, error); } else { file.close(); - return parseAdvanceMapPal(filepath, error); + return AdvanceMapParser::parsePalette(filepath, error); } } @@ -152,38 +152,6 @@ QList parseJASC(QString filepath, bool *error) { return palette; } -QList parseAdvanceMapPal(QString filepath, bool *error) { - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); - return QList(); - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() % 4 != 0) { - *error = true; - logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); - return QList(); - } - - QList palette; - int i = 0; - while (i < in.length()) { - unsigned char red = static_cast(in.at(i)); - unsigned char green = static_cast(in.at(i + 1)); - unsigned char blue = static_cast(in.at(i + 2)); - palette.append(qRgb(clampColorValue(red), - clampColorValue(green), - clampColorValue(blue))); - i += 4; - } - - return palette; -} - QList parseAdobeColorTable(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9664fdc7..1aff9cd5 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -593,13 +593,13 @@ bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } -QMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash memberMap) { +tsl::ordered_map> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { QString filePath = this->root + "/" + filename; auto cParser = fex::Parser(); auto tokens = fex::Lexer().LexFile(filePath.toStdString()); - auto structs = cParser.ParseTopLevelObjects(tokens); - QMap> structMaps; - for (auto it = structs.begin(); it != structs.end(); it++) { + auto topLevelObjects = cParser.ParseTopLevelObjects(tokens); + tsl::ordered_map> structs; + for (auto it = topLevelObjects.begin(); it != topLevelObjects.end(); it++) { QString structLabel = QString::fromStdString(it->first); if (structLabel.isEmpty()) continue; if (!label.isEmpty() && label != structLabel) continue; // Speed up parsing if only looking for a particular symbol @@ -617,9 +617,9 @@ QMap> ParseUtil::readCStructs(const QString &fi } i++; } - structMaps.insert(structLabel, values); + structs[structLabel] = values; } - return structMaps; + return structs; } QList ParseUtil::getLabelMacros(const QList &list, const QString &label) { diff --git a/src/lib/fex/parser.cpp b/src/lib/fex/parser.cpp index bb5c90a8..1c010528 100644 --- a/src/lib/fex/parser.cpp +++ b/src/lib/fex/parser.cpp @@ -337,9 +337,9 @@ namespace fex return DefineStatement(identifer, value); } - std::map Parser::ReadDefines(const std::string &filename, std::vector matching) + tsl::ordered_map Parser::ReadDefines(const std::string &filename, std::vector matching) { - std::map out; + tsl::ordered_map out; Lexer lexer; auto tokens = lexer.LexFile(filename); @@ -488,12 +488,12 @@ namespace fex return items; } - std::map Parser::ParseTopLevelObjects(std::vector tokens) + tsl::ordered_map Parser::ParseTopLevelObjects(std::vector tokens) { index_ = 0; tokens_ = std::move(tokens); - std::map items; + tsl::ordered_map items; while (index_ < tokens_.size()) { diff --git a/src/lib/fex/parser_util.cpp b/src/lib/fex/parser_util.cpp index 0f375b81..3a5d47f9 100644 --- a/src/lib/fex/parser_util.cpp +++ b/src/lib/fex/parser_util.cpp @@ -17,7 +17,7 @@ QStringList ParserUtil::ReadDefines(QString filename, QString prefix) fex::Parser parser; std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); + tsl::ordered_map defines = parser.ReadDefines(filepath.toStdString(), match_list); QStringList out; for(auto const& define : defines) { @@ -39,7 +39,7 @@ QStringList ParserUtil::ReadDefinesValueSort(QString filename, QString prefix) fex::Parser parser; std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); + tsl::ordered_map defines = parser.ReadDefines(filepath.toStdString(), match_list); QMultiMap defines_keyed_by_value; for (const auto& pair : defines) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8c483f1f..7af3d100 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -14,7 +14,7 @@ #include "editcommands.h" #include "flowlayout.h" #include "shortcut.h" -#include "mapparser.h" +#include "advancemapparser.h" #include "prefab.h" #include "montabwidget.h" #include "imageexport.h" @@ -1634,7 +1634,7 @@ void MainWindow::on_actionNew_Tileset_triggered() { int index = insertTilesetLabel(&editor->project->secondaryTilesetLabels, createTilesetDialog->fullSymbolName); this->ui->comboBox_SecondaryTileset->insertItem(index, createTilesetDialog->fullSymbolName); } - insertTilesetLabel(&editor->project->tilesetLabelsOrdered, createTilesetDialog->fullSymbolName); + editor->project->tilesetLabelsOrdered.append(createTilesetDialog->fullSymbolName); QMessageBox msgBox(this); msgBox.setText("Successfully created tileset."); @@ -2712,20 +2712,14 @@ void MainWindow::on_actionExport_Map_Timelapse_Image_triggered() { showExportMapImageWindow(ImageExporterMode::Timelapse); } -void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered(){ - importMapFromAdvanceMap1_92(); -} - -void MainWindow::importMapFromAdvanceMap1_92() -{ - QString filepath = FileDialog::getOpenFileName(this, "Import Map from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); +void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { + QString filepath = FileDialog::getOpenFileName(this, "Import Layout from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); if (filepath.isEmpty()) { return; } - MapParser parser; bool error = false; - Layout *mapLayout = parser.parse(filepath, &error, editor->project); + Layout *mapLayout = AdvanceMapParser::parseLayout(filepath, &error, editor->project); if (error) { QMessageBox msgBox(this); msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); @@ -2734,11 +2728,13 @@ void MainWindow::importMapFromAdvanceMap1_92() msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Icon::Critical); msgBox.exec(); + delete mapLayout; return; } - openNewMapDialog(); - this->newMapDialog->init(mapLayout); + openNewLayoutDialog(); + this->newLayoutDialog->copyFrom(*mapLayout); + delete mapLayout; } void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { diff --git a/src/project.cpp b/src/project.cpp index 825c3c48..2f37e24a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1115,6 +1115,9 @@ bool Project::loadLayoutTilesets(Layout *layout) { return true; } +// TODO: We are parsing the tileset headers file whenever we load a tileset for the first time. +// At a minimum this means we're parsing the file three times per session (twice here for the first map's tilesets, once on launch in Project::readTilesetLabels). +// We can cache the header data instead and only parse it once on launch. Tileset* Project::loadTileset(QString label, Tileset *tileset) { auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets); if (this->usingAsmTilesets) { @@ -1134,14 +1137,14 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->metatile_attrs_label = values.value(memberMap.key("metatileAttributes")); } else { // Read C tileset header - const auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap); + auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap); if (!structs.contains(label)) { return nullptr; } if (tileset == nullptr) { tileset = new Tileset; } - const auto tilesetAttributes = structs[label]; + auto tilesetAttributes = structs[label]; tileset->name = label; tileset->is_secondary = ParseUtil::gameStringToBool(tilesetAttributes.value("isSecondary")); tileset->tiles_label = tilesetAttributes.value("tiles"); @@ -1579,7 +1582,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { } QString Project::findMetatileLabelsTileset(QString label) { - for (QString tilesetName : this->tilesetLabelsOrdered) { + for (const QString &tilesetName : this->tilesetLabelsOrdered) { QString metatileLabelPrefix = Tileset::getMetatileLabelPrefix(tilesetName); if (label.startsWith(metatileLabelPrefix)) return tilesetName; @@ -2046,7 +2049,7 @@ QString Project::getDefaultSecondaryTilesetLabel() const { return defaultLabel; } -void Project::appendTilesetLabel(QString label, QString isSecondaryStr) { +void Project::appendTilesetLabel(const QString &label, const QString &isSecondaryStr) { bool ok; bool isSecondary = ParseUtil::gameStringToBool(isSecondaryStr, &ok); if (!ok) { @@ -2080,20 +2083,18 @@ bool Project::readTilesetLabels() { QRegularExpressionMatch match = iter.next(); appendTilesetLabel(match.captured("label"), match.captured("isSecondary")); } - this->primaryTilesetLabels.sort(); - this->secondaryTilesetLabels.sort(); - this->tilesetLabelsOrdered.sort(); filename = asm_filename; // For error reporting further down } else { this->usingAsmTilesets = false; const auto structs = parser.readCStructs(filename, "", Tileset::getHeaderMemberMap(this->usingAsmTilesets)); - const QStringList labels = structs.keys(); - // TODO: This is alphabetical, AdvanceMap import wants the vanilla order in tilesetLabelsOrdered - for (const auto &tilesetLabel : labels){ - appendTilesetLabel(tilesetLabel, structs[tilesetLabel].value("isSecondary")); + for (auto i = structs.cbegin(); i != structs.cend(); i++){ + appendTilesetLabel(i.key(), i.value().value("isSecondary")); } } + this->primaryTilesetLabels.sort(); + this->secondaryTilesetLabels.sort(); + bool success = true; if (this->secondaryTilesetLabels.isEmpty()) { logError(QString("Failed to find any secondary tilesets in %1").arg(filename)); @@ -2784,7 +2785,7 @@ bool Project::readEventGraphics() { }; QString filepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); - const auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap); + auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap); QMap picTables = parser.readCArrayMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables)); QMap graphicIncbins = parser.readCIncbinMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); @@ -2794,7 +2795,7 @@ bool Project::readEventGraphics() { if (!gfxInfos.contains(info_label)) continue; - const auto gfxInfoAttributes = gfxInfos[info_label]; + auto gfxInfoAttributes = gfxInfos[info_label]; auto eventGraphics = new EventGraphics; eventGraphics->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 0f67545e..73822ba8 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -41,18 +41,27 @@ NewLayoutDialog::~NewLayoutDialog() delete ui; } -// Creating new map from AdvanceMap import +// Creating new layout from AdvanceMap import // TODO: Re-use for a "Duplicate Layout" option? -void NewLayoutDialog::init(Layout *layoutToCopy) { +void NewLayoutDialog::copyFrom(const 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; + this->importedLayout->blockdata = layoutToCopy.blockdata; + if (!layoutToCopy.border.isEmpty()) + this->importedLayout->border = layoutToCopy.border; - useLayoutSettings(this->importedLayout); + 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); } void NewLayoutDialog::saveSettings() { @@ -61,20 +70,6 @@ void NewLayoutDialog::saveSettings() { 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(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e709bed1..b6bf1735 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -2,7 +2,7 @@ #include "ui_tileseteditor.h" #include "log.h" #include "imageproviders.h" -#include "metatileparser.h" +#include "advancemapparser.h" #include "paletteutil.h" #include "imageexport.h" #include "config.h" @@ -978,7 +978,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) } bool error = false; - QList metatiles = MetatileParser::parse(filepath, &error, primary); + QList metatiles = AdvanceMapParser::parseMetatiles(filepath, &error, primary); if (error) { QMessageBox msgBox(this); msgBox.setText("Failed to import metatiles from Advance Map 1.92 .bvd file."); From d0101d807e985615771274e1eb7666ebdbf3dd5f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 21 Nov 2024 15:04:42 -0500 Subject: [PATCH 090/364] Finish new layout dialog redesign --- forms/mainwindow.ui | 4 +- forms/newlayoutdialog.ui | 93 ++++++++----- forms/newmapdialog.ui | 178 ++++++++++++++---------- include/core/maplayout.h | 7 +- include/mainwindow.h | 10 +- include/project.h | 14 +- include/ui/mapheaderform.h | 4 +- include/ui/newlayoutdialog.h | 14 +- include/ui/newmapdialog.h | 22 +-- src/core/maplayout.cpp | 27 ++-- src/mainwindow.cpp | 183 ++++--------------------- src/project.cpp | 165 +++++++++++------------ src/ui/mapheaderform.cpp | 7 +- src/ui/newlayoutdialog.cpp | 104 +++++++------- src/ui/newmapdialog.cpp | 255 +++++++++++++++++++---------------- src/ui/newtilesetdialog.cpp | 3 +- 16 files changed, 529 insertions(+), 561 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 50e03e88..e9a3c440 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2926,7 +2926,7 @@ - + @@ -3283,7 +3283,7 @@ Grid Settings... - + New Layout... diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index 8e623ba0..53d950fe 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -2,8 +2,16 @@ NewLayoutDialog + + + 0 + 0 + 264 + 173 + + - New Map Options + New Layout Options @@ -17,13 +25,46 @@ 0 0 238 - 146 + 106 10 + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> + + + + + + + Layout Name + + + @@ -34,20 +75,13 @@ - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + <html><head/><body><p>The name of the new layout. The name cannot be the same as any other existing layout.</p></body></html> true - - - - Layout Name - - - @@ -64,32 +98,6 @@ - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - - - @@ -107,6 +115,19 @@
+ + + + color: rgb(255, 0, 0) + + + + + + true + + + diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index ae3ba2d9..e6aece18 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -2,6 +2,14 @@ NewMapDialog + + + 0 + 0 + 255 + 320 + + New Map Options @@ -17,32 +25,51 @@ 0 0 229 - 254 + 306 10 - - + + + + false + + + color: rgb(255, 0, 0) + - Map ID + + + + true - - - - Qt::Orientation::Vertical + + + + true - - - 20 - 40 - + + QComboBox::InsertPolicy::NoInsert - + + + + + + color: rgb(255, 0, 0) + + + + + + true + + @@ -60,17 +87,21 @@ - - - - true - - - QComboBox::InsertPolicy::NoInsert + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - + + + + Map ID + + + + @@ -88,39 +119,20 @@ - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - true - - - - + Can Fly To + + + + Map Name + + + @@ -134,10 +146,23 @@ - - + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + - Map Group + @@ -157,20 +182,13 @@ - - - - Map Name - - + + - - - - <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> - + + - + Map Group @@ -181,20 +199,36 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> + + + + Qt::Orientation::Vertical - - - - + + + 20 + 40 + + + + + + + color: rgb(255, 0, 0) + + + + + + true + + + diff --git a/include/core/maplayout.h b/include/core/maplayout.h index caef753a..8dea0795 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -21,6 +21,7 @@ class Layout : public QObject { public: Layout() {} + static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); bool loaded = false; @@ -83,10 +84,10 @@ public: QString primaryTilesetLabel; QString secondaryTilesetLabel; }; + Settings settings() const; -public: - Layout *copy(); - void copyFrom(Layout *other); + Layout *copy() const; + void copyFrom(const Layout *other); int getWidth() const { return width; } int getHeight() const { return height; } diff --git a/include/mainwindow.h b/include/mainwindow.h index 53889dff..775d364e 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,8 +22,6 @@ #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" #include "maplistmodels.h" -#include "newmapdialog.h" -#include "newlayoutdialog.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" @@ -188,8 +186,6 @@ private slots: void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); - void openNewMapDialog(); - void openNewLayoutDialog(); void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); void onNewLayoutCreated(Layout *layout); @@ -199,7 +195,6 @@ private slots: void markMapEdited(); void markSpecificMapEdited(Map*); - void on_action_NewMap_triggered(); void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); void on_action_Exit_triggered(); @@ -306,8 +301,6 @@ private: QPointer regionMapEditor = nullptr; QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; - QPointer newMapDialog = nullptr; - QPointer newLayoutDialog = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; @@ -353,6 +346,8 @@ private: bool setProjectUI(); void clearProjectUI(); + void openNewMapDialog(); + void openNewLayoutDialog(); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, QString itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -371,7 +366,6 @@ private: void updateMapList(); void mapListAddGroup(); - void mapListAddLayout(); void mapListAddArea(); void openMapListItem(const QModelIndex &index); void saveMapListTab(int index); diff --git a/include/project.h b/include/project.h index c1a36a80..41593350 100644 --- a/include/project.h +++ b/include/project.h @@ -82,8 +82,8 @@ public: bool saveEmptyMapsec; struct NewMapSettings { - QString mapName; - QString mapId; + QString name; + QString id; QString group; bool canFlyTo; Layout::Settings layout; @@ -133,9 +133,9 @@ public: void addNewMap(Map* newMap, const QString &groupName); void addNewMapGroup(const QString &groupName); void addNewLayout(Layout* newLayout); - QString getNewMapName(); - QString getNewLayoutName(); - bool isLayoutNameUnique(const QString &name); + NewMapSettings getNewMapSettings() const; + Layout::Settings getNewLayoutSettings() const; + bool isIdentifierUnique(const QString &identifier) const; QString getProjectTitle(); bool readWildMonData(); @@ -159,7 +159,7 @@ public: bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); - Layout *createNewLayout(const Layout::Settings &layoutSettings); + Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -234,8 +234,6 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); - void initNewMapSettings(); - void initNewLayoutSettings(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 897b8e9a..136499e5 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -32,11 +32,13 @@ public: MapHeader headerData() const; void setLocations(QStringList locations); - void setLocationsDisabled(bool disabled); + void setLocationDisabled(bool disabled); + bool isLocationDisabled() const { return m_locationDisabled; } private: Ui::MapHeaderForm *ui; QPointer m_header = nullptr; + bool m_locationDisabled = false; void updateUi(); void updateSong(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index c90a408b..ba4396b9 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -18,10 +18,11 @@ class NewLayoutDialog : public QDialog { Q_OBJECT public: - explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr); + explicit NewLayoutDialog(Project *project, QWidget *parent = nullptr); + explicit NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent = nullptr); ~NewLayoutDialog(); - void copyFrom(const Layout &); - void accept() override; + + virtual void accept() override; signals: void applied(const QString &newLayoutId); @@ -30,18 +31,21 @@ private: Ui::NewLayoutDialog *ui; Project *project; Layout *importedLayout = nullptr; - Layout::Settings *settings = nullptr; + + static Layout::Settings settings; + static bool initializedSettings; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. bool validateLayoutID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); + void refresh(); + void saveSettings(); bool isExistingLayout() const; private slots: - //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); void on_lineEdit_LayoutID_textChanged(const QString &); diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 8a769740..cdc324e2 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -18,12 +18,12 @@ class NewMapDialog : public QDialog { Q_OBJECT public: - explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr); + explicit NewMapDialog(Project *project, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent = nullptr); ~NewMapDialog(); - void init(); - void init(int tabIndex, QString data); - void init(Layout *); - void accept() override; + + virtual void accept() override; signals: void applied(const QString &newMapName); @@ -33,25 +33,29 @@ private: Project *project; CollapsibleSection *headerSection; MapHeaderForm *headerForm; - Layout *importedLayout = nullptr; - Project::NewMapSettings *settings = nullptr; + Map *importedMap = nullptr; + + static Project::NewMapSettings settings; + static bool initializedSettings; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. bool validateMapID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); bool validateGroup(bool allowEmpty = false); + bool validateLayoutID(bool allowEmpty = false); + + void refresh(); void saveSettings(); - bool isExistingLayout() const; void useLayoutSettings(const Layout *mapLayout); - void useLayoutIdSettings(const QString &layoutId); private slots: void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); void on_lineEdit_MapID_textChanged(const QString &); void on_comboBox_Group_currentTextChanged(const QString &text); + void on_comboBox_LayoutID_currentTextChanged(const QString &text); }; #endif // NEWMAPDIALOG_H diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 11e9943d..921c0894 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -7,13 +7,13 @@ -Layout *Layout::copy() { +Layout *Layout::copy() const { Layout *layout = new Layout; layout->copyFrom(this); return layout; } -void Layout::copyFrom(Layout *other) { +void Layout::copyFrom(const Layout *other) { this->id = other->id; this->name = other->name; this->width = other->width; @@ -30,19 +30,30 @@ void Layout::copyFrom(Layout *other) { this->border = other->border; } +QString Layout::layoutNameFromMapName(const QString &mapName) { + return QString("%1_Layout").arg(mapName); +} + QString Layout::layoutConstantFromName(QString mapName) { // Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'. static const QRegularExpression caseChange("([a-z])([A-Z])"); QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper(); static const QRegularExpression underscores("_+"); - QString constantName = withMapAndUppercase.replace(underscores, "_"); + return withMapAndUppercase.replace(underscores, "_"); +} - // Handle special cases. - // SSTidal should be SS_TIDAL, rather than SSTIDAL - constantName = constantName.replace("SSTIDAL", "SS_TIDAL"); - - return constantName; +Layout::Settings Layout::settings() const { + Layout::Settings settings; + settings.id = this->id; + settings.name = this->name; + settings.width = this->width; + settings.height = this->height; + settings.borderWidth = this->border_width; + settings.borderHeight = this->border_height; + settings.primaryTilesetLabel = this->tileset_primary_label; + settings.secondaryTilesetLabel = this->tileset_secondary_label; + return settings; } bool Layout::isWithinBounds(int x, int y) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7af3d100..41f85724 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -23,6 +23,8 @@ #include "newmapconnectiondialog.h" #include "config.h" #include "filedialog.h" +#include "newmapdialog.h" +#include "newlayoutdialog.h" #include #include @@ -291,7 +293,8 @@ void MainWindow::initExtraSignals() { label_MapRulerStatus->setTextFormat(Qt::PlainText); label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse); - connect(ui->actionNew_Layout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); + connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog); + connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -406,7 +409,7 @@ void MainWindow::initMapList() { // Create add map/layout button // TODO: Tool tip QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); - connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::on_action_NewMap_triggered); + connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::openNewMapDialog); layout->addWidget(buttonAdd); /* TODO: Remove button disabled, no current support for deleting maps/layouts @@ -932,6 +935,10 @@ bool MainWindow::userSetLayout(QString layoutId) { msgBox.critical(nullptr, "Error Opening Layout", errorMsg); return false; } + + // Only the Layouts tab of the map list shows Layouts, so if we're not already on that tab we'll open it now. + ui->mapListContainer->setCurrentIndex(MapListTab::Layouts); + return true; } @@ -1228,8 +1235,9 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (addToFolderAction) { // All folders only contain maps, so adding an item to any folder is adding a new map. connect(addToFolderAction, &QAction::triggered, [this, itemName] { - openNewMapDialog(); - this->newMapDialog->init(ui->mapListContainer->currentIndex(), itemName); + auto dialog = new NewMapDialog(this->editor->project, ui->mapListContainer->currentIndex(), itemName, this); + connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); + dialog->open(); }); } if (deleteFolderAction) { @@ -1267,8 +1275,8 @@ void MainWindow::mapListAddGroup() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ const QString mapGroupName = newNameEdit->text(); - if (this->editor->project->groupNames.contains(mapGroupName)) { - errorMessageLabel->setText(QString("A map group with the name '%1' already exists").arg(mapGroupName)); + if (!this->editor->project->isIdentifierUnique(mapGroupName)) { + errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(mapGroupName)); errorMessageLabel->setVisible(true); } else { dialog.accept(); @@ -1288,126 +1296,6 @@ void MainWindow::mapListAddGroup() { } } -// TODO: Pull this all out into a custom window. Connect that to an action in the main menu as well. -// (or, re-use the new map dialog with some tweaks) -// TODO: This needs to take the same default settings you would get for a new map (tilesets, dimensions, etc.) -// and initialize it with the same fill settings (default metatile/collision/elevation, default border) -// TODO: Remove -void MainWindow::mapListAddLayout() { - /* - if (!editor || !editor->project) return; - - QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::ApplicationModal); - QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - - QLineEdit *newNameEdit = new QLineEdit(&dialog); - newNameEdit->setClearButtonEnabled(true); - - static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); - newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); - - // TODO: Support arbitrary LAYOUT_ ID names (Note from GriffinR: This is already handled in an unopened PR) - QLabel *newId = new QLabel("LAYOUT_", &dialog); - connect(newNameEdit, &QLineEdit::textChanged, [&](QString text){ - newId->setText(Layout::layoutConstantFromName(text.remove("_Layout"))); - }); - - NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog); - useExistingCombo->addItems(this->editor->project->layoutIds); - useExistingCombo->setEnabled(false); - - QCheckBox *useExistingCheck = new QCheckBox(&dialog); - - QLabel *errorMessageLabel = new QLabel(&dialog); - errorMessageLabel->setVisible(false); - errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - - QComboBox *primaryCombo = new QComboBox(&dialog); - primaryCombo->addItems(this->editor->project->primaryTilesetLabels); - QComboBox *secondaryCombo = new QComboBox(&dialog); - secondaryCombo->addItems(this->editor->project->secondaryTilesetLabels); - - QSpinBox *widthSpin = new QSpinBox(&dialog); - QSpinBox *heightSpin = new QSpinBox(&dialog); - - widthSpin->setMinimum(1); - heightSpin->setMinimum(1); - widthSpin->setMaximum(this->editor->project->getMaxMapWidth()); - heightSpin->setMaximum(this->editor->project->getMaxMapHeight()); - - connect(useExistingCheck, &QCheckBox::stateChanged, [&](int state){ - bool useExisting = (state == Qt::Checked); - useExistingCombo->setEnabled(useExisting); - primaryCombo->setEnabled(!useExisting); - secondaryCombo->setEnabled(!useExisting); - widthSpin->setEnabled(!useExisting); - heightSpin->setEnabled(!useExisting); - }); - - QFormLayout form(&dialog); - form.addRow("New Layout Name", newNameEdit); - form.addRow("New Layout ID", newId); - form.addRow("Copy Existing Layout", useExistingCheck); - form.addRow("", useExistingCombo); - form.addRow("Primary Tileset", primaryCombo); - form.addRow("Secondary Tileset", secondaryCombo); - form.addRow("Layout Width", widthSpin); - form.addRow("Layout Height", heightSpin); - form.addRow("", errorMessageLabel); - - connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - // verify some things - QString errorMessage; - QString tryLayoutName = newNameEdit->text(); - // name not empty - if (tryLayoutName.isEmpty()) { - errorMessage = "Name cannot be empty"; - } - // unique layout name & id - else if (this->editor->project->layoutIds.contains(newId->text()) - || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { - errorMessage = "Layout Name / ID is not unique"; - } - // from id is existing value - else if (useExistingCheck->isChecked()) { - if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) { - errorMessage = "Existing layout ID is not valid"; - } - } - - if (!errorMessage.isEmpty()) { - // show error - errorMessageLabel->setText(errorMessage); - errorMessageLabel->setVisible(true); - } - else { - dialog.accept(); - } - }); - - form.addRow(&newItemButtonBox); - - if (dialog.exec() == QDialog::Accepted) { - Layout::SimpleSettings layoutSettings; - QString layoutName = newNameEdit->text(); - layoutSettings.name = layoutName; - layoutSettings.id = Layout::layoutConstantFromName(layoutName.remove("_Layout")); - if (useExistingCheck->isChecked()) { - layoutSettings.from_id = useExistingCombo->currentText(); - } else { - layoutSettings.width = widthSpin->value(); - layoutSettings.height = heightSpin->value(); - layoutSettings.tileset_primary_label = primaryCombo->currentText(); - layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); - } - Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); - setLayout(newLayout->id); - } - */ -} - void MainWindow::mapListAddArea() { QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::ApplicationModal); @@ -1433,8 +1321,8 @@ void MainWindow::mapListAddArea() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ const QString newAreaName = newNameDisplay->text(); - if (this->editor->project->mapSectionIdNames.contains(newAreaName)){ - errorMessageLabel->setText(QString("An area with the name '%1' already exists").arg(newAreaName)); + if (!this->editor->project->isIdentifierUnique(newAreaName)) { + errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(newAreaName)); errorMessageLabel->setVisible(true); } else { dialog.accept(); @@ -1485,6 +1373,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { } } +// Called any time a new layout is created (including as a byproduct of creating a new map) void MainWindow::onNewLayoutCreated(Layout *layout) { logInfo(QString("Created a new layout named %1.").arg(layout->name)); @@ -1504,31 +1393,16 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { this->mapGroupModel->insertGroupItem(groupName); } -// TODO: This and the new layout dialog are modal. We shouldn't need to reference their dialogs outside these open functions, -// so we should be able to remove them as members of MainWindow. -// (plus, the opening then init() call after showing for NewMapDialog is Bad) void MainWindow::openNewMapDialog() { - if (!this->newMapDialog) { - this->newMapDialog = new NewMapDialog(this, this->editor->project); - connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); - } - - openSubWindow(this->newMapDialog); -} - -void MainWindow::on_action_NewMap_triggered() { - openNewMapDialog(); - //this->newMapDialog->initUi();//TODO - this->newMapDialog->init(); + auto dialog = new NewMapDialog(this->editor->project, this); + connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); + dialog->open(); } void MainWindow::openNewLayoutDialog() { - if (!this->newLayoutDialog) { - this->newLayoutDialog = new NewLayoutDialog(this, this->editor->project); - connect(this->newLayoutDialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); - } - - openSubWindow(this->newLayoutDialog); + auto dialog = new NewLayoutDialog(this->editor->project, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); } // Insert label for newly-created tileset into sorted list of existing labels @@ -2732,8 +2606,9 @@ void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { return; } - openNewLayoutDialog(); - this->newLayoutDialog->copyFrom(*mapLayout); + auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); delete mapLayout; } @@ -2756,7 +2631,7 @@ void MainWindow::on_pushButton_AddConnection_clicked() { auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames); connect(dialog, &NewMapConnectionDialog::accepted, this->editor, &Editor::addConnection); - dialog->exec(); + dialog->open(); } void MainWindow::on_pushButton_NewWildMonGroup_clicked() { @@ -3244,10 +3119,6 @@ bool MainWindow::closeSupplementaryWindows() { return false; this->mapImageExporter = nullptr; - if (this->newMapDialog && !this->newMapDialog->close()) - return false; - this->newMapDialog = nullptr; - if (this->shortcutsEditor && !this->shortcutsEditor->close()) return false; this->shortcutsEditor = nullptr; diff --git a/src/project.cpp b/src/project.cpp index 2f37e24a..9976c549 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -107,7 +107,6 @@ bool Project::load() { && readSongNames() && readMapGroups(); applyParsedLimits(); - initNewMapSettings(); return success; } @@ -348,12 +347,7 @@ bool Project::loadMapData(Map* map) { const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - if (this->mapConstantsToMapNames.contains(mapConstant)) { - // Successully read map connection - map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant), direction, offset)); - } else { - logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant)); - } + map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset)); } } @@ -369,40 +363,7 @@ bool Project::loadMapData(Map* map) { return true; } -/* -void Project::addNewLayout(Layout* newLayout) { - - if (newLayout->blockdata.isEmpty()) { - // Fill layout using default fill settings - setNewLayoutBlockdata(newLayout); - } - if (newLayout->border.isEmpty()) { - // Fill border using default fill settings - setNewLayoutBorder(newLayout); - } - - emit layoutAdded(newLayout); -} -*/ - -// TODO: Fold back into createNewLayout? -/* -Layout *Project::duplicateLayout(const Layout *toDuplicate) { - //TODO - if (!settings.from_id.isEmpty()) { - // load from layout - loadLayout(mapLayouts[settings.from_id]); - layout = mapLayouts[settings.from_id]->copy(); - layout->name = settings.name; - layout->id = settings.id; - layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); - } -} -*/ - -// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout -Layout *Project::createNewLayout(const Layout::Settings &settings) { +Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout *toDuplicate) { Layout *layout = new Layout; layout->id = settings.id; layout->name = settings.name; @@ -413,6 +374,13 @@ Layout *Project::createNewLayout(const Layout::Settings &settings) { layout->tileset_primary_label = settings.primaryTilesetLabel; layout->tileset_secondary_label = settings.secondaryTilesetLabel; + if (toDuplicate) { + // If we're duplicating an existing layout we'll copy over the blockdata. + // Otherwise addNewLayout will fill our new layout using the default settings. + layout->blockdata = toDuplicate->blockdata; + layout->border = toDuplicate->border; + } + const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); @@ -1276,6 +1244,7 @@ void Project::saveMap(Map *map) { } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); + // TODO: Either simplify this redundancy or explain why we need it (to create folders without the _Layout suffix) if (map->needsLayoutDir()) { QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); if (!QDir::root().mkdir(newLayoutDir)) { @@ -1314,19 +1283,15 @@ void Project::saveMap(Map *map) { mapObj["battle_scene"] = map->header()->battleScene(); // Connections - auto connections = map->getConnections(); + const auto connections = map->getConnections(); if (connections.length() > 0) { OrderedJson::array connectionsArr; - for (auto connection : connections) { - if (this->mapNamesToMapConstants.contains(connection->targetMapName())) { - OrderedJson::object connectionObj; - connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName()); - connectionObj["offset"] = connection->offset(); - connectionObj["direction"] = connection->direction(); - connectionsArr.append(connectionObj); - } else { - logError(QString("Failed to write map connection. '%1' is not a valid map name").arg(connection->targetMapName())); - } + for (const auto &connection : connections) { + OrderedJson::object connectionObj; + connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName(), connection->targetMapName()); + connectionObj["offset"] = connection->offset(); + connectionObj["direction"] = connection->direction(); + connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; } else { @@ -1986,31 +1951,81 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } -QString Project::getNewMapName() { - // Ensure default name doesn't already exist. +Project::NewMapSettings Project::getNewMapSettings() const { + // Ensure default name/ID doesn't already exist. int i = 0; QString newMapName; + QString newMapId; do { newMapName = QString("NewMap%1").arg(++i); - } while (this->mapNames.contains(newMapName)); + newMapId = Map::mapConstantFromName(newMapName); + } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(newMapId)); - return newMapName; + NewMapSettings settings; + settings.name = newMapName; + settings.id = newMapId; + settings.group = this->groupNames.at(0); + settings.canFlyTo = false; + settings.layout = getNewLayoutSettings(); + settings.layout.id = Layout::layoutConstantFromName(newMapName); + settings.layout.name = Layout::layoutNameFromMapName(newMapName); + settings.header.setSong(this->defaultSong); + settings.header.setLocation(this->mapSectionIdNames.value(0, "0")); + settings.header.setRequiresFlash(false); + settings.header.setWeather(this->weatherNames.value(0, "0")); + settings.header.setType(this->mapTypes.value(0, "0")); + settings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); + settings.header.setShowsLocationName(true); + settings.header.setAllowsRunning(false); + settings.header.setAllowsBiking(false); + settings.header.setAllowsEscaping(false); + settings.header.setFloorNumber(0); + return settings; } -QString Project::getNewLayoutName() { - // Ensure default name doesn't already exist. +Layout::Settings Project::getNewLayoutSettings() const { + // Ensure default name/ID doesn't already exist. int i = 0; QString newLayoutName; + QString newLayoutId; do { newLayoutName = QString("NewLayout%1").arg(++i); - } while (!isLayoutNameUnique(newLayoutName)); + newLayoutId = Layout::layoutConstantFromName(newLayoutName); + } while (!isIdentifierUnique(newLayoutId) || !isIdentifierUnique(newLayoutName)); - return newLayoutName; + Layout::Settings settings; + settings.name = newLayoutName; + settings.id = newLayoutId; + settings.width = getDefaultMapDimension(); + settings.height = getDefaultMapDimension(); + settings.borderWidth = DEFAULT_BORDER_WIDTH; + settings.borderHeight = DEFAULT_BORDER_HEIGHT; + settings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); + settings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); + return settings; } -bool Project::isLayoutNameUnique(const QString &name) { +// When we ask the user to provide a new identifier for something (like a map/layout name or ID) +// we use this to make sure that it doesn't collide with any known identifiers first. +// Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap. +// In general this only matters to Porymap if the identifier will be added to the group it collides with, +// but name collisions are likely undesirable in the project. +// TODO: Use elsewhere +bool Project::isIdentifierUnique(const QString &identifier) const { + if (this->mapNames.contains(identifier)) + return false; + if (this->mapConstantsToMapNames.contains(identifier)) + return false; + if (this->groupNames.contains(identifier)) + return false; + if (this->mapSectionIdNames.contains(identifier)) + return false; + if (this->tilesetLabelsOrdered.contains(identifier)) + return false; + if (this->layoutIds.contains(identifier)) + return false; for (const auto &layout : this->mapLayouts) { - if (layout->name == name) { + if (layout->name == identifier) { return false; } } @@ -3056,32 +3071,6 @@ void Project::applyParsedLimits() { projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1); } -void Project::initNewMapSettings() { - this->newMapSettings.group = this->groupNames.at(0); - this->newMapSettings.canFlyTo = false; - this->newMapSettings.header.setSong(this->defaultSong); - this->newMapSettings.header.setLocation(this->mapSectionIdNames.value(0, "0")); - this->newMapSettings.header.setRequiresFlash(false); - this->newMapSettings.header.setWeather(this->weatherNames.value(0, "0")); - this->newMapSettings.header.setType(this->mapTypes.value(0, "0")); - this->newMapSettings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); - this->newMapSettings.header.setShowsLocationName(true); - this->newMapSettings.header.setAllowsRunning(false); - this->newMapSettings.header.setAllowsBiking(false); - this->newMapSettings.header.setAllowsEscaping(false); - this->newMapSettings.header.setFloorNumber(0); - initNewLayoutSettings(); -} - -void Project::initNewLayoutSettings() { - this->newMapSettings.layout.width = getDefaultMapDimension(); - this->newMapSettings.layout.height = getDefaultMapDimension(); - this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH; - this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; - this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); - this->newMapSettings.layout.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); -} - bool Project::hasUnsavedChanges() { if (this->hasUnsavedDataChanges) return true; diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 1fd9084c..fa0e2c1c 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -154,9 +154,10 @@ MapHeader MapHeaderForm::headerData() const { return header; } -void MapHeaderForm::setLocationsDisabled(bool disabled) { - ui->label_Location->setDisabled(disabled); - ui->comboBox_Location->setDisabled(disabled); +void MapHeaderForm::setLocationDisabled(bool disabled) { + m_locationDisabled = disabled; + ui->label_Location->setDisabled(m_locationDisabled); + ui->comboBox_Location->setDisabled(m_locationDisabled); } void MapHeaderForm::updateSong() { diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 73822ba8..3cf7b9a9 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -9,31 +9,55 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewLayoutDialog::NewLayoutDialog(QWidget *parent, Project *project) : +Layout::Settings NewLayoutDialog::settings = {}; +bool NewLayoutDialog::initializedSettings = false; + +NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : QDialog(parent), ui(new Ui::NewLayoutDialog) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); + ui->label_GenericError->setVisible(false); this->project = project; - this->settings = &project->newMapSettings.layout; - - ui->lineEdit_Name->setText(project->getNewLayoutName()); + Layout::Settings newSettings = project->getNewLayoutSettings(); + if (!initializedSettings) { + // The first time this dialog is opened we initialize all the default settings. + settings = newSettings; + initializedSettings = true; + } else { + // On subsequent openings we only initialize the settings that should be unique, + // preserving all other settings from the last time the dialog was open. + settings.name = newSettings.name; + settings.id = newSettings.id; + } ui->newLayoutForm->initUi(project); - ui->newLayoutForm->setSettings(*this->settings); - // Names and IDs can only contain word characters, and cannot start with a digit. + // Identifiers can only contain word characters, and cannot start with a digit. static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_LayoutID->setValidator(validator); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked); + + refresh(); adjustSize(); } +// Creating new layout from AdvanceMap import +// TODO: Re-use for a "Duplicate Layout" option +NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layout, QWidget *parent) : + NewLayoutDialog(project, parent) +{ + if (layout) { + this->importedLayout = layout->copy(); + refresh(); + } +} + NewLayoutDialog::~NewLayoutDialog() { saveSettings(); @@ -41,33 +65,24 @@ NewLayoutDialog::~NewLayoutDialog() delete ui; } -// Creating new layout from AdvanceMap import -// TODO: Re-use for a "Duplicate Layout" option? -void NewLayoutDialog::copyFrom(const Layout &layoutToCopy) { - if (this->importedLayout) - delete this->importedLayout; +void NewLayoutDialog::refresh() { + if (this->importedLayout) { + // If we're importing a layout then some settings will be enforced. + ui->newLayoutForm->setSettings(this->importedLayout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { + ui->newLayoutForm->setSettings(settings); + ui->newLayoutForm->setDisabled(false); + } - this->importedLayout = new Layout(); - this->importedLayout->blockdata = layoutToCopy.blockdata; - if (!layoutToCopy.border.isEmpty()) - this->importedLayout->border = layoutToCopy.border; - - this->settings->width = layoutToCopy.width; - this->settings->height = layoutToCopy.height; - this->settings->borderWidth = layoutToCopy.border_width; - this->settings->borderHeight = layoutToCopy.border_height; - this->settings->primaryTilesetLabel = layoutToCopy.tileset_primary_label; - this->settings->secondaryTilesetLabel = layoutToCopy.tileset_secondary_label; - - // Don't allow changes to the layout settings - ui->newLayoutForm->setSettings(*this->settings); - ui->newLayoutForm->setDisabled(true); + ui->lineEdit_Name->setText(settings.name); + ui->lineEdit_LayoutID->setText(settings.id); } void NewLayoutDialog::saveSettings() { - *this->settings = ui->newLayoutForm->settings(); - this->settings->id = ui->lineEdit_LayoutID->text(); - this->settings->name = ui->lineEdit_Name->text(); + settings = ui->newLayoutForm->settings(); + settings.id = ui->lineEdit_LayoutID->text(); + settings.name = ui->lineEdit_Name->text(); } bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { @@ -76,8 +91,8 @@ bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { QString errorText; if (id.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); - } else if (this->project->mapLayouts.contains(id)) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_LayoutID->text()).arg(id); + } else if (!this->project->isIdentifierUnique(id)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_LayoutID->text()).arg(id); } bool isValid = errorText.isEmpty(); @@ -97,8 +112,8 @@ bool NewLayoutDialog::validateName(bool allowEmpty) { QString errorText; if (name.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (!this->project->isLayoutNameUnique(name)) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); @@ -110,6 +125,8 @@ bool NewLayoutDialog::validateName(bool allowEmpty) { void NewLayoutDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); + + // Changing the layout name updates the layout ID field to match. ui->lineEdit_LayoutID->setText(Layout::layoutConstantFromName(text)); } @@ -118,8 +135,8 @@ void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - this->project->initNewLayoutSettings(); // TODO: Don't allow this to change locked settings - ui->newLayoutForm->setSettings(*this->settings); + settings = this->project->getNewLayoutSettings(); + refresh(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -137,18 +154,13 @@ void NewLayoutDialog::accept() { // Update settings from UI saveSettings(); - /* - if (this->importedLayout) { - // Copy layout data from imported layout - layout->blockdata = this->importedLayout->blockdata; - if (!this->importedLayout->border.isEmpty()) - layout->border = this->importedLayout->border; - } - */ - - Layout *layout = this->project->createNewLayout(*this->settings); - if (!layout) + Layout *layout = this->project->createNewLayout(settings, this->importedLayout); + if (!layout) { + ui->label_GenericError->setText(QString("Failed to create layout. See %1 for details.").arg(getLogPath())); + ui->label_GenericError->setVisible(true); return; + } + ui->label_GenericError->setVisible(false); emit applied(layout->id); QDialog::accept(); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index ef31a7b1..a97fd2ab 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -10,23 +10,36 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : +Project::NewMapSettings NewMapDialog::settings = {}; +bool NewMapDialog::initializedSettings = false; + +NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : QDialog(parent), ui(new Ui::NewMapDialog) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); + ui->label_GenericError->setVisible(false); this->project = project; - this->settings = &project->newMapSettings; - // Populate UI using data from project - this->settings->mapName = project->getNewMapName(); + Project::NewMapSettings newSettings = project->getNewMapSettings(); + if (!initializedSettings) { + // The first time this dialog is opened we initialize all the default settings. + settings = newSettings; + initializedSettings = true; + } else { + // On subsequent openings we only initialize the settings that should be unique, + // preserving all other settings from the last time the dialog was open. + settings.name = newSettings.name; + settings.id = newSettings.id; + } ui->newLayoutForm->initUi(project); + ui->comboBox_Group->addItems(project->groupNames); ui->comboBox_LayoutID->addItems(project->layoutIds); - // Names and IDs can only contain word characters, and cannot start with a digit. + // Identifiers can only contain word characters, and cannot start with a digit. static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); @@ -37,7 +50,7 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); this->headerForm->init(project); - this->headerForm->setHeader(&this->settings->header); + this->headerForm->setHeader(&settings.header); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -47,103 +60,91 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); - connect(ui->comboBox_LayoutID, &QComboBox::currentTextChanged, this, &NewMapDialog::useLayoutIdSettings); + refresh(); adjustSize(); // TODO: Save geometry? } +// Adding new map to existing map list folder. +NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) : + NewMapDialog(project, parent) +{ + switch (mapListTab) + { + case MapListTab::Groups: + settings.group = mapListItem; + ui->label_Group->setDisabled(true); + ui->comboBox_Group->setDisabled(true); + ui->comboBox_Group->setTextItem(settings.group); + break; + case MapListTab::Areas: + settings.header.setLocation(mapListItem); + this->headerForm->setLocationDisabled(true); + // Header UI is kept in sync automatically by MapHeaderForm + break; + case MapListTab::Layouts: + settings.layout.id = mapListItem; + ui->label_LayoutID->setDisabled(true); + ui->comboBox_LayoutID->setDisabled(true); + ui->comboBox_LayoutID->setTextItem(settings.layout.id); + break; + } +} + +// TODO: Use for a "Duplicate Map" option +NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : + NewMapDialog(project, parent) +{ + /* + if (this->importedMap) + delete this->importedMap; + + this->importedMap = new Map(mapToCopy); + useLayoutSettings(this->importedMap->layout()); + */ +} + NewMapDialog::~NewMapDialog() { saveSettings(); - delete this->importedLayout; + delete this->importedMap; delete ui; } -void NewMapDialog::init() { - const QSignalBlocker b_LayoutId(ui->comboBox_LayoutID); - ui->comboBox_LayoutID->setCurrentText(this->settings->layout.id); - - ui->lineEdit_Name->setText(this->settings->mapName); - ui->comboBox_Group->setTextItem(this->settings->group); - ui->checkBox_CanFlyTo->setChecked(this->settings->canFlyTo); - ui->newLayoutForm->setSettings(this->settings->layout); -} - -// Creating new map by right-clicking in the map list -void NewMapDialog::init(int tabIndex, QString fieldName) { - switch (tabIndex) - { - case MapListTab::Groups: - this->settings->group = fieldName; - ui->label_Group->setDisabled(true); - ui->comboBox_Group->setDisabled(true); - break; - case MapListTab::Areas: - this->settings->header.setLocation(fieldName); - this->headerForm->setLocationsDisabled(true); - break; - case MapListTab::Layouts: - ui->label_LayoutID->setDisabled(true); - ui->comboBox_LayoutID->setDisabled(true); - useLayoutIdSettings(fieldName); - break; - } - init(); -} - -// Creating new map from AdvanceMap import -// TODO: Re-use for a "Duplicate Map/Layout" option? -void NewMapDialog::init(Layout *layoutToCopy) { - if (this->importedLayout) - delete this->importedLayout; - - this->importedLayout = new Layout(); - this->importedLayout->blockdata = layoutToCopy->blockdata; - if (!layoutToCopy->border.isEmpty()) - this->importedLayout->border = layoutToCopy->border; - - useLayoutSettings(this->importedLayout); - init(); +// Sync UI with settings. If any UI elements are disabled (because their settings are being enforced) +// then we don't update them using the settings here. +void NewMapDialog::refresh() { + ui->lineEdit_Name->setText(settings.name); + ui->lineEdit_MapID->setText(settings.id); + + ui->comboBox_Group->setTextItem(settings.group); + ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); + ui->comboBox_LayoutID->setTextItem(settings.layout.id); + ui->newLayoutForm->setSettings(settings.layout); + // Header UI is kept in sync automatically by MapHeaderForm } void NewMapDialog::saveSettings() { - this->settings->mapName = ui->lineEdit_Name->text(); - this->settings->mapId = ui->lineEdit_MapID->text(); - this->settings->group = ui->comboBox_Group->currentText(); - this->settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); - this->settings->layout = ui->newLayoutForm->settings(); - this->settings->layout.id = ui->comboBox_LayoutID->currentText(); - this->settings->layout.name = QString("%1_Layout").arg(this->settings->mapName); - this->settings->header = this->headerForm->headerData(); + settings.name = ui->lineEdit_Name->text(); + settings.id = ui->lineEdit_MapID->text(); + settings.group = ui->comboBox_Group->currentText(); + settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + settings.layout = ui->newLayoutForm->settings(); + settings.layout.id = ui->comboBox_LayoutID->currentText(); + // We don't provide full control for naming new layouts here (just via the ID). + // If a user wants to explicitly name a layout they can create it individually before creating the map. + settings.layout.name = Layout::layoutNameFromMapName(settings.name); + settings.header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(const Layout *layout) { - if (!layout) { + if (layout) { + ui->newLayoutForm->setSettings(layout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { ui->newLayoutForm->setDisabled(false); - return; } - - this->settings->layout.width = layout->width; - this->settings->layout.height = layout->height; - this->settings->layout.borderWidth = layout->border_width; - this->settings->layout.borderHeight = layout->border_height; - this->settings->layout.primaryTilesetLabel = layout->tileset_primary_label; - this->settings->layout.secondaryTilesetLabel = layout->tileset_secondary_label; - - // Don't allow changes to the layout settings - ui->newLayoutForm->setSettings(this->settings->layout); - ui->newLayoutForm->setDisabled(true); -} - -void NewMapDialog::useLayoutIdSettings(const QString &layoutId) { - this->settings->layout.id = layoutId; - useLayoutSettings(this->project->mapLayouts.value(layoutId)); -} - -// Return true if the "layout ID" field is specifying a layout that already exists. -bool NewMapDialog::isExistingLayout() const { - return this->project->mapLayouts.contains(this->settings->layout.id); } bool NewMapDialog::validateMapID(bool allowEmpty) { @@ -155,13 +156,8 @@ bool NewMapDialog::validateMapID(bool allowEmpty) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text()); } else if (!id.startsWith(expectedPrefix)) { errorText = QString("%1 '%2' must start with '%3'.").arg(ui->label_MapID->text()).arg(id).arg(expectedPrefix); - } else { - for (auto i = this->project->mapNamesToMapConstants.constBegin(), end = this->project->mapNamesToMapConstants.constEnd(); i != end; i++) { - if (id == i.value()) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_MapID->text()).arg(id); - break; - } - } + } else if (!this->project->isIdentifierUnique(id)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id); } bool isValid = errorText.isEmpty(); @@ -181,8 +177,8 @@ bool NewMapDialog::validateName(bool allowEmpty) { QString errorText; if (name.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (project->mapNames.contains(name)) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); @@ -206,6 +202,8 @@ bool NewMapDialog::validateGroup(bool allowEmpty) { QString errorText; if (groupName.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Group->text()); + } else if (!this->project->groupNames.contains(groupName) && !this->project->isIdentifierUnique(groupName)) { + errorText = QString("%1 must either be the name of an existing map group, or a unique identifier for a new map group.").arg(ui->label_Group->text()); } bool isValid = errorText.isEmpty(); @@ -219,13 +217,43 @@ void NewMapDialog::on_comboBox_Group_currentTextChanged(const QString &) { validateGroup(true); } +bool NewMapDialog::validateLayoutID(bool allowEmpty) { + QString layoutId = ui->comboBox_LayoutID->currentText(); + + QString errorText; + if (layoutId.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); + } else if (!this->project->layoutIds.contains(layoutId) && !this->project->isIdentifierUnique(layoutId)) { + errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_LayoutIDError->setText(errorText); + ui->label_LayoutIDError->setVisible(!isValid); + ui->comboBox_LayoutID->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { + validateLayoutID(true); + useLayoutSettings(this->project->mapLayouts.value(text)); +} + void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { auto role = ui->buttonBox->buttonRole(button); if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - this->project->initNewMapSettings(); // TODO: Don't allow this to change locked settings - init(); + auto newSettings = this->project->getNewMapSettings(); + + // If the location setting is disabled we need to enforce that setting on the new header. + if (this->headerForm->isLocationDisabled()) + newSettings.header.setLocation(settings.header.location()); + + settings = newSettings; + this->headerForm->setHeader(&settings.header); // TODO: Unnecessary? + refresh(); + } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -238,6 +266,7 @@ void NewMapDialog::accept() { if (!validateMapID()) success = false; if (!validateName()) success = false; if (!validateGroup()) success = false; + if (!validateLayoutID()) success = false; if (!success) return; @@ -245,33 +274,29 @@ void NewMapDialog::accept() { saveSettings(); Map *newMap = new Map; - newMap->setName(this->settings->mapName); - newMap->setConstantName(this->settings->mapId); - newMap->setHeader(this->settings->header); - newMap->setNeedsHealLocation(this->settings->canFlyTo); + newMap->setName(settings.name); + newMap->setConstantName(settings.id); + newMap->setHeader(settings.header); + newMap->setNeedsHealLocation(settings.canFlyTo); - Layout *layout = nullptr; - const bool existingLayout = isExistingLayout(); - if (existingLayout) { - layout = this->project->mapLayouts.value(this->settings->layout.id); - newMap->setNeedsLayoutDir(false); // TODO: Remove this member + Layout *layout = this->project->mapLayouts.value(settings.layout.id); + if (layout) { + // Layout already exists + newMap->setNeedsLayoutDir(false); // TODO: Remove this member? } else { - /* TODO: Re-implement (make sure this won't ever override an existing layout) - if (this->importedLayout) { - // Copy layout data from imported layout - layout->blockdata = this->importedLayout->blockdata; - if (!this->importedLayout->border.isEmpty()) - layout->border = this->importedLayout->border; - } - */ - layout = this->project->createNewLayout(this->settings->layout); + layout = this->project->createNewLayout(settings.layout); } - if (!layout) + if (!layout) { + ui->label_GenericError->setText(QString("Failed to create layout for map. See %1 for details.").arg(getLogPath())); + ui->label_GenericError->setVisible(true); + delete newMap; return; + } + ui->label_GenericError->setVisible(false); newMap->setLayout(layout); - this->project->addNewMap(newMap, this->settings->group); + this->project->addNewMap(newMap, settings.group); emit applied(newMap->name()); QDialog::accept(); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index e9eee946..8cb4c5f9 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -10,7 +10,7 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : this->setFixedSize(this->width(), this->height()); this->project = project; //only allow characters valid for a symbol - static const QRegularExpression expression("[_A-Za-z0-9]+$"); + static const QRegularExpression expression("[_A-Za-z0-9]+$"); // TODO: Incorrect, allows digits at beginning QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); this->ui->nameLineEdit->setValidator(validator); @@ -35,6 +35,7 @@ void NewTilesetDialog::SecondaryChanged(){ NameOrSecondaryChanged(); } +// TODO: No validation void NewTilesetDialog::NameOrSecondaryChanged() { this->friendlyName = this->ui->nameLineEdit->text(); this->fullSymbolName = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + this->friendlyName; From 7eafae8cf7123fe6ee759bdf76a83d56eb11f931 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 22 Nov 2024 23:13:26 -0500 Subject: [PATCH 091/364] Fix map grid not clipping in layout-only mode --- src/ui/graphicsview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index a9761139..73827211 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -46,9 +46,9 @@ void MapView::drawForeground(QPainter *painter, const QRectF&) { // Draw map grid if (editor->mapGrid && editor->mapGrid->isVisible()) { painter->save(); - if (editor->map) { + if (editor->layout) { // We're clipping here to hide parts of the grid that are outside the map. - const QRectF mapRect(-0.5, -0.5, editor->map->getWidth() * 16 + 1.5, editor->map->getHeight() * 16 + 1.5); + const QRectF mapRect(-0.5, -0.5, editor->layout->getWidth() * 16 + 1.5, editor->layout->getHeight() * 16 + 1.5); painter->setClipping(true); painter->setClipRect(mapRect); } From 59c525e9fe511936e06cb7d6361e4db69e8a4203 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 23 Nov 2024 14:39:01 -0500 Subject: [PATCH 092/364] Add icon for Summary Chart button --- forms/mainwindow.ui | 4 ++++ resources/icons/chart_bar.ico | Bin 0 -> 1049 bytes resources/images.qrc | 1 + 3 files changed, 5 insertions(+) create mode 100755 resources/icons/chart_bar.ico diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 44d69d30..6a373edc 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -3028,6 +3028,10 @@ Summary Chart... + + + :/icons/chart_bar.ico:/icons/chart_bar.ico + diff --git a/resources/icons/chart_bar.ico b/resources/icons/chart_bar.ico new file mode 100755 index 0000000000000000000000000000000000000000..a66163a4381f1330ea1c0a01f37073b85cf53076 GIT binary patch literal 1049 zcmV+!1m^pRP)?9f9=W?@Hkof`zWHVmw!X}plSf`^>lVp%Z$JfLwOT&??&CSWEw=R082~JZ z^JAATZn|mZ$LvhiWhQ1^vjIR!gJZv{WE|E?!1rR?dhGw20mm6o z>|Cj42IQV?ifFz7&w#e491Vv$G*Aj$wn-;POGbO4ZlNe7!Rb*%&u0AvnST_72V0XS?AYSR?i z9E4x3iDpV0K-C2%0H_0X6L;Kvkgzl-%_4zQrL7ZyJhqs3H>M7@Sc;=cReTh=%{bpC z&OosBXF$@dy8(DxS&(uVp=#-T{r}WwfMON*JgB!~1AxUpzTT}fkST3?5^VLPfDI zLX!Zdk*UxHx&uIO21*X(n{XhX6rdLXVbBTi8HfRRaH1id08pab0O=iy+DD#=jkV1` zP+G;@pJpd0UdC#v;-Mmk5uT#9R_7@IalEj`b%R#ggoKo?@d;udc)iuS_rQ=z3-Y8N z4|n?`S`#8OU?G8#+0|&@^v1PYxlF-=2ZD3hj!q7|#)6d#t)B^<=LLSn@L?n=a!_^* zNbU2S5L5|?RRZ@PKFY+)kgr309S|_EGfPC}D_^S^oDRz(?C2Wv^$Y&@`YXTyvmIbY TYF0c_00000NkvXXu0mjfiDAtd literal 0 HcmV?d00001 diff --git a/resources/images.qrc b/resources/images.qrc index a89535a9..888c5d9d 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -1,6 +1,7 @@ icons/add.ico + icons/chart_bar.ico icons/collapse_all.ico icons/cursor.ico icons/delete.ico From 4671321690f008eafbe719675e924e30df07235b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 22 Nov 2024 15:17:25 -0500 Subject: [PATCH 093/364] Add item to duplicate map/layouts from list --- include/core/map.h | 3 +- include/core/mapheader.h | 22 ++-- include/core/maplayout.h | 1 + include/project.h | 8 +- include/ui/mapheaderform.h | 41 ++++--- include/ui/newmapdialog.h | 8 +- src/core/map.cpp | 30 ++++- src/core/mapheader.cpp | 33 ++---- src/core/maplayout.cpp | 4 +- src/mainwindow.cpp | 20 +++- src/project.cpp | 132 ++++++++++----------- src/ui/mapheaderform.cpp | 230 +++++++++++++------------------------ src/ui/newlayoutdialog.cpp | 25 +++- src/ui/newmapdialog.cpp | 106 ++++++----------- 14 files changed, 304 insertions(+), 359 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 55eee303..00e7e63c 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -35,6 +35,7 @@ class Map : public QObject Q_OBJECT public: explicit Map(QObject *parent = nullptr); + explicit Map(const Map &other, QObject *parent = nullptr); ~Map(); public: @@ -125,7 +126,7 @@ private: bool m_scriptsLoaded = false; QMap> m_events; - QList m_ownedEvents; // for memory management + QSet m_ownedEvents; // for memory management QList m_metatileLayerOrder; QList m_metatileLayerOpacity; diff --git a/include/core/mapheader.h b/include/core/mapheader.h index ee2c354e..2058280a 100644 --- a/include/core/mapheader.h +++ b/include/core/mapheader.h @@ -53,17 +53,17 @@ public: QString battleScene() const { return m_battleScene; } signals: - void songChanged(QString, QString); - void locationChanged(QString, QString); - void requiresFlashChanged(bool, bool); - void weatherChanged(QString, QString); - void typeChanged(QString, QString); - void showsLocationNameChanged(bool, bool); - void allowsRunningChanged(bool, bool); - void allowsBikingChanged(bool, bool); - void allowsEscapingChanged(bool, bool); - void floorNumberChanged(int, int); - void battleSceneChanged(QString, QString); + void songChanged(QString); + void locationChanged(QString); + void requiresFlashChanged(bool); + void weatherChanged(QString); + void typeChanged(QString); + void showsLocationNameChanged(bool); + void allowsRunningChanged(bool); + void allowsBikingChanged(bool); + void allowsEscapingChanged(bool); + void floorNumberChanged(int); + void battleSceneChanged(QString); void modified(); private: diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 8dea0795..cfd093ef 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -20,6 +20,7 @@ class Layout : public QObject { Q_OBJECT public: Layout() {} + Layout(const Layout &other); static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); diff --git a/include/project.h b/include/project.h index 41593350..e65b755f 100644 --- a/include/project.h +++ b/include/project.h @@ -132,7 +132,8 @@ public: bool readMapGroups(); void addNewMap(Map* newMap, const QString &groupName); void addNewMapGroup(const QString &groupName); - void addNewLayout(Layout* newLayout); + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); + Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); NewMapSettings getNewMapSettings() const; Layout::Settings getNewLayoutSettings() const; bool isIdentifierUnique(const QString &identifier) const; @@ -159,7 +160,6 @@ public: bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); - Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -277,9 +277,9 @@ signals: void fileChanged(QString filepath); void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); - void mapAdded(Map *newMap, const QString &groupName); + void mapCreated(Map *newMap, const QString &groupName); void mapGroupAdded(const QString &groupName); - void layoutAdded(Layout *newLayout); + void layoutCreated(Layout *newLayout); }; #endif // PROJECT_H diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 136499e5..13e1b67d 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -29,29 +29,38 @@ public: void clear(); void setHeader(MapHeader *header); + void setHeaderData(const MapHeader &header); MapHeader headerData() const; + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setBattleScene(const QString &battleScene); + void setShowsLocationName(bool showsLocationName); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + + QString song() const; + QString location() const; + bool requiresFlash() const; + QString weather() const; + QString type() const; + QString battleScene() const; + bool showsLocationName() const; + bool allowsRunning() const; + bool allowsBiking() const; + bool allowsEscaping() const; + int floorNumber() const; + void setLocations(QStringList locations); - void setLocationDisabled(bool disabled); - bool isLocationDisabled() const { return m_locationDisabled; } private: Ui::MapHeaderForm *ui; QPointer m_header = nullptr; - bool m_locationDisabled = false; - - void updateUi(); - void updateSong(); - void updateLocation(); - void updateRequiresFlash(); - void updateWeather(); - void updateType(); - void updateBattleScene(); - void updateShowsLocationName(); - void updateAllowsRunning(); - void updateAllowsBiking(); - void updateAllowsEscaping(); - void updateFloorNumber(); void onSongUpdated(const QString &song); void onLocationChanged(const QString &location); diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index cdc324e2..15dd3f6c 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -25,9 +25,6 @@ public: virtual void accept() override; -signals: - void applied(const QString &newMapName); - private: Ui::NewMapDialog *ui; Project *project; @@ -45,10 +42,9 @@ private: bool validateGroup(bool allowEmpty = false); bool validateLayoutID(bool allowEmpty = false); - void refresh(); - + void setUI(const Project::NewMapSettings &settings); void saveSettings(); - void useLayoutSettings(const Layout *mapLayout); + void setLayout(const Layout *mapLayout); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/core/map.cpp b/src/core/map.cpp index 1d7c4555..a42faa27 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -13,7 +13,6 @@ Map::Map(QObject *parent) : QObject(parent) { - m_scriptsLoaded = false; m_editHistory = new QUndoStack(this); resetEvents(); @@ -21,6 +20,33 @@ Map::Map(QObject *parent) : QObject(parent) connect(m_header, &MapHeader::modified, this, &Map::modified); } +Map::Map(const Map &other, QObject *parent) : Map(parent) { + m_name = other.m_name; + m_constantName = other.m_constantName; + m_layoutId = other.m_layoutId; + m_sharedEventsMap = other.m_sharedEventsMap; + m_sharedScriptsMap = other.m_sharedScriptsMap; + m_customAttributes = other.m_customAttributes; + *m_header = *other.m_header; + m_layout = other.m_layout; + m_isPersistedToFile = false; + m_metatileLayerOrder = other.m_metatileLayerOrder; + m_metatileLayerOpacity = other.m_metatileLayerOpacity; + + // Copy events + for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) { + QList newEvents; + for (const auto &event : i.value()) { + auto newEvent = event->duplicate(); + m_ownedEvents.insert(newEvent); + newEvents.append(newEvent); + } + m_events[i.key()] = newEvents; + } + + // Duplicating the map connections is probably not desirable, so we skip them. +} + Map::~Map() { qDeleteAll(m_ownedEvents); m_ownedEvents.clear(); @@ -201,7 +227,7 @@ void Map::removeEvent(Event *event) { void Map::addEvent(Event *event) { event->setMap(this); m_events[event->getEventGroup()].append(event); - if (!m_ownedEvents.contains(event)) m_ownedEvents.append(event); + if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event); } int Map::getIndexOfEvent(Event *event) const { diff --git a/src/core/mapheader.cpp b/src/core/mapheader.cpp index 4c0bc06e..689a52dc 100644 --- a/src/core/mapheader.cpp +++ b/src/core/mapheader.cpp @@ -35,98 +35,87 @@ MapHeader &MapHeader::operator=(const MapHeader &other) { void MapHeader::setSong(const QString &song) { if (m_song == song) return; - auto before = m_song; m_song = song; - emit songChanged(before, m_song); + emit songChanged(m_song); emit modified(); } void MapHeader::setLocation(const QString &location) { if (m_location == location) return; - auto before = m_location; m_location = location; - emit locationChanged(before, m_location); + emit locationChanged(m_location); emit modified(); } void MapHeader::setRequiresFlash(bool requiresFlash) { if (m_requiresFlash == requiresFlash) return; - auto before = m_requiresFlash; m_requiresFlash = requiresFlash; - emit requiresFlashChanged(before, m_requiresFlash); + emit requiresFlashChanged(m_requiresFlash); emit modified(); } void MapHeader::setWeather(const QString &weather) { if (m_weather == weather) return; - auto before = m_weather; m_weather = weather; - emit weatherChanged(before, m_weather); + emit weatherChanged(m_weather); emit modified(); } void MapHeader::setType(const QString &type) { if (m_type == type) return; - auto before = m_type; m_type = type; - emit typeChanged(before, m_type); + emit typeChanged(m_type); emit modified(); } void MapHeader::setShowsLocationName(bool showsLocationName) { if (m_showsLocationName == showsLocationName) return; - auto before = m_showsLocationName; m_showsLocationName = showsLocationName; - emit showsLocationNameChanged(before, m_showsLocationName); + emit showsLocationNameChanged(m_showsLocationName); emit modified(); } void MapHeader::setAllowsRunning(bool allowsRunning) { if (m_allowsRunning == allowsRunning) return; - auto before = m_allowsRunning; m_allowsRunning = allowsRunning; - emit allowsRunningChanged(before, m_allowsRunning); + emit allowsRunningChanged(m_allowsRunning); emit modified(); } void MapHeader::setAllowsBiking(bool allowsBiking) { if (m_allowsBiking == allowsBiking) return; - auto before = m_allowsBiking; m_allowsBiking = allowsBiking; - emit allowsBikingChanged(before, m_allowsBiking); + emit allowsBikingChanged(m_allowsBiking); emit modified(); } void MapHeader::setAllowsEscaping(bool allowsEscaping) { if (m_allowsEscaping == allowsEscaping) return; - auto before = m_allowsEscaping; m_allowsEscaping = allowsEscaping; - emit allowsEscapingChanged(before, m_allowsEscaping); + emit allowsEscapingChanged(m_allowsEscaping); emit modified(); } void MapHeader::setFloorNumber(int floorNumber) { if (m_floorNumber == floorNumber) return; - auto before = m_floorNumber; m_floorNumber = floorNumber; - emit floorNumberChanged(before, m_floorNumber); + emit floorNumberChanged(m_floorNumber); emit modified(); } void MapHeader::setBattleScene(const QString &battleScene) { if (m_battleScene == battleScene) return; - auto before = m_battleScene; m_battleScene = battleScene; - emit battleSceneChanged(before, m_battleScene); + emit battleSceneChanged(m_battleScene); emit modified(); } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 921c0894..c30ae798 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -5,7 +5,9 @@ #include "scripting.h" #include "imageproviders.h" - +Layout::Layout(const Layout &other) : Layout() { + copyFrom(&other); +} Layout *Layout::copy() const { Layout *layout = new Layout; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 41f85724..17f05726 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -621,9 +621,9 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - connect(project, &Project::mapAdded, this, &MainWindow::onNewMapCreated); + connect(project, &Project::mapCreated, this, &MainWindow::onNewMapCreated); + connect(project, &Project::layoutCreated, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); - connect(project, &Project::layoutAdded, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); @@ -1210,6 +1210,10 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); + connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { + auto dialog = new NewMapDialog(this->editor->project, this->editor->project->getMap(itemName), this); + dialog->open(); + }); //menu.addSeparator(); //connect(menu.addAction("Delete Map"), &QAction::triggered, [this, index] { deleteMapListItem(index); }); // TODO: No support for deleting maps } else if (itemType == "map_group") { @@ -1227,6 +1231,14 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } else if (itemType == "map_layout") { // Right-clicking on a map layout openItemAction = menu.addAction("Open Layout"); + connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { + auto layout = this->editor->project->loadLayout(itemName); + if (layout) { + auto dialog = new NewLayoutDialog(this->editor->project, layout, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); + } + }); addToFolderAction = menu.addAction("Add New Map with Layout"); //menu.addSeparator(); //deleteFolderAction = menu.addAction("Delete Layout"); // TODO: No support for deleting layouts @@ -1236,7 +1248,6 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { // All folders only contain maps, so adding an item to any folder is adding a new map. connect(addToFolderAction, &QAction::triggered, [this, itemName] { auto dialog = new NewMapDialog(this->editor->project, ui->mapListContainer->currentIndex(), itemName, this); - connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); dialog->open(); }); } @@ -1371,6 +1382,8 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { editor->project->saveHealLocations(newMap); editor->save(); } + + userSetMap(newMap->name()); } // Called any time a new layout is created (including as a byproduct of creating a new map) @@ -1395,7 +1408,6 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); - connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); dialog->open(); } diff --git a/src/project.cpp b/src/project.cpp index 9976c549..cf80369e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -363,8 +363,59 @@ bool Project::loadMapData(Map* map) { return true; } +Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* toDuplicate) { + Map *map = toDuplicate ? new Map(*toDuplicate) : new Map; + map->setName(settings.name); + map->setConstantName(settings.id); + map->setHeader(settings.header); + map->setNeedsHealLocation(settings.canFlyTo); + + Layout *layout = this->mapLayouts.value(settings.layout.id); + if (layout) { + // Layout already exists + map->setNeedsLayoutDir(false); // TODO: Remove this member? + } else { + layout = createNewLayout(settings.layout); + } + if (!layout) { + delete map; + return nullptr; + } + map->setLayout(layout); + + // Make sure we keep the order of the map names the same as in the map group order. + int mapNamePos; + if (this->groupNames.contains(settings.group)) { + mapNamePos = 0; + for (const auto &name : this->groupNames) { + mapNamePos += this->groupNameToMapNames[name].length(); + if (name == settings.group) + break; + } + } else { + // Adding map to a map group that doesn't exist yet. + // Create the group, and we already know the map will be last in the list. + addNewMapGroup(settings.group); + mapNamePos = this->mapNames.length(); + } + + this->mapNames.insert(mapNamePos, map->name()); + this->groupNameToMapNames[settings.group].append(map->name()); + this->mapConstantsToMapNames.insert(map->constantName(), map->name()); + this->mapNamesToMapConstants.insert(map->name(), map->constantName()); + + map->setIsPersistedToFile(false); + + emit mapCreated(map, settings.group); + + return map; +} + Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout *toDuplicate) { - Layout *layout = new Layout; + if (this->layoutIds.contains(settings.id)) + return nullptr; + + Layout *layout = toDuplicate ? new Layout(*toDuplicate) : new Layout(); layout->id = settings.id; layout->name = settings.name; layout->width = settings.width; @@ -374,13 +425,6 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout 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); @@ -393,9 +437,22 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout return nullptr; } - addNewLayout(layout); + this->mapLayouts.insert(layout->id, layout); + this->layoutIds.append(layout->id); + + if (layout->blockdata.isEmpty()) { + // Fill layout using default fill settings + setNewLayoutBlockdata(layout); + } + if (layout->border.isEmpty()) { + // Fill border using default fill settings + setNewLayoutBorder(layout); + } + saveLayout(layout); // TODO: Ideally we shouldn't automatically save new layouts + emit layoutCreated(layout); + return layout; } @@ -1886,60 +1943,6 @@ bool Project::readMapGroups() { return true; } -void Project::addNewMap(Map *newMap, const QString &groupName) { - if (!newMap) - return; - - // Make sure we keep the order of the map names the same as in the map group order. - int mapNamePos; - if (this->groupNames.contains(groupName)) { - mapNamePos = 0; - for (const auto &name : this->groupNames) { - mapNamePos += this->groupNameToMapNames[name].length(); - if (name == groupName) - break; - } - } else { - // Adding map to a map group that doesn't exist yet. - // Create the group, and we already know the map will be last in the list. - addNewMapGroup(groupName); - mapNamePos = this->mapNames.length(); - } - - this->mapNames.insert(mapNamePos, newMap->name()); - this->groupNameToMapNames[groupName].append(newMap->name()); - this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); - this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); - - newMap->setIsPersistedToFile(false); - - // If we don't recognize the layout ID (i.e., it's also new) we'll add that too. - if (!this->layoutIds.contains(newMap->layout()->id)) { - addNewLayout(newMap->layout()); - } - - emit mapAdded(newMap, groupName); -} - -void Project::addNewLayout(Layout* newLayout) { - if (!newLayout || this->layoutIds.contains(newLayout->id)) - return; - - this->mapLayouts.insert(newLayout->id, newLayout); - this->layoutIds.append(newLayout->id); - - 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); -} - void Project::addNewMapGroup(const QString &groupName) { if (this->groupNames.contains(groupName)) return; @@ -2005,12 +2008,11 @@ Layout::Settings Project::getNewLayoutSettings() const { return settings; } -// When we ask the user to provide a new identifier for something (like a map/layout name or ID) +// When we ask the user to provide a new identifier for something (like a map name or MAPSEC 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; diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index fa0e2c1c..6f6e4dec 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -92,46 +92,47 @@ void MapHeaderForm::setHeader(MapHeader *header) { if (m_header) { m_header->disconnect(this); } - m_header = header; - if (m_header) { - // If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly - connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::updateSong); - connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::updateLocation); - connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::updateRequiresFlash); - connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::updateWeather); - connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::updateType); - connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::updateBattleScene); - connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::updateShowsLocationName); - connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::updateAllowsRunning); - connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::updateAllowsBiking); - connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::updateAllowsEscaping); - connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::updateFloorNumber); + if (!m_header) { + clear(); + return; } + // If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly + connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::setSong); + connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::setLocation); + connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::setRequiresFlash); + connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::setWeather); + connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::setType); + connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::setBattleScene); + connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::setShowsLocationName); + connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::setAllowsRunning); + connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::setAllowsBiking); + connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::setAllowsEscaping); + connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::setFloorNumber); + // Immediately update the UI to reflect the assigned MapHeader - updateUi(); + setHeaderData(*m_header); } void MapHeaderForm::clear() { m_header = nullptr; - updateUi(); + setHeaderData(MapHeader()); } -void MapHeaderForm::updateUi() { - updateSong(); - updateLocation(); - updateRequiresFlash(); - updateWeather(); - updateType(); - updateBattleScene(); - updateShowsLocationName(); - updateAllowsRunning(); - updateAllowsBiking(); - updateAllowsEscaping(); - updateFloorNumber(); - +void MapHeaderForm::setHeaderData(const MapHeader &header) { + setSong(header.song()); + setLocation(header.location()); + setRequiresFlash(header.requiresFlash()); + setWeather(header.weather()); + setType(header.type()); + setBattleScene(header.battleScene()); + setShowsLocationName(header.showsLocationName()); + setAllowsRunning(header.allowsRunning()); + setAllowsBiking(header.allowsBiking()); + setAllowsEscaping(header.allowsEscaping()); + setFloorNumber(header.floorNumber()); } MapHeader MapHeaderForm::headerData() const { @@ -140,132 +141,55 @@ MapHeader MapHeaderForm::headerData() const { // Build header from UI MapHeader header; - header.setSong(ui->comboBox_Song->currentText()); - header.setLocation(ui->comboBox_Location->currentText()); - header.setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); - header.setWeather(ui->comboBox_Weather->currentText()); - header.setType(ui->comboBox_Type->currentText()); - header.setBattleScene(ui->comboBox_BattleScene->currentText()); - header.setShowsLocationName(ui->checkBox_ShowLocationName->isChecked()); - header.setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); - header.setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); - header.setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); - header.setFloorNumber(ui->spinBox_FloorNumber->value()); + header.setSong(song()); + header.setLocation(location()); + header.setRequiresFlash(requiresFlash()); + header.setWeather(weather()); + header.setType(type()); + header.setBattleScene(battleScene()); + header.setShowsLocationName(showsLocationName()); + header.setAllowsRunning(allowsRunning()); + header.setAllowsBiking(allowsBiking()); + header.setAllowsEscaping(allowsEscaping()); + header.setFloorNumber(floorNumber()); return header; } -void MapHeaderForm::setLocationDisabled(bool disabled) { - m_locationDisabled = disabled; - ui->label_Location->setDisabled(m_locationDisabled); - ui->comboBox_Location->setDisabled(m_locationDisabled); -} +// Set data in UI +void MapHeaderForm::setSong(const QString &song) { ui->comboBox_Song->setCurrentText(song); } +void MapHeaderForm::setLocation(const QString &location) { ui->comboBox_Location->setCurrentText(location); } +void MapHeaderForm::setRequiresFlash(bool requiresFlash) { ui->checkBox_RequiresFlash->setChecked(requiresFlash); } +void MapHeaderForm::setWeather(const QString &weather) { ui->comboBox_Weather->setCurrentText(weather); } +void MapHeaderForm::setType(const QString &type) { ui->comboBox_Type->setCurrentText(type); } +void MapHeaderForm::setBattleScene(const QString &battleScene) { ui->comboBox_BattleScene->setCurrentText(battleScene); } +void MapHeaderForm::setShowsLocationName(bool showsLocationName) { ui->checkBox_ShowLocationName->setChecked(showsLocationName); } +void MapHeaderForm::setAllowsRunning(bool allowsRunning) { ui->checkBox_AllowRunning->setChecked(allowsRunning); } +void MapHeaderForm::setAllowsBiking(bool allowsBiking) { ui->checkBox_AllowBiking->setChecked(allowsBiking); } +void MapHeaderForm::setAllowsEscaping(bool allowsEscaping) { ui->checkBox_AllowEscaping->setChecked(allowsEscaping); } +void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_FloorNumber->setValue(floorNumber); } -void MapHeaderForm::updateSong() { - const QSignalBlocker b(ui->comboBox_Song); - ui->comboBox_Song->setCurrentText(m_header ? m_header->song() : QString()); -} +// Read data from UI +QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); } +QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); } +bool MapHeaderForm::requiresFlash() const { return ui->checkBox_RequiresFlash->isChecked(); } +QString MapHeaderForm::weather() const { return ui->comboBox_Weather->currentText(); } +QString MapHeaderForm::type() const { return ui->comboBox_Type->currentText(); } +QString MapHeaderForm::battleScene() const { return ui->comboBox_BattleScene->currentText(); } +bool MapHeaderForm::showsLocationName() const { return ui->checkBox_ShowLocationName->isChecked(); } +bool MapHeaderForm::allowsRunning() const { return ui->checkBox_AllowRunning->isChecked(); } +bool MapHeaderForm::allowsBiking() const { return ui->checkBox_AllowBiking->isChecked(); } +bool MapHeaderForm::allowsEscaping() const { return ui->checkBox_AllowEscaping->isChecked(); } +int MapHeaderForm::floorNumber() const { return ui->spinBox_FloorNumber->value(); } -void MapHeaderForm::updateLocation() { - const QSignalBlocker b(ui->comboBox_Location); - ui->comboBox_Location->setCurrentText(m_header ? m_header->location() : QString()); -} - -void MapHeaderForm::updateRequiresFlash() { - const QSignalBlocker b(ui->checkBox_RequiresFlash); - ui->checkBox_RequiresFlash->setChecked(m_header ? m_header->requiresFlash() : false); -} - -void MapHeaderForm::updateWeather() { - const QSignalBlocker b(ui->comboBox_Weather); - ui->comboBox_Weather->setCurrentText(m_header ? m_header->weather() : QString()); -} - -void MapHeaderForm::updateType() { - const QSignalBlocker b(ui->comboBox_Type); - ui->comboBox_Type->setCurrentText(m_header ? m_header->type() : QString()); -} - -void MapHeaderForm::updateBattleScene() { - const QSignalBlocker b(ui->comboBox_BattleScene); - ui->comboBox_BattleScene->setCurrentText(m_header ? m_header->battleScene() : QString()); -} - -void MapHeaderForm::updateShowsLocationName() { - const QSignalBlocker b(ui->checkBox_ShowLocationName); - ui->checkBox_ShowLocationName->setChecked(m_header ? m_header->showsLocationName() : false); -} - -void MapHeaderForm::updateAllowsRunning() { - const QSignalBlocker b(ui->checkBox_AllowRunning); - ui->checkBox_AllowRunning->setChecked(m_header ? m_header->allowsRunning() : false); -} - -void MapHeaderForm::updateAllowsBiking() { - const QSignalBlocker b(ui->checkBox_AllowBiking); - ui->checkBox_AllowBiking->setChecked(m_header ? m_header->allowsBiking() : false); -} - -void MapHeaderForm::updateAllowsEscaping() { - const QSignalBlocker b(ui->checkBox_AllowEscaping); - ui->checkBox_AllowEscaping->setChecked(m_header ? m_header->allowsEscaping() : false); -} - -void MapHeaderForm::updateFloorNumber() { - const QSignalBlocker b(ui->spinBox_FloorNumber); - ui->spinBox_FloorNumber->setValue(m_header ? m_header->floorNumber() : 0); -} - -void MapHeaderForm::onSongUpdated(const QString &song) -{ - if (m_header) m_header->setSong(song); -} - -void MapHeaderForm::onLocationChanged(const QString &location) -{ - if (m_header) m_header->setLocation(location); -} - -void MapHeaderForm::onWeatherChanged(const QString &weather) -{ - if (m_header) m_header->setWeather(weather); -} - -void MapHeaderForm::onTypeChanged(const QString &type) -{ - if (m_header) m_header->setType(type); -} - -void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) -{ - if (m_header) m_header->setBattleScene(battleScene); -} - -void MapHeaderForm::onRequiresFlashChanged(int selected) -{ - if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); -} - -void MapHeaderForm::onShowLocationNameChanged(int selected) -{ - if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); -} - -void MapHeaderForm::onAllowRunningChanged(int selected) -{ - if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); -} - -void MapHeaderForm::onAllowBikingChanged(int selected) -{ - if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); -} - -void MapHeaderForm::onAllowEscapingChanged(int selected) -{ - if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); -} - -void MapHeaderForm::onFloorNumberChanged(int offset) -{ - if (m_header) m_header->setFloorNumber(offset); -} +// Send changes in UI to tracked MapHeader (if there is one) +void MapHeaderForm::onSongUpdated(const QString &song) { if (m_header) m_header->setSong(song); } +void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); } +void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); } +void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); } +void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); } +void MapHeaderForm::onRequiresFlashChanged(int selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } +void MapHeaderForm::onShowLocationNameChanged(int selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } +void MapHeaderForm::onAllowRunningChanged(int selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } +void MapHeaderForm::onAllowBikingChanged(int selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } +void MapHeaderForm::onAllowEscapingChanged(int selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } +void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 3cf7b9a9..de41a3d6 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -47,15 +47,26 @@ NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : adjustSize(); } -// Creating new layout from AdvanceMap import -// TODO: Re-use for a "Duplicate Layout" option -NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layout, QWidget *parent) : +// Creating new layout from an existing layout (e.g. via AdvanceMap import, or duplicating from map list). +NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) : NewLayoutDialog(project, parent) { - if (layout) { - this->importedLayout = layout->copy(); - refresh(); + if (!layoutToCopy) + return; + + this->importedLayout = layoutToCopy->copy(); + if (!this->importedLayout->name.isEmpty()) { + // If the layout we're duplicating has a name and ID we'll initialize the name/ID fields + // using that name and add a suffix to make it unique. + // Layouts imported with AdvanceMap won't have a name/ID. + int i = 2; + do { + settings.name = QString("%1_%2").arg(this->importedLayout->name).arg(i); + settings.id = QString("%1_%2").arg(this->importedLayout->id).arg(i); + i++; + } while (!this->project->isIdentifierUnique(settings.name) || !this->project->isIdentifierUnique(settings.id)); } + refresh(); } NewLayoutDialog::~NewLayoutDialog() @@ -162,6 +173,8 @@ void NewLayoutDialog::accept() { } ui->label_GenericError->setVisible(false); + // TODO: See if we can get away with emitting this from Project so that we don't need to connect + // to this signal every time we create the dialog. emit applied(layout->id); QDialog::accept(); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index a97fd2ab..59fe0a16 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -50,7 +50,6 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); this->headerForm->init(project); - this->headerForm->setHeader(&settings.header); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -61,47 +60,36 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); - refresh(); + setUI(settings); adjustSize(); // TODO: Save geometry? } -// Adding new map to existing map list folder. +// Adding new map to existing map list folder. Initialize settings accordingly. +// Even if we initialize settings like this we'll allow users to change them afterwards, +// because nothing is expecting them to stay at these values. 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); + ui->comboBox_Group->setTextItem(mapListItem); break; case MapListTab::Areas: - settings.header.setLocation(mapListItem); - this->headerForm->setLocationDisabled(true); - // Header UI is kept in sync automatically by MapHeaderForm + this->headerForm->setLocation(mapListItem); 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); + ui->comboBox_LayoutID->setTextItem(mapListItem); 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()); - */ + if (!mapToCopy) + return; + // TODO } NewMapDialog::~NewMapDialog() @@ -111,35 +99,38 @@ NewMapDialog::~NewMapDialog() delete ui; } -// 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() { +void NewMapDialog::setUI(const Project::NewMapSettings &settings) { 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 + if (this->importedMap && this->importedMap->layout()) { + // When importing a layout these settings shouldn't be changed. + ui->newLayoutForm->setSettings(this->importedMap->layout()->settings()); + } else { + ui->newLayoutForm->setSettings(settings.layout); + } + ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); + this->headerForm->setHeaderData(settings.header); } void NewMapDialog::saveSettings() { 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.layout.name = Layout::layoutNameFromMapName(settings.name); // TODO: Verify uniqueness + settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings.header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } -void NewMapDialog::useLayoutSettings(const Layout *layout) { +void NewMapDialog::setLayout(const Layout *layout) { if (layout) { + ui->comboBox_LayoutID->setTextItem(layout->id); ui->newLayoutForm->setSettings(layout->settings()); ui->newLayoutForm->setDisabled(true); } else { @@ -152,10 +143,10 @@ bool NewMapDialog::validateMapID(bool allowEmpty) { const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); QString errorText; - if (id.isEmpty()) { + if (id.isEmpty() || id == expectedPrefix) { 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); + errorText = QString("%1 must start with '%2'.").arg(ui->label_MapID->text()).arg(expectedPrefix); } else if (!this->project->isIdentifierUnique(id)) { errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id); } @@ -223,8 +214,14 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { 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()); + } else if (!this->project->isIdentifierUnique(layoutId)) { + // Layout name is already in use by something. If we're duplicating a map this isn't allowed. + if (this->importedMap) { + errorText = QString("%1 is not unique.").arg(ui->label_LayoutID->text()); + // If we're not duplicating a map this is ok as long as it's the name of an existing layout. + } else if (!this->project->layoutIds.contains(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(); @@ -236,7 +233,7 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { validateLayoutID(true); - useLayoutSettings(this->project->mapLayouts.value(text)); + setLayout(this->project->mapLayouts.value(text)); } void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { @@ -244,16 +241,7 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - 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(); - + setUI(this->project->getNewMapSettings()); } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -273,30 +261,12 @@ void NewMapDialog::accept() { // Update settings from UI saveSettings(); - Map *newMap = new Map; - newMap->setName(settings.name); - newMap->setConstantName(settings.id); - newMap->setHeader(settings.header); - newMap->setNeedsHealLocation(settings.canFlyTo); - - Layout *layout = this->project->mapLayouts.value(settings.layout.id); - if (layout) { - // Layout already exists - newMap->setNeedsLayoutDir(false); // TODO: Remove this member? - } else { - layout = this->project->createNewLayout(settings.layout); - } - if (!layout) { - ui->label_GenericError->setText(QString("Failed to create layout for map. See %1 for details.").arg(getLogPath())); + Map *map = this->project->createNewMap(settings, this->importedMap); + if (!map) { + ui->label_GenericError->setText(QString("Failed to create 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, settings.group); - emit applied(newMap->name()); QDialog::accept(); } From b230f21e8dfe83fa8e12f402799985e7772d8df8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 23 Nov 2024 23:17:57 -0500 Subject: [PATCH 094/364] Automatically add new MAPSEC values from New Map dialog --- include/mainwindow.h | 1 + include/project.h | 7 ++++--- src/mainwindow.cpp | 10 +++++++++- src/project.cpp | 31 +++++++++++++++++++++---------- src/ui/maplistmodels.cpp | 1 - 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 775d364e..a144d6e3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -188,6 +188,7 @@ private slots: void onTilesetsSaved(QString, QString); void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); + void onNewMapSectionCreated(const QString &idName); void onNewLayoutCreated(Layout *layout); void onMapLoaded(Map *map); void onMapRulerStatusChanged(const QString &); diff --git a/include/project.h b/include/project.h index e65b755f..58e2d5a3 100644 --- a/include/project.h +++ b/include/project.h @@ -130,8 +130,8 @@ public: void deleteFile(QString path); bool readMapGroups(); - void addNewMap(Map* newMap, const QString &groupName); void addNewMapGroup(const QString &groupName); + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); NewMapSettings getNewMapSettings() const; @@ -275,11 +275,12 @@ private: signals: void fileChanged(QString filepath); - void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); void mapCreated(Map *newMap, const QString &groupName); - void mapGroupAdded(const QString &groupName); void layoutCreated(Layout *newLayout); + void mapGroupAdded(const QString &groupName); + void mapSectionAdded(const QString &idName); + void mapSectionIdNamesChanged(const QStringList &idNames); }; #endif // PROJECT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 17f05726..615fd40d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -624,6 +624,7 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::mapCreated, this, &MainWindow::onNewMapCreated); connect(project, &Project::layoutCreated, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); + connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); @@ -1351,7 +1352,7 @@ void MainWindow::mapListAddArea() { if (dialog.exec() == QDialog::Accepted) { if (newNameEdit->text().isEmpty()) return; - this->mapAreaModel->insertAreaItem(newNameDisplay->text()); + this->editor->project->addNewMapsec(newNameDisplay->text()); } } @@ -1406,6 +1407,13 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { this->mapGroupModel->insertGroupItem(groupName); } +void MainWindow::onNewMapSectionCreated(const QString &idName) { + // Add new map section to the Areas map list view + this->mapAreaModel->insertAreaItem(idName); + + // TODO: Refresh Region Map Editor's map section dropdown, if it's open +} + void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); dialog->open(); diff --git a/src/project.cpp b/src/project.cpp index cf80369e..3d8d3f9c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -399,10 +399,18 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t mapNamePos = this->mapNames.length(); } + if (!this->mapSectionIdNames.contains(map->header()->location())) { + // Unrecognized MAPSEC value. Add it. + // TODO: Validate location before adding + addNewMapsec(map->header()->location()); + } + this->mapNames.insert(mapNamePos, map->name()); this->groupNameToMapNames[settings.group].append(map->name()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); this->mapNamesToMapConstants.insert(map->name(), map->constantName()); + this->mapNameToLayoutId.insert(map->name(), map->layoutId()); + this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); map->setIsPersistedToFile(false); @@ -730,8 +738,6 @@ void Project::saveRegionMapSections() { const QString emptyMapsecName = getEmptyMapsecName(); OrderedJson::array mapSectionArray; for (const auto &idName : this->mapSectionIdNames) { - // The 'empty' map section (MAPSEC_NONE) isn't normally present in the region map sections data file. - // We append this name to mapSectionIdNames ourselves if it isn't present, in which case we don't want to output data for it here. if (!this->saveEmptyMapsec && idName == emptyMapsecName) continue; @@ -2308,9 +2314,10 @@ bool Project::readRegionMapSections() { const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); QJsonDocument doc; - const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); + const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); + const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath); if (!parser.tryParseJsonFile(&doc, filepath)) { - logError(QString("Failed to read region map sections from '%1'").arg(filepath)); + logError(QString("Failed to read region map sections from '%1'").arg(baseFilepath)); return false; } fileWatcher.addPath(filepath); @@ -2319,22 +2326,23 @@ bool Project::readRegionMapSections() { for (int i = 0; i < mapSections.size(); i++) { QJsonObject mapSectionObj = mapSections.at(i).toObject(); - // For each map section, "id" is the only required field. This is the field we use to display the location names in various drop-downs. + // For each map section, "id" is the only required field. This is the field we use to display the location names in the map list, and in various drop-downs. const QString idField = "id"; if (!mapSectionObj.contains(idField)) { - logWarn(QString("Ignoring data for map section %1. Missing required field \"%2\"").arg(i).arg(idField)); + logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField)); continue; } const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); if (!idName.startsWith(requiredPrefix)) { - logWarn(QString("Ignoring data for map section '%1'. IDs must start with the prefix '%2'").arg(idName).arg(requiredPrefix)); + logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(baseFilepath).arg(requiredPrefix)); continue; } this->mapSectionIdNames.append(idName); if (idName == defaultName) { - // If the user has data for the 'empty' MAPSEC we need to know to output it later, - // because we will otherwise add a dummy entry for this value. + // The default map section (MAPSEC_NONE) isn't normally present in the region map sections data file. + // We append this name to mapSectionIdNames ourselves if it isn't present. + // We need to record whether we found it in the data file, so that we can preserve the data when we save the file later. this->saveEmptyMapsec = true; } @@ -2375,13 +2383,16 @@ QString Project::getEmptyMapsecName() { // This function assumes a valid and unique name void Project::addNewMapsec(const QString &name) { - if (!this->mapSectionIdNames.isEmpty() && this->mapSectionIdNames.last() == getEmptyMapsecName()) { + if (this->mapSectionIdNames.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); } else { this->mapSectionIdNames.append(name); } this->hasUnsavedDataChanges = true; + + // TODO: Simplify into a single signal that updates the map list only if necessary + emit mapSectionAdded(name); emit mapSectionIdNamesChanged(this->mapSectionIdNames); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 0fd6fea9..be5b0932 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -429,7 +429,6 @@ QStandardItem *MapAreaModel::createMapItem(QString mapName) { } QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { - this->project->addNewMapsec(areaName); QStandardItem *item = createAreaItem(areaName); this->root->appendRow(item); this->sort(0, Qt::AscendingOrder); From ff04a41db25e012ed42a254cd5fe79b6056891fa Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 26 Nov 2024 11:36:11 -0500 Subject: [PATCH 095/364] Add map list tool tips / copy actions, simplify MapListModel --- include/mainwindow.h | 3 +- include/ui/maplistmodels.h | 125 +++------ src/mainwindow.cpp | 80 ++++-- src/ui/maplistmodels.cpp | 538 ++++++++++++------------------------- 4 files changed, 263 insertions(+), 483 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index a144d6e3..17f19623 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -180,6 +180,7 @@ private slots: void duplicate(); void setClipboardData(poryjson::Json::object); void setClipboardData(QImage); + void setClipboardData(const QString &text); void copy(); void paste(); @@ -350,7 +351,7 @@ private: void openNewMapDialog(); void openNewLayoutDialog(); void openSubWindow(QWidget * window); - void scrollMapList(MapTree *list, QString itemName); + void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); void scrollMapListToCurrentLayout(MapTree *list); void resetMapListFilters(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 59b65e50..1962ad40 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -61,15 +61,42 @@ class MapListModel : public QStandardItemModel { Q_OBJECT public: - MapListModel(QObject *parent = nullptr) : QStandardItemModel(parent) {}; + MapListModel(Project *project, QObject *parent = nullptr); ~MapListModel() { } - virtual QModelIndex indexOf(QString id) const = 0; + void setActiveItem(const QString &itemName) { this->activeItemName = itemName; } + + virtual QStandardItem *insertMapItem(const QString &mapName, const QString &folderName); + virtual QStandardItem *insertMapFolderItem(const QString &folderName); + + virtual QModelIndex indexOf(const QString &itemName) const; virtual void removeItemAt(const QModelIndex &index); - virtual QStandardItem *getItem(const QModelIndex &index) const = 0; + virtual QStandardItem *getItem(const QModelIndex &index) const; + + virtual QVariant data(const QModelIndex &index, int role) const override; protected: - virtual void removeItem(QStandardItem *item) = 0; + Project *project; + QStandardItem *root = nullptr; + + QString activeItemName; + QString folderTypeName; + bool sortingEnabled = false; + bool editable = false; + + QIcon mapGrayIcon; + QIcon mapIcon; + QIcon mapEditedIcon; + QIcon mapOpenedIcon; + QIcon mapFolderIcon; + QIcon emptyMapFolderIcon; + + QMap mapFolderItems; + QMap mapItems; + + virtual QStandardItem *createMapItem(const QString &mapName, QStandardItem *map = nullptr); + virtual QStandardItem *createMapFolderItem(const QString &groupName, QStandardItem *fromItem = nullptr); + virtual void removeItem(QStandardItem *item) = 0; }; class MapGroupModel : public MapListModel { @@ -80,44 +107,20 @@ public: ~MapGroupModel() { } QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; - virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - -public: - void setMap(QString mapName) { this->openMap = mapName; } - - QStandardItem *createGroupItem(QString groupName, QStandardItem *fromItem = nullptr); - QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); - - QStandardItem *insertGroupItem(QString groupName); - QStandardItem *insertMapItem(QString mapName, QString groupName); - - virtual QStandardItem *getItem(const QModelIndex &index) const override; - virtual QModelIndex indexOf(QString mapName) const override; - - void initialize(); + QMimeData *mimeData(const QModelIndexList &indexes) const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; protected: - virtual void removeItem(QStandardItem *item) override; + void removeItem(QStandardItem *item) override; private: friend class MapTree; void updateProject(); -private: - Project *project; - QStandardItem *root = nullptr; - - QMap groupItems; - QMap mapItems; - - QString openMap; - signals: void dragMoveCompleted(); }; @@ -131,36 +134,8 @@ public: MapAreaModel(Project *project, QObject *parent = nullptr); ~MapAreaModel() {} - QVariant data(const QModelIndex &index, int role) const override; - -public: - void setMap(QString mapName) { this->openMap = mapName; } - - QStandardItem *createAreaItem(QString areaName); - QStandardItem *createMapItem(QString mapName); - - QStandardItem *insertAreaItem(QString areaName); - QStandardItem *insertMapItem(QString mapName, QString areaName); - - virtual QStandardItem *getItem(const QModelIndex &index) const override; - virtual QModelIndex indexOf(QString mapName) const override; - - void initialize(); - protected: - virtual void removeItem(QStandardItem *item) override; - -private: - Project *project; - QStandardItem *root = nullptr; - - QMap areaItems; - QMap mapItems; - - QString openMap; - -signals: - void edited(); + void removeItem(QStandardItem *item) override; }; @@ -174,34 +149,8 @@ public: QVariant data(const QModelIndex &index, int role) const override; -public: - void setLayout(QString layoutId) { this->openLayout = layoutId; } - - QStandardItem *createLayoutItem(QString layoutId); - QStandardItem *createMapItem(QString mapName); - - QStandardItem *insertLayoutItem(QString layoutId); - QStandardItem *insertMapItem(QString mapName, QString layoutId); - - virtual QStandardItem *getItem(const QModelIndex &index) const override; - virtual QModelIndex indexOf(QString layoutName) const override; - - void initialize(); - protected: - virtual void removeItem(QStandardItem *item) override; - -private: - Project *project; - QStandardItem *root = nullptr; - - QMap layoutItems; - QMap mapItems; - - QString openLayout; - -signals: - void edited(); + void removeItem(QStandardItem *item) override; }; #endif // MAPLISTMODELS_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 615fd40d..bb01d5c5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -956,9 +956,6 @@ bool MainWindow::setLayout(QString layoutId) { logInfo("Switching to layout-only editing mode. Disabling map-related edits."); unsetMap(); - - layoutTreeModel->setLayout(layoutId); - refreshMapScene(); updateWindowTitle(); updateMapList(); @@ -1163,7 +1160,7 @@ void MainWindow::clearProjectUI() { Event::clearIcons(); } -void MainWindow::scrollMapList(MapTree *list, QString itemName) { +void MainWindow::scrollMapList(MapTree *list, const QString &itemName) { if (!list || itemName.isEmpty()) return; auto model = static_cast(list->model()); @@ -1208,9 +1205,16 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QAction* addToFolderAction = nullptr; QAction* deleteFolderAction = nullptr; QAction* openItemAction = nullptr; + QAction* copyDisplayNameAction = nullptr; + QAction* copyToolTipAction = nullptr; + if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); + menu.addSeparator(); + copyDisplayNameAction = menu.addAction("Copy Map Name"); + copyToolTipAction = menu.addAction("Copy Map ID"); + menu.addSeparator(); connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { auto dialog = new NewMapDialog(this->editor->project, this->editor->project->getMap(itemName), this); dialog->open(); @@ -1226,12 +1230,18 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { // Right-clicking on a MAPSEC folder addToFolderAction = menu.addAction("Add New Map to Area"); menu.addSeparator(); + copyDisplayNameAction = menu.addAction("Copy Area Name"); + menu.addSeparator(); deleteFolderAction = menu.addAction("Delete Area"); if (itemName == this->editor->project->getEmptyMapsecName()) deleteFolderAction->setEnabled(false); // Disallow deleting the default name } else if (itemType == "map_layout") { // Right-clicking on a map layout openItemAction = menu.addAction("Open Layout"); + menu.addSeparator(); + copyDisplayNameAction = menu.addAction("Copy Layout Name"); + copyToolTipAction = menu.addAction("Copy Layout ID"); + menu.addSeparator(); connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { auto layout = this->editor->project->loadLayout(itemName); if (layout) { @@ -1261,8 +1271,21 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { deleteFolderAction->setEnabled(false); } } + if (openItemAction) { - connect(openItemAction, &QAction::triggered, [this, index] { openMapListItem(index); }); + connect(openItemAction, &QAction::triggered, [this, index] { + openMapListItem(index); + }); + } + if (copyDisplayNameAction) { + connect(copyDisplayNameAction, &QAction::triggered, [this, sourceModel, index] { + setClipboardData(sourceModel->data(index, Qt::DisplayRole).toString()); + }); + } + if (copyToolTipAction) { + connect(copyToolTipAction, &QAction::triggered, [this, sourceModel, index] { + setClipboardData(sourceModel->data(index, Qt::ToolTipRole).toString()); + }); } if (menu.actions().length() != 0) @@ -1399,17 +1422,17 @@ void MainWindow::onNewLayoutCreated(Layout *layout) { } // Add new layout to the Layouts map list view - this->layoutTreeModel->insertLayoutItem(layout->id); + this->layoutTreeModel->insertMapFolderItem(layout->id); } void MainWindow::onNewMapGroupCreated(const QString &groupName) { // Add new map group to the Groups map list view - this->mapGroupModel->insertGroupItem(groupName); + this->mapGroupModel->insertMapFolderItem(groupName); } void MainWindow::onNewMapSectionCreated(const QString &idName) { // Add new map section to the Areas map list view - this->mapAreaModel->insertAreaItem(idName); + this->mapAreaModel->insertMapFolderItem(idName); // TODO: Refresh Region Map Editor's map section dropdown, if it's open } @@ -1627,28 +1650,28 @@ void MainWindow::openMapListItem(const QModelIndex &index) { } void MainWindow::updateMapList() { + // Get the name of the open map/layout (or clear the relevant selection if there is none). + QString activeItemName; if (this->editor->map) { - this->mapGroupModel->setMap(this->editor->map->name()); - this->groupListProxyModel->layoutChanged(); - this->mapAreaModel->setMap(this->editor->map->name()); - this->areaListProxyModel->layoutChanged(); + activeItemName = this->editor->map->name(); } else { - this->mapGroupModel->setMap(QString()); - this->groupListProxyModel->layoutChanged(); - this->ui->mapList->clearSelection(); - this->mapAreaModel->setMap(QString()); - this->areaListProxyModel->layoutChanged(); - this->ui->areaList->clearSelection(); + ui->mapList->clearSelection(); + ui->areaList->clearSelection(); + + if (this->editor->layout) { + activeItemName = this->editor->layout->id; + } else { + ui->layoutList->clearSelection(); + } } - if (this->editor->layout) { - this->layoutTreeModel->setLayout(this->editor->layout->id); - this->layoutListProxyModel->layoutChanged(); - } else { - this->layoutTreeModel->setLayout(QString()); - this->layoutListProxyModel->layoutChanged(); - this->ui->layoutList->clearSelection(); - } + this->mapGroupModel->setActiveItem(activeItemName); + this->mapAreaModel->setActiveItem(activeItemName); + this->layoutTreeModel->setActiveItem(activeItemName); + + this->groupListProxyModel->layoutChanged(); + this->areaListProxyModel->layoutChanged(); + this->layoutListProxyModel->layoutChanged(); } void MainWindow::on_action_Save_Project_triggered() { @@ -1782,6 +1805,11 @@ void MainWindow::setClipboardData(OrderedJson::object object) { clipboard->setText(newText); } +void MainWindow::setClipboardData(const QString &text) { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); +} + void MainWindow::setClipboardData(QImage image) { QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setImage(image); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index be5b0932..6d5dd45d 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -38,6 +38,43 @@ void MapTree::keyPressEvent(QKeyEvent *event) { } } + + +MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemModel(parent) { + this->project = project; + this->root = invisibleRootItem(); + + this->mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); + this->mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + this->mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + this->mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + + this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); +} + +QStandardItem *MapListModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex MapListModel::indexOf(const QString &itemName) const { + if (this->mapItems.contains(itemName)) + return this->mapItems.value(itemName)->index(); + + if (this->mapFolderItems.contains(itemName)) + return this->mapFolderItems.value(itemName)->index(); + + return QModelIndex(); +} + void MapListModel::removeItemAt(const QModelIndex &index) { QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); if (!item) @@ -49,11 +86,89 @@ void MapListModel::removeItemAt(const QModelIndex &index) { } else { // TODO: Because there's no support for deleting maps we can only delete empty folders if (!item->hasChildren()) { - this->removeItem(item); + removeItem(item); } } } +QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem *map) { + if (!map) map = new QStandardItem; + map->setText(mapName); + map->setData(mapName, MapListUserRoles::NameRole); + map->setData("map_name", MapListUserRoles::TypeRole); + map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); + map->setEditable(this->editable); // Will override flags if necessary + this->mapItems.insert(mapName, map); + return map; +} + +QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) { + if (!folder) folder = new QStandardItem; + folder->setText(folderName); + folder->setData(folderName, MapListUserRoles::NameRole); + folder->setData(this->folderTypeName, MapListUserRoles::TypeRole); + folder->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); + folder->setEditable(this->editable); // Will override flags if necessary + this->mapFolderItems.insert(folderName, folder); + return folder; +} + +QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) { + // Disallow adding MAP_DYNAMIC to the map list. + if (mapName == this->project->getDynamicMapName()) + return nullptr; + + QStandardItem *folder = this->mapFolderItems[folderName]; + if (!folder) folder = insertMapFolderItem(folderName); + + QStandardItem *map = createMapItem(mapName); + folder->appendRow(map); + if (this->sortingEnabled) + this->sort(0, Qt::AscendingOrder); + return map; +} + +QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) { + QStandardItem *item = createMapFolderItem(folderName); + this->root->appendRow(item); + if (this->sortingEnabled) + this->sort(0, Qt::AscendingOrder); + return item; +} + +QVariant MapListModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int col = index.column(); + + const QStandardItem *item = this->getItem(index)->child(row, col); + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QString name = item->data(MapListUserRoles::NameRole).toString(); + + if (type == "map_name") { + // Data for maps in the map list + if (role == Qt::DecorationRole) { + if (name == this->activeItemName) + return this->mapOpenedIcon; + + const Map* map = this->project->mapCache.value(name); + if (!map) + return this->mapGrayIcon; + return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; + } else if (role == Qt::ToolTipRole) { + return this->project->mapNamesToMapConstants.value(name); + } + } else if (type == this->folderTypeName) { + // Data for map folders in the map list + if (role == Qt::DecorationRole) { + return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon; + } + } + return QStandardItemModel::data(index, role); +} + QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { @@ -83,11 +198,15 @@ void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOption -MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(parent) { - this->project = project; - this->root = this->invisibleRootItem(); +MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(project, parent) { + this->folderTypeName = "map_group"; + this->editable = true; - initialize(); + for (const auto &groupName : this->project->groupNames) { + for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { + insertMapItem(mapName, groupName); + } + } } Qt::DropActions MapGroupModel::supportedDropActions() const { @@ -173,7 +292,7 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i QModelIndex groupIndex = index(row, 0, parentIndex); QStandardItem *groupItem = this->itemFromIndex(groupIndex); - createGroupItem(groupName, groupItem); + createMapFolderItem(groupName, groupItem); for (QString mapName : mapsToMove) { QStandardItem *mapItem = createMapItem(mapName); @@ -251,143 +370,36 @@ void MapGroupModel::updateProject() { this->project->hasUnsavedDataChanges = true; } -QStandardItem *MapGroupModel::createGroupItem(QString groupName, QStandardItem *group) { - if (!group) group = new QStandardItem; - group->setText(groupName); - group->setData(groupName, MapListUserRoles::NameRole); - group->setData("map_group", MapListUserRoles::TypeRole); - group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); - this->groupItems.insert(groupName, group); - return group; -} - -QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { - if (!map) map = new QStandardItem; - map->setData(mapName, MapListUserRoles::NameRole); - map->setData("map_name", MapListUserRoles::TypeRole); - map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->mapItems[mapName] = map; - return map; -} - -QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { - QStandardItem *group = createGroupItem(groupName); - this->root->appendRow(group); - return group; -} - void MapGroupModel::removeItem(QStandardItem *item) { this->removeRow(item->row()); this->updateProject(); } -QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { - QStandardItem *group = this->groupItems[groupName]; - if (!group) { - group = insertGroupItem(groupName); - } - QStandardItem *map = createMapItem(mapName); - group->appendRow(map); - return map; -} - -void MapGroupModel::initialize() { - this->groupItems.clear(); - this->mapItems.clear(); - - - for (const auto &groupName : this->project->groupNames) { - QStandardItem *group = createGroupItem(groupName); - root->appendRow(group); - for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { - group->appendRow(createMapItem(mapName)); - } - } -} - -QStandardItem *MapGroupModel::getItem(const QModelIndex &index) const { - if (index.isValid()) { - QStandardItem *item = static_cast(index.internalPointer()); - if (item) - return item; - } - return this->root; -} - -QModelIndex MapGroupModel::indexOf(QString mapName) const { - if (this->mapItems.contains(mapName)) { - return this->mapItems[mapName]->index(); - } - return QModelIndex(); -} - QVariant MapGroupModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) return QVariant(); + if (!index.isValid()) + return QVariant(); int row = index.row(); int col = index.column(); - if (role == Qt::DecorationRole) { - static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); - static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); - static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); - - static QIcon mapFolderIcon; - static QIcon folderIcon; - static bool loaded = false; - if (!loaded) { - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - loaded = true; - } - - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); - - if (type == "map_group") { - if (!item->hasChildren()) { - return folderIcon; - } - return mapFolderIcon; - } else if (type == "map_name") { - QString mapName = item->data(MapListUserRoles::NameRole).toString(); - if (mapName == this->openMap) { - return mapOpenedIcon; - } - else if (this->project->mapCache.contains(mapName)) { - if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { - return mapEditedIcon; - } - else { - return mapIcon; - } - } - return mapGrayIcon; - } - } - else if (role == Qt::DisplayRole) { - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QStandardItem *item = this->getItem(index)->child(row, col); + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QString name = item->data(MapListUserRoles::NameRole).toString(); + if (role == Qt::DisplayRole) { if (type == "map_name") { - return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(MapListUserRoles::NameRole).toString(); + return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name; } - else if (type == "map_group") { - return item->data(MapListUserRoles::NameRole).toString(); + else if (type == this->folderTypeName) { + return name; } } - - return QStandardItemModel::data(index, role); + return MapListModel::data(index, role); } bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == MapListUserRoles::NameRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { - // verify uniqueness of new group name - // TODO: Check that the name is a valid symbol name (i.e. only word characters, not starting with a number) - if (this->project->groupNames.contains(value.toString())) { + if (!this->project->isIdentifierUnique(value.toString())) { return false; } } @@ -399,50 +411,15 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int -MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) { - this->project = project; - this->root = this->invisibleRootItem(); +MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(project, parent) { + this->folderTypeName = "map_section"; - initialize(); -} - -QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) { - QStandardItem *area = new QStandardItem; - area->setText(mapsecName); - area->setEditable(false); - area->setData(mapsecName, MapListUserRoles::NameRole); - area->setData("map_section", MapListUserRoles::TypeRole); - // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->areaItems.insert(mapsecName, area); - return area; -} - -QStandardItem *MapAreaModel::createMapItem(QString mapName) { - QStandardItem *map = new QStandardItem; - map->setText(mapName); - map->setEditable(false); - map->setData(mapName, MapListUserRoles::NameRole); - map->setData("map_name", MapListUserRoles::TypeRole); - // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->mapItems.insert(mapName, map); - return map; -} - -QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { - QStandardItem *item = createAreaItem(areaName); - this->root->appendRow(item); - this->sort(0, Qt::AscendingOrder); - return item; -} - -QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName) { - QStandardItem *area = this->areaItems[areaName]; - if (!area) { - return nullptr; + for (const auto &mapName : this->project->mapNames) { + insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); } - QStandardItem *map = createMapItem(mapName); - area->appendRow(map); - return map; + + this->sortingEnabled = true; + sort(0, Qt::AscendingOrder); } void MapAreaModel::removeItem(QStandardItem *item) { @@ -450,227 +427,52 @@ void MapAreaModel::removeItem(QStandardItem *item) { this->removeRow(item->row()); } -void MapAreaModel::initialize() { - this->areaItems.clear(); - this->mapItems.clear(); - for (const auto &idName : this->project->mapSectionIdNames) { - this->root->appendRow(createAreaItem(idName)); - } + +LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) { + this->folderTypeName = "map_layout"; for (const auto &mapName : this->project->mapNames) { - const QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); - if (this->areaItems.contains(mapsecName)) - this->areaItems[mapsecName]->appendRow(createMapItem(mapName)); + insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); } - this->sort(0, Qt::AscendingOrder); -} - -QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const { - if (index.isValid()) { - QStandardItem *item = static_cast(index.internalPointer()); - if (item) - return item; - } - return this->root; -} - -QModelIndex MapAreaModel::indexOf(QString mapName) const { - if (this->mapItems.contains(mapName)) { - return this->mapItems[mapName]->index(); - } - return QModelIndex(); -} - -QVariant MapAreaModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) return QVariant(); - - int row = index.row(); - int col = index.column(); - - if (role == Qt::DecorationRole) { - static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); - static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); - static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); - - static QIcon mapFolderIcon; - static QIcon folderIcon; - static bool loaded = false; - if (!loaded) { - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - loaded = true; - } - - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); - - if (type == "map_section") { - if (item->hasChildren()) { - return mapFolderIcon; - } - return folderIcon; - } else if (type == "map_name") { - QString mapName = item->data(MapListUserRoles::NameRole).toString(); - if (mapName == this->openMap) { - return mapOpenedIcon; - } - else if (this->project->mapCache.contains(mapName)) { - if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { - return mapEditedIcon; - } - else { - return mapIcon; - } - } - return mapGrayIcon; - } - } - else if (role == Qt::DisplayRole) { - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); - - if (type == "map_section") { - return item->data(MapListUserRoles::NameRole).toString(); - } - } - - return QStandardItemModel::data(index, role); -} - - - -LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(parent) { - this->project = project; - this->root = this->invisibleRootItem(); - - initialize(); -} - -QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { - QStandardItem *layout = new QStandardItem; - layout->setText(this->project->mapLayouts[layoutId]->name); - layout->setEditable(false); - layout->setData(layoutId, MapListUserRoles::NameRole); - layout->setData("map_layout", MapListUserRoles::TypeRole); - // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->layoutItems.insert(layoutId, layout); - return layout; -} - -QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { - QStandardItem *map = new QStandardItem; - map->setText(mapName); - map->setEditable(false); - map->setData(mapName, MapListUserRoles::NameRole); - map->setData("map_name", MapListUserRoles::TypeRole); - map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren); - this->mapItems.insert(mapName, map); - return map; -} - -QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { - QStandardItem *layoutItem = this->createLayoutItem(layoutId); - this->root->appendRow(layoutItem); - this->sort(0, Qt::AscendingOrder); - return layoutItem; -} - -QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { - QStandardItem *layout = nullptr; - if (this->layoutItems.contains(layoutId)) { - layout = this->layoutItems[layoutId]; - } - else { - layout = createLayoutItem(layoutId); - this->root->appendRow(layout); - } - if (!layout) { - return nullptr; - } - QStandardItem *map = createMapItem(mapName); - layout->appendRow(map); - return map; + this->sortingEnabled = true; + sort(0, Qt::AscendingOrder); } void LayoutTreeModel::removeItem(QStandardItem *) { // TODO: Deleting layouts not supported } - -void LayoutTreeModel::initialize() { - this->layoutItems.clear(); - this->mapItems.clear(); - - for (const auto &layoutId : this->project->layoutIds) { - this->root->appendRow(createLayoutItem(layoutId)); - } - - for (const auto &mapName : this->project->mapNames) { - QString layoutId = project->mapNameToLayoutId.value(mapName); - if (this->layoutItems.contains(layoutId)) - this->layoutItems[layoutId]->appendRow(createMapItem(mapName)); - } - - this->sort(0, Qt::AscendingOrder); -} - -QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { - if (index.isValid()) { - QStandardItem *item = static_cast(index.internalPointer()); - if (item) - return item; - } - return this->root; -} - -QModelIndex LayoutTreeModel::indexOf(QString layoutName) const { - if (this->layoutItems.contains(layoutName)) { - return this->layoutItems[layoutName]->index(); - } - return QModelIndex(); -} - QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) return QVariant(); + if (!index.isValid()) + return QVariant(); int row = index.row(); int col = index.column(); - if (role == Qt::DecorationRole) { - static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); - static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); - static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + const QStandardItem *item = this->getItem(index)->child(row, col); + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QString name = item->data(MapListUserRoles::NameRole).toString(); - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); + if (type == this->folderTypeName) { + const Layout* layout = this->project->mapLayouts.value(name); - if (type == "map_layout") { - QString layoutId = item->data(MapListUserRoles::NameRole).toString(); - if (layoutId == this->openLayout) { - return mapOpenedIcon; - } - else if (this->project->mapLayouts.contains(layoutId)) { - if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) { - return mapEditedIcon; - } - else if (!this->project->mapLayouts[layoutId]->loaded) { - return mapGrayIcon; - } - } - return mapIcon; + if (role == Qt::DecorationRole) { + // Map layouts are used as folders, but we display them with the same icons as maps. + if (name == this->activeItemName) + return this->mapOpenedIcon; + + if (!layout || !layout->loaded) + return this->mapGrayIcon; + return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } - else if (type == "map_name") { - return QVariant(); + else if (role == Qt::DisplayRole) { + // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. + if (layout) return layout->name; + } else if (role == Qt::ToolTipRole) { + if (layout) return layout->id; } - - return QVariant(); } - - return QStandardItemModel::data(index, role); + return MapListModel::data(index, role); } From f1a4b78ca9177b29a02fde8992ef81488eb4ecfc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 26 Nov 2024 15:33:56 -0500 Subject: [PATCH 096/364] Fix map duplication --- forms/newlayoutdialog.ui | 3 + forms/newmapdialog.ui | 154 +++++++++++++++-------------------- include/core/maplayout.h | 1 - include/project.h | 27 +++--- include/ui/newlayoutdialog.h | 6 +- include/ui/newmapdialog.h | 11 +-- src/project.cpp | 127 ++++++++++++++++------------- src/ui/newlayoutdialog.cpp | 101 ++++++++++++----------- src/ui/newmapdialog.cpp | 134 ++++++++++++++---------------- 9 files changed, 272 insertions(+), 292 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index 53d950fe..d285bdb1 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -117,6 +117,9 @@ + + false + color: rgb(255, 0, 0) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index e6aece18..3e133dfd 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,15 +25,40 @@ 0 0 229 - 306 + 228 10 + + + + Map Name + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + - + false @@ -48,29 +73,6 @@ - - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - color: rgb(255, 0, 0) - - - - - - true - - - @@ -87,57 +89,22 @@ - - - - <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> + + + + Layout ID - + - Map ID - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Can Fly To - - - - - - - Map Name + Map Group - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> - + true @@ -146,17 +113,7 @@ - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - true - - - - + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -166,8 +123,8 @@ - - + + false @@ -182,24 +139,40 @@ - + - - - - Map Group + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert - - + + - Layout ID + Can Fly To - + + + + <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 + + + + Qt::Orientation::Vertical @@ -218,6 +191,9 @@ + + false + color: rgb(255, 0, 0) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index cfd093ef..5d717a43 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -74,7 +74,6 @@ public: QUndoStack editHistory; // to simplify new layout settings transfer between functions - // TODO: Make this the equivalent of struct MapHeader struct Settings { QString id; QString name; diff --git a/include/project.h b/include/project.h index 58e2d5a3..9b6f64bf 100644 --- a/include/project.h +++ b/include/project.h @@ -81,16 +81,6 @@ public: bool wildEncountersLoaded; bool saveEmptyMapsec; - struct NewMapSettings { - QString name; - QString id; - QString group; - bool canFlyTo; - Layout::Settings layout; - MapHeader header; - }; - NewMapSettings newMapSettings; - void set_root(QString); void clearMapCache(); @@ -132,10 +122,23 @@ public: bool readMapGroups(); void addNewMapGroup(const QString &groupName); + struct NewMapSettings { + QString name; + QString group; + bool canFlyTo; + Layout::Settings layout; + MapHeader header; + }; + NewMapSettings newMapSettings; + Layout::Settings newLayoutSettings; + + QString getNewMapName() const; + QString getNewLayoutName() const; + void initNewMapSettings(); + void initNewLayoutSettings(); + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); - NewMapSettings getNewMapSettings() const; - Layout::Settings getNewLayoutSettings() const; bool isIdentifierUnique(const QString &identifier) const; QString getProjectTitle(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index ba4396b9..5fdb780f 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -30,10 +30,7 @@ signals: private: Ui::NewLayoutDialog *ui; Project *project; - Layout *importedLayout = nullptr; - - static Layout::Settings settings; - static bool initializedSettings; + const Layout *layoutToCopy; // 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. @@ -41,7 +38,6 @@ private: bool validateName(bool allowEmpty = false); void refresh(); - void saveSettings(); bool isExistingLayout() const; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 15dd3f6c..1d0f316f 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -19,8 +19,8 @@ class NewMapDialog : public QDialog Q_OBJECT public: explicit NewMapDialog(Project *project, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, const Map *mapToCopy = nullptr, 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(); virtual void accept() override; @@ -30,26 +30,21 @@ private: Project *project; CollapsibleSection *headerSection; MapHeaderForm *headerForm; - Map *importedMap = nullptr; - - static Project::NewMapSettings settings; - static bool initializedSettings; + const Map *mapToCopy; // 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 setUI(const Project::NewMapSettings &settings); + void refresh(); void saveSettings(); void setLayout(const Layout *mapLayout); 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); }; diff --git a/src/project.cpp b/src/project.cpp index 3d8d3f9c..f6cddfec 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -106,6 +106,9 @@ bool Project::load() { && readEventGraphics() && readSongNames() && readMapGroups(); + + initNewLayoutSettings(); + initNewMapSettings(); applyParsedLimits(); return success; } @@ -366,16 +369,24 @@ bool Project::loadMapData(Map* map) { Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* toDuplicate) { Map *map = toDuplicate ? new Map(*toDuplicate) : new Map; map->setName(settings.name); - map->setConstantName(settings.id); map->setHeader(settings.header); map->setNeedsHealLocation(settings.canFlyTo); + // Generate a unique MAP constant. + int suffix = 2; + const QString baseMapConstant = Map::mapConstantFromName(map->name()); + QString mapConstant = baseMapConstant; + while (!isIdentifierUnique(mapConstant)) { + mapConstant = QString("%1_%2").arg(baseMapConstant).arg(suffix++); + } + map->setConstantName(mapConstant); + Layout *layout = this->mapLayouts.value(settings.layout.id); if (layout) { // Layout already exists map->setNeedsLayoutDir(false); // TODO: Remove this member? } else { - layout = createNewLayout(settings.layout); + layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); } if (!layout) { delete map; @@ -1960,60 +1971,6 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } -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); - newMapId = Map::mapConstantFromName(newMapName); - } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(newMapId)); - - 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; -} - -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); - newLayoutId = Layout::layoutConstantFromName(newLayoutName); - } while (!isIdentifierUnique(newLayoutId) || !isIdentifierUnique(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; -} - // When we ask the user to provide a new identifier for something (like a map name or MAPSEC 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. @@ -2040,6 +1997,64 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return true; } +QString Project::getNewMapName() const { + // Ensure default name/ID doesn't already exist. + int i = 0; + QString newMapName; + do { + newMapName = QString("NewMap%1").arg(++i); + } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(Map::mapConstantFromName(newMapName))); + return newMapName; +} + +QString Project::getNewLayoutName() const { + // Ensure default name/ID doesn't already exist. + int i = 0; + QString newLayoutName; + do { + newLayoutName = QString("NewLayout%1").arg(++i); + } while (!isIdentifierUnique(newLayoutName) || !isIdentifierUnique(Layout::layoutConstantFromName(newLayoutName))); + return newLayoutName; +} + +void Project::initNewMapSettings() { + this->newMapSettings.name = getNewMapName(); + this->newMapSettings.group = this->groupNames.at(0); + this->newMapSettings.canFlyTo = false; + + this->newMapSettings.layout.name = Layout::layoutNameFromMapName(this->newMapSettings.name); + this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); + 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(); + + 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); +} + +void Project::initNewLayoutSettings() { + this->newLayoutSettings.name = getNewLayoutName(); + this->newLayoutSettings.id = Layout::layoutConstantFromName(this->newLayoutSettings.name); + this->newLayoutSettings.width = getDefaultMapDimension(); + this->newLayoutSettings.height = getDefaultMapDimension(); + this->newLayoutSettings.borderWidth = DEFAULT_BORDER_WIDTH; + this->newLayoutSettings.borderHeight = DEFAULT_BORDER_HEIGHT; + this->newLayoutSettings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); + this->newLayoutSettings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); +} + Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) { Project::DataQualifiers qualifiers; diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index de41a3d6..7b9d347f 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -9,30 +9,56 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -Layout::Settings NewLayoutDialog::settings = {}; -bool NewLayoutDialog::initializedSettings = false; - NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : + NewLayoutDialog(project, nullptr, parent) +{} + +NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) : QDialog(parent), - ui(new Ui::NewLayoutDialog) + ui(new Ui::NewLayoutDialog), + layoutToCopy(layoutToCopy) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); - ui->label_GenericError->setVisible(false); this->project = project; - Layout::Settings newSettings = project->getNewLayoutSettings(); - if (!initializedSettings) { - // The first time this dialog is opened we initialize all the default settings. - settings = newSettings; - initializedSettings = true; + QString newName; + QString newId; + if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { + // Duplicating a layout, the initial name will be the base layout's name + // with a numbered suffix to make it unique. + // Note: Layouts imported with AdvanceMap have no name, so they'll use the default new layout name instead. + + // If the layout name ends with the default '_Layout' suffix we'll ignore it. + // This is because (normally) the ID for these layouts will not have this suffix, + // so you can end up in a situation where you might have Map_Layout and Map_2_Layout, + // and if you try to duplicate Map_Layout the next available name (because of ID collisions) + // would be Map_Layout_3 instead of Map_3_Layout. + QString baseName = this->layoutToCopy->name; + QString suffix = "_Layout"; + if (baseName.length() > suffix.length() && baseName.endsWith(suffix)) { + baseName.truncate(baseName.length() - suffix.length()); + } else { + suffix = ""; + } + + int i = 2; + do { + newName = QString("%1_%2%3").arg(baseName).arg(i).arg(suffix); + newId = QString("%1_%2").arg(this->layoutToCopy->id).arg(i); + i++; + } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); } 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; + newName = project->getNewLayoutName(); + newId = Layout::layoutConstantFromName(newName); } + + // We reset these settings for every session with the new layout dialog. + // The rest of the settings are preserved in the project between sessions. + project->newLayoutSettings.name = newName; + project->newLayoutSettings.id = newId; + ui->newLayoutForm->initUi(project); // Identifiers can only contain word characters, and cannot start with a digit. @@ -47,53 +73,34 @@ NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : adjustSize(); } -// Creating new layout from an existing layout (e.g. via AdvanceMap import, or duplicating from map list). -NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) : - NewLayoutDialog(project, parent) -{ - if (!layoutToCopy) - return; - - this->importedLayout = layoutToCopy->copy(); - if (!this->importedLayout->name.isEmpty()) { - // If the layout we're duplicating has a name and ID we'll initialize the name/ID fields - // using that name and add a suffix to make it unique. - // Layouts imported with AdvanceMap won't have a name/ID. - int i = 2; - do { - settings.name = QString("%1_%2").arg(this->importedLayout->name).arg(i); - settings.id = QString("%1_%2").arg(this->importedLayout->id).arg(i); - i++; - } while (!this->project->isIdentifierUnique(settings.name) || !this->project->isIdentifierUnique(settings.id)); - } - refresh(); -} - NewLayoutDialog::~NewLayoutDialog() { saveSettings(); - delete this->importedLayout; delete ui; } void NewLayoutDialog::refresh() { - if (this->importedLayout) { + const Layout::Settings *settings = &this->project->newLayoutSettings; + + if (this->layoutToCopy) { // If we're importing a layout then some settings will be enforced. - ui->newLayoutForm->setSettings(this->importedLayout->settings()); + ui->newLayoutForm->setSettings(this->layoutToCopy->settings()); ui->newLayoutForm->setDisabled(true); } else { - ui->newLayoutForm->setSettings(settings); + ui->newLayoutForm->setSettings(*settings); ui->newLayoutForm->setDisabled(false); } - ui->lineEdit_Name->setText(settings.name); - ui->lineEdit_LayoutID->setText(settings.id); + ui->lineEdit_Name->setText(settings->name); + ui->lineEdit_LayoutID->setText(settings->id); } void NewLayoutDialog::saveSettings() { - settings = ui->newLayoutForm->settings(); - settings.id = ui->lineEdit_LayoutID->text(); - settings.name = ui->lineEdit_Name->text(); + Layout::Settings *settings = &this->project->newLayoutSettings; + + *settings = ui->newLayoutForm->settings(); + settings->id = ui->lineEdit_LayoutID->text(); + settings->name = ui->lineEdit_Name->text(); } bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { @@ -146,7 +153,7 @@ void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - settings = this->project->getNewLayoutSettings(); + this->project->initNewLayoutSettings(); refresh(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); @@ -165,7 +172,7 @@ void NewLayoutDialog::accept() { // Update settings from UI saveSettings(); - Layout *layout = this->project->createNewLayout(settings, this->importedLayout); + Layout *layout = this->project->createNewLayout(this->project->newLayoutSettings, this->layoutToCopy); if (!layout) { ui->label_GenericError->setText(QString("Failed to create layout. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 59fe0a16..e6d3101b 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -10,30 +10,41 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -Project::NewMapSettings NewMapDialog::settings = {}; -bool NewMapDialog::initializedSettings = false; - NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : + NewMapDialog(project, nullptr, parent) +{} + +NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : QDialog(parent), - ui(new Ui::NewMapDialog) + ui(new Ui::NewMapDialog), + mapToCopy(mapToCopy) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); - ui->label_GenericError->setVisible(false); this->project = project; - Project::NewMapSettings newSettings = project->getNewMapSettings(); - if (!initializedSettings) { - // The first time this dialog is opened we initialize all the default settings. - settings = newSettings; - initializedSettings = true; + QString newMapName; + QString newLayoutId; + if (this->mapToCopy) { + // Duplicating a map, the initial name will be the base map's name + // with a numbered suffix to make it unique. + int i = 2; + do { + newMapName = QString("%1_%2").arg(this->mapToCopy->name()).arg(i++); + newLayoutId = Layout::layoutConstantFromName(newMapName); + } while (!project->isIdentifierUnique(newMapName) || !project->isIdentifierUnique(newLayoutId)); } 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; + // Not duplicating a map, get a generic new map name. + newMapName = project->getNewMapName(); + newLayoutId = Layout::layoutConstantFromName(newMapName); } + + // We reset these settings for every session with the new map dialog. + // The rest of the settings are preserved in the project between sessions. + project->newMapSettings.name = newMapName; + project->newMapSettings.layout.id = newLayoutId; + ui->newLayoutForm->initUi(project); ui->comboBox_Group->addItems(project->groupNames); @@ -43,7 +54,6 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : 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); @@ -60,7 +70,7 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); - setUI(settings); + refresh(); adjustSize(); // TODO: Save geometry? } @@ -79,52 +89,53 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL this->headerForm->setLocation(mapListItem); break; case MapListTab::Layouts: + // We specifically lock the layout ID because otherwise the setting would be overwritten when + // the user changes the map name (which will normally automatically update the layout ID to match). + // For the Group/Area settings above we don't care if the user changes them afterwards. ui->comboBox_LayoutID->setTextItem(mapListItem); + ui->comboBox_LayoutID->setDisabled(true); break; } } -NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : - NewMapDialog(project, parent) -{ - if (!mapToCopy) - return; - // TODO -} - NewMapDialog::~NewMapDialog() { saveSettings(); - delete this->importedMap; delete ui; } -void NewMapDialog::setUI(const Project::NewMapSettings &settings) { - ui->lineEdit_Name->setText(settings.name); - ui->lineEdit_MapID->setText(settings.id); - ui->comboBox_Group->setTextItem(settings.group); - ui->comboBox_LayoutID->setTextItem(settings.layout.id); - if (this->importedMap && this->importedMap->layout()) { +// Reload the UI from the last-saved settings. +void NewMapDialog::refresh() { + const Project::NewMapSettings *settings = &this->project->newMapSettings; + + ui->lineEdit_Name->setText(settings->name); + ui->comboBox_Group->setTextItem(settings->group); + + // If the layout combo box is disabled, it's because we're enforcing the setting. Leave it unchanged. + if (ui->comboBox_LayoutID->isEnabled()) + ui->comboBox_LayoutID->setTextItem(settings->layout.id); + + if (this->mapToCopy && this->mapToCopy->layout()) { // When importing a layout these settings shouldn't be changed. - ui->newLayoutForm->setSettings(this->importedMap->layout()->settings()); + ui->newLayoutForm->setSettings(this->mapToCopy->layout()->settings()); } else { - ui->newLayoutForm->setSettings(settings.layout); + ui->newLayoutForm->setSettings(settings->layout); } - ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); - this->headerForm->setHeaderData(settings.header); + ui->checkBox_CanFlyTo->setChecked(settings->canFlyTo); + this->headerForm->setHeaderData(settings->header); } void NewMapDialog::saveSettings() { - settings.name = ui->lineEdit_Name->text(); - settings.id = ui->lineEdit_MapID->text(); - settings.group = ui->comboBox_Group->currentText(); - 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); // TODO: Verify uniqueness - settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); - settings.header = this->headerForm->headerData(); + Project::NewMapSettings *settings = &this->project->newMapSettings; + + settings->name = ui->lineEdit_Name->text(); + settings->group = ui->comboBox_Group->currentText(); + settings->layout = ui->newLayoutForm->settings(); + settings->layout.id = ui->comboBox_LayoutID->currentText(); + settings->layout.name = Layout::layoutNameFromMapName(settings->name); // TODO: Verify uniqueness + settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + settings->header = this->headerForm->headerData(); + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } @@ -138,30 +149,6 @@ void NewMapDialog::setLayout(const Layout *layout) { } } -bool NewMapDialog::validateMapID(bool allowEmpty) { - QString id = ui->lineEdit_MapID->text(); - const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - - QString errorText; - if (id.isEmpty() || id == expectedPrefix) { - if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text()); - } else if (!id.startsWith(expectedPrefix)) { - errorText = QString("%1 must start with '%2'.").arg(ui->label_MapID->text()).arg(expectedPrefix); - } else if (!this->project->isIdentifierUnique(id)) { - errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id); - } - - bool isValid = errorText.isEmpty(); - ui->label_MapIDError->setText(errorText); - ui->label_MapIDError->setVisible(!isValid); - ui->lineEdit_MapID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); - return isValid; -} - -void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { - validateMapID(true); -} - bool NewMapDialog::validateName(bool allowEmpty) { QString name = ui->lineEdit_Name->text(); @@ -181,7 +168,6 @@ 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)); } @@ -216,7 +202,7 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); } else if (!this->project->isIdentifierUnique(layoutId)) { // Layout name is already in use by something. If we're duplicating a map this isn't allowed. - if (this->importedMap) { + if (this->mapToCopy) { errorText = QString("%1 is not unique.").arg(ui->label_LayoutID->text()); // If we're not duplicating a map this is ok as long as it's the name of an existing layout. } else if (!this->project->layoutIds.contains(layoutId)) { @@ -241,7 +227,8 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - setUI(this->project->getNewMapSettings()); + this->project->initNewMapSettings(); + refresh(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -251,7 +238,6 @@ void NewMapDialog::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 (!validateMapID()) success = false; if (!validateName()) success = false; if (!validateGroup()) success = false; if (!validateLayoutID()) success = false; @@ -261,7 +247,7 @@ void NewMapDialog::accept() { // Update settings from UI saveSettings(); - Map *map = this->project->createNewMap(settings, this->importedMap); + Map *map = this->project->createNewMap(this->project->newMapSettings, this->mapToCopy); if (!map) { ui->label_GenericError->setText(QString("Failed to create map. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); From 6aa88023336013a53230eb536202c0da7ded337b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 27 Nov 2024 00:15:21 -0500 Subject: [PATCH 097/364] Fix map list empty folder regression --- src/ui/maplistmodels.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 6d5dd45d..1890684b 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -203,6 +203,7 @@ MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(p this->editable = true; for (const auto &groupName : this->project->groupNames) { + insertMapFolderItem(groupName); for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { insertMapItem(mapName, groupName); } @@ -414,6 +415,9 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(project, parent) { this->folderTypeName = "map_section"; + for (const auto &idName : this->project->mapSectionIdNames) { + insertMapFolderItem(idName); + } for (const auto &mapName : this->project->mapNames) { insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); } @@ -432,6 +436,9 @@ void MapAreaModel::removeItem(QStandardItem *item) { LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) { this->folderTypeName = "map_layout"; + for (const auto &layoutId : this->project->layoutIds) { + insertMapFolderItem(layoutId); + } for (const auto &mapName : this->project->mapNames) { insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); } From 06a263c6895370cc968be49c7fa7251eb1a30474 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 27 Nov 2024 02:41:57 -0500 Subject: [PATCH 098/364] Fix regression to map stitch images from layout split --- include/ui/mapimageexporter.h | 17 ++- src/ui/mapimageexporter.cpp | 277 ++++++++++++++++++---------------- 2 files changed, 160 insertions(+), 134 deletions(-) diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 37df3038..88a00447 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -45,22 +45,25 @@ public: private: Ui::MapImageExporter *ui; - Layout *layout = nullptr; - Map *map = nullptr; - Editor *editor = nullptr; - QGraphicsScene *scene = nullptr; + Layout *m_layout = nullptr; + Map *m_map = nullptr; + Editor *m_editor = nullptr; + QGraphicsScene *m_scene = nullptr; - QPixmap preview; + QPixmap m_preview; - ImageExporterSettings settings; - ImageExporterMode mode = ImageExporterMode::Normal; + ImageExporterSettings m_settings; + ImageExporterMode m_mode = ImageExporterMode::Normal; void updatePreview(); void scalePreview(); void updateShowBorderState(); void saveImage(); QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder); + QPixmap getFormattedMapPixmap(); QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false); + QPixmap getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder = false, bool ignoreGrid = false); + void paintGrid(QPixmap *pixmap, bool ignoreBorder = false); bool historyItemAppliesToFrame(const QUndoCommand *command); protected: diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index c92b7a4b..4596ba36 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -36,25 +36,25 @@ QString getDescription(ImageExporterMode mode) { return ""; } -MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExporterMode mode) : - QDialog(parent_), +MapImageExporter::MapImageExporter(QWidget *parent, Editor *editor, ImageExporterMode mode) : + QDialog(parent), ui(new Ui::MapImageExporter) { - this->setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); - this->map = editor_->map; - this->layout = editor_->layout; - this->editor = editor_; - this->mode = mode; - this->setWindowTitle(getTitle(this->mode)); - this->ui->label_Description->setText(getDescription(this->mode)); - this->ui->groupBox_Connections->setVisible(this->mode != ImageExporterMode::Stitch); - this->ui->groupBox_Timelapse->setVisible(this->mode == ImageExporterMode::Timelapse); + m_map = editor->map; + m_layout = editor->layout; + m_editor = editor; + m_mode = mode; + setWindowTitle(getTitle(m_mode)); + ui->label_Description->setText(getDescription(m_mode)); + ui->groupBox_Connections->setVisible(m_mode != ImageExporterMode::Stitch); + ui->groupBox_Timelapse->setVisible(m_mode == ImageExporterMode::Timelapse); - if (this->map) { - this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); - this->ui->comboBox_MapSelection->setCurrentText(map->name()); - this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + if (m_map) { + ui->comboBox_MapSelection->addItems(editor->project->mapNames); + ui->comboBox_MapSelection->setCurrentText(m_map->name()); + ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down } connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage); @@ -62,7 +62,7 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor } MapImageExporter::~MapImageExporter() { - delete scene; + delete m_scene; delete ui; } @@ -80,15 +80,15 @@ void MapImageExporter::resizeEvent(QResizeEvent *event) { void MapImageExporter::saveImage() { // Make sure preview is up-to-date before we save. - if (this->preview.isNull()) + if (m_preview.isNull()) updatePreview(); - if (this->preview.isNull()) + if (m_preview.isNull()) return; - const QString title = getTitle(this->mode); - const QString itemName = this->map ? this->map->name() : this->layout->name; + const QString title = getTitle(m_mode); + const QString itemName = m_map ? m_map->name() : m_layout->name; QString defaultFilename; - switch (this->mode) + switch (m_mode) { case ImageExporterMode::Normal: defaultFilename = itemName; @@ -104,21 +104,21 @@ void MapImageExporter::saveImage() { QString defaultFilepath = QString("%1/%2.%3") .arg(FileDialog::getDirectory()) .arg(defaultFilename) - .arg(this->mode == ImageExporterMode::Timelapse ? "gif" : "png"); - QString filter = this->mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)"; + .arg(m_mode == ImageExporterMode::Timelapse ? "gif" : "png"); + QString filter = m_mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)"; QString filepath = FileDialog::getSaveFileName(this, title, defaultFilepath, filter); if (!filepath.isEmpty()) { - switch (this->mode) { + switch (m_mode) { case ImageExporterMode::Normal: case ImageExporterMode::Stitch: // Normal and Stitch modes already have the image ready to go in the preview. - this->preview.save(filepath); + m_preview.save(filepath); break; case ImageExporterMode::Timelapse: // Timelapse will play in order of layout changes then map changes (events) // TODO: potentially update in the future? QGifImage timelapseImg; - timelapseImg.setDefaultDelay(this->settings.timelapseDelayMs); + timelapseImg.setDefaultDelay(m_settings.timelapseDelayMs); timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); // lambda to avoid redundancy @@ -130,9 +130,9 @@ void MapImageExporter::saveImage() { progress.setMaximum(1); progress.setValue(0); - int maxWidth = this->layout->getWidth() * 16; - int maxHeight = this->layout->getHeight() * 16; - if (this->settings.showBorder) { + int maxWidth = m_layout->getWidth() * 16; + int maxHeight = m_layout->getHeight() * 16; + if (m_settings.showBorder) { maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; } @@ -141,9 +141,9 @@ void MapImageExporter::saveImage() { while (historyStack->canUndo()) { progress.setValue(i); historyStack->undo(); - int width = this->layout->getWidth() * 16; - int height = this->layout->getHeight() * 16; - if (this->settings.showBorder) { + int width = m_layout->getWidth() * 16; + int height = m_layout->getHeight() * 16; + if (m_settings.showBorder) { width += 2 * STITCH_MODE_BORDER_DISTANCE * 16; height += 2 * STITCH_MODE_BORDER_DISTANCE * 16; } @@ -174,7 +174,7 @@ void MapImageExporter::saveImage() { historyStack->redo(); } progress.setValue(progress.maximum() - i); - QPixmap pixmap = this->getFormattedMapPixmap(this->map); + QPixmap pixmap = getFormattedMapPixmap(); if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) { QPixmap pixmap2 = QPixmap(maxWidth, maxHeight); QPainter painter(&pixmap2); @@ -184,7 +184,7 @@ void MapImageExporter::saveImage() { pixmap = pixmap2; } timelapseImg.addFrame(pixmap.toImage()); - for (int j = 0; j < this->settings.timelapseSkipAmount; j++) { + for (int j = 0; j < m_settings.timelapseSkipAmount; j++) { if (i > 0) { i--; historyStack->redo(); @@ -197,21 +197,21 @@ void MapImageExporter::saveImage() { } } // The latest map state is the last animated frame. - QPixmap pixmap = this->getFormattedMapPixmap(this->map); + QPixmap pixmap = getFormattedMapPixmap(); timelapseImg.addFrame(pixmap.toImage()); progress.close(); }; - if (this->layout) - generateTimelapseFromHistory("Building layout timelapse...", &this->layout->editHistory); + if (m_layout) + generateTimelapseFromHistory("Building layout timelapse...", &m_layout->editHistory); - if (this->map) - generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory()); + if (m_map) + generateTimelapseFromHistory("Building map timelapse...", m_map->editHistory()); timelapseImg.save(filepath); break; } - this->close(); + close(); } } @@ -230,26 +230,26 @@ bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) { case CommandId::ID_PaintCollision: case CommandId::ID_BucketFillCollision: case CommandId::ID_MagicFillCollision: - return this->settings.showCollision; + return m_settings.showCollision; case CommandId::ID_PaintBorder: - return this->settings.showBorder; + return m_settings.showBorder; case CommandId::ID_MapConnectionMove: case CommandId::ID_MapConnectionChangeDirection: case CommandId::ID_MapConnectionChangeMap: case CommandId::ID_MapConnectionAdd: case CommandId::ID_MapConnectionRemove: - return this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections; + return m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections; case CommandId::ID_EventMove: case CommandId::ID_EventShift: case CommandId::ID_EventCreate: case CommandId::ID_EventDelete: case CommandId::ID_EventDuplicate: { bool eventTypeIsApplicable = - (this->settings.showObjects && (command->id() & IDMask_EventType_Object) != 0) - || (this->settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0) - || (this->settings.showBGs && (command->id() & IDMask_EventType_BG) != 0) - || (this->settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0) - || (this->settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0); + (m_settings.showObjects && (command->id() & IDMask_EventType_Object) != 0) + || (m_settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0) + || (m_settings.showBGs && (command->id() & IDMask_EventType_BG) != 0) + || (m_settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0) + || (m_settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0); return eventTypeIsApplicable; } default: @@ -269,7 +269,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu QSet visited; QList stitchedMaps; QList unvisited; - unvisited.append(StitchedMap{0, 0, this->editor->map}); + unvisited.append(StitchedMap{0, 0, m_editor->map}); progress->setLabelText("Gathering stitched maps..."); while (!unvisited.isEmpty()) { @@ -362,7 +362,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu pixelX -= STITCH_MODE_BORDER_DISTANCE * 16; pixelY -= STITCH_MODE_BORDER_DISTANCE * 16; } - QPixmap pixmap = this->getFormattedMapPixmap(map.map); + QPixmap pixmap = getFormattedMapPixmap(map.map); painter.drawPixmap(pixelX, pixelY, pixmap); } @@ -383,7 +383,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu int pixelX = (map.x - minX) * 16; int pixelY = (map.y - minY) * 16; - QPixmap pixmapWithoutBorders = this->getFormattedMapPixmap(map.map, true); + QPixmap pixmapWithoutBorders = getFormattedMapPixmap(map.map, true); painter.drawPixmap(pixelX, pixelY, pixmapWithoutBorders); } } @@ -392,45 +392,50 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu } void MapImageExporter::updatePreview() { - if (this->scene) { - delete this->scene; - this->scene = nullptr; + if (m_scene) { + delete m_scene; + m_scene = nullptr; } - this->scene = new QGraphicsScene; + m_scene = new QGraphicsScene; - if (this->mode == ImageExporterMode::Stitch) { + if (m_mode == ImageExporterMode::Stitch) { QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); progress.setModal(true); progress.setMinimumDuration(1000); - this->preview = getStitchedImage(&progress, this->settings.showBorder); + m_preview = getStitchedImage(&progress, m_settings.showBorder); progress.close(); } else { // Timelapse mode doesn't currently have a real preview. It just displays the current map as in Normal mode. - this->preview = getFormattedMapPixmap(this->map); + m_preview = getFormattedMapPixmap(); } - this->scene->addPixmap(this->preview); - ui->graphicsView_Preview->setScene(scene); + m_scene->addPixmap(m_preview); + ui->graphicsView_Preview->setScene(m_scene); scalePreview(); } void MapImageExporter::scalePreview() { - if (this->scene && !this->settings.previewActualSize){ - ui->graphicsView_Preview->fitInView(this->scene->sceneRect(), Qt::KeepAspectRatioByExpanding); + if (m_scene && !m_settings.previewActualSize){ + ui->graphicsView_Preview->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatioByExpanding); } } -QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { - Layout *layout = this->map ? this->map->layout() : this->layout; - layout->render(true); +QPixmap MapImageExporter::getFormattedMapPixmap() { + return m_map ? getFormattedMapPixmap(m_map) : getFormattedLayoutPixmap(m_layout); +} +QPixmap MapImageExporter::getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder, bool ignoreGrid) { + if (!layout) + return QPixmap(); + + layout->render(true); QPixmap pixmap = layout->pixmap; - if (this->settings.showCollision) { + if (m_settings.showCollision) { QPainter collisionPainter(&pixmap); layout->renderCollision(true); - collisionPainter.setOpacity(editor->collisionOpacity); + collisionPainter.setOpacity(m_editor->collisionOpacity); collisionPainter.drawPixmap(0, 0, layout->collision_pixmap); collisionPainter.end(); } @@ -438,11 +443,11 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { // draw map border // note: this will break when allowing map to be selected from drop down maybe int borderHeight = 0, borderWidth = 0; - if (!ignoreBorder && this->settings.showBorder) { - int borderDistance = this->mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; + if (!ignoreBorder && m_settings.showBorder) { + int borderDistance = m_mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; layout->renderBorder(); - int borderHorzDist = editor->getBorderDrawDistance(layout->getBorderWidth()); - int borderVertDist = editor->getBorderDrawDistance(layout->getBorderHeight()); + int borderHorzDist = m_editor->getBorderDrawDistance(layout->getBorderWidth()); + int borderVertDist = m_editor->getBorderDrawDistance(layout->getBorderHeight()); borderWidth = borderDistance * 16; borderHeight = borderDistance * 16; QPixmap newPixmap = QPixmap(layout->pixmap.width() + borderWidth * 2, layout->pixmap.height() + borderHeight * 2); @@ -457,20 +462,34 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { pixmap = newPixmap; } - if (!this->map) { - return pixmap; - } + // The grid should be painted last, so if this layout pixmap is being painted + // as part of a map (which has more to paint after this) then don't paint the grid yet. + if (!ignoreGrid) + paintGrid(&pixmap, ignoreBorder); - if (!ignoreBorder && (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections)) { + return pixmap; +} + +QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { + if (!map) + return QPixmap(); + + QPixmap pixmap = getFormattedLayoutPixmap(map->layout(), ignoreBorder, true); + + if (!ignoreBorder && (m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections)) { // if showing connections, draw on outside of image QPainter connectionPainter(&pixmap); + + int borderDistance = m_mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; + int borderWidth = borderDistance * 16; + int borderHeight = borderDistance * 16; // TODO: Reading the connections from the editor and not 'map' is incorrect. - for (auto connectionItem : editor->connection_items) { + for (auto connectionItem : m_editor->connection_items) { const QString direction = connectionItem->connection->direction(); - if ((this->settings.showUpConnections && direction == "up") - || (this->settings.showDownConnections && direction == "down") - || (this->settings.showLeftConnections && direction == "left") - || (this->settings.showRightConnections && direction == "right")) + if ((m_settings.showUpConnections && direction == "up") + || (m_settings.showDownConnections && direction == "down") + || (m_settings.showLeftConnections && direction == "left") + || (m_settings.showRightConnections && direction == "right")) connectionPainter.drawImage(connectionItem->x() + borderWidth, connectionItem->y() + borderHeight, connectionItem->connection->getPixmap().toImage()); } @@ -478,37 +497,43 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { } // draw events - if (this->settings.showObjects || this->settings.showWarps || this->settings.showBGs || this->settings.showTriggers || this->settings.showHealLocations) { + if (m_settings.showObjects || m_settings.showWarps || m_settings.showBGs || m_settings.showTriggers || m_settings.showHealLocations) { QPainter eventPainter(&pixmap); int pixelOffset = 0; - if (!ignoreBorder && this->settings.showBorder) { - pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; + if (!ignoreBorder && m_settings.showBorder) { + pixelOffset = m_mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; } const QList events = map->getEvents(); for (const auto &event : events) { Event::Group group = event->getEventGroup(); - if ((this->settings.showObjects && group == Event::Group::Object) - || (this->settings.showWarps && group == Event::Group::Warp) - || (this->settings.showBGs && group == Event::Group::Bg) - || (this->settings.showTriggers && group == Event::Group::Coord) - || (this->settings.showHealLocations && group == Event::Group::Heal)) { - editor->project->setEventPixmap(event); + if ((m_settings.showObjects && group == Event::Group::Object) + || (m_settings.showWarps && group == Event::Group::Warp) + || (m_settings.showBGs && group == Event::Group::Bg) + || (m_settings.showTriggers && group == Event::Group::Coord) + || (m_settings.showHealLocations && group == Event::Group::Heal)) { + m_editor->project->setEventPixmap(event); eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage()); } } eventPainter.end(); } + paintGrid(&pixmap, ignoreBorder); + return pixmap; +} + +void MapImageExporter::paintGrid(QPixmap *pixmap, bool ignoreBorder) { // draw grid directly onto the pixmap // since the last grid lines are outside of the pixmap, add a pixel to the bottom and right - if (this->settings.showGrid) { + if (m_settings.showGrid) { + bool hasBorder = !ignoreBorder && m_settings.showBorder; int addX = 1, addY = 1; - if (borderHeight) addY = 0; - if (borderWidth) addX = 0; + if (hasBorder) addY = 0; + if (hasBorder) addX = 0; - QPixmap newPixmap= QPixmap(pixmap.width() + addX, pixmap.height() + addY); + QPixmap newPixmap= QPixmap(pixmap->width() + addX, pixmap->height() + addY); QPainter gridPainter(&newPixmap); - gridPainter.drawImage(QPoint(0, 0), pixmap.toImage()); + gridPainter.drawImage(QPoint(0, 0), pixmap->toImage()); for (int x = 0; x < newPixmap.width(); x += 16) { gridPainter.drawLine(x, 0, x, newPixmap.height()); } @@ -516,58 +541,56 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { gridPainter.drawLine(0, y, newPixmap.width(), y); } gridPainter.end(); - pixmap = newPixmap; + *pixmap = newPixmap; } - - return pixmap; } void MapImageExporter::updateShowBorderState() { // If any of the Connections settings are enabled then this setting is locked (it's implicitly enabled) - bool on = (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections); + bool on = (m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections); const QSignalBlocker blocker(ui->checkBox_Border); ui->checkBox_Border->setChecked(on); ui->checkBox_Border->setDisabled(on); - this->settings.showBorder = on; + m_settings.showBorder = on; } void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) { - this->settings.showCollision = (state == Qt::Checked); + m_settings.showCollision = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Grid_stateChanged(int state) { - this->settings.showGrid = (state == Qt::Checked); + m_settings.showGrid = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Border_stateChanged(int state) { - this->settings.showBorder = (state == Qt::Checked); + m_settings.showBorder = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Objects_stateChanged(int state) { - this->settings.showObjects = (state == Qt::Checked); + m_settings.showObjects = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Warps_stateChanged(int state) { - this->settings.showWarps = (state == Qt::Checked); + m_settings.showWarps = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_BGs_stateChanged(int state) { - this->settings.showBGs = (state == Qt::Checked); + m_settings.showBGs = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Triggers_stateChanged(int state) { - this->settings.showTriggers = (state == Qt::Checked); + m_settings.showTriggers = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_HealLocations_stateChanged(int state) { - this->settings.showHealLocations = (state == Qt::Checked); + m_settings.showHealLocations = (state == Qt::Checked); updatePreview(); } @@ -578,51 +601,51 @@ void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { const QSignalBlocker b_Objects(ui->checkBox_Objects); ui->checkBox_Objects->setChecked(on); ui->checkBox_Objects->setDisabled(on); - this->settings.showObjects = on; + m_settings.showObjects = on; const QSignalBlocker b_Warps(ui->checkBox_Warps); ui->checkBox_Warps->setChecked(on); ui->checkBox_Warps->setDisabled(on); - this->settings.showWarps = on; + m_settings.showWarps = on; const QSignalBlocker b_BGs(ui->checkBox_BGs); ui->checkBox_BGs->setChecked(on); ui->checkBox_BGs->setDisabled(on); - this->settings.showBGs = on; + m_settings.showBGs = on; const QSignalBlocker b_Triggers(ui->checkBox_Triggers); ui->checkBox_Triggers->setChecked(on); ui->checkBox_Triggers->setDisabled(on); - this->settings.showTriggers = on; + m_settings.showTriggers = on; const QSignalBlocker b_HealLocations(ui->checkBox_HealLocations); ui->checkBox_HealLocations->setChecked(on); ui->checkBox_HealLocations->setDisabled(on); - this->settings.showHealLocations = on; + m_settings.showHealLocations = on; updatePreview(); } void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) { - this->settings.showUpConnections = (state == Qt::Checked); + m_settings.showUpConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) { - this->settings.showDownConnections = (state == Qt::Checked); + m_settings.showDownConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) { - this->settings.showLeftConnections = (state == Qt::Checked); + m_settings.showLeftConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) { - this->settings.showRightConnections = (state == Qt::Checked); + m_settings.showRightConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } @@ -634,30 +657,30 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); ui->checkBox_ConnectionUp->setChecked(on); ui->checkBox_ConnectionUp->setDisabled(on); - this->settings.showUpConnections = on; + m_settings.showUpConnections = on; const QSignalBlocker b_Down(ui->checkBox_ConnectionDown); ui->checkBox_ConnectionDown->setChecked(on); ui->checkBox_ConnectionDown->setDisabled(on); - this->settings.showDownConnections = on; + m_settings.showDownConnections = on; const QSignalBlocker b_Left(ui->checkBox_ConnectionLeft); ui->checkBox_ConnectionLeft->setChecked(on); ui->checkBox_ConnectionLeft->setDisabled(on); - this->settings.showLeftConnections = on; + m_settings.showLeftConnections = on; const QSignalBlocker b_Right(ui->checkBox_ConnectionRight); ui->checkBox_ConnectionRight->setChecked(on); ui->checkBox_ConnectionRight->setDisabled(on); - this->settings.showRightConnections = on; + m_settings.showRightConnections = on; updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) { - this->settings.previewActualSize = (state == Qt::Checked); - if (this->settings.previewActualSize) { + m_settings.previewActualSize = (state == Qt::Checked); + if (m_settings.previewActualSize) { ui->graphicsView_Preview->resetTransform(); } else { scalePreview(); @@ -665,20 +688,20 @@ void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) { } void MapImageExporter::on_pushButton_Reset_pressed() { - this->settings = {}; + m_settings = {}; for (auto widget : this->findChildren()) { const QSignalBlocker b(widget); // Prevent calls to updatePreview widget->setChecked(false); } - ui->spinBox_TimelapseDelay->setValue(this->settings.timelapseDelayMs); - ui->spinBox_FrameSkip->setValue(this->settings.timelapseSkipAmount); + ui->spinBox_TimelapseDelay->setValue(m_settings.timelapseDelayMs); + ui->spinBox_FrameSkip->setValue(m_settings.timelapseSkipAmount); updatePreview(); } void MapImageExporter::on_spinBox_TimelapseDelay_valueChanged(int delayMs) { - this->settings.timelapseDelayMs = delayMs; + m_settings.timelapseDelayMs = delayMs; } void MapImageExporter::on_spinBox_FrameSkip_valueChanged(int skip) { - this->settings.timelapseSkipAmount = skip; + m_settings.timelapseSkipAmount = skip; } From 83ef14a2420e5418f92f866dcdc69b2f5fef77f8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 27 Nov 2024 02:42:14 -0500 Subject: [PATCH 099/364] Fix some problems with layout directory creation --- include/core/map.h | 3 -- include/core/maplayout.h | 6 ++- include/ui/newmapdialog.h | 1 - src/core/maplayout.cpp | 4 +- src/mainwindow.cpp | 4 +- src/project.cpp | 81 ++++++++++++++++++-------------------- src/ui/newlayoutdialog.cpp | 22 ++--------- src/ui/newlayoutform.cpp | 1 + src/ui/newmapdialog.cpp | 37 ++++++++++------- 9 files changed, 76 insertions(+), 83 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 00e7e63c..6e953877 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -67,12 +67,10 @@ public: QString sharedEventsMap() const { return m_sharedEventsMap; } QString sharedScriptsMap() const { return m_sharedScriptsMap; } - void setNeedsLayoutDir(bool needsLayoutDir) { m_needsLayoutDir = needsLayoutDir; } void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } - bool needsLayoutDir() const { return m_needsLayoutDir; } bool needsHealLocation() const { return m_needsHealLocation; } bool isPersistedToFile() const { return m_isPersistedToFile; } bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } @@ -121,7 +119,6 @@ private: bool m_isPersistedToFile = true; bool m_hasUnsavedDataChanges = false; - bool m_needsLayoutDir = true; bool m_needsHealLocation = false; bool m_scriptsLoaded = false; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 5d717a43..7761d7fc 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -22,8 +22,9 @@ public: Layout() {} Layout(const Layout &other); - static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); + static QString defaultSuffix(); + bool loaded = false; @@ -77,6 +78,9 @@ public: struct Settings { QString id; QString name; + // The name of a new layout's folder in `data/layouts/` is not always the same as the layout's name + // (e.g. the majority of the default layouts use the name of their associated map). + QString folderName; int width; int height; int borderWidth; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 1d0f316f..4b560bf1 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -40,7 +40,6 @@ private: void refresh(); void saveSettings(); - void setLayout(const Layout *mapLayout); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index c30ae798..26d4cb89 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,8 +32,8 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -QString Layout::layoutNameFromMapName(const QString &mapName) { - return QString("%1_Layout").arg(mapName); +QString Layout::defaultSuffix() { + return "_Layout"; } QString Layout::layoutConstantFromName(QString mapName) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bb01d5c5..9623963a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1382,7 +1382,9 @@ void MainWindow::mapListAddArea() { void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); - // TODO: Creating a new map shouldn't be automatically saved + // TODO: Creating a new map shouldn't be automatically saved. + // For one, it takes away the option to discard the new map. + // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); diff --git a/src/project.cpp b/src/project.cpp index f6cddfec..7772ac8c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -382,15 +382,17 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setConstantName(mapConstant); Layout *layout = this->mapLayouts.value(settings.layout.id); - if (layout) { - // Layout already exists - map->setNeedsLayoutDir(false); // TODO: Remove this member? - } else { - layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); - } if (!layout) { - delete map; - return nullptr; + // Layout doesn't already exist, create it. + layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); + if (!layout) { + // Layout creation failed. + delete map; + return nullptr; + } + } else { + // This layout already exists. Make sure it's loaded. + loadLayout(layout); } map->setLayout(layout); @@ -444,14 +446,17 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout layout->tileset_primary_label = settings.primaryTilesetLabel; layout->tileset_secondary_label = settings.secondaryTilesetLabel; - const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); + // If a special folder name was specified (as in the case when we're creating a layout for a new map) then use that name. + // Otherwise the new layout's folder name will just be the layout's name. + const QString folderName = !settings.folderName.isEmpty() ? settings.folderName : layout->name; + const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders) + folderName; + layout->border_path = folderPath + "/border.bin"; + layout->blockdata_path = folderPath + "/map.bin"; - // Create a new directory for the layout - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); - if (!QDir::root().mkdir(newLayoutDir)) { - logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); + // Create a new directory for the layout, if it doesn't already exist. + const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); delete layout; return nullptr; } @@ -493,14 +498,14 @@ bool Project::loadLayout(Layout *layout) { } Layout *Project::loadLayout(QString layoutId) { - if (mapLayouts.contains(layoutId)) { - Layout *layout = mapLayouts[layoutId]; + if (this->mapLayouts.contains(layoutId)) { + Layout *layout = this->mapLayouts[layoutId]; if (loadLayout(layout)) { return layout; } } - logError(QString("Error: Failed to load layout '%1'").arg(layoutId)); + logError(QString("Failed to load layout '%1'").arg(layoutId)); return nullptr; } @@ -509,10 +514,10 @@ bool Project::loadMapLayout(Map* map) { return true; } - if (mapLayouts.contains(map->layoutId())) { - map->setLayout(mapLayouts[map->layoutId()]); + if (this->mapLayouts.contains(map->layoutId())) { + map->setLayout(this->mapLayouts[map->layoutId()]); } else { - logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); + logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } @@ -535,8 +540,8 @@ void Project::clearMapLayouts() { bool Project::readMapLayouts() { clearMapLayouts(); - QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); - QString fullFilepath = QString("%1/%2").arg(root).arg(layoutsFilepath); + const QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); + const QString fullFilepath = QString("%1/%2").arg(this->root).arg(layoutsFilepath); fileWatcher.addPath(fullFilepath); QJsonDocument layoutsDoc; if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath)) { @@ -1295,40 +1300,32 @@ void Project::saveAllMaps() { void Project::saveMap(Map *map) { // Create/Modify a few collateral files for brand new maps. - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_map_folders); - QString mapDataDir = root + "/" + basePath + map->name(); + const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); + const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); if (!map->isPersistedToFile()) { - if (!QDir::root().mkdir(mapDataDir)) { - logError(QString("Error: failed to create directory for new map: '%1'").arg(mapDataDir)); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new map: '%1'").arg(fullPath)); } // Create file data/maps//scripts.inc QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name()); - saveTextFile(mapDataDir + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); + saveTextFile(fullPath + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); if (projectConfig.createMapTextFileEnabled) { // Create file data/maps//text.inc - saveTextFile(mapDataDir + "/text" + this->getScriptFileExtension(projectConfig.usePoryScript), "\n"); + saveTextFile(fullPath + "/text" + this->getScriptFileExtension(projectConfig.usePoryScript), "\n"); } // Simply append to data/event_scripts.s. - text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name()); + text = QString("\n\t.include \"%1/scripts.inc\"\n").arg(folderPath); if (projectConfig.createMapTextFileEnabled) { - text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name()); + text += QString("\t.include \"%1/text.inc\"\n").arg(folderPath); } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); - - // TODO: Either simplify this redundancy or explain why we need it (to create folders without the _Layout suffix) - if (map->needsLayoutDir()) { - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); - if (!QDir::root().mkdir(newLayoutDir)) { - logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); - } - } } // Create map.json for map data. - QString mapFilepath = QString("%1/map.json").arg(mapDataDir); + QString mapFilepath = fullPath + "/map.json"; QFile mapFile(mapFilepath); if (!mapFile.open(QIODevice::WriteOnly)) { logError(QString("Error: Could not open %1 for writing").arg(mapFilepath)); @@ -1428,7 +1425,6 @@ void Project::saveMap(Map *map) { } void Project::saveLayout(Layout *layout) { - // saveLayoutBorder(layout); saveLayoutBlockdata(layout); @@ -2022,7 +2018,8 @@ void Project::initNewMapSettings() { this->newMapSettings.group = this->groupNames.at(0); this->newMapSettings.canFlyTo = false; - this->newMapSettings.layout.name = Layout::layoutNameFromMapName(this->newMapSettings.name); + this->newMapSettings.layout.folderName = this->newMapSettings.name; + this->newMapSettings.layout.name = QString("%1%2").arg(this->newMapSettings.name).arg(Layout::defaultSuffix()); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); this->newMapSettings.layout.width = getDefaultMapDimension(); this->newMapSettings.layout.height = getDefaultMapDimension(); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 7b9d347f..79af74e5 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -28,26 +28,12 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { // Duplicating a layout, the initial name will be the base layout's name // with a numbered suffix to make it unique. - // Note: Layouts imported with AdvanceMap have no name, so they'll use the default new layout name instead. - - // If the layout name ends with the default '_Layout' suffix we'll ignore it. - // This is because (normally) the ID for these layouts will not have this suffix, - // so you can end up in a situation where you might have Map_Layout and Map_2_Layout, - // and if you try to duplicate Map_Layout the next available name (because of ID collisions) - // would be Map_Layout_3 instead of Map_3_Layout. - QString baseName = this->layoutToCopy->name; - QString suffix = "_Layout"; - if (baseName.length() > suffix.length() && baseName.endsWith(suffix)) { - baseName.truncate(baseName.length() - suffix.length()); - } else { - suffix = ""; - } - + // Note: If 'layoutToCopy' is an imported AdvanceMap layout it won't have + // a name, so it uses the default new layout name instead. int i = 2; do { - newName = QString("%1_%2%3").arg(baseName).arg(i).arg(suffix); - newId = QString("%1_%2").arg(this->layoutToCopy->id).arg(i); - i++; + newName = QString("%1_%2").arg(this->layoutToCopy->name).arg(i++); + newId = Layout::layoutConstantFromName(newName); } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); } else { newName = project->getNewLayoutName(); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 3b77b5c7..64c2fdfb 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -71,6 +71,7 @@ Layout::Settings NewLayoutForm::settings() const { return settings; } +// TODO: Validate while typing bool NewLayoutForm::validate() { // Make sure to call each validation function so that all errors are shown at once. bool valid = true; diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index e6d3101b..d362d2dc 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -71,12 +71,12 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); - adjustSize(); // TODO: Save geometry? + adjustSize(); } -// Adding new map to existing map list folder. Initialize settings accordingly. +// Adding new map to an existing map list folder. Initialize settings accordingly. // Even if we initialize settings like this we'll allow users to change them afterwards, -// because nothing is expecting them to stay at these values. +// because nothing is expecting them to stay at these values (with exception to layouts). NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) : NewMapDialog(project, parent) { @@ -132,21 +132,18 @@ void NewMapDialog::saveSettings() { settings->group = ui->comboBox_Group->currentText(); settings->layout = ui->newLayoutForm->settings(); settings->layout.id = ui->comboBox_LayoutID->currentText(); - settings->layout.name = Layout::layoutNameFromMapName(settings->name); // TODO: Verify uniqueness settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); -} + // TODO: Verify uniqueness. If the layout ID belongs to an existing layout we don't need to do this at all. + settings->layout.name = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); -void NewMapDialog::setLayout(const Layout *layout) { - if (layout) { - ui->comboBox_LayoutID->setTextItem(layout->id); - ui->newLayoutForm->setSettings(layout->settings()); - ui->newLayoutForm->setDisabled(true); - } else { - ui->newLayoutForm->setDisabled(false); - } + // Folders for new layouts created for new maps use the map name, rather than the layout name. + // There's no real reason for this, aside from maintaining consistency with the default layout + // folder names that do this (which would otherwise all have a '_Layout' suffix in the name). + settings->layout.folderName = settings->name; + + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } bool NewMapDialog::validateName(bool allowEmpty) { @@ -168,6 +165,8 @@ bool NewMapDialog::validateName(bool allowEmpty) { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); + + // Changing the map name updates the layout ID field to match. if (ui->comboBox_LayoutID->isEnabled()) { ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); } @@ -219,7 +218,15 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { validateLayoutID(true); - setLayout(this->project->mapLayouts.value(text)); + + // Changing the layout ID to an existing layout updates the layout settings to match. + const Layout *layout = this->project->mapLayouts.value(text); + if (layout) { + ui->newLayoutForm->setSettings(layout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { + ui->newLayoutForm->setDisabled(false); + } } void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { From ba4a43d5957d237292bba227cc09e35c4c6a1c06 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 3 Dec 2024 15:46:25 -0500 Subject: [PATCH 100/364] Reserve MAP_UNDEFINED --- include/project.h | 1 + src/project.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/project.h b/include/project.h index 9b6f64bf..2b84d8d4 100644 --- a/include/project.h +++ b/include/project.h @@ -238,6 +238,7 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static int getNumTilesPrimary(); diff --git a/src/project.cpp b/src/project.cpp index 7772ac8c..22e1924e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1990,6 +1990,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return false; } } + if (identifier == getEmptyMapDefineName()) + return false; return true; } @@ -3050,6 +3052,11 @@ int Project::getMaxObjectEvents() return Project::max_object_events; } +QString Project::getEmptyMapDefineName() { + const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_empty); +} + QString Project::getDynamicMapDefineName() { const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); From b7c34a67e5faeabe3215f7eea5c2e34a1feab4e7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 3 Dec 2024 16:08:53 -0500 Subject: [PATCH 101/364] Fix AdvanceMap import memory leaks, revert name change --- forms/mainwindow.ui | 6 +++--- include/mainwindow.h | 2 +- src/mainwindow.cpp | 6 +++--- src/ui/tileseteditor.cpp | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index e9a3c440..c431d469 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2931,7 +2931,7 @@ - + @@ -3222,9 +3222,9 @@ Open Config Folder - + - Import Layout from Advance Map 1.92... + Import Map from Advance Map 1.92... diff --git a/include/mainwindow.h b/include/mainwindow.h index 17f19623..22f64a81 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -234,7 +234,7 @@ private slots: void on_action_Export_Map_Image_triggered(); void on_actionExport_Stitched_Map_Image_triggered(); void on_actionExport_Map_Timelapse_Image_triggered(); - void on_actionImport_Layout_from_Advance_Map_1_92_triggered(); + void on_actionImport_Map_from_Advance_Map_1_92_triggered(); void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9623963a..ff39fef7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2636,8 +2636,8 @@ void MainWindow::on_actionExport_Map_Timelapse_Image_triggered() { showExportMapImageWindow(ImageExporterMode::Timelapse); } -void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { - QString filepath = FileDialog::getOpenFileName(this, "Import Layout from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); +void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() { + QString filepath = FileDialog::getOpenFileName(this, "Import Map from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); if (filepath.isEmpty()) { return; } @@ -2658,8 +2658,8 @@ void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this); connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + connect(dialog, &NewLayoutDialog::finished, [mapLayout] { mapLayout->deleteLater(); }); dialog->open(); - delete mapLayout; } void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index b6bf1735..940761ad 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -987,6 +987,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Icon::Critical); msgBox.exec(); + qDeleteAll(metatiles); return; } From c2cf3cc9c781faae1ba0851ddc28a35c7946ba79 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 4 Dec 2024 15:41:29 -0500 Subject: [PATCH 102/364] Fix tileset palette saving crash --- CHANGELOG.md | 1 + src/project.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f92f02b4..11e4696d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix some file dialogs returning to an incorrect window when closed. - Fix bug where reloading a layout would overwrite all unsaved changes. - Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. +- Fix crash when saving tilesets with fewer palettes than the maximum. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/src/project.cpp b/src/project.cpp index 88e3d9d1..43b62ecf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1130,7 +1130,8 @@ void Project::saveTilesetTilesImage(Tileset *tileset) { } void Project::saveTilesetPalettes(Tileset *tileset) { - for (int i = 0; i < Project::getNumPalettesTotal(); i++) { + int numPalettes = qMin(tileset->palettePaths.length(), tileset->palettes.length()); + for (int i = 0; i < numPalettes; i++) { QString filepath = tileset->palettePaths.at(i); PaletteUtil::writeJASC(filepath, tileset->palettes.at(i).toVector(), 0, 16); } From 9c40b04ad541d4a78dcbe98e8ce738708489b01a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 3 Dec 2024 22:27:29 -0500 Subject: [PATCH 103/364] Redesign new tileset dialog --- forms/mainwindow.ui | 1 + forms/newtilesetdialog.ui | 356 +++++++++++++--------------------- include/core/tileset.h | 18 +- include/mainwindow.h | 3 +- include/project.h | 9 +- include/ui/newlayoutdialog.h | 1 - include/ui/newtilesetdialog.h | 20 +- src/core/tileset.cpp | 184 +++++++++++++++++- src/mainwindow.cpp | 134 ++----------- src/project.cpp | 253 +++++++++--------------- src/scriptapi/apimap.cpp | 29 +-- src/ui/newtilesetdialog.cpp | 89 ++++++--- src/ui/tileseteditor.cpp | 15 +- 13 files changed, 530 insertions(+), 582 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index c431d469..602ff620 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2928,6 +2928,7 @@ + diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index c582eba0..1fed2558 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -6,238 +6,152 @@ 0 0 - 400 - 216 + 450 + 209 - - - 0 - 0 - - Add new Tileset - - - - 0 - 0 - 400 - 216 - - - - - 0 - 0 - - - - - 10 - - - 10 - - - 10 - - - 10 - - - 6 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 380 - 161 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 380 - 155 - - - - - 6 + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + Name - - 10 + + + + + + true - - 10 + + + + + + false - - 10 + + color: rgb(255, 0, 0) - - - - Name - - + + + + + + + + + Symbol Name + + + + + + + + + + + + + + Type + + + + + + + false + + + + Primary + - - - - true - - + + + Secondary + - - - - Type - - - - - - - - Primary - - - - - Secondary - - - - - - - - Path - - - - - - - false - - - true - - - - - - - Symbol Name - - - - - - - false - - - true - - - - - - - Checkerboard Fill - - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - + + + + + + Checkerboard Fill + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
- - - buttonBox - accepted() - NewTilesetDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - NewTilesetDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + diff --git a/include/core/tileset.h b/include/core/tileset.h index bb7631d7..999e731d 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -38,8 +38,6 @@ public: QList> palettes; QList> palettePreviews; - bool hasUnsavedTilesImage; - static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); @@ -56,10 +54,25 @@ public: static QHash getHeaderMemberMap(bool usingAsm); static QString getExpectedDir(QString tilesetName, bool isSecondary); QString getExpectedDir(); + + void load(); + void loadMetatiles(); + void loadMetatileAttributes(); + void loadTilesImage(QImage *importedImage = nullptr); + void loadPalettes(); + + void save(); + void saveMetatileAttributes(); + void saveMetatiles(); + void saveTilesImage(); + void savePalettes(); + bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); bool appendToMetatiles(QString root, QString friendlyName, bool usingAsm); + void setTilesImage(const QImage &image); + void setMetatiles(const QList &metatiles); void addMetatile(Metatile* metatile); @@ -72,6 +85,7 @@ public: private: QList m_metatiles; + bool m_hasUnsavedTilesImage = false; }; #endif // TILESET_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 22f64a81..c51b10cf 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,7 +22,6 @@ #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" #include "maplistmodels.h" -#include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" #include "projectsettingseditor.h" @@ -191,6 +190,7 @@ private slots: void onNewMapGroupCreated(const QString &groupName); void onNewMapSectionCreated(const QString &idName); void onNewLayoutCreated(Layout *layout); + void onNewTilesetCreated(Tileset *tileset); void onMapLoaded(Map *map); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); @@ -412,7 +412,6 @@ private: QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); - int insertTilesetLabel(QStringList * list, QString label); void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); diff --git a/include/project.h b/include/project.h index 2b84d8d4..8a08cef6 100644 --- a/include/project.h +++ b/include/project.h @@ -139,6 +139,7 @@ public: Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); + Tileset *createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; QString getProjectTitle(); @@ -167,10 +168,7 @@ public: bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); void loadTilesetAssets(Tileset*); - void loadTilesetTiles(Tileset*, QImage); - void loadTilesetMetatiles(Tileset*); void loadTilesetMetatileLabels(Tileset*); - void loadTilesetPalettes(Tileset*); void readTilesetPaths(Tileset* tileset); void saveLayout(Layout *); @@ -188,10 +186,6 @@ public: void saveHealLocations(Map*); void saveTilesets(Tileset*, Tileset*); void saveTilesetMetatileLabels(Tileset*, Tileset*); - void saveTilesetMetatileAttributes(Tileset*); - void saveTilesetMetatiles(Tileset*); - void saveTilesetTilesImage(Tileset*); - void saveTilesetPalettes(Tileset*); void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); @@ -282,6 +276,7 @@ signals: void mapLoaded(Map *map); void mapCreated(Map *newMap, const QString &groupName); void layoutCreated(Layout *newLayout); + void tilesetCreated(Tileset *newTileset); void mapGroupAdded(const QString &groupName); void mapSectionAdded(const QString &idName); void mapSectionIdNamesChanged(const QStringList &idNames); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index 5fdb780f..b1a2c47b 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -39,7 +39,6 @@ private: void refresh(); void saveSettings(); - bool isExistingLayout() const; private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index c2563f21..06012708 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -2,7 +2,10 @@ #define NEWTILESETDIALOG_H #include -#include "project.h" +#include + +class Project; +class Tileset; namespace Ui { class NewTilesetDialog; @@ -15,20 +18,17 @@ class NewTilesetDialog : public QDialog public: explicit NewTilesetDialog(Project *project, QWidget *parent = nullptr); ~NewTilesetDialog(); - QString path; - QString fullSymbolName; - QString friendlyName; - bool isSecondary; - bool checkerboardFill; -private slots: - void NameOrSecondaryChanged(); - void SecondaryChanged(); - void FillChanged(); + virtual void accept() override; private: Ui::NewTilesetDialog *ui; Project *project = nullptr; + const QString symbolPrefix; + + bool validateName(bool allowEmpty = false); + void onFriendlyNameChanged(const QString &friendlyName); + void dialogButtonClicked(QAbstractButton *button); }; #endif // NEWTILESETDIALOG_H diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index be3a04d4..cf21a675 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -3,6 +3,7 @@ #include "project.h" #include "log.h" #include "config.h" +#include "imageproviders.h" #include #include @@ -23,7 +24,7 @@ Tileset::Tileset(const Tileset &other) metatileLabels(other.metatileLabels), palettes(other.palettes), palettePreviews(other.palettePreviews), - hasUnsavedTilesImage(false) + m_hasUnsavedTilesImage(other.m_hasUnsavedTilesImage) { for (auto tile : other.tiles) { tiles.append(tile.copy()); @@ -397,3 +398,184 @@ QHash Tileset::getHeaderMemberMap(bool usingAsm) map.insert(metatileAttrPosition, "metatileAttributes"); return map; } + +void Tileset::loadMetatiles() { + clearMetatiles(); + + QFile metatiles_file(this->metatiles_path); + if (!metatiles_file.open(QIODevice::ReadOnly)) { + logError(QString("Could not open '%1' for reading.").arg(this->metatiles_path)); + return; + } + + QByteArray data = metatiles_file.readAll(); + int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); + int bytesPerMetatile = 2 * tilesPerMetatile; + int num_metatiles = data.length() / bytesPerMetatile; + for (int i = 0; i < num_metatiles; i++) { + auto metatile = new Metatile; + int index = i * bytesPerMetatile; + for (int j = 0; j < tilesPerMetatile; j++) { + uint16_t tileRaw = static_cast(data[index++]); + tileRaw |= static_cast(data[index++]) << 8; + metatile->tiles.append(Tile(tileRaw)); + } + m_metatiles.append(metatile); + } +} + +void Tileset::saveMetatiles() { + QFile metatiles_file(this->metatiles_path); + if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + logError(QString("Could not open '%1' for writing.").arg(this->metatiles_path)); + return; + } + + QByteArray data; + int numTiles = projectConfig.getNumTilesInMetatile(); + for (const auto &metatile : m_metatiles) { + for (int i = 0; i < numTiles; i++) { + uint16_t tile = metatile->tiles.at(i).rawValue(); + data.append(static_cast(tile)); + data.append(static_cast(tile >> 8)); + } + } + metatiles_file.write(data); +} + +void Tileset::loadMetatileAttributes() { + QFile attrs_file(this->metatile_attrs_path); + if (!attrs_file.open(QIODevice::ReadOnly)) { + logError(QString("Could not open '%1' for reading.").arg(this->metatile_attrs_path)); + return; + } + + QByteArray data = attrs_file.readAll(); + int attrSize = projectConfig.metatileAttributesSize; + int numMetatiles = m_metatiles.length(); + int numMetatileAttrs = data.length() / attrSize; + if (numMetatiles != numMetatileAttrs) { + logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(numMetatiles).arg(numMetatileAttrs).arg(this->name)); + } + + for (int i = 0; i < qMin(numMetatiles, numMetatileAttrs); i++) { + uint32_t attributes = 0; + for (int j = 0; j < attrSize; j++) + attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); + m_metatiles.at(i)->setAttributes(attributes); + } +} + +void Tileset::saveMetatileAttributes() { + QFile attrs_file(this->metatile_attrs_path); + if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + logError(QString("Could not open '%1' for writing.").arg(this->metatile_attrs_path)); + return; + } + + QByteArray data; + for (const auto &metatile : m_metatiles) { + uint32_t attributes = metatile->getAttributes(); + for (int i = 0; i < projectConfig.metatileAttributesSize; i++) + data.append(static_cast(attributes >> (8 * i))); + } + attrs_file.write(data); +} + +void Tileset::loadTilesImage(QImage *importedImage) { + QImage image; + if (importedImage) { + image = *importedImage; + m_hasUnsavedTilesImage = true; + } else if (QFile::exists(this->tilesImagePath)) { + // No image provided, load from file path. + image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); + } else { + // Use default image + image = QImage(8, 8, QImage::Format_Indexed8); + } + + // Validate image contains 16 colors. + int colorCount = image.colorCount(); + if (colorCount > 16) { + flattenTo4bppImage(&image); + } else if (colorCount < 16) { + QVector colorTable = image.colorTable(); + for (int i = colorTable.length(); i < 16; i++) { + colorTable.append(Qt::black); + } + image.setColorTable(colorTable); + } + + QList tiles; + int w = 8; + int h = 8; + for (int y = 0; y < image.height(); y += h) + for (int x = 0; x < image.width(); x += w) { + QImage tile = image.copy(x, y, w, h); + tiles.append(tile); + } + this->tilesImage = image; + this->tiles = tiles; +} + +void Tileset::saveTilesImage() { + // Only write the tiles image if it was changed. + // Porymap will only ever change an existing tiles image by importing a new one. + if (!m_hasUnsavedTilesImage) + return; + + if (!this->tilesImage.save(this->tilesImagePath, "PNG")) { + logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath)); + return; + } + + m_hasUnsavedTilesImage = false; +} + +void Tileset::loadPalettes() { + this->palettes.clear(); + this->palettePreviews.clear(); + + for (int i = 0; i < Project::getNumPalettesTotal(); i++) { + QList palette; + QString path = this->palettePaths.value(i); + if (!path.isEmpty()) { + bool error = false; + palette = PaletteUtil::parse(path, &error); + if (error) palette.clear(); + } + if (palette.isEmpty()) { + // Either the palette failed to load, or no palette exists. + // We expect tilesets to have a certain number of palettes, + // so fill this palette with dummy colors. + for (int j = 0; j < 16; j++) { + palette.append(qRgb(j * 16, j * 16, j * 16)); + } + } + this->palettes.append(palette); + this->palettePreviews.append(palette); + } +} + +void Tileset::savePalettes() { + int numPalettes = qMin(this->palettePaths.length(), this->palettes.length()); + for (int i = 0; i < numPalettes; i++) { + PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16); + } +} + +void Tileset::load() { + loadMetatiles(); + loadMetatileAttributes(); + loadTilesImage(); + loadPalettes(); +} + +// Because metatile labels are global (and handled by the project) we don't save them here. +void Tileset::save() { + saveMetatiles(); + saveMetatileAttributes(); + saveTilesImage(); + savePalettes(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ff39fef7..3c16b0f4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -25,6 +25,7 @@ #include "filedialog.h" #include "newmapdialog.h" #include "newlayoutdialog.h" +#include "newtilesetdialog.h" #include #include @@ -407,8 +408,8 @@ 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"), ""); + buttonAdd->setToolTip("Create New Map"); connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::openNewMapDialog); layout->addWidget(buttonAdd); @@ -623,6 +624,7 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); connect(project, &Project::mapCreated, this, &MainWindow::onNewMapCreated); connect(project, &Project::layoutCreated, this, &MainWindow::onNewLayoutCreated); + connect(project, &Project::tilesetCreated, this, &MainWindow::onNewTilesetCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); @@ -925,7 +927,6 @@ 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); @@ -1439,6 +1440,21 @@ void MainWindow::onNewMapSectionCreated(const QString &idName) { // TODO: Refresh Region Map Editor's map section dropdown, if it's open } +void MainWindow::onNewTilesetCreated(Tileset *tileset) { + QString message = QString("Created a new tileset named %1.").arg(tileset->name); + logInfo(message); + statusBar()->showMessage(message); + + // Refresh tileset combo boxes + if (!tileset->is_secondary) { + int index = this->editor->project->primaryTilesetLabels.indexOf(tileset->name); + ui->comboBox_PrimaryTileset->insertItem(index, tileset->name); + } else { + int index = this->editor->project->secondaryTilesetLabels.indexOf(tileset->name); + ui->comboBox_SecondaryTileset->insertItem(index, tileset->name); + } +} + void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); dialog->open(); @@ -1450,119 +1466,9 @@ void MainWindow::openNewLayoutDialog() { dialog->open(); } -// Insert label for newly-created tileset into sorted list of existing labels -int MainWindow::insertTilesetLabel(QStringList * list, QString label) { - int i = 0; - for (; i < list->length(); i++) - if (list->at(i) > label) break; - list->insert(i, label); - return i; -} - void MainWindow::on_actionNew_Tileset_triggered() { - NewTilesetDialog *createTilesetDialog = new NewTilesetDialog(editor->project, this); - if(createTilesetDialog->exec() == QDialog::Accepted){ - if(createTilesetDialog->friendlyName.isEmpty()) { - logError(QString("Tried to create a directory with an empty name.")); - QMessageBox msgBox(this); - msgBox.setText("Failed to add new tileset."); - QString message = QString("The given name was empty."); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; - } - QString fullDirectoryPath = editor->project->root + "/" + createTilesetDialog->path; - QDir directory; - if(directory.exists(fullDirectoryPath)) { - logError(QString("Could not create tileset \"%1\", the folder \"%2\" already exists.").arg(createTilesetDialog->friendlyName, fullDirectoryPath)); - QMessageBox msgBox(this); - msgBox.setText("Failed to add new tileset."); - QString message = QString("The folder for tileset \"%1\" already exists. View porymap.log for specific errors.").arg(createTilesetDialog->friendlyName); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; - } - if (editor->project->tilesetLabelsOrdered.contains(createTilesetDialog->fullSymbolName)) { - logError(QString("Could not create tileset \"%1\", the symbol \"%2\" already exists.").arg(createTilesetDialog->friendlyName, createTilesetDialog->fullSymbolName)); - QMessageBox msgBox(this); - msgBox.setText("Failed to add new tileset."); - QString message = QString("The symbol for tileset \"%1\" (\"%2\") already exists.").arg(createTilesetDialog->friendlyName, createTilesetDialog->fullSymbolName); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; - } - directory.mkdir(fullDirectoryPath); - directory.mkdir(fullDirectoryPath + "/palettes"); - Tileset newSet; - newSet.name = createTilesetDialog->fullSymbolName; - newSet.tilesImagePath = fullDirectoryPath + "/tiles.png"; - newSet.metatiles_path = fullDirectoryPath + "/metatiles.bin"; - newSet.metatile_attrs_path = fullDirectoryPath + "/metatile_attributes.bin"; - newSet.is_secondary = createTilesetDialog->isSecondary; - int numMetatiles = createTilesetDialog->isSecondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); - QImage tilesImage(":/images/blank_tileset.png"); - editor->project->loadTilesetTiles(&newSet, tilesImage); - int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); - for(int i = 0; i < numMetatiles; ++i) { - Metatile *mt = new Metatile(); - for(int j = 0; j < tilesPerMetatile; ++j){ - Tile tile = Tile(); - if (createTilesetDialog->checkerboardFill) { - // Create a checkerboard-style dummy tileset - if (((i / 8) % 2) == 0) - tile.tileId = ((i % 2) == 0) ? 1 : 2; - else - tile.tileId = ((i % 2) == 1) ? 1 : 2; - } - mt->tiles.append(tile); - } - newSet.addMetatile(mt); - } - for(int i = 0; i < 16; ++i) { - QList currentPal; - for(int i = 0; i < 16;++i) { - currentPal.append(qRgb(0,0,0)); - } - newSet.palettes.append(currentPal); - newSet.palettePreviews.append(currentPal); - QString fileName = QString("%1.pal").arg(i, 2, 10, QLatin1Char('0')); - newSet.palettePaths.append(fullDirectoryPath+"/palettes/" + fileName); - } - newSet.palettes[0][1] = qRgb(255,0,255); - newSet.palettePreviews[0][1] = qRgb(255,0,255); - exportIndexed4BPPPng(newSet.tilesImage, newSet.tilesImagePath); - editor->project->saveTilesetMetatiles(&newSet); - editor->project->saveTilesetMetatileAttributes(&newSet); - editor->project->saveTilesetPalettes(&newSet); - - //append to tileset specific files - newSet.appendToHeaders(editor->project->root, createTilesetDialog->friendlyName, editor->project->usingAsmTilesets); - newSet.appendToGraphics(editor->project->root, createTilesetDialog->friendlyName, editor->project->usingAsmTilesets); - newSet.appendToMetatiles(editor->project->root, createTilesetDialog->friendlyName, editor->project->usingAsmTilesets); - - if (!createTilesetDialog->isSecondary) { - int index = insertTilesetLabel(&editor->project->primaryTilesetLabels, createTilesetDialog->fullSymbolName); - this->ui->comboBox_PrimaryTileset->insertItem(index, createTilesetDialog->fullSymbolName); - } else { - int index = insertTilesetLabel(&editor->project->secondaryTilesetLabels, createTilesetDialog->fullSymbolName); - this->ui->comboBox_SecondaryTileset->insertItem(index, createTilesetDialog->fullSymbolName); - } - editor->project->tilesetLabelsOrdered.append(createTilesetDialog->fullSymbolName); - - QMessageBox msgBox(this); - msgBox.setText("Successfully created tileset."); - QString message = QString("Tileset \"%1\" was created successfully.").arg(createTilesetDialog->friendlyName); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Information); - msgBox.exec(); - } + auto dialog = new NewTilesetDialog(editor->project, this); + dialog->open(); } void MainWindow::updateTilesetEditor() { diff --git a/src/project.cpp b/src/project.cpp index 22e1924e..e7fc3ab2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -122,19 +122,13 @@ QString Project::getProjectTitle() { } void Project::clearMapCache() { - for (auto *map : mapCache.values()) { - if (map) - delete map; - } - mapCache.clear(); + qDeleteAll(this->mapCache); + this->mapCache.clear(); } void Project::clearTilesetCache() { - for (auto *tileset : tilesetCache.values()) { - if (tileset) - delete tileset; - } - tilesetCache.clear(); + qDeleteAll(this->tilesetCache); + this->tilesetCache.clear(); } Map* Project::loadMap(QString mapName) { @@ -1005,25 +999,21 @@ void Project::saveHealLocationsConstants() { void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { saveTilesetMetatileLabels(primaryTileset, secondaryTileset); - saveTilesetMetatileAttributes(primaryTileset); - saveTilesetMetatileAttributes(secondaryTileset); - saveTilesetMetatiles(primaryTileset); - saveTilesetMetatiles(secondaryTileset); - saveTilesetTilesImage(primaryTileset); - saveTilesetTilesImage(secondaryTileset); - saveTilesetPalettes(primaryTileset); - saveTilesetPalettes(secondaryTileset); + if (primaryTileset) + primaryTileset->save(); + if (secondaryTileset) + secondaryTileset->save(); } void Project::updateTilesetMetatileLabels(Tileset *tileset) { // Erase old labels, then repopulate with new labels const QString prefix = tileset->getMetatileLabelPrefix(); - metatileLabelsMap[tileset->name].clear(); + this->metatileLabelsMap[tileset->name].clear(); for (int metatileId : tileset->metatileLabels.keys()) { if (tileset->metatileLabels[metatileId].isEmpty()) continue; QString label = prefix + tileset->metatileLabels[metatileId]; - metatileLabelsMap[tileset->name][label] = metatileId; + this->metatileLabelsMap[tileset->name][label] = metatileId; } } @@ -1082,59 +1072,6 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second saveTextFile(root + "/" + filename, outputText); } -void Project::saveTilesetMetatileAttributes(Tileset *tileset) { - QFile attrs_file(tileset->metatile_attrs_path); - if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QByteArray data; - for (const auto &metatile : tileset->metatiles()) { - uint32_t attributes = metatile->getAttributes(); - for (int i = 0; i < projectConfig.metatileAttributesSize; i++) - data.append(static_cast(attributes >> (8 * i))); - } - attrs_file.write(data); - } else { - logError(QString("Could not save tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); - } -} - -void Project::saveTilesetMetatiles(Tileset *tileset) { - QFile metatiles_file(tileset->metatiles_path); - if (metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QByteArray data; - int numTiles = projectConfig.getNumTilesInMetatile(); - for (const auto &metatile : tileset->metatiles()) { - for (int i = 0; i < numTiles; i++) { - uint16_t tile = metatile->tiles.at(i).rawValue(); - data.append(static_cast(tile)); - data.append(static_cast(tile >> 8)); - } - } - metatiles_file.write(data); - } else { - tileset->clearMetatiles(); - logError(QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path)); - } -} - -void Project::saveTilesetTilesImage(Tileset *tileset) { - // Only write the tiles image if it was changed. - // Porymap will only ever change an existing tiles image by importing a new one. - if (tileset->hasUnsavedTilesImage) { - if (!tileset->tilesImage.save(tileset->tilesImagePath, "PNG")) { - logError(QString("Failed to save tiles image '%1'").arg(tileset->tilesImagePath)); - return; - } - tileset->hasUnsavedTilesImage = false; - } -} - -void Project::saveTilesetPalettes(Tileset *tileset) { - for (int i = 0; i < Project::getNumPalettesTotal(); i++) { - QString filepath = tileset->palettePaths.at(i); - PaletteUtil::writeJASC(filepath, tileset->palettes.at(i).toVector(), 0, 16); - } -} - bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { @@ -1465,18 +1402,9 @@ void Project::loadTilesetAssets(Tileset* tileset) { if (tileset->name.isNull()) { return; } - this->readTilesetPaths(tileset); - QImage image; - if (QFile::exists(tileset->tilesImagePath)) { - image = QImage(tileset->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); - flattenTo4bppImage(&image); - } else { - image = QImage(8, 8, QImage::Format_Indexed8); - } - this->loadTilesetTiles(tileset, image); - this->loadTilesetMetatiles(tileset); - this->loadTilesetMetatileLabels(tileset); - this->loadTilesetPalettes(tileset); + readTilesetPaths(tileset); + loadTilesetMetatileLabels(tileset); + tileset->load(); } void Project::readTilesetPaths(Tileset* tileset) { @@ -1536,84 +1464,89 @@ void Project::readTilesetPaths(Tileset* tileset) { } } -void Project::loadTilesetPalettes(Tileset* tileset) { - QList> palettes; - QList> palettePreviews; - for (int i = 0; i < tileset->palettePaths.length(); i++) { - QString path = tileset->palettePaths.value(i); - bool error = false; - QList palette = PaletteUtil::parse(path, &error); - if (error) { - for (int j = 0; j < 16; j++) { - palette.append(qRgb(j * 16, j * 16, j * 16)); +Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill) { + auto tileset = new Tileset(); + tileset->name = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + friendlyName; + tileset->is_secondary = secondary; + + // Create tileset directories + const QString fullDirectoryPath = QString("%1/%2").arg(this->root).arg(tileset->getExpectedDir()); + QDir directory; + if (!directory.mkpath(fullDirectoryPath)) { + logError(QString("Failed to create directory '%1' for new tileset '%2'").arg(fullDirectoryPath).arg(tileset->name)); + delete tileset; + return nullptr; + } + const QString palettesPath = fullDirectoryPath + "/palettes"; + if (!directory.mkpath(palettesPath)) { + logError(QString("Failed to create palettes directory '%1' for new tileset '%2'").arg(palettesPath).arg(tileset->name)); + delete tileset; + return nullptr; + } + + tileset->tilesImagePath = fullDirectoryPath + "/tiles.png"; + tileset->metatiles_path = fullDirectoryPath + "/metatiles.bin"; + tileset->metatile_attrs_path = fullDirectoryPath + "/metatile_attributes.bin"; + + // Set default tiles image + QImage tilesImage(":/images/blank_tileset.png"); + tileset->loadTilesImage(&tilesImage); + //exportIndexed4BPPPng(tileset->tilesImage, tileset->tilesImagePath); // TODO: Make sure we can now properly handle the 8bpp images that get written without this. + + // Create default metatiles + const int numMetatiles = tileset->is_secondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); + const int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); + for (int i = 0; i < numMetatiles; ++i) { + auto metatile = new Metatile(); + for(int j = 0; j < tilesPerMetatile; ++j){ + Tile tile = Tile(); + if (checkerboardFill) { + // Create a checkerboard-style dummy tileset + if (((i / 8) % 2) == 0) + tile.tileId = ((i % 2) == 0) ? 1 : 2; + else + tile.tileId = ((i % 2) == 1) ? 1 : 2; } + metatile->tiles.append(tile); } - - palettes.append(palette); - palettePreviews.append(palette); - } - tileset->palettes = palettes; - tileset->palettePreviews = palettePreviews; -} - -void Project::loadTilesetTiles(Tileset *tileset, QImage image) { - QList tiles; - int w = 8; - int h = 8; - for (int y = 0; y < image.height(); y += h) - for (int x = 0; x < image.width(); x += w) { - QImage tile = image.copy(x, y, w, h); - tiles.append(tile); - } - tileset->tilesImage = image; - tileset->tiles = tiles; -} - -void Project::loadTilesetMetatiles(Tileset* tileset) { - QFile metatiles_file(tileset->metatiles_path); - if (metatiles_file.open(QIODevice::ReadOnly)) { - QByteArray data = metatiles_file.readAll(); - int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); - int bytesPerMetatile = 2 * tilesPerMetatile; - int num_metatiles = data.length() / bytesPerMetatile; - QList metatiles; - for (int i = 0; i < num_metatiles; i++) { - Metatile *metatile = new Metatile; - int index = i * bytesPerMetatile; - for (int j = 0; j < tilesPerMetatile; j++) { - uint16_t tileRaw = static_cast(data[index++]); - tileRaw |= static_cast(data[index++]) << 8; - metatile->tiles.append(Tile(tileRaw)); - } - metatiles.append(metatile); - } - tileset->setMetatiles(metatiles); - } else { - tileset->clearMetatiles(); - logError(QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path)); + tileset->addMetatile(metatile); } - QFile attrs_file(tileset->metatile_attrs_path); - if (attrs_file.open(QIODevice::ReadOnly)) { - QByteArray data = attrs_file.readAll(); - int num_metatiles = tileset->numMetatiles(); - int attrSize = projectConfig.metatileAttributesSize; - int num_metatileAttrs = data.length() / attrSize; - if (num_metatiles != num_metatileAttrs) { - logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name)); - if (num_metatileAttrs > num_metatiles) - num_metatileAttrs = num_metatiles; + // Create default palettes + for(int i = 0; i < 16; ++i) { + QList currentPal; + for(int i = 0; i < 16;++i) { + currentPal.append(qRgb(0,0,0)); } - - for (int i = 0; i < num_metatileAttrs; i++) { - uint32_t attributes = 0; - for (int j = 0; j < attrSize; j++) - attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); - tileset->metatileAt(i)->setAttributes(attributes); - } - } else { - logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); + tileset->palettes.append(currentPal); + tileset->palettePreviews.append(currentPal); + tileset->palettePaths.append(QString("%1/%2.pal").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0'))); } + tileset->palettes[0][1] = qRgb(255,0,255); + tileset->palettePreviews[0][1] = qRgb(255,0,255); + + // Update tileset label arrays + QStringList *labelList = tileset->is_secondary ? &this->secondaryTilesetLabels : &this->primaryTilesetLabels; + for (int i = 0; i < labelList->length(); i++) { + if (labelList->at(i) > tileset->name) { + labelList->insert(i, tileset->name); + break; + } + } + this->tilesetLabelsOrdered.append(tileset->name); + + // TODO: Ideally we wouldn't save new Tilesets immediately + // Append to tileset specific files + tileset->appendToHeaders(this->root, friendlyName, this->usingAsmTilesets); + tileset->appendToGraphics(this->root, friendlyName, this->usingAsmTilesets); + tileset->appendToMetatiles(this->root, friendlyName, this->usingAsmTilesets); + + tileset->save(); + + this->tilesetCache.insert(tileset->name, tileset); + + emit tilesetCreated(tileset); + return tileset; } QString Project::findMetatileLabelsTileset(QString label) { @@ -1658,9 +1591,9 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) { QString metatileLabelPrefix = tileset->getMetatileLabelPrefix(); // Reverse map for faster lookup by metatile id - for (QString labelName : metatileLabelsMap[tileset->name].keys()) { - auto metatileId = metatileLabelsMap[tileset->name][labelName]; - tileset->metatileLabels[metatileId] = labelName.replace(metatileLabelPrefix, ""); + for (auto it = this->metatileLabelsMap[tileset->name].constBegin(); it != this->metatileLabelsMap[tileset->name].constEnd(); it++) { + QString labelName = it.key(); + tileset->metatileLabels[it.value()] = labelName.replace(metatileLabelPrefix, ""); } } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index f041b7b1..91cc4e67 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -337,7 +337,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { this->editor->map_item->draw(true); this->editor->updateMapBorder(); this->editor->updateMapConnections(); - this->editor->project->saveTilesetPalettes(tileset); + tileset->savePalettes(); } void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors) { @@ -576,14 +576,14 @@ void MainWindow::setSecondaryTileset(QString tileset) { void MainWindow::saveMetatilesByMetatileId(int metatileId) { Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); - if (this->editor->project && tileset) - this->editor->project->saveTilesetMetatiles(tileset); + if (tileset) + tileset->saveMetatiles(); } void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); - if (this->editor->project && tileset) - this->editor->project->saveTilesetMetatileAttributes(tileset); + if (tileset) + tileset->saveMetatileAttributes(); // If the tileset editor is open it needs to be refreshed with the new changes. // Rather than do a full refresh (which is costly) we tell the editor it will need @@ -811,9 +811,6 @@ QJSValue MainWindow::getTilePixels(int tileId) { // Editing map header //===================== -// TODO: Connect signals from new function calls to update UI -// TODO: Is the error-checking for known constant names still reasonable / needed? (you can type anything after all) - QString MainWindow::getSong() { if (!this->editor || !this->editor->map) return QString(); @@ -823,10 +820,6 @@ QString MainWindow::getSong() { void MainWindow::setSong(QString song) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->songNames.contains(song)) { - logError(QString("Unknown song '%1'").arg(song)); - return; - } this->editor->map->header()->setSong(song); } @@ -867,10 +860,6 @@ QString MainWindow::getWeather() { void MainWindow::setWeather(QString weather) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->weatherNames.contains(weather)) { - logError(QString("Unknown weather '%1'").arg(weather)); - return; - } this->editor->map->header()->setWeather(weather); } @@ -883,10 +872,6 @@ QString MainWindow::getType() { void MainWindow::setType(QString type) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->mapTypes.contains(type)) { - logError(QString("Unknown map type '%1'").arg(type)); - return; - } this->editor->map->header()->setType(type); } @@ -899,10 +884,6 @@ QString MainWindow::getBattleScene() { void MainWindow::setBattleScene(QString battleScene) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->mapBattleScenes.contains(battleScene)) { - logError(QString("Unknown battle scene '%1'").arg(battleScene)); - return; - } this->editor->map->header()->setBattleScene(battleScene); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 8cb4c5f9..1ddd975d 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -1,50 +1,87 @@ #include "newtilesetdialog.h" #include "ui_newtilesetdialog.h" #include "project.h" +#include "imageexport.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : QDialog(parent), - ui(new Ui::NewTilesetDialog) + ui(new Ui::NewTilesetDialog), + symbolPrefix(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix)) { + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); ui->setupUi(this); - this->setFixedSize(this->width(), this->height()); this->project = project; + + ui->checkBox_CheckerboardFill->setChecked(porymapConfig.tilesetCheckerboardFill); + ui->label_SymbolNameDisplay->setText(this->symbolPrefix); + ui->comboBox_Type->setMinimumContentsLength(12); + //only allow characters valid for a symbol - static const QRegularExpression expression("[_A-Za-z0-9]+$"); // TODO: Incorrect, allows digits at beginning - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); - this->ui->nameLineEdit->setValidator(validator); + static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); + ui->lineEdit_FriendlyName->setValidator(validator); - bool checkerboard = porymapConfig.tilesetCheckerboardFill; - this->ui->fillCheckBox->setChecked(checkerboard); - this->checkerboardFill = checkerboard; + connect(ui->lineEdit_FriendlyName, &QLineEdit::textChanged, this, &NewTilesetDialog::onFriendlyNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewTilesetDialog::dialogButtonClicked); - connect(this->ui->nameLineEdit, &QLineEdit::textChanged, this, &NewTilesetDialog::NameOrSecondaryChanged); - connect(this->ui->typeComboBox, &QComboBox::currentTextChanged, this, &NewTilesetDialog::SecondaryChanged); - connect(this->ui->fillCheckBox, &QCheckBox::stateChanged, this, &NewTilesetDialog::FillChanged); - //connect(this->ui->toolButton, &QToolButton::clicked, this, &NewTilesetDialog::ChangeFilePath); - this->SecondaryChanged(); + adjustSize(); } NewTilesetDialog::~NewTilesetDialog() { + porymapConfig.tilesetCheckerboardFill = ui->checkBox_CheckerboardFill->isChecked(); delete ui; } -void NewTilesetDialog::SecondaryChanged(){ - this->isSecondary = (this->ui->typeComboBox->currentIndex() == 1); - NameOrSecondaryChanged(); +void NewTilesetDialog::onFriendlyNameChanged(const QString &friendlyName) { + // When the tileset name is changed, update this label to display the full symbol name. + ui->label_SymbolNameDisplay->setText(this->symbolPrefix + friendlyName); + + validateName(true); } -// TODO: No validation -void NewTilesetDialog::NameOrSecondaryChanged() { - this->friendlyName = this->ui->nameLineEdit->text(); - this->fullSymbolName = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + this->friendlyName; - this->ui->symbolNameLineEdit->setText(this->fullSymbolName); - this->path = Tileset::getExpectedDir(this->fullSymbolName, this->isSecondary); - this->ui->pathLineEdit->setText(this->path); +bool NewTilesetDialog::validateName(bool allowEmpty) { + const QString friendlyName = ui->lineEdit_FriendlyName->text(); + const QString symbolName = ui->label_SymbolNameDisplay->text(); + + QString errorText; + if (friendlyName.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_FriendlyName->text()); + } else if (!this->project->isIdentifierUnique(symbolName)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_SymbolName->text()).arg(symbolName); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_FriendlyName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; } -void NewTilesetDialog::FillChanged() { - this->checkerboardFill = this->ui->fillCheckBox->isChecked(); - porymapConfig.tilesetCheckerboardFill = this->checkerboardFill; +void NewTilesetDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewTilesetDialog::accept() { + if (!validateName()) + return; + + bool secondary = ui->comboBox_Type->currentIndex() == 1; + Tileset *tileset = this->project->createNewTileset(ui->lineEdit_FriendlyName->text(), secondary, ui->checkBox_CheckerboardFill->isChecked()); + if (!tileset) { + ui->label_GenericError->setText(QString("Failed to create tileset. See %1 for details.").arg(getLogPath())); + ui->label_GenericError->setVisible(true); + return; + } + ui->label_GenericError->setVisible(false); + + QDialog::accept(); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 940761ad..f3c4a1c9 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -716,22 +716,9 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { image = image.convertToFormat(QImage::Format::Format_Indexed8, colorTable); } - // Validate image is properly indexed to 16 colors. - int colorCount = image.colorCount(); - if (colorCount > 16) { - flattenTo4bppImage(&image); - } else if (colorCount < 16) { - QVector colorTable = image.colorTable(); - for (int i = colorTable.length(); i < 16; i++) { - colorTable.append(Qt::black); - } - image.setColorTable(colorTable); - } - - this->project->loadTilesetTiles(tileset, image); + tileset->loadTilesImage(&image); this->refresh(); this->hasUnsavedChanges = true; - tileset->hasUnsavedTilesImage = true; } void TilesetEditor::closeEvent(QCloseEvent *event) From 435d22c63a16aaaea9bd4f00b1b702fffb505840 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 6 Dec 2024 11:02:38 -0500 Subject: [PATCH 104/364] Fix layouts list sorting by ID rather than name --- include/ui/maplistmodels.h | 1 + src/mainwindow.cpp | 8 ++++---- src/ui/maplistmodels.cpp | 37 ++++++++++++++++++++----------------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 1962ad40..920bf9ba 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -148,6 +148,7 @@ public: ~LayoutTreeModel() {} QVariant data(const QModelIndex &index, int role) const override; + QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override; protected: void removeItem(QStandardItem *item) override; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3c16b0f4..71484f2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1279,13 +1279,13 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { }); } if (copyDisplayNameAction) { - connect(copyDisplayNameAction, &QAction::triggered, [this, sourceModel, index] { - setClipboardData(sourceModel->data(index, Qt::DisplayRole).toString()); + connect(copyDisplayNameAction, &QAction::triggered, [this, selectedItem] { + setClipboardData(selectedItem->text()); }); } if (copyToolTipAction) { - connect(copyToolTipAction, &QAction::triggered, [this, sourceModel, index] { - setClipboardData(sourceModel->data(index, Qt::ToolTipRole).toString()); + connect(copyToolTipAction, &QAction::triggered, [this, selectedItem] { + setClipboardData(selectedItem->toolTip()); }); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 1890684b..d6b20af9 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -98,6 +98,7 @@ QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); map->setEditable(this->editable); // Will override flags if necessary + map->setToolTip(this->project->mapNamesToMapConstants.value(mapName)); this->mapItems.insert(mapName, map); return map; } @@ -147,9 +148,9 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); - if (type == "map_name") { - // Data for maps in the map list - if (role == Qt::DecorationRole) { + if (role == Qt::DecorationRole) { + if (type == "map_name") { + // Decorating map in the map list if (name == this->activeItemName) return this->mapOpenedIcon; @@ -157,12 +158,8 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { if (!map) return this->mapGrayIcon; return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; - } else if (role == Qt::ToolTipRole) { - return this->project->mapNamesToMapConstants.value(name); - } - } else if (type == this->folderTypeName) { - // Data for map folders in the map list - if (role == Qt::DecorationRole) { + } else if (type == this->folderTypeName) { + // Decorating map folder in the map list return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon; } } @@ -451,6 +448,19 @@ void LayoutTreeModel::removeItem(QStandardItem *) { // TODO: Deleting layouts not supported } +QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) { + folder = MapListModel::createMapFolderItem(folderName, folder); + + // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. + // We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name. + const Layout* layout = this->project->mapLayouts.value(folderName); + if (layout) { + folder->setText(layout->name); + folder->setToolTip(layout->id); + } + return folder; +} + QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -463,23 +473,16 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { const QString name = item->data(MapListUserRoles::NameRole).toString(); if (type == this->folderTypeName) { - const Layout* layout = this->project->mapLayouts.value(name); - if (role == Qt::DecorationRole) { // Map layouts are used as folders, but we display them with the same icons as maps. if (name == this->activeItemName) return this->mapOpenedIcon; + const Layout* layout = this->project->mapLayouts.value(name); if (!layout || !layout->loaded) return this->mapGrayIcon; return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } - else if (role == Qt::DisplayRole) { - // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. - if (layout) return layout->name; - } else if (role == Qt::ToolTipRole) { - if (layout) return layout->id; - } } return MapListModel::data(index, role); } From 391f7b16855d0c32bd099c8ac75eb8ad59847cc8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 6 Dec 2024 12:29:40 -0500 Subject: [PATCH 105/364] Reserve MAP_DYNAMIC, fix some MAPSEC displays not updating, new error messages/handling --- forms/newlayoutdialog.ui | 5 +- include/mainwindow.h | 5 +- include/project.h | 1 + include/ui/regionmapeditor.h | 2 + src/core/regionmap.cpp | 7 ++- src/mainwindow.cpp | 91 +++++++++++++++++++++-------- src/project.cpp | 110 +++++++++++++++++++++-------------- src/scriptapi/apimap.cpp | 4 -- src/ui/mapheaderform.cpp | 9 ++- src/ui/maplistmodels.cpp | 9 +-- src/ui/regionmapeditor.cpp | 27 ++++++--- 11 files changed, 179 insertions(+), 91 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index d285bdb1..bfbc9b02 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -25,7 +25,7 @@ 0 0 238 - 106 + 107 @@ -56,6 +56,9 @@ <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> + + true +
diff --git a/include/mainwindow.h b/include/mainwindow.h index c51b10cf..1e8c53bb 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -349,7 +349,9 @@ private: void clearProjectUI(); void openNewMapDialog(); + void openDuplicateMapDialog(const QString &mapName); void openNewLayoutDialog(); + void openDuplicateLayoutDialog(const QString &layoutId); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -360,6 +362,7 @@ private: bool openProject(QString dir, bool initial = false); bool closeProject(); void showProjectOpenFailure(); + void showMapsExcludedAlert(const QStringList &excludedMapNames); bool setInitialMap(); void saveGlobalConfigs(); @@ -408,7 +411,7 @@ private: void scrollMetatileSelectorToSelection(); MapListToolBar* getCurrentMapListToolBar(); MapTree* getCurrentMapList(); - void refreshLocationsComboBox(); + void setLocationComboBoxes(const QStringList &locations); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); diff --git a/include/project.h b/include/project.h index 8a08cef6..895f9979 100644 --- a/include/project.h +++ b/include/project.h @@ -280,6 +280,7 @@ signals: void mapGroupAdded(const QString &groupName); void mapSectionAdded(const QString &idName); void mapSectionIdNamesChanged(const QStringList &idNames); + void mapsExcluded(const QStringList &excludedMapNames); }; #endif // PROJECT_H diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index c1941651..d5269548 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -44,6 +44,8 @@ public: bool reconfigure(); + void setLocations(const QStringList &locations); + QObjectList shortcutableObjects() const; public slots: diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index 94650ccd..587be33c 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -397,10 +397,15 @@ void RegionMap::saveLayout() { case LayoutFormat::Binary: { QByteArray data; + int defaultValue = this->project->mapSectionIdNames.indexOf(this->default_map_section); for (int m = 0; m < this->layout_height; m++) { for (int n = 0; n < this->layout_width; n++) { int i = n + this->layout_width * m; - data.append(this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section)); + int mapSectionValue = this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section); + if (mapSectionValue < 0){ + mapSectionValue = defaultValue; + } + data.append(mapSectionValue); } } QFile bfile(fullPath(this->layout_path)); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 71484f2f..e006ed9a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -627,7 +627,8 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::tilesetCreated, this, &MainWindow::onNewTilesetCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); - connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); + connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::setLocationComboBoxes); + connect(project, &Project::mapsExcluded, this, &MainWindow::showMapsExcludedAlert); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -702,6 +703,22 @@ void MainWindow::showProjectOpenFailure() { error.exec(); } +// Alert the user that one or more maps have been excluded while loading the project. +void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { + QMessageBox msgBox(QMessageBox::Icon::Warning, "porymap", "", QMessageBox::Ok, this); + + QString errorMsg; + if (excludedMapNames.length() == 1) { + errorMsg = QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first()); + } else { + errorMsg = QString("Failed to load the maps listed below. Saving will exclude these maps from your project."); + msgBox.setDetailedText(excludedMapNames.join("\n")); + } + errorMsg.append(QString("\n\nPlease see %1 for full error details.").arg(getLogPath())); + msgBox.setText(errorMsg); + msgBox.exec(); +} + bool MainWindow::isProjectOpen() { return editor && editor->project; } @@ -861,19 +878,23 @@ bool MainWindow::userSetMap(QString map_name) { return true; // Already set if (map_name == editor->project->getDynamicMapName()) { - QMessageBox msgBox(this); - QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name); - msgBox.warning(nullptr, "Cannot Open Map", errorMsg); + QMessageBox msgBox(QMessageBox::Icon::Warning, + "Cannot Open Map", + QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name), + QMessageBox::Ok, + this); + msgBox.exec(); return false; } if (!setMap(map_name)) { - QMessageBox msgBox(this); - QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3") - .arg(map_name) - .arg(getLogPath()) - .arg(getMostRecentError()); - msgBox.critical(nullptr, "Error Opening Map", errorMsg); + QMessageBox msgBox(QMessageBox::Icon::Critical, + "Error Opening Map", + QString("There was an error opening map %1.\n\nPlease see %2 for full error details.").arg(map_name).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); return false; } return true; @@ -929,12 +950,13 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // Use when the user is specifically requesting a layout to open. bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { - QMessageBox msgBox(this); - QString errorMsg = QString("There was an error opening layout %1. Please see %2 for full error details.\n\n%3") - .arg(layoutId) - .arg(getLogPath()) - .arg(getMostRecentError()); - msgBox.critical(nullptr, "Error Opening Layout", errorMsg); + QMessageBox msgBox(QMessageBox::Icon::Critical, + "Error Opening Layout", + QString("There was an error opening layout %1.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); return false; } @@ -1217,8 +1239,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { copyToolTipAction = menu.addAction("Copy Map ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { - auto dialog = new NewMapDialog(this->editor->project, this->editor->project->getMap(itemName), this); - dialog->open(); + openDuplicateMapDialog(itemName); }); //menu.addSeparator(); //connect(menu.addAction("Delete Map"), &QAction::triggered, [this, index] { deleteMapListItem(index); }); // TODO: No support for deleting maps @@ -1244,12 +1265,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { copyToolTipAction = menu.addAction("Copy Layout ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { - auto layout = this->editor->project->loadLayout(itemName); - if (layout) { - auto dialog = new NewLayoutDialog(this->editor->project, layout, this); - connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); - dialog->open(); - } + openDuplicateLayoutDialog(itemName); }); addToFolderAction = menu.addAction("Add New Map with Layout"); //menu.addSeparator(); @@ -1436,8 +1452,12 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { void MainWindow::onNewMapSectionCreated(const QString &idName) { // Add new map section to the Areas map list view this->mapAreaModel->insertMapFolderItem(idName); +} - // TODO: Refresh Region Map Editor's map section dropdown, if it's open +void MainWindow::setLocationComboBoxes(const QStringList &locations) { + this->mapHeaderForm->setLocations(locations); + if (this->regionMapEditor) + this->regionMapEditor->setLocations(locations); } void MainWindow::onNewTilesetCreated(Tileset *tileset) { @@ -1460,12 +1480,33 @@ void MainWindow::openNewMapDialog() { dialog->open(); } +void MainWindow::openDuplicateMapDialog(const QString &mapName) { + const Map *map = this->editor->project->getMap(mapName); + if (map) { + auto dialog = new NewMapDialog(this->editor->project, map, this); + dialog->open(); + } else { + //TODO + } +} + void MainWindow::openNewLayoutDialog() { auto dialog = new NewLayoutDialog(this->editor->project, this); connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); dialog->open(); } +void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { + auto layout = this->editor->project->loadLayout(layoutId); + if (layout) { + auto dialog = new NewLayoutDialog(this->editor->project, layout, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); + } else { + //TODO + } +} + void MainWindow::on_actionNew_Tileset_triggered() { auto dialog = new NewTilesetDialog(editor->project, this); dialog->open(); diff --git a/src/project.cpp b/src/project.cpp index e7fc3ab2..578d20d2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -406,10 +406,10 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t mapNamePos = this->mapNames.length(); } - if (!this->mapSectionIdNames.contains(map->header()->location())) { + const QString location = map->header()->location(); + if (!this->mapSectionIdNames.contains(location) && isIdentifierUnique(location)) { // Unrecognized MAPSEC value. Add it. - // TODO: Validate location before adding - addNewMapsec(map->header()->location()); + addNewMapsec(location); } this->mapNames.insert(mapNamePos, map->name()); @@ -492,15 +492,12 @@ bool Project::loadLayout(Layout *layout) { } Layout *Project::loadLayout(QString layoutId) { - if (this->mapLayouts.contains(layoutId)) { - Layout *layout = this->mapLayouts[layoutId]; - if (loadLayout(layout)) { - return layout; - } + Layout *layout = this->mapLayouts.value(layoutId); + if (!layout || !loadLayout(layout)) { + logError(QString("Failed to load layout '%1'").arg(layoutId)); + return nullptr; } - - logError(QString("Failed to load layout '%1'").arg(layoutId)); - return nullptr; + return layout; } bool Project::loadMapLayout(Map* map) { @@ -508,12 +505,12 @@ bool Project::loadMapLayout(Map* map) { return true; } - if (this->mapLayouts.contains(map->layoutId())) { - map->setLayout(this->mapLayouts[map->layoutId()]); - } else { + Layout *layout = this->mapLayouts.value(map->layoutId()); + if (!layout) { logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } + map->setLayout(layout); if (map->hasUnsavedChanges()) { return true; @@ -558,24 +555,11 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - static const QList requiredFields = QList{ - "id", - "name", - "width", - "height", - "primary_tileset", - "secondary_tileset", - "border_filepath", - "blockdata_filepath", - }; + QStringList failedLayoutNames; // TODO: Populate for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) continue; - if (!parser.ensureFieldsExist(layoutObj, requiredFields)) { - logError(QString("Layout %1 is missing field(s) in %2.").arg(i).arg(layoutsFilepath)); - return false; - } Layout *layout = new Layout(); layout->id = ParseUtil::jsonToQString(layoutObj["id"]); if (layout->id.isEmpty()) { @@ -611,15 +595,11 @@ bool Project::readMapLayouts() { if (projectConfig.useCustomBorderSize) { int bwidth = ParseUtil::jsonToInt(layoutObj["border_width"]); if (bwidth <= 0) { // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG - logWarn(QString("Invalid 'border_width' value '%1' for %2 in %3. Must be greater than 0. Using default (%4) instead.") - .arg(bwidth).arg(layout->id).arg(layoutsFilepath).arg(DEFAULT_BORDER_WIDTH)); bwidth = DEFAULT_BORDER_WIDTH; } layout->border_width = bwidth; int bheight = ParseUtil::jsonToInt(layoutObj["border_height"]); if (bheight <= 0) { - logWarn(QString("Invalid 'border_height' value '%1' for %2 in %3. Must be greater than 0. Using default (%4) instead.") - .arg(bheight).arg(layout->id).arg(layoutsFilepath).arg(DEFAULT_BORDER_HEIGHT)); bheight = DEFAULT_BORDER_HEIGHT; } layout->border_height = bheight; @@ -1817,8 +1797,10 @@ bool Project::readMapGroups() { QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); const QString dynamicMapName = getDynamicMapName(); + const QString dynamicMapConstant = getDynamicMapDefineName(); // Process the map group lists + QStringList failedMapNames; for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); @@ -1829,45 +1811,73 @@ bool Project::readMapGroups() { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); if (mapName == dynamicMapName) { logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); + failedMapNames.append(mapName); continue; } if (this->mapNames.contains(mapName)) { logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); + failedMapNames.append(mapName); continue; } // Load the map's json file so we can get its ID constant (and two other constants we use for the map list). QJsonDocument mapDoc; - if (!readMapJson(mapName, &mapDoc)) + if (!readMapJson(mapName, &mapDoc)) { + failedMapNames.append(mapName); continue; // Error message has already been logged + } // Read and validate the map's ID from its JSON data. const QJsonObject mapObj = mapDoc.object(); const QString mapConstant = ParseUtil::jsonToQString(mapObj["id"]); if (mapConstant.isEmpty()) { logWarn(QString("Map '%1' is missing an \"id\" value and will be ignored.").arg(mapName)); + failedMapNames.append(mapName); + continue; + } + if (mapConstant == dynamicMapConstant) { + logWarn(QString("Ignoring map with reserved \"id\" value '%1'.").arg(mapName)); + failedMapNames.append(mapName); continue; } const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); if (!mapConstant.startsWith(expectedPrefix)) { logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); + failedMapNames.append(mapName); continue; } auto it = this->mapConstantsToMapNames.constFind(mapConstant); if (it != this->mapConstantsToMapNames.constEnd()) { logWarn(QString("Map '%1' has the same \"id\" value '%2' as map '%3' and will be ignored.").arg(mapName).arg(it.key()).arg(it.value())); + failedMapNames.append(mapName); continue; } + // Read layout ID for map list + const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + if (!this->layoutIds.contains(layoutId)) { + // If a map has an unknown layout ID it won't be able to load it at all anyway, so skip it. + // Skipping these will let us assume all the map layout IDs are valid, which simplies some handling elsewhere. + logWarn(QString("Map '%1' has unknown \"layout\" value '%2' and will be ignored.").arg(mapName).arg(layoutId)); + failedMapNames.append(mapName); + continue; + } + + // Read MAPSEC name for map list + const QString mapSectionName = ParseUtil::jsonToQString(mapObj["region_map_section"]); + if (!this->mapSectionIdNames.contains(mapSectionName)) { + // An unknown location is OK. Aside from that name not appearing in the dropdowns this shouldn't cause problems. + // We'll log a warning, but allow this map to be displayed. + logWarn(QString("Map '%1' has unknown \"region_map_section\" value '%2'.").arg(mapName).arg(mapSectionName)); + } + // Success, save the constants to the project this->mapNames.append(mapName); this->groupNameToMapNames[groupName].append(mapName); - // TODO: These are not well-kept in sync (and that's probably a bad design indication. Maybe Maps should have a not-fully-loaded state, but have all their map.json data cached) this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); - // TODO: Either verify that these are known IDs, or make sure nothing breaks when they're unknown. - this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); - this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); + this->mapNameToLayoutId.insert(mapName, layoutId); + this->mapNameToMapSectionName.insert(mapName, mapSectionName); } } @@ -1880,10 +1890,15 @@ bool Project::readMapGroups() { return false; } + if (!failedMapNames.isEmpty()) { + // At least 1 map was excluded due to an error. + // User should be alerted of this, rather than just silently logging the details. + emit mapsExcluded(failedMapNames); + } + // Save special "Dynamic" constant - const QString defineName = this->getDynamicMapDefineName(); - this->mapConstantsToMapNames.insert(defineName, dynamicMapName); - this->mapNamesToMapConstants.insert(dynamicMapName, defineName); + this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); + this->mapNamesToMapConstants.insert(dynamicMapName, dynamicMapConstant); this->mapNames.append(dynamicMapName); return true; @@ -2274,10 +2289,18 @@ bool Project::readRegionMapSections() { QJsonObject mapSectionObj = mapSections.at(i).toObject(); // For each map section, "id" is the only required field. This is the field we use to display the location names in the map list, and in various drop-downs. - const QString idField = "id"; + QString idField = "id"; if (!mapSectionObj.contains(idField)) { - logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField)); - continue; + const QString oldIdField = "map_section"; + if (mapSectionObj.contains(oldIdField)) { + // User has the old name for this field. Parse using this name, then save with the new name. + // This will presumably stop the user's project from compiling, but that's preferable to + // ignoring everything here and then wiping the file's data when we save later. + idField = oldIdField; + } else { + logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField)); + continue; + } } const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); if (!idName.startsWith(requiredPrefix)) { @@ -2338,7 +2361,6 @@ void Project::addNewMapsec(const QString &name) { } this->hasUnsavedDataChanges = true; - // TODO: Simplify into a single signal that updates the map list only if necessary emit mapSectionAdded(name); emit mapSectionIdNamesChanged(this->mapSectionIdNames); } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 91cc4e67..2532f132 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -832,10 +832,6 @@ QString MainWindow::getLocation() { void MainWindow::setLocation(QString location) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->mapSectionIdNames.contains(location)) { - logError(QString("Unknown location '%1'").arg(location)); - return; - } this->editor->map->header()->setLocation(location); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 6f6e4dec..7aa5a628 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -55,7 +55,11 @@ void MapHeaderForm::init(const Project * project) { ui->comboBox_BattleScene->clear(); ui->comboBox_BattleScene->addItems(project->mapBattleScenes); - setLocations(project->mapSectionIdNames); + QStringList locations = project->mapSectionIdNames; + locations.sort(); + const QSignalBlocker b_Locations(ui->comboBox_Location); + ui->comboBox_Location->clear(); + ui->comboBox_Location->addItems(locations); // Hide config-specific settings @@ -72,8 +76,7 @@ void MapHeaderForm::init(const Project * project) { ui->label_FloorNumber->setVisible(floorNumEnabled); } -// This combo box is treated specially because (unlike the other combo boxes) -// items that should be in this drop-down can be added or removed externally. +// Unlike other combo boxes in the map header form, locations can be added or removed externally. void MapHeaderForm::setLocations(QStringList locations) { locations.sort(); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index d6b20af9..0f352f4f 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -454,10 +454,11 @@ QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, Q // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. // We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name. const Layout* layout = this->project->mapLayouts.value(folderName); - if (layout) { - folder->setText(layout->name); - folder->setToolTip(layout->id); - } + if (layout) folder->setText(layout->name); + + // The layout ID will instead be shown as a tool tip. + folder->setToolTip(folderName); + return folder; } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index c6485d51..cb0f6d44 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -651,10 +651,9 @@ void RegionMapEditor::displayRegionMapLayout() { void RegionMapEditor::displayRegionMapLayoutOptions() { if (!this->region_map->layoutEnabled()) return; - this->ui->comboBox_RM_ConnectedMap->blockSignals(true); + const QSignalBlocker b(ui->comboBox_RM_ConnectedMap); this->ui->comboBox_RM_ConnectedMap->clear(); this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionIdNames); - this->ui->comboBox_RM_ConnectedMap->blockSignals(false); this->ui->frame_RM_Options->setEnabled(true); @@ -662,22 +661,19 @@ void RegionMapEditor::displayRegionMapLayoutOptions() { } void RegionMapEditor::updateRegionMapLayoutOptions(int index) { - this->ui->comboBox_RM_ConnectedMap->blockSignals(true); + const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap); this->ui->comboBox_RM_ConnectedMap->setCurrentText(this->region_map->squareMapSection(index)); - this->ui->comboBox_RM_ConnectedMap->blockSignals(false); this->ui->pushButton_RM_Options_delete->setEnabled(this->region_map->squareHasMap(index)); - this->ui->spinBox_RM_LayoutWidth->blockSignals(true); - this->ui->spinBox_RM_LayoutHeight->blockSignals(true); + const QSignalBlocker b_LayoutWidth(ui->spinBox_RM_LayoutWidth); + const QSignalBlocker b_LayoutHeight(ui->spinBox_RM_LayoutHeight); this->ui->spinBox_RM_LayoutWidth->setMinimum(1); this->ui->spinBox_RM_LayoutWidth->setMaximum(this->region_map->tilemapWidth() - this->region_map->padLeft()); this->ui->spinBox_RM_LayoutHeight->setMinimum(1); this->ui->spinBox_RM_LayoutHeight->setMaximum(this->region_map->tilemapHeight() - this->region_map->padTop()); this->ui->spinBox_RM_LayoutWidth->setValue(this->region_map->layoutWidth()); this->ui->spinBox_RM_LayoutHeight->setValue(this->region_map->layoutHeight()); - this->ui->spinBox_RM_LayoutWidth->blockSignals(false); - this->ui->spinBox_RM_LayoutHeight->blockSignals(false); } void RegionMapEditor::displayRegionMapEntriesImage() { @@ -1323,3 +1319,18 @@ void RegionMapEditor::on_verticalSlider_Zoom_Image_Tiles_valueChanged(int val) { ui->graphicsView_RegionMap_Tiles->setTransform(transform); ui->graphicsView_RegionMap_Tiles->setFixedSize(width + 2, height + 2); } + +// Repopulate the combo boxes that display MAPSEC names. +void RegionMapEditor::setLocations(const QStringList &locations) { + const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap); + auto before = ui->comboBox_RM_ConnectedMap->currentText(); + ui->comboBox_RM_ConnectedMap->clear(); + ui->comboBox_RM_ConnectedMap->addItems(locations); + ui->comboBox_RM_ConnectedMap->setCurrentText(before); + + const QSignalBlocker b_MapSection(ui->comboBox_RM_Entry_MapSection); + before = ui->comboBox_RM_Entry_MapSection->currentText(); + ui->comboBox_RM_Entry_MapSection->clear(); + ui->comboBox_RM_Entry_MapSection->addItems(locations); + ui->comboBox_RM_Entry_MapSection->setCurrentText(before); +} From 11dd7306d32fb897a108c34c41c1860044b94c26 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 11 Dec 2024 01:01:00 -0500 Subject: [PATCH 106/364] Remove unused parser_util --- include/lib/fex/parser_util.h | 19 ------------- porymap.pro | 2 -- src/lib/fex/parser_util.cpp | 50 ----------------------------------- 3 files changed, 71 deletions(-) delete mode 100644 include/lib/fex/parser_util.h delete mode 100644 src/lib/fex/parser_util.cpp diff --git a/include/lib/fex/parser_util.h b/include/lib/fex/parser_util.h deleted file mode 100644 index 58ff89fc..00000000 --- a/include/lib/fex/parser_util.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PARSER_UTIL_H -#define PARSER_UTIL_H - -#include -#include - -class ParserUtil -{ -public: - ParserUtil(QString root); - QStringList ReadDefines(QString filename, QString prefix); - QStringList ReadDefinesValueSort(QString filename, QString prefix); - -private: - QString root_; -}; - - -#endif // PARSER_UTIL_H diff --git a/porymap.pro b/porymap.pro index 1b1c693e..5734fd73 100644 --- a/porymap.pro +++ b/porymap.pro @@ -44,7 +44,6 @@ SOURCES += src/core/block.cpp \ src/core/editcommands.cpp \ src/lib/fex/lexer.cpp \ src/lib/fex/parser.cpp \ - src/lib/fex/parser_util.cpp \ src/lib/orderedjson.cpp \ src/core/regionmapeditcommands.cpp \ src/scriptapi/apimap.cpp \ @@ -150,7 +149,6 @@ HEADERS += include/core/block.h \ include/lib/fex/define_statement.h \ include/lib/fex/lexer.h \ include/lib/fex/parser.h \ - include/lib/fex/parser_util.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ diff --git a/src/lib/fex/parser_util.cpp b/src/lib/fex/parser_util.cpp deleted file mode 100644 index 0f375b81..00000000 --- a/src/lib/fex/parser_util.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "lib/fex/parser_util.h" - -#include - -#include "lib/fex/parser.h" - -ParserUtil::ParserUtil(QString root): root_(root) {} - -QStringList ParserUtil::ReadDefines(QString filename, QString prefix) -{ - if (filename.isEmpty()) { - return QStringList(); - } - - QString filepath = root_ + "/" + filename; - - fex::Parser parser; - - std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); - - QStringList out; - for(auto const& define : defines) { - out.append(QString::fromStdString(define.first)); - } - - return out; -} - -QStringList ParserUtil::ReadDefinesValueSort(QString filename, QString prefix) -{ - - if (filename.isEmpty()) { - return QStringList(); - } - - QString filepath = root_ + "/" + filename; - - fex::Parser parser; - - std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); - - QMultiMap defines_keyed_by_value; - for (const auto& pair : defines) { - defines_keyed_by_value.insert(pair.second, QString::fromStdString(pair.first)); - } - - return defines_keyed_by_value.values(); -} From 6b70abaaf0c74a46a8e92fbf32be09fe92cd4f2d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 11 Dec 2024 09:00:51 -0500 Subject: [PATCH 107/364] Use QFile/QString for C parser files and paths --- include/lib/fex/lexer.h | 5 ++--- include/lib/fex/parser.h | 2 +- src/core/parseutil.cpp | 2 +- src/lib/fex/lexer.cpp | 39 +++++++++------------------------------ src/lib/fex/parser.cpp | 2 +- 5 files changed, 14 insertions(+), 36 deletions(-) diff --git a/include/lib/fex/lexer.h b/include/lib/fex/lexer.h index 9b22976d..d4d65271 100644 --- a/include/lib/fex/lexer.h +++ b/include/lib/fex/lexer.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace fex { @@ -89,9 +90,7 @@ namespace fex Lexer() = default; ~Lexer() = default; - std::vector LexFile(const std::string &path); - std::vector LexString(const std::string &data); - void LexFileDumpTokens(const std::string &path, const std::string &out); + std::vector LexFile(const QString &path); private: std::vector Lex(); diff --git a/include/lib/fex/parser.h b/include/lib/fex/parser.h index 6a6b9e43..b73dd81e 100644 --- a/include/lib/fex/parser.h +++ b/include/lib/fex/parser.h @@ -21,7 +21,7 @@ namespace fex std::vector ParseTopLevelArrays(std::vector tokens); std::map ParseTopLevelObjects(std::vector tokens); - std::map ReadDefines(const std::string &filename, std::vector matching); + std::map ReadDefines(const QString &filename, std::vector matching); private: int EvaluateExpression(std::vector tokens); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9664fdc7..2c357776 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -596,7 +596,7 @@ bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { QMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash memberMap) { QString filePath = this->root + "/" + filename; auto cParser = fex::Parser(); - auto tokens = fex::Lexer().LexFile(filePath.toStdString()); + auto tokens = fex::Lexer().LexFile(filePath); auto structs = cParser.ParseTopLevelObjects(tokens); QMap> structMaps; for (auto it = structs.begin(); it != structs.end(); it++) { diff --git a/src/lib/fex/lexer.cpp b/src/lib/fex/lexer.cpp index 2dd4b249..e8545f2e 100644 --- a/src/lib/fex/lexer.cpp +++ b/src/lib/fex/lexer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace fex { @@ -155,48 +156,26 @@ namespace fex return Token(Token::Type::kDefine, filename_, line_number_); } - std::vector Lexer::LexString(const std::string &data) + std::vector Lexer::LexFile(const QString &path) { - filename_ = "string literal"; - line_number_ = 1; - index_ = 0; - data_ = data; - - return Lex(); - } - - std::vector Lexer::LexFile(const std::string &path) - { - filename_ = path; + filename_ = path.toStdString(); line_number_ = 1; - std::ifstream file; - file.open(path); + // Note: Using QFile instead of ifstream to handle encoding differences between platforms + // (specifically to handle accented characters on Windows) + QFile file(path); + file.open(QIODevice::ReadOnly); - std::stringstream stream; - stream << file.rdbuf(); + const QByteArray data = file.readAll(); index_ = 0; - data_ = stream.str(); + data_ = data.toStdString(); file.close(); return Lex(); } - void Lexer::LexFileDumpTokens(const std::string &path, const std::string &out) - { - std::ofstream file; - file.open(out); - - for (Token token : LexFile(path)) - { - file << token.ToString() << std::endl; - } - - file.close(); - } - std::vector Lexer::Lex() { std::vector tokens; diff --git a/src/lib/fex/parser.cpp b/src/lib/fex/parser.cpp index bb5c90a8..2e2a6f3e 100644 --- a/src/lib/fex/parser.cpp +++ b/src/lib/fex/parser.cpp @@ -337,7 +337,7 @@ namespace fex return DefineStatement(identifer, value); } - std::map Parser::ReadDefines(const std::string &filename, std::vector matching) + std::map Parser::ReadDefines(const QString &filename, std::vector matching) { std::map out; From 8e6aa7888486d501f5dd29b0c4e441a6e6f8e90c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 11 Dec 2024 23:24:52 -0500 Subject: [PATCH 108/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11e4696d..2dcd180e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix bug where reloading a layout would overwrite all unsaved changes. - Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. - Fix crash when saving tilesets with fewer palettes than the maximum. +- Fix projects not opening on Windows if the project filepath contains certain characters. ## [5.4.1] - 2024-03-21 ### Fixed From 52a7cd4f56181007f9e5ad3ee20349937cc6a310 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 10 Dec 2024 18:22:22 -0500 Subject: [PATCH 109/364] Combine minor creation dialogs --- forms/newlayoutform.ui | 24 ++++++++-- forms/newnamedialog.ui | 82 +++++++++++++++++++++++++++++++++++ include/ui/newlayoutform.h | 3 +- include/ui/newnamedialog.h | 42 ++++++++++++++++++ porymap.pro | 3 ++ src/mainwindow.cpp | 89 ++++---------------------------------- src/ui/newlayoutform.cpp | 73 ++++++++++++++++++------------- src/ui/newnamedialog.cpp | 76 ++++++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+), 117 deletions(-) create mode 100644 forms/newnamedialog.ui create mode 100644 include/ui/newnamedialog.h create mode 100644 src/ui/newnamedialog.cpp diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui index f51fb742..bc3e7502 100644 --- a/forms/newlayoutform.ui +++ b/forms/newlayoutform.ui @@ -170,14 +170,30 @@ - + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + Secondary - + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> @@ -190,8 +206,8 @@ - - + + false diff --git a/forms/newnamedialog.ui b/forms/newnamedialog.ui new file mode 100644 index 00000000..6fa6b56d --- /dev/null +++ b/forms/newnamedialog.ui @@ -0,0 +1,82 @@ + + + NewNameDialog + + + + 0 + 0 + 252 + 87 + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Name + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index 6f8d9905..3e77af18 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -33,7 +33,8 @@ private: Project *m_project; bool validateMapDimensions(); - bool validateTilesets(); + bool validatePrimaryTileset(bool allowEmpty = false); + bool validateSecondaryTileset(bool allowEmpty = false); }; #endif // NEWLAYOUTFORM_H diff --git a/include/ui/newnamedialog.h b/include/ui/newnamedialog.h new file mode 100644 index 00000000..23979fc5 --- /dev/null +++ b/include/ui/newnamedialog.h @@ -0,0 +1,42 @@ +#ifndef NEWNAMEDIALOG_H +#define NEWNAMEDIALOG_H + +/* + This is a generic dialog for requesting a new unique name from the user. +*/ + +#include +#include + +class Project; + +namespace Ui { +class NewNameDialog; +} + +class NewNameDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewNameDialog(const QString &label, Project *project, QWidget *parent = nullptr); + ~NewNameDialog(); + + void setNamePrefix(const QString &prefix); + + virtual void accept() override; + +signals: + void applied(const QString &newName); + +private: + Ui::NewNameDialog *ui; + Project *project = nullptr; + const QString symbolPrefix; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWNAMEDIALOG_H diff --git a/porymap.pro b/porymap.pro index 1dfaf4b1..171ea48d 100644 --- a/porymap.pro +++ b/porymap.pro @@ -91,6 +91,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ + src/ui/newnamedialog.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -197,6 +198,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ + include/ui/newnamedialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ include/ui/montabwidget.h \ @@ -243,6 +245,7 @@ FORMS += forms/mainwindow.ui \ forms/maplisttoolbar.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ + forms/newnamedialog.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e006ed9a..629a582f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,6 +26,7 @@ #include "newmapdialog.h" #include "newlayoutdialog.h" #include "newtilesetdialog.h" +#include "newnamedialog.h" #include #include @@ -1310,90 +1311,16 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } void MainWindow::mapListAddGroup() { - 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)); - - QLabel *errorMessageLabel = new QLabel(&dialog); - errorMessageLabel->setVisible(false); - errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - - connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - const QString mapGroupName = newNameEdit->text(); - if (!this->editor->project->isIdentifierUnique(mapGroupName)) { - errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(mapGroupName)); - errorMessageLabel->setVisible(true); - } else { - dialog.accept(); - } - }); - - QFormLayout form(&dialog); - - form.addRow("New Group Name", newNameEdit); - form.addRow("", errorMessageLabel); - form.addRow(&newItemButtonBox); - - if (dialog.exec() == QDialog::Accepted) { - QString newFieldName = newNameEdit->text(); - if (newFieldName.isEmpty()) return; - this->editor->project->addNewMapGroup(newFieldName); - } + auto dialog = new NewNameDialog("New Group Name", this->editor->project, this); + connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); + dialog->open(); } void MainWindow::mapListAddArea() { - 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); - - // 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); - newNameDisplay->setText(prefix); - connect(newNameEdit, &QLineEdit::textEdited, [newNameDisplay, prefix] (const QString &text) { - // As the user types a name, update the label to show the name with the prefix. - newNameDisplay->setText(prefix + text); - }); - - QLabel *errorMessageLabel = new QLabel(&dialog); - errorMessageLabel->setVisible(false); - errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - - static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); - newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); - - connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - const QString newAreaName = newNameDisplay->text(); - if (!this->editor->project->isIdentifierUnique(newAreaName)) { - errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(newAreaName)); - errorMessageLabel->setVisible(true); - } else { - dialog.accept(); - } - }); - - QLabel *newNameEditLabel = new QLabel("New Area Name", &dialog); - QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); - - QFormLayout form(&dialog); - form.addRow(newNameEditLabel, newNameEdit); - form.addRow(newNameDisplayLabel, newNameDisplay); - form.addRow("", errorMessageLabel); - form.addRow(&newItemButtonBox); - - if (dialog.exec() == QDialog::Accepted) { - if (newNameEdit->text().isEmpty()) return; - this->editor->project->addNewMapsec(newNameDisplay->text()); - } + auto dialog = new NewNameDialog("New Area Name", this->editor->project, this); + dialog->setNamePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); + connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); + dialog->open(); } void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 64c2fdfb..aa8178ce 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -12,12 +12,14 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - // TODO: Read from project? ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); - connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); - connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); }); + connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); }); + + connect(ui->comboBox_PrimaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validatePrimaryTileset(true); }); + connect(ui->comboBox_SecondaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validateSecondaryTileset(true); }); } NewLayoutForm::~NewLayoutForm() @@ -71,12 +73,12 @@ Layout::Settings NewLayoutForm::settings() const { return settings; } -// TODO: Validate while typing bool NewLayoutForm::validate() { // Make sure to call each validation function so that all errors are shown at once. bool valid = true; if (!validateMapDimensions()) valid = false; - if (!validateTilesets()) valid = false; + if (!validatePrimaryTileset()) valid = false; + if (!validateSecondaryTileset()) valid = false; return valid; } @@ -84,11 +86,17 @@ bool NewLayoutForm::validateMapDimensions() { int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); int maxSize = m_project->getMaxMapDataSize(); + // TODO: Get from project + const int additionalWidth = 15; + const int additionalHeight = 14; + 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") + "The maximum map width and height is the following: (width + %1) * (height + %2) <= %3\n" + "The specified map width and height was: (%4 + %1) * (%5 + %2) = %6") + .arg(additionalWidth) + .arg(additionalHeight) .arg(maxSize) .arg(ui->spinBox_MapWidth->value()) .arg(ui->spinBox_MapHeight->value()) @@ -101,33 +109,36 @@ bool NewLayoutForm::validateMapDimensions() { return isValid; } -bool NewLayoutForm::validateTilesets() { - QString primaryTileset = ui->comboBox_PrimaryTileset->currentText(); - QString secondaryTileset = ui->comboBox_SecondaryTileset->currentText(); +bool NewLayoutForm::validatePrimaryTileset(bool allowEmpty) { + const QString name = ui->comboBox_PrimaryTileset->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 errorText; + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("The Primary Tileset cannot be empty."); + } else if (ui->comboBox_PrimaryTileset->findText(name) < 0) { + errorText = QString("The Primary Tileset '%1' does not exist.").arg(ui->label_PrimaryTileset->text()).arg(name); } - 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); - ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!primaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); - ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!secondaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); + ui->label_PrimaryTilesetError->setText(errorText); + ui->label_PrimaryTilesetError->setVisible(!isValid); + ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +bool NewLayoutForm::validateSecondaryTileset(bool allowEmpty) { + const QString name = ui->comboBox_SecondaryTileset->currentText(); + + QString errorText; + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("The Secondary Tileset cannot be empty."); + } else if (ui->comboBox_SecondaryTileset->findText(name) < 0) { + errorText = QString("The Secondary Tileset '%1' does not exist.").arg(name); + } + + bool isValid = errorText.isEmpty(); + ui->label_SecondaryTilesetError->setText(errorText); + ui->label_SecondaryTilesetError->setVisible(!isValid); + ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); return isValid; } diff --git a/src/ui/newnamedialog.cpp b/src/ui/newnamedialog.cpp new file mode 100644 index 00000000..d5800daa --- /dev/null +++ b/src/ui/newnamedialog.cpp @@ -0,0 +1,76 @@ +#include "newnamedialog.h" +#include "ui_newnamedialog.h" +#include "project.h" +#include "imageexport.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *parent) : + QDialog(parent), + ui(new Ui::NewNameDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + ui->setupUi(this); + this->project = project; + + if (!label.isEmpty()) + ui->label_Name->setText(label); + + // Identifiers must only contain word characters, and cannot start with a digit. + static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); + ui->lineEdit_Name->setValidator(validator); + + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewNameDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewNameDialog::dialogButtonClicked); + + adjustSize(); +} + +NewNameDialog::~NewNameDialog() +{ + delete ui; +} + +void NewNameDialog::setNamePrefix(const QString &) { + //TODO +} + +void NewNameDialog::onNameChanged(const QString &) { + validateName(true); +} + +bool NewNameDialog::validateName(bool allowEmpty) { + const 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->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").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 NewNameDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewNameDialog::accept() { + if (!validateName()) + return; + + emit applied(ui->lineEdit_Name->text()); + QDialog::accept(); +} From a6233e97c247bd5162bfe882f4f251f2206b7688 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 12 Dec 2024 16:13:12 -0500 Subject: [PATCH 110/364] Ensure automatic new layout names are unique --- src/ui/newmapdialog.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d362d2dc..5d9c7413 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -135,12 +135,29 @@ void NewMapDialog::saveSettings() { settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - // TODO: Verify uniqueness. If the layout ID belongs to an existing layout we don't need to do this at all. - settings->layout.name = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + // This dialog doesn't give users the option to give new layouts a name. + // If a new layout is being created we'll generate a unique layout name using the map name and a suffix. + // (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for + // something the majority of users creating a map won't need. If they want to give a specific name to a layout + // they can create the layout first, then create a new map that uses that layout.) + const Layout *layout = this->project->mapLayouts.value(settings->layout.id); + if (!layout) { + const QString baseLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + QString newLayoutName = baseLayoutName; + int i = 2; + while (!this->project->isIdentifierUnique(newLayoutName)) { + newLayoutName = QString("%1_%2").arg(baseLayoutName).arg(i++); + } + settings->layout.name = newLayoutName; + } else { + // Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway. + settings->layout.name = layout->name; + } // Folders for new layouts created for new maps use the map name, rather than the layout name. // There's no real reason for this, aside from maintaining consistency with the default layout - // folder names that do this (which would otherwise all have a '_Layout' suffix in the name). + // folder names that do this (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', + // rather than 'data/layouts/MyMap_Layout/'). settings->layout.folderName = settings->name; porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); From 4209c3e3f809a99295cf1546f0c7b520b19a9e1d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 12 Dec 2024 16:38:51 -0500 Subject: [PATCH 111/364] Fix checkerboard pattern for secondary tilesets --- src/project.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/project.cpp b/src/project.cpp index 578d20d2..0ad2114e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1486,6 +1486,9 @@ Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, tile.tileId = ((i % 2) == 0) ? 1 : 2; else tile.tileId = ((i % 2) == 1) ? 1 : 2; + + if (tileset->is_secondary) + tile.tileId += Project::getNumTilesPrimary(); } metatile->tiles.append(tile); } From bdd64a6c6b2a26d1986050595d10e2fbab2bfe2d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 14 Dec 2024 16:22:28 -0500 Subject: [PATCH 112/364] Use applicationName() for window titles, clean up some remaining TODO items --- forms/newlayoutdialog.ui | 16 +++++- forms/newmapdialog.ui | 26 ++++++++-- forms/newnamedialog.ui | 9 ++-- forms/newtilesetdialog.ui | 21 +++++--- include/core/map.h | 2 +- include/core/maplayout.h | 2 - include/lib/collapsiblesection.h | 2 +- include/mainwindow.h | 6 ++- include/project.h | 5 +- src/lib/collapsiblesection.cpp | 4 +- src/mainwindow.cpp | 68 ++++++++++++++++---------- src/project.cpp | 83 +++++++++++++++++++++++--------- src/ui/customscriptseditor.cpp | 4 +- src/ui/maplistmodels.cpp | 21 +++++--- src/ui/newlayoutdialog.cpp | 27 +++-------- src/ui/newmapdialog.cpp | 69 +++++++++----------------- src/ui/newnamedialog.cpp | 1 - src/ui/newtilesetdialog.cpp | 1 - src/ui/prefab.cpp | 2 +- src/ui/projectsettingseditor.cpp | 2 +- src/ui/regionmapeditor.cpp | 2 +- src/ui/shortcutseditor.cpp | 2 +- src/ui/tileseteditor.cpp | 4 +- src/ui/wildmonchart.cpp | 2 +- 24 files changed, 221 insertions(+), 160 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index bfbc9b02..42c7733c 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -13,9 +13,21 @@ New Layout Options + + true + + + true + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + true @@ -24,8 +36,8 @@ 0 0 - 238 - 107 + 240 + 109 diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 3e133dfd..6b32810a 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -6,16 +6,31 @@ 0 0 - 255 - 320 + 559 + 614 New Map Options + + true + + + true + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents + true @@ -24,8 +39,8 @@ 0 0 - 229 - 228 + 535 + 550 @@ -42,6 +57,9 @@ + + QLayout::SizeConstraint::SetMinAndMaxSize + 0 diff --git a/forms/newnamedialog.ui b/forms/newnamedialog.ui index 6fa6b56d..fa6cb5ae 100644 --- a/forms/newnamedialog.ui +++ b/forms/newnamedialog.ui @@ -10,15 +10,12 @@ 87 + + true + - - QFrame::Shape::NoFrame - - - QFrame::Shadow::Plain - 0 diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index 1fed2558..3b898761 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -13,16 +13,25 @@ Add new Tileset + + true + - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - + + 0 + + + 0 + + + 0 + + + 0 + diff --git a/include/core/map.h b/include/core/map.h index 6e953877..9cc2a69b 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -107,7 +107,7 @@ public: private: QString m_name; QString m_constantName; - QString m_layoutId; // TODO: Why do we do half this->layout()->id and half this->layoutId. Should these ever be different? + QString m_layoutId; QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 7761d7fc..1579b4ad 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -14,8 +14,6 @@ class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; -// TODO: Privatize members as appropriate - class Layout : public QObject { Q_OBJECT public: diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 584e44cb..73169303 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -41,7 +41,7 @@ public: explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0); void setContentLayout(QLayout* contentLayout); - void setTitle(QString title); + void setTitle(const QString &title); bool isExpanded() const { return this->expanded; } public slots: diff --git a/include/mainwindow.h b/include/mainwindow.h index 1e8c53bb..0eac8947 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -31,6 +31,7 @@ #include "updatepromoter.h" #include "aboutporymap.h" #include "mapheaderform.h" +#include "newlayoutdialog.h" @@ -350,8 +351,11 @@ private: void openNewMapDialog(); void openDuplicateMapDialog(const QString &mapName); + NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr); void openNewLayoutDialog(); void openDuplicateLayoutDialog(const QString &layoutId); + void openNewMapGroupDialog(); + void openNewAreaDialog(); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -370,8 +374,6 @@ private: void refreshRecentProjectsMenu(); void updateMapList(); - void mapListAddGroup(); - void mapListAddArea(); void openMapListItem(const QModelIndex &index); void saveMapListTab(int index); diff --git a/include/project.h b/include/project.h index 895f9979..9c9fdf33 100644 --- a/include/project.h +++ b/include/project.h @@ -111,7 +111,7 @@ public: QStringList secondaryTilesetLabels; QStringList tilesetLabelsOrdered; - Blockdata readBlockdata(QString); + Blockdata readBlockdata(QString, bool *ok = nullptr); bool loadBlockdata(Layout *); bool loadLayoutBorder(Layout *); @@ -121,6 +121,7 @@ public: bool readMapGroups(); void addNewMapGroup(const QString &groupName); + QString mapNameToMapGroup(const QString &mapName); struct NewMapSettings { QString name; @@ -141,6 +142,8 @@ public: Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); Tileset *createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; + bool isValidNewIdentifier(const QString &identifier) const; + QString toUniqueIdentifier(const QString &identifier) const; QString getProjectTitle(); bool readWildMonData(); diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp index 34dfb780..8ad8560a 100644 --- a/src/lib/collapsiblesection.cpp +++ b/src/lib/collapsiblesection.cpp @@ -119,9 +119,9 @@ void CollapsibleSection::setContentLayout(QLayout* contentLayout) updateAnimationTargets(); } -void CollapsibleSection::setTitle(QString title) +void CollapsibleSection::setTitle(const QString &title) { - toggleButton->setText(std::move(title)); + toggleButton->setText(title); } int CollapsibleSection::getContentHeight() const diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 629a582f..65a323e2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -24,7 +24,6 @@ #include "config.h" #include "filedialog.h" #include "newmapdialog.h" -#include "newlayoutdialog.h" #include "newtilesetdialog.h" #include "newnamedialog.h" @@ -69,7 +68,7 @@ MainWindow::MainWindow(QWidget *parent) : QCoreApplication::setOrganizationName("pret"); QCoreApplication::setApplicationName("porymap"); QCoreApplication::setApplicationVersion(PORYMAP_VERSION); - QApplication::setApplicationDisplayName("porymap"); + QApplication::setApplicationDisplayName(QApplication::applicationName()); QApplication::setWindowIcon(QIcon(":/icons/porymap-icon-2.ico")); ui->setupUi(this); @@ -448,8 +447,8 @@ void MainWindow::initMapList() { connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); // 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_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewMapGroupDialog); + connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewAreaDialog); connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); @@ -699,14 +698,14 @@ bool MainWindow::checkProjectSanity() { void MainWindow::showProjectOpenFailure() { QString errorMsg = QString("There was an error opening the project. Please see %1 for full error details.").arg(getLogPath()); - QMessageBox error(QMessageBox::Critical, "porymap", errorMsg, QMessageBox::Ok, this); + QMessageBox error(QMessageBox::Critical, QApplication::applicationName(), errorMsg, QMessageBox::Ok, this); error.setDetailedText(getMostRecentError()); error.exec(); } // Alert the user that one or more maps have been excluded while loading the project. void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { - QMessageBox msgBox(QMessageBox::Icon::Warning, "porymap", "", QMessageBox::Ok, this); + QMessageBox msgBox(QMessageBox::Icon::Warning, QApplication::applicationName(), "", QMessageBox::Ok, this); QString errorMsg; if (excludedMapNames.length() == 1) { @@ -880,7 +879,7 @@ bool MainWindow::userSetMap(QString map_name) { if (map_name == editor->project->getDynamicMapName()) { QMessageBox msgBox(QMessageBox::Icon::Warning, - "Cannot Open Map", + QApplication::applicationName(), QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name), QMessageBox::Ok, this); @@ -890,7 +889,7 @@ bool MainWindow::userSetMap(QString map_name) { if (!setMap(map_name)) { QMessageBox msgBox(QMessageBox::Icon::Critical, - "Error Opening Map", + QApplication::applicationName(), QString("There was an error opening map %1.\n\nPlease see %2 for full error details.").arg(map_name).arg(getLogPath()), QMessageBox::Ok, this); @@ -952,7 +951,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { QMessageBox msgBox(QMessageBox::Icon::Critical, - "Error Opening Layout", + QApplication::applicationName(), QString("There was an error opening layout %1.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), QMessageBox::Ok, this); @@ -1310,13 +1309,13 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { menu.exec(QCursor::pos()); } -void MainWindow::mapListAddGroup() { +void MainWindow::openNewMapGroupDialog() { auto dialog = new NewNameDialog("New Group Name", this->editor->project, this); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); dialog->open(); } -void MainWindow::mapListAddArea() { +void MainWindow::openNewAreaDialog() { auto dialog = new NewNameDialog("New Area Name", this->editor->project, this); dialog->setNamePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); @@ -1388,9 +1387,11 @@ void MainWindow::setLocationComboBoxes(const QStringList &locations) { } void MainWindow::onNewTilesetCreated(Tileset *tileset) { - QString message = QString("Created a new tileset named %1.").arg(tileset->name); - logInfo(message); - statusBar()->showMessage(message); + logInfo(QString("Created a new tileset named %1.").arg(tileset->name)); + + // Unlike creating a new map or layout (which immediately opens the new item) + // creating a new tileset has no visual feedback that it succeeded, so we show a message. + QMessageBox::information(this, QApplication::applicationName(), QString( "New tileset created at '%1'!").arg(tileset->getExpectedDir())); // Refresh tileset combo boxes if (!tileset->is_secondary) { @@ -1413,24 +1414,40 @@ void MainWindow::openDuplicateMapDialog(const QString &mapName) { auto dialog = new NewMapDialog(this->editor->project, map, this); dialog->open(); } else { - //TODO + QMessageBox msgBox(QMessageBox::Icon::Critical, + QApplication::applicationName(), + QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(mapName).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); } } -void MainWindow::openNewLayoutDialog() { - auto dialog = new NewLayoutDialog(this->editor->project, this); +NewLayoutDialog* MainWindow::createNewLayoutDialog(const Layout *layoutToCopy) { + auto dialog = new NewLayoutDialog(this->editor->project, layoutToCopy, this); connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + return dialog; +} + +void MainWindow::openNewLayoutDialog() { + auto dialog = createNewLayoutDialog(); dialog->open(); } void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { auto layout = this->editor->project->loadLayout(layoutId); if (layout) { - auto dialog = new NewLayoutDialog(this->editor->project, layout, this); - connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + auto dialog = createNewLayoutDialog(layout); dialog->open(); } else { - //TODO + QMessageBox msgBox(QMessageBox::Icon::Critical, + QApplication::applicationName(), + QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); } } @@ -1675,7 +1692,7 @@ void MainWindow::setClipboardData(OrderedJson::object object) { QClipboard *clipboard = QGuiApplication::clipboard(); QString newText; int indent = 0; - object["application"] = "porymap"; + object["application"] = QApplication::applicationName(); OrderedJson data(object); data.dump(newText, &indent); clipboard->setText(newText); @@ -1714,7 +1731,7 @@ void MainWindow::paste() { QJsonObject pasteObject = pasteJsonDoc.object(); //OrderedJson::object pasteObject = pasteJson.object_items(); - if (pasteObject["application"].toString() != "porymap") { + if (pasteObject["application"].toString() != QApplication::applicationName()) { return; } @@ -2530,8 +2547,7 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() { return; } - auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this); - connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + auto dialog = createNewLayoutDialog(mapLayout); connect(dialog, &NewLayoutDialog::finished, [mapLayout] { mapLayout->deleteLater(); }); dialog->open(); } @@ -2858,7 +2874,7 @@ void MainWindow::onWarpBehaviorWarningClicked() { "You can disable this warning or edit the list of behaviors that silence this warning under Options -> Project Settings..." "

" ); - QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this); + QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this); QPushButton *settings = msgBox.addButton("Open Settings...", QMessageBox::ActionRole); msgBox.setDefaultButton(QMessageBox::Close); msgBox.setTextFormat(Qt::RichText); @@ -3074,7 +3090,7 @@ bool MainWindow::closeProject() { if (this->editor->project->hasUnsavedChanges()) { QMessageBox::StandardButton result = QMessageBox::question( - this, "porymap", "The project has been modified, save changes?", + this, QApplication::applicationName(), "The project has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (result == QMessageBox::Yes) { diff --git a/src/project.cpp b/src/project.cpp index 0ad2114e..0a66c0e8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -367,13 +367,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setNeedsHealLocation(settings.canFlyTo); // Generate a unique MAP constant. - int suffix = 2; - const QString baseMapConstant = Map::mapConstantFromName(map->name()); - QString mapConstant = baseMapConstant; - while (!isIdentifierUnique(mapConstant)) { - mapConstant = QString("%1_%2").arg(baseMapConstant).arg(suffix++); - } - map->setConstantName(mapConstant); + map->setConstantName(toUniqueIdentifier(Map::mapConstantFromName(map->name()))); Layout *layout = this->mapLayouts.value(settings.layout.id); if (!layout) { @@ -402,13 +396,15 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t } else { // Adding map to a map group that doesn't exist yet. // Create the group, and we already know the map will be last in the list. - addNewMapGroup(settings.group); + if (isValidNewIdentifier(settings.group)) { + addNewMapGroup(settings.group); + } mapNamePos = this->mapNames.length(); } const QString location = map->header()->location(); - if (!this->mapSectionIdNames.contains(location) && isIdentifierUnique(location)) { - // Unrecognized MAPSEC value. Add it. + if (!this->mapSectionIdNames.contains(location) && isValidNewIdentifier(location)) { + // Unrecognized MAPSEC name, we can automatically add a new MAPSEC for it. addNewMapsec(location); } @@ -555,7 +551,6 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - QStringList failedLayoutNames; // TODO: Populate for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) @@ -1124,9 +1119,16 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { } bool Project::loadBlockdata(Layout *layout) { + bool ok = true; QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); - layout->blockdata = readBlockdata(path); - layout->lastCommitBlocks.blocks = layout->blockdata; + auto blockdata = readBlockdata(path, &ok); + if (!ok) { + logError(QString("Failed to load layout blockdata from '%1'").arg(path)); + return false; + } + + layout->blockdata = blockdata; + layout->lastCommitBlocks.blocks = blockdata; layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) { @@ -1153,9 +1155,16 @@ void Project::setNewLayoutBlockdata(Layout *layout) { } bool Project::loadLayoutBorder(Layout *layout) { + bool ok = true; QString path = QString("%1/%2").arg(root).arg(layout->border_path); - layout->border = readBlockdata(path); - layout->lastCommitBlocks.border = layout->border; + auto blockdata = readBlockdata(path, &ok); + if (!ok) { + logError(QString("Failed to load layout border from '%1'").arg(path)); + return false; + } + + layout->border = blockdata; + layout->lastCommitBlocks.border = blockdata; layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); int borderLength = layout->getBorderWidth() * layout->getBorderHeight(); @@ -1471,7 +1480,6 @@ Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, // Set default tiles image QImage tilesImage(":/images/blank_tileset.png"); tileset->loadTilesImage(&tilesImage); - //exportIndexed4BPPPng(tileset->tilesImage, tileset->tilesImagePath); // TODO: Make sure we can now properly handle the 8bpp images that get written without this. // Create default metatiles const int numMetatiles = tileset->is_secondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); @@ -1580,7 +1588,7 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) { } } -Blockdata Project::readBlockdata(QString path) { +Blockdata Project::readBlockdata(QString path, bool *ok) { Blockdata blockdata; QFile file(path); if (file.open(QIODevice::ReadOnly)) { @@ -1589,8 +1597,10 @@ Blockdata Project::readBlockdata(QString path) { uint16_t word = static_cast((data[i] & 0xff) + ((data[i + 1] & 0xff) << 8)); blockdata.append(word); } + if (ok) *ok = true; } else { - logError(QString("Failed to open blockdata path '%1'").arg(path)); + // Failed + if (ok) *ok = false; } return blockdata; @@ -1918,6 +1928,16 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } +QString Project::mapNameToMapGroup(const QString &mapName) { + for (auto it = this->groupNameToMapNames.constBegin(); it != this->groupNameToMapNames.constEnd(); it++) { + const QStringList mapNames = it.value(); + if (mapNames.contains(mapName)) { + return it.key(); + } + } + return QString(); +} + // When we ask the user to provide a new identifier for something (like a map name or MAPSEC 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. @@ -1946,22 +1966,41 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return true; } +// For some arbitrary string, return true if it's both a valid identifier name +// and not one that's already in-use. +bool Project::isValidNewIdentifier(const QString &identifier) const { + static const QRegularExpression re_identifier("[A-Za-z_]+[\\w]*"); + QRegularExpressionMatch match = re_identifier.match(identifier); + return match.hasMatch() && isIdentifierUnique(identifier); +} + +// Assumes 'identifier' is a valid name. If 'identifier' is unique, returns 'identifier'. +// Otherwise returns the identifier with a numbered suffix added to make it unique. +QString Project::toUniqueIdentifier(const QString &identifier) const { + int suffix = 2; + QString uniqueIdentifier = identifier; + while (!isIdentifierUnique(uniqueIdentifier)) { + uniqueIdentifier = QString("%1_%2").arg(identifier).arg(suffix++); + } + return uniqueIdentifier; +} + QString Project::getNewMapName() const { // Ensure default name/ID doesn't already exist. - int i = 0; + int suffix = 1; QString newMapName; do { - newMapName = QString("NewMap%1").arg(++i); + newMapName = QString("NewMap%1").arg(suffix++); } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(Map::mapConstantFromName(newMapName))); return newMapName; } QString Project::getNewLayoutName() const { // Ensure default name/ID doesn't already exist. - int i = 0; + int suffix = 1; QString newLayoutName; do { - newLayoutName = QString("NewLayout%1").arg(++i); + newLayoutName = QString("NewLayout%1").arg(suffix++); } while (!isIdentifierUnique(newLayoutName) || !isIdentifierUnique(Layout::layoutConstantFromName(newLayoutName))); return newLayoutName; } diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index 284ea333..c3c412c2 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -185,7 +185,7 @@ void CustomScriptsEditor::displayNewScript(QString filepath) { // Verify new script path is not already in list for (int i = 0; i < ui->list->count(); i++) { if (filepath == this->getScriptFilepath(ui->list->item(i), false)) { - QMessageBox::information(this, "", QString("The script '%1' is already loaded").arg(filepath)); + QMessageBox::information(this, QApplication::applicationName(), QString("The script '%1' is already loaded").arg(filepath)); return; } } @@ -219,7 +219,7 @@ void CustomScriptsEditor::openScript(QListWidgetItem * item) { const QString path = this->getScriptFilepath(item); QFileInfo fileInfo(path); if (!fileInfo.exists() || !fileInfo.isFile()){ - QMessageBox::warning(this, "", QString("Failed to open script '%1'").arg(path)); + QMessageBox::warning(this, QApplication::applicationName(), QString("Failed to open script '%1'").arg(path)); return; } Editor::openInTextEditor(path); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 0f352f4f..dc1e3d7e 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -115,21 +115,30 @@ QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QSta } QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) { - // Disallow adding MAP_DYNAMIC to the map list. - if (mapName == this->project->getDynamicMapName()) + if (mapName.isEmpty() || mapName == this->project->getDynamicMapName()) // Disallow adding MAP_DYNAMIC to the map list. return nullptr; - QStandardItem *folder = this->mapFolderItems[folderName]; - if (!folder) folder = insertMapFolderItem(folderName); - QStandardItem *map = createMapItem(mapName); - folder->appendRow(map); + + QStandardItem *folder = this->mapFolderItems[folderName]; + if (!folder) { + // Folder doesn't exist yet, add it. + folder = insertMapFolderItem(folderName); + } + // If folder is still nullptr here it's because we failed to create it. + if (folder) { + folder->appendRow(map); + } + if (this->sortingEnabled) this->sort(0, Qt::AscendingOrder); return map; } QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) { + if (folderName.isEmpty()) + return nullptr; + QStandardItem *item = createMapFolderItem(folderName); this->root->appendRow(item); if (this->sortingEnabled) diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 79af74e5..086bc346 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -19,31 +19,18 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q layoutToCopy(layoutToCopy) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; + Layout::Settings *settings = &project->newLayoutSettings; - QString newName; - QString newId; + // Note: 'layoutToCopy' will have an empty name if it's an import from AdvanceMap if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { - // Duplicating a layout, the initial name will be the base layout's name - // with a numbered suffix to make it unique. - // Note: If 'layoutToCopy' is an imported AdvanceMap layout it won't have - // a name, so it uses the default new layout name instead. - int i = 2; - do { - newName = QString("%1_%2").arg(this->layoutToCopy->name).arg(i++); - newId = Layout::layoutConstantFromName(newName); - } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); + settings->name = project->toUniqueIdentifier(this->layoutToCopy->name); } else { - newName = project->getNewLayoutName(); - newId = Layout::layoutConstantFromName(newName); + settings->name = project->getNewLayoutName(); } - - // We reset these settings for every session with the new layout dialog. - // The rest of the settings are preserved in the project between sessions. - project->newLayoutSettings.name = newName; - project->newLayoutSettings.id = newId; + // Generate a unique Layout constant + settings->id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); ui->newLayoutForm->initUi(project); @@ -166,8 +153,6 @@ void NewLayoutDialog::accept() { } ui->label_GenericError->setVisible(false); - // TODO: See if we can get away with emitting this from Project so that we don't need to connect - // to this signal every time we create the dialog. emit applied(layout->id); QDialog::accept(); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 5d9c7413..caf98060 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -20,33 +20,28 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare mapToCopy(mapToCopy) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; + Project::NewMapSettings *settings = &project->newMapSettings; - QString newMapName; - QString newLayoutId; if (this->mapToCopy) { - // Duplicating a map, the initial name will be the base map's name - // with a numbered suffix to make it unique. - int i = 2; - do { - newMapName = QString("%1_%2").arg(this->mapToCopy->name()).arg(i++); - newLayoutId = Layout::layoutConstantFromName(newMapName); - } while (!project->isIdentifierUnique(newMapName) || !project->isIdentifierUnique(newLayoutId)); + // Copy settings from the map we're duplicating + if (this->mapToCopy->layout()){ + settings->layout = this->mapToCopy->layout()->settings(); + } + settings->header = *this->mapToCopy->header(); + settings->group = project->mapNameToMapGroup(this->mapToCopy->name()); + settings->name = project->toUniqueIdentifier(this->mapToCopy->name()); + } else { // Not duplicating a map, get a generic new map name. - newMapName = project->getNewMapName(); - newLayoutId = Layout::layoutConstantFromName(newMapName); + // The rest of the settings are preserved in the project between sessions. + settings->name = project->getNewMapName(); } - - // We reset these settings for every session with the new map dialog. - // The rest of the settings are preserved in the project between sessions. - project->newMapSettings.name = newMapName; - project->newMapSettings.layout.id = newLayoutId; + // Generate a unique Layout constant + settings->layout.id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); ui->newLayoutForm->initUi(project); - ui->comboBox_Group->addItems(project->groupNames); ui->comboBox_LayoutID->addItems(project->layoutIds); @@ -66,12 +61,10 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); this->headerSection->setContentLayout(sectionLayout); ui->layout_HeaderData->addWidget(this->headerSection); - ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); - adjustSize(); } // Adding new map to an existing map list folder. Initialize settings accordingly. @@ -115,12 +108,7 @@ void NewMapDialog::refresh() { if (ui->comboBox_LayoutID->isEnabled()) ui->comboBox_LayoutID->setTextItem(settings->layout.id); - if (this->mapToCopy && this->mapToCopy->layout()) { - // When importing a layout these settings shouldn't be changed. - ui->newLayoutForm->setSettings(this->mapToCopy->layout()->settings()); - } else { - ui->newLayoutForm->setSettings(settings->layout); - } + ui->newLayoutForm->setSettings(settings->layout); ui->checkBox_CanFlyTo->setChecked(settings->canFlyTo); this->headerForm->setHeaderData(settings->header); } @@ -135,29 +123,22 @@ void NewMapDialog::saveSettings() { settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - // This dialog doesn't give users the option to give new layouts a name. - // If a new layout is being created we'll generate a unique layout name using the map name and a suffix. + // This dialog doesn't give users the option to give new layouts a name, we generate one using the map name. // (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for // something the majority of users creating a map won't need. If they want to give a specific name to a layout // they can create the layout first, then create a new map that uses that layout.) const Layout *layout = this->project->mapLayouts.value(settings->layout.id); if (!layout) { - const QString baseLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); - QString newLayoutName = baseLayoutName; - int i = 2; - while (!this->project->isIdentifierUnique(newLayoutName)) { - newLayoutName = QString("%1_%2").arg(baseLayoutName).arg(i++); - } - settings->layout.name = newLayoutName; + const QString newLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + settings->layout.name = this->project->toUniqueIdentifier(newLayoutName); } else { // Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway. settings->layout.name = layout->name; } - // Folders for new layouts created for new maps use the map name, rather than the layout name. - // There's no real reason for this, aside from maintaining consistency with the default layout - // folder names that do this (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', - // rather than 'data/layouts/MyMap_Layout/'). + // Folders for new layouts created for new maps use the map name, rather than the layout name + // (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', rather than 'data/layouts/MyMap_Layout/'). + // There's no real reason for this, aside from maintaining consistency with the default layout folder names that do this. settings->layout.folderName = settings->name; porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); @@ -216,14 +197,8 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { QString errorText; if (layoutId.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); - } else if (!this->project->isIdentifierUnique(layoutId)) { - // Layout name is already in use by something. If we're duplicating a map this isn't allowed. - if (this->mapToCopy) { - errorText = QString("%1 is not unique.").arg(ui->label_LayoutID->text()); - // If we're not duplicating a map this is ok as long as it's the name of an existing layout. - } else if (!this->project->layoutIds.contains(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()); - } + } 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(); diff --git a/src/ui/newnamedialog.cpp b/src/ui/newnamedialog.cpp index d5800daa..8f9cdc42 100644 --- a/src/ui/newnamedialog.cpp +++ b/src/ui/newnamedialog.cpp @@ -10,7 +10,6 @@ NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *pa ui(new Ui::NewNameDialog) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 1ddd975d..1e8c569f 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -11,7 +11,6 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : symbolPrefix(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix)) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index 10642178..e1fc83e9 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -303,7 +303,7 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, // into their project. QMessageBox::StandardButton prompt = QMessageBox::question(parent, - "Import Default Prefabs", + QApplication::applicationName(), QString("Would you like to import the default prefabs for %1? %2.") .arg(projectConfig.getBaseGameVersionString(version)) .arg(fileWarning), diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 346591f4..58d808ca 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -395,7 +395,7 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath) if (!path.startsWith(this->baseDir)){ // Most of Porymap's file-parsing code for project files will assume that filepaths // are relative to the root project folder, so we enforce that here. - QMessageBox::warning(this, "Failed to set custom filepath", + QMessageBox::warning(this, QApplication::applicationName(), QString("Custom filepaths must be inside the root project folder '%1'").arg(this->baseDir)); return QString(); } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index cb0f6d44..f0415726 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -1265,7 +1265,7 @@ void RegionMapEditor::closeEvent(QCloseEvent *event) if (this->modified()) { QMessageBox::StandardButton result = QMessageBox::question( this, - "porymap", + QApplication::applicationName(), "The region map has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index 30f9d62a..36bc30d0 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -158,7 +158,7 @@ void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyE .arg(duplicateKeySequence.toString()).arg(siblingLabel); const auto result = QMessageBox::question( - this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + this, QApplication::applicationName(), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) removeKeySequence(duplicateKeySequence, sibling); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index f3c4a1c9..0603e8cb 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -57,7 +57,7 @@ void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondar if (this->hasUnsavedChanges) { QMessageBox::StandardButton result = QMessageBox::question( this, - "porymap", + QApplication::applicationName(), "Tileset has been modified, save changes?", QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes); @@ -726,7 +726,7 @@ void TilesetEditor::closeEvent(QCloseEvent *event) if (this->hasUnsavedChanges) { QMessageBox::StandardButton result = QMessageBox::question( this, - "porymap", + QApplication::applicationName(), "Tileset has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 32ff9bc9..80c41f81 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -465,7 +465,7 @@ void WildMonChart::showHelpDialog() { informativeText = levelTabInfo; } - QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this); + QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this); msgBox.setTextFormat(Qt::RichText); msgBox.setInformativeText(informativeText); msgBox.exec(); From bf3820745a1870d6c0395c14de5febc608b315a6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 14 Dec 2024 16:25:11 -0500 Subject: [PATCH 113/364] Add new QValidator classes --- forms/newtilesetdialog.ui | 26 +++---------- include/core/validator.h | 66 ++++++++++++++++++++++++++++++++ include/project.h | 4 +- include/ui/aboutporymap.h | 1 - include/ui/colorinputwidget.h | 1 - include/ui/maplistmodels.h | 3 -- include/ui/newnamedialog.h | 6 +-- include/ui/newtilesetdialog.h | 2 +- include/ui/tileseteditor.h | 2 - porymap.pro | 2 + src/config.cpp | 7 ++-- src/core/tileset.cpp | 7 ++-- src/core/validator.cpp | 30 +++++++++++++++ src/editor.cpp | 5 +-- src/mainwindow.cpp | 5 +-- src/project.cpp | 31 ++++++++------- src/ui/colorinputwidget.cpp | 12 ++---- src/ui/maplistmodels.cpp | 9 ++--- src/ui/newlayoutdialog.cpp | 5 +-- src/ui/newmapdialog.cpp | 5 +-- src/ui/newnamedialog.cpp | 20 ++++------ src/ui/newtilesetdialog.cpp | 33 +++++++--------- src/ui/projectsettingseditor.cpp | 5 --- src/ui/tileseteditor.cpp | 50 ++++++++++-------------- 24 files changed, 189 insertions(+), 148 deletions(-) create mode 100644 include/core/validator.h create mode 100644 src/core/validator.cpp diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index 3b898761..13996c73 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -33,14 +33,14 @@ 0
- + Name - + true @@ -60,27 +60,13 @@ - - - Symbol Name - - - - - - - - - - - Type - + false @@ -97,17 +83,17 @@
- + Checkerboard Fill - + - + Qt::Orientation::Vertical diff --git a/include/core/validator.h b/include/core/validator.h new file mode 100644 index 00000000..f5f2ef91 --- /dev/null +++ b/include/core/validator.h @@ -0,0 +1,66 @@ +#ifndef VALIDATOR_H +#define VALIDATOR_H + +/* + This file contains our subclasses of QValidator. + + - PrefixValidator is for input widgets that want to enforce a particular prefix. + It differs from a QRegularExpressionValidator with a prefix in the regex because + it will automatically enforce the prefix in fixup() if it isn't present. + It's preferable to QLineEdit's input mask because it won't affect cursor behavior. + + - IdentifierValidator is for validating that input text can be used for the name of an identifier in the project. + (i.e., starts with a letter or underscore, then may continue with letters, numbers, or underscores). + Unless a prefix is specified this is a normal QRegularExpressionValidator, we only have a subclass because we use it so often. + + - UppercaseValidator is just a validator that uppercases input text. +*/ + +#include + +class PrefixValidator : public QRegularExpressionValidator { + Q_OBJECT + +public: + explicit PrefixValidator(const QString &prefix, QObject *parent = nullptr) + : QRegularExpressionValidator(parent), m_prefix(prefix) {}; + explicit PrefixValidator(const QString &prefix, const QRegularExpression &re, QObject *parent = nullptr) + : QRegularExpressionValidator(re, parent), m_prefix(prefix) {}; + ~PrefixValidator() {}; + + virtual QValidator::State validate(QString &input, int &) const override; + virtual void fixup(QString &input) const override; + + QString prefix() const { return m_prefix; } + void setPrefix(const QString &prefix); + + bool isValid(QString &input) const; + +private: + QString m_prefix; + + bool missingPrefix(const QString &input) const; +}; + +class IdentifierValidator : public PrefixValidator { + Q_OBJECT + +public: + explicit IdentifierValidator(QObject *parent = nullptr) + : PrefixValidator("", re_identifier, parent) {}; + explicit IdentifierValidator(const QString &prefix, QObject *parent = nullptr) + : PrefixValidator(prefix, re_identifier, parent) {}; + ~IdentifierValidator() {}; + +private: + static const QRegularExpression re_identifier; +}; + +class UppercaseValidator : public QValidator { + virtual QValidator::State validate(QString &input, int &) const override { + input = input.toUpper(); + return QValidator::Acceptable; + } +}; + +#endif // VALIDATOR_H diff --git a/include/project.h b/include/project.h index 9c9fdf33..fefa1f34 100644 --- a/include/project.h +++ b/include/project.h @@ -140,9 +140,9 @@ public: Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); - Tileset *createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill); + Tileset *createNewTileset(QString name, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; - bool isValidNewIdentifier(const QString &identifier) const; + bool isValidNewIdentifier(QString identifier) const; QString toUniqueIdentifier(const QString &identifier) const; QString getProjectTitle(); diff --git a/include/ui/aboutporymap.h b/include/ui/aboutporymap.h index 28b06249..f960ab8c 100644 --- a/include/ui/aboutporymap.h +++ b/include/ui/aboutporymap.h @@ -2,7 +2,6 @@ #define ABOUTPORYMAP_H #include -#include #include namespace Ui { diff --git a/include/ui/colorinputwidget.h b/include/ui/colorinputwidget.h index cd871e0b..527794d9 100644 --- a/include/ui/colorinputwidget.h +++ b/include/ui/colorinputwidget.h @@ -2,7 +2,6 @@ #define COLORINPUTWIDGET_H #include -#include namespace Ui { class ColorInputWidget; diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 920bf9ba..53a79f4d 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -54,9 +54,6 @@ private: }; - -class QRegularExpressionValidator; - class MapListModel : public QStandardItemModel { Q_OBJECT diff --git a/include/ui/newnamedialog.h b/include/ui/newnamedialog.h index 23979fc5..4abe02df 100644 --- a/include/ui/newnamedialog.h +++ b/include/ui/newnamedialog.h @@ -19,11 +19,9 @@ class NewNameDialog : public QDialog Q_OBJECT public: - explicit NewNameDialog(const QString &label, Project *project, QWidget *parent = nullptr); + explicit NewNameDialog(const QString &label, const QString &prefix = "", Project *project = nullptr, QWidget *parent = nullptr); ~NewNameDialog(); - void setNamePrefix(const QString &prefix); - virtual void accept() override; signals: @@ -32,7 +30,7 @@ signals: private: Ui::NewNameDialog *ui; Project *project = nullptr; - const QString symbolPrefix; + const QString namePrefix; bool validateName(bool allowEmpty = false); void onNameChanged(const QString &name); diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index 06012708..374c85a7 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -27,7 +27,7 @@ private: const QString symbolPrefix; bool validateName(bool allowEmpty = false); - void onFriendlyNameChanged(const QString &friendlyName); + void onNameChanged(const QString &name); void dialogButtonClicked(QAbstractButton *button); }; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 32bdc97f..5cd6205e 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -120,9 +120,7 @@ private slots: void on_horizontalSlider_TilesZoom_valueChanged(int value); private: - void initUi(); void setAttributesUi(); - void setMetatileLabelValidator(); void initMetatileSelector(); void initTileSelector(); void initSelectedTileItem(); diff --git a/porymap.pro b/porymap.pro index 171ea48d..5ba1e6e0 100644 --- a/porymap.pro +++ b/porymap.pro @@ -39,6 +39,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/core/parseutil.cpp \ src/core/tile.cpp \ src/core/tileset.cpp \ + src/core/validator.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ src/core/editcommands.cpp \ @@ -146,6 +147,7 @@ HEADERS += include/core/advancemapparser.h \ include/core/parseutil.h \ include/core/tile.h \ include/core/tileset.h \ + include/core/validator.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ include/core/editcommands.h \ diff --git a/src/config.cpp b/src/config.cpp index 234a0a33..bcfa3489 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -2,6 +2,7 @@ #include "log.h" #include "shortcut.h" #include "map.h" +#include "validator.h" #include #include #include @@ -952,10 +953,8 @@ void ProjectConfig::setIdentifier(ProjectIdentifier id, QString text) { const QString idName = defaultIdentifiers.value(id).first; if (idName.startsWith("define_") || idName.startsWith("symbol_")) { // Validate the input for the identifier, depending on the type. - static const QRegularExpression re("[A-Za-z_]+[\\w]*"); - auto validator = QRegularExpressionValidator(re); - int temp = 0; - if (validator.validate(text, temp) != QValidator::Acceptable) { + IdentifierValidator validator; + if (!validator.isValid(text)) { logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName)); return; } diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index cf21a675..e79e7706 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -4,6 +4,7 @@ #include "log.h" #include "config.h" #include "imageproviders.h" +#include "validator.h" #include #include @@ -180,10 +181,8 @@ bool Tileset::setMetatileLabel(int metatileId, QString label, Tileset *primaryTi if (!tileset) return false; - static const QRegularExpression expression("[_A-Za-z0-9]*$"); - QRegularExpressionValidator validator(expression); - int pos = 0; - if (validator.validate(label, pos) != QValidator::Acceptable) + IdentifierValidator validator; + if (!validator.isValid(label)) return false; tileset->metatileLabels[metatileId] = label; diff --git a/src/core/validator.cpp b/src/core/validator.cpp new file mode 100644 index 00000000..b3b36589 --- /dev/null +++ b/src/core/validator.cpp @@ -0,0 +1,30 @@ +#include "validator.h" + +// Identifiers must only contain word characters, and cannot start with a digit. +const QRegularExpression IdentifierValidator::re_identifier = QRegularExpression("[A-Za-z_]+[\\w]*"); + + +bool PrefixValidator::missingPrefix(const QString &input) const { + return !m_prefix.isEmpty() && !input.startsWith(m_prefix); +} + +QValidator::State PrefixValidator::validate(QString &input, int &pos) const { + auto state = QRegularExpressionValidator::validate(input, pos); + if (state == QValidator::Acceptable) { + // This input could be valid. If there's a prefix we should require it now. + if (missingPrefix(input)) + state = QValidator::Intermediate; + } + return state; +} + +void PrefixValidator::fixup(QString &input) const { + QRegularExpressionValidator::fixup(input); + if (missingPrefix(input)) + input.prepend(m_prefix); +} + +bool PrefixValidator::isValid(QString &input) const { + int pos = 0; + return validate(input, pos) == QValidator::Acceptable; +} diff --git a/src/editor.cpp b/src/editor.cpp index e13f76eb..56914c30 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -11,6 +11,7 @@ #include "config.h" #include "scripting.h" #include "customattributestable.h" +#include "validator.h" #include #include #include @@ -299,9 +300,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { QLineEdit *lineEdit = new QLineEdit(); lineEdit->setClearButtonEnabled(true); form.addRow(new QLabel("Group Base Label:"), lineEdit); - static const QRegularExpression re_validChars("[_A-Za-z0-9]*"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); - lineEdit->setValidator(validator); + lineEdit->setValidator(new IdentifierValidator(lineEdit)); connect(lineEdit, &QLineEdit::textChanged, [this, &lineEdit, &buttonBox](QString text){ if (this->project->encounterGroupLabels.contains(text)) { lineEdit->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 65a323e2..50c8beae 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1310,14 +1310,13 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } void MainWindow::openNewMapGroupDialog() { - auto dialog = new NewNameDialog("New Group Name", this->editor->project, this); + auto dialog = new NewNameDialog("New Group Name", "", this->editor->project, this); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); dialog->open(); } void MainWindow::openNewAreaDialog() { - auto dialog = new NewNameDialog("New Area Name", this->editor->project, this); - dialog->setNamePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); + auto dialog = new NewNameDialog("New Area Name", projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix), this->editor->project, this); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); dialog->open(); } diff --git a/src/project.cpp b/src/project.cpp index 0a66c0e8..efc591e4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -8,7 +8,7 @@ #include "tileset.h" #include "map.h" #include "filedialog.h" - +#include "validator.h" #include "orderedjson.h" #include @@ -1453,9 +1453,15 @@ void Project::readTilesetPaths(Tileset* tileset) { } } -Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill) { +Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboardFill) { + const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix); + if (!name.startsWith(prefix)) { + logError(QString("Tileset name '%1' doesn't begin with the prefix '%2'.").arg(name).arg(prefix)); + return nullptr; + } + auto tileset = new Tileset(); - tileset->name = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + friendlyName; + tileset->name = name; tileset->is_secondary = secondary; // Create tileset directories @@ -1527,10 +1533,11 @@ Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, this->tilesetLabelsOrdered.append(tileset->name); // TODO: Ideally we wouldn't save new Tilesets immediately - // Append to tileset specific files - tileset->appendToHeaders(this->root, friendlyName, this->usingAsmTilesets); - tileset->appendToGraphics(this->root, friendlyName, this->usingAsmTilesets); - tileset->appendToMetatiles(this->root, friendlyName, this->usingAsmTilesets); + // Append to tileset specific files. Strip prefix from name to get base name for use in other symbols. + name.remove(0, prefix.length()); + tileset->appendToHeaders(this->root, name, this->usingAsmTilesets); + tileset->appendToGraphics(this->root, name, this->usingAsmTilesets); + tileset->appendToMetatiles(this->root, name, this->usingAsmTilesets); tileset->save(); @@ -1966,12 +1973,10 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return true; } -// For some arbitrary string, return true if it's both a valid identifier name -// and not one that's already in-use. -bool Project::isValidNewIdentifier(const QString &identifier) const { - static const QRegularExpression re_identifier("[A-Za-z_]+[\\w]*"); - QRegularExpressionMatch match = re_identifier.match(identifier); - return match.hasMatch() && isIdentifierUnique(identifier); +// For some arbitrary string, return true if it's both a valid identifier name and not one that's already in-use. +bool Project::isValidNewIdentifier(QString identifier) const { + IdentifierValidator validator; + return validator.isValid(identifier) && isIdentifierUnique(identifier); } // Assumes 'identifier' is a valid name. If 'identifier' is unique, returns 'identifier'. diff --git a/src/ui/colorinputwidget.cpp b/src/ui/colorinputwidget.cpp index 8b40be27..6ed587aa 100644 --- a/src/ui/colorinputwidget.cpp +++ b/src/ui/colorinputwidget.cpp @@ -1,16 +1,10 @@ #include "colorinputwidget.h" #include "ui_colorinputwidget.h" #include "colorpicker.h" +#include "validator.h" #include -class HexCodeValidator : public QValidator { - virtual QValidator::State validate(QString &input, int &) const override { - input = input.toUpper(); - return QValidator::Acceptable; - } -}; - static inline int rgb5(int rgb) { return round(static_cast(rgb * 31) / 255.0); } static inline int rgb8(int rgb) { return round(rgb * 255. / 31.); } static inline int gbaRed(int rgb) { return rgb & 0x1f; } @@ -43,8 +37,8 @@ void ColorInputWidget::init() { connect(ui->spinBox_Green, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); connect(ui->spinBox_Blue, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); - static const HexCodeValidator hexValidator; - ui->lineEdit_Hex->setValidator(&hexValidator); + static const UppercaseValidator uppercaseValidator; + ui->lineEdit_Hex->setValidator(&uppercaseValidator); connect(ui->lineEdit_Hex, &QLineEdit::textEdited, this, &ColorInputWidget::setRgbFromHexString); // We have separate signals for when color input editing finishes. diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index dc1e3d7e..a46dfc58 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -1,11 +1,11 @@ #include "maplistmodels.h" +#include "validator.h" +#include "project.h" +#include "filterchildrenproxymodel.h" #include #include -#include "project.h" -#include "filterchildrenproxymodel.h" - void MapTree::removeSelected() { @@ -179,9 +179,8 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); - static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); editor->setPlaceholderText("gMapGroup_"); - editor->setValidator(new QRegularExpressionValidator(expression, parent)); + editor->setValidator(new IdentifierValidator(parent)); editor->setFrame(false); return editor; } diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 086bc346..226df897 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -2,6 +2,7 @@ #include "maplayout.h" #include "ui_newlayoutdialog.h" #include "config.h" +#include "validator.h" #include #include @@ -34,9 +35,7 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q ui->newLayoutForm->initUi(project); - // 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); + auto validator = new IdentifierValidator(this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_LayoutID->setValidator(validator); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index caf98060..2d1fa46f 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -3,6 +3,7 @@ #include "mainwindow.h" #include "ui_newmapdialog.h" #include "config.h" +#include "validator.h" #include #include @@ -45,9 +46,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare ui->comboBox_Group->addItems(project->groupNames); ui->comboBox_LayoutID->addItems(project->layoutIds); - // 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); + auto validator = new IdentifierValidator(this); ui->lineEdit_Name->setValidator(validator); ui->comboBox_Group->setValidator(validator); ui->comboBox_LayoutID->setValidator(validator); diff --git a/src/ui/newnamedialog.cpp b/src/ui/newnamedialog.cpp index 8f9cdc42..842bf4b7 100644 --- a/src/ui/newnamedialog.cpp +++ b/src/ui/newnamedialog.cpp @@ -2,12 +2,14 @@ #include "ui_newnamedialog.h" #include "project.h" #include "imageexport.h" +#include "validator.h" const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *parent) : +NewNameDialog::NewNameDialog(const QString &label, const QString &prefix, Project* project, QWidget *parent) : QDialog(parent), - ui(new Ui::NewNameDialog) + ui(new Ui::NewNameDialog), + namePrefix(prefix) { setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); @@ -16,10 +18,8 @@ NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *pa if (!label.isEmpty()) ui->label_Name->setText(label); - // Identifiers must only contain word characters, and cannot start with a digit. - static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); - ui->lineEdit_Name->setValidator(validator); + ui->lineEdit_Name->setValidator(new IdentifierValidator(namePrefix, this)); + ui->lineEdit_Name->setText(namePrefix); connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewNameDialog::onNameChanged); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewNameDialog::dialogButtonClicked); @@ -32,10 +32,6 @@ NewNameDialog::~NewNameDialog() delete ui; } -void NewNameDialog::setNamePrefix(const QString &) { - //TODO -} - void NewNameDialog::onNameChanged(const QString &) { validateName(true); } @@ -44,9 +40,9 @@ bool NewNameDialog::validateName(bool allowEmpty) { const QString name = ui->lineEdit_Name->text(); QString errorText; - if (name.isEmpty()) { + if (name.isEmpty() || name == namePrefix) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (!this->project->isIdentifierUnique(name)) { + } else if (this->project && !this->project->isIdentifierUnique(name)) { errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 1e8c569f..049b317d 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -2,6 +2,7 @@ #include "ui_newtilesetdialog.h" #include "project.h" #include "imageexport.h" +#include "validator.h" const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; @@ -15,15 +16,11 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : this->project = project; ui->checkBox_CheckerboardFill->setChecked(porymapConfig.tilesetCheckerboardFill); - ui->label_SymbolNameDisplay->setText(this->symbolPrefix); - ui->comboBox_Type->setMinimumContentsLength(12); + ui->comboBox_Type->setMinimumContentsLength(12 + this->symbolPrefix.length()); + ui->lineEdit_Name->setValidator(new IdentifierValidator(this->symbolPrefix, this)); + ui->lineEdit_Name->setText(this->symbolPrefix); - //only allow characters valid for a symbol - static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); - ui->lineEdit_FriendlyName->setValidator(validator); - - connect(ui->lineEdit_FriendlyName, &QLineEdit::textChanged, this, &NewTilesetDialog::onFriendlyNameChanged); + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewTilesetDialog::onNameChanged); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewTilesetDialog::dialogButtonClicked); adjustSize(); @@ -35,28 +32,24 @@ NewTilesetDialog::~NewTilesetDialog() delete ui; } -void NewTilesetDialog::onFriendlyNameChanged(const QString &friendlyName) { - // When the tileset name is changed, update this label to display the full symbol name. - ui->label_SymbolNameDisplay->setText(this->symbolPrefix + friendlyName); - +void NewTilesetDialog::onNameChanged(const QString &) { validateName(true); } bool NewTilesetDialog::validateName(bool allowEmpty) { - const QString friendlyName = ui->lineEdit_FriendlyName->text(); - const QString symbolName = ui->label_SymbolNameDisplay->text(); + const QString name = ui->lineEdit_Name->text(); QString errorText; - if (friendlyName.isEmpty()) { - if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_FriendlyName->text()); - } else if (!this->project->isIdentifierUnique(symbolName)) { - errorText = QString("%1 '%2' is not unique.").arg(ui->label_SymbolName->text()).arg(symbolName); + if (name.isEmpty() || name == symbolPrefix) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); ui->label_NameError->setText(errorText); ui->label_NameError->setVisible(!isValid); - ui->lineEdit_FriendlyName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); return isValid; } @@ -74,7 +67,7 @@ void NewTilesetDialog::accept() { return; bool secondary = ui->comboBox_Type->currentIndex() == 1; - Tileset *tileset = this->project->createNewTileset(ui->lineEdit_FriendlyName->text(), secondary, ui->checkBox_CheckerboardFill->isChecked()); + Tileset *tileset = this->project->createNewTileset(ui->lineEdit_Name->text(), secondary, ui->checkBox_CheckerboardFill->isChecked()); if (!tileset) { ui->label_GenericError->setText(QString("Failed to create tileset. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 58d808ca..d01a3351 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -114,11 +114,6 @@ void ProjectSettingsEditor::initUi() { ui->lineEdit_BorderMetatiles->setValidator(validator_HexList); this->setBorderMetatilesUi(projectConfig.useCustomBorderSize); - // Validate that the text added to the warp behavior list could be a valid define - // (we don't care whether it actually is a metatile behavior define) - static const QRegularExpression expression_Word("^[A-Za-z0-9_]*$"); - QRegularExpressionValidator *validator_Word = new QRegularExpressionValidator(expression_Word); - ui->comboBox_WarpBehaviors->setValidator(validator_Word); ui->textEdit_WarpBehaviors->setTextColor(Qt::gray); // Set spin box limits diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 0603e8cb..fab59928 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -8,6 +8,7 @@ #include "config.h" #include "shortcut.h" #include "filedialog.h" +#include "validator.h" #include #include #include @@ -20,9 +21,25 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) layout(layout), hasUnsavedChanges(false) { - this->setAttribute(Qt::WA_DeleteOnClose); - this->setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); - this->initUi(); + setAttribute(Qt::WA_DeleteOnClose); + setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); + ui->setupUi(this); + + this->tileXFlip = ui->checkBox_xFlip->isChecked(); + this->tileYFlip = ui->checkBox_yFlip->isChecked(); + this->paletteId = ui->spinBox_paletteSelector->value(); + ui->spinBox_paletteSelector->setMinimum(0); + ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); + ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this)); + + setAttributesUi(); + initMetatileSelector(); + initMetatileLayersItem(); + initTileSelector(); + initSelectedTileItem(); + initShortcuts(); + this->metatileSelector->select(0); + restoreWindowState(); } TilesetEditor::~TilesetEditor() @@ -92,26 +109,6 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi this->initMetatileHistory(); } -void TilesetEditor::initUi() { - ui->setupUi(this); - this->tileXFlip = ui->checkBox_xFlip->isChecked(); - this->tileYFlip = ui->checkBox_yFlip->isChecked(); - this->paletteId = ui->spinBox_paletteSelector->value(); - this->ui->spinBox_paletteSelector->setMinimum(0); - this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); - - this->setAttributesUi(); - this->setMetatileLabelValidator(); - - this->initMetatileSelector(); - this->initMetatileLayersItem(); - this->initTileSelector(); - this->initSelectedTileItem(); - this->initShortcuts(); - this->metatileSelector->select(0); - this->restoreWindowState(); -} - void TilesetEditor::setAttributesUi() { // Behavior if (projectConfig.metatileBehaviorMask) { @@ -171,13 +168,6 @@ void TilesetEditor::setAttributesUi() { this->ui->frame_Properties->adjustSize(); } -void TilesetEditor::setMetatileLabelValidator() { - //only allow characters valid for a symbol - static const QRegularExpression expression("[_A-Za-z0-9]*$"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); - this->ui->lineEdit_metatileLabel->setValidator(validator); -} - void TilesetEditor::initMetatileSelector() { this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout); From 59464aa89cebc50375f62d7345261eb0fcb5db89 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Dec 2024 14:39:23 -0500 Subject: [PATCH 114/364] Fix possible crash when layout fails to open --- src/editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 56914c30..d0a122ec 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1166,7 +1166,7 @@ void Editor::setCursorRectVisible(bool visible) { void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { int x = pos.x(); int y = pos.y(); - if (!layout->isWithinBounds(x, y)) + if (!layout || !layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); @@ -1198,7 +1198,7 @@ void Editor::onHoveredMapMetatileCleared() { } void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { - if (!layout->isWithinBounds(x, y)) + if (!layout || !layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); From d9be7d594e4abe755ad2d35ea1390d479c6ff2b3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Dec 2024 15:21:41 -0500 Subject: [PATCH 115/364] Fix Qt5 build --- include/lib/collapsiblesection.h | 1 + src/core/advancemapparser.cpp | 6 +++--- src/ui/mapheaderform.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 73169303..b74835f4 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp index 6af7476e..6a8428ec 100644 --- a/src/core/advancemapparser.cpp +++ b/src/core/advancemapparser.cpp @@ -206,9 +206,9 @@ QList AdvanceMapParser::parsePalette(const QString &filepath, bool *error) QList palette; int i = 0; while (i < in.length()) { - unsigned char red = qMin(qMax(static_cast(in.at(i + 0)), 0u), 255u); - unsigned char green = qMin(qMax(static_cast(in.at(i + 1)), 0u), 255u); - unsigned char blue = qMin(qMax(static_cast(in.at(i + 2)), 0u), 255u); + unsigned char red = static_cast(in.at(i + 0)); + unsigned char green = static_cast(in.at(i + 1)); + unsigned char blue = static_cast(in.at(i + 2)); palette.append(qRgb(red, green, blue)); i += 4; } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 7aa5a628..4bea0bef 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -23,7 +23,7 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); - connect(ui->spinBox_FloorNumber, &QSpinBox::valueChanged, this, &MapHeaderForm::onFloorNumberChanged); + connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); } MapHeaderForm::~MapHeaderForm() From 81b6cfa5374c28f4d773730b79a9e30bf69d7593 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 12 Dec 2024 23:27:19 -0500 Subject: [PATCH 116/364] Fix exported tile images writing garbage pixels --- CHANGELOG.md | 1 + src/ui/tileseteditortileselector.cpp | 20 ++++---------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcd180e..199bd659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. - Fix crash when saving tilesets with fewer palettes than the maximum. - Fix projects not opening on Windows if the project filepath contains certain characters. +- Fix exported tile images containing garbage pixels after the end of the tiles. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 6984c0e3..2bc80f75 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -226,17 +226,11 @@ QImage TilesetEditorTileSelector::buildPrimaryTilesIndexedImage() { int primaryLength = this->primaryTileset->tiles.length(); int height = qCeil(primaryLength / static_cast(this->numTilesWide)); QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); + image.fill(0); QPainter painter(&image); for (uint16_t tile = 0; tile < primaryLength; tile++) { - QImage tileImage; - if (tile < primaryLength) { - tileImage = getGreyscaleTileImage(tile, this->primaryTileset, this->secondaryTileset); - } else { - tileImage = QImage(8, 8, QImage::Format_RGBA8888); - tileImage.fill(qRgb(0, 0, 0)); - } - + QImage tileImage = getGreyscaleTileImage(tile, this->primaryTileset, this->secondaryTileset); int y = tile / this->numTilesWide; int x = tile % this->numTilesWide; QPoint origin = QPoint(x * 8, y * 8); @@ -261,18 +255,12 @@ QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() { int secondaryLength = this->secondaryTileset->tiles.length(); int height = qCeil(secondaryLength / static_cast(this->numTilesWide)); QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); + image.fill(0); QPainter painter(&image); uint16_t primaryLength = static_cast(Project::getNumTilesPrimary()); for (uint16_t tile = 0; tile < secondaryLength; tile++) { - QImage tileImage; - if (tile < secondaryLength) { - tileImage = getGreyscaleTileImage(tile + primaryLength, this->primaryTileset, this->secondaryTileset); - } else { - tileImage = QImage(8, 8, QImage::Format_RGBA8888); - tileImage.fill(qRgb(0, 0, 0)); - } - + QImage tileImage = getGreyscaleTileImage(tile + primaryLength, this->primaryTileset, this->secondaryTileset); int y = tile / this->numTilesWide; int x = tile % this->numTilesWide; QPoint origin = QPoint(x * 8, y * 8); From 8f1e1128584815bd6af0c24a2e40a6998c28dc2a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 12 Dec 2024 23:38:45 -0500 Subject: [PATCH 117/364] Combine tile image export functions --- include/ui/tileseteditortileselector.h | 1 + src/ui/tileseteditortileselector.cpp | 46 +++++++------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index bfbc4946..7e34d52a 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -61,6 +61,7 @@ private: QPoint getTileCoords(uint16_t); QList getCurPaletteTable(); QList buildSelectedTiles(int, int, QList); + QImage buildImage(int tileIdStart, int numTiles); void drawUnused(); diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 2bc80f75..7431e928 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -219,54 +219,32 @@ QPoint TilesetEditorTileSelector::getTileCoordsOnWidget(uint16_t tile) { } QImage TilesetEditorTileSelector::buildPrimaryTilesIndexedImage() { - if (!this->primaryTileset || !this->secondaryTileset) { + if (!this->primaryTileset) return QImage(); - } - int primaryLength = this->primaryTileset->tiles.length(); - int height = qCeil(primaryLength / static_cast(this->numTilesWide)); - QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); - image.fill(0); - - QPainter painter(&image); - for (uint16_t tile = 0; tile < primaryLength; tile++) { - QImage tileImage = getGreyscaleTileImage(tile, this->primaryTileset, this->secondaryTileset); - int y = tile / this->numTilesWide; - int x = tile % this->numTilesWide; - QPoint origin = QPoint(x * 8, y * 8); - painter.drawImage(origin, tileImage); - } - - painter.end(); - - // Image is first converted using greyscale so that palettes with duplicate colors - // are properly represented in the final image. - QImage indexedImage = image.convertToFormat(QImage::Format::Format_Indexed8, greyscalePalette.toVector()); - QList palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset, true); - indexedImage.setColorTable(palette.toVector()); - return indexedImage; + return buildImage(0, this->primaryTileset->tiles.length()); } QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() { - if (!this->primaryTileset || !this->secondaryTileset) { + if (!this->secondaryTileset) return QImage(); - } - int secondaryLength = this->secondaryTileset->tiles.length(); - int height = qCeil(secondaryLength / static_cast(this->numTilesWide)); + return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->tiles.length()); +} + +QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) { + int height = qCeil(numTiles / static_cast(this->numTilesWide)); QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); image.fill(0); QPainter painter(&image); - uint16_t primaryLength = static_cast(Project::getNumTilesPrimary()); - for (uint16_t tile = 0; tile < secondaryLength; tile++) { - QImage tileImage = getGreyscaleTileImage(tile + primaryLength, this->primaryTileset, this->secondaryTileset); - int y = tile / this->numTilesWide; - int x = tile % this->numTilesWide; + for (int i = 0; i < numTiles; i++) { + QImage tileImage = getGreyscaleTileImage(tileIdStart + i, this->primaryTileset, this->secondaryTileset); + int y = i / this->numTilesWide; + int x = i % this->numTilesWide; QPoint origin = QPoint(x * 8, y * 8); painter.drawImage(origin, tileImage); } - painter.end(); // Image is first converted using greyscale so that palettes with duplicate colors From bce32121f0f24e8ff8e5ca048da84b69507d640f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Dec 2024 12:17:12 -0500 Subject: [PATCH 118/364] Bump GitHub Actions versions --- .github/workflows/main.yml | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f71ab5c5..c0f50884 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,21 +20,14 @@ jobs: runs-on: ubuntu-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache + - uses: actions/checkout@v4 - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: version: '5.14.2' - modules: 'qtwidgets qtqml qtcharts' - cached: ${{ steps.cache-qt.outputs.cache-hit }} + modules: 'qtcharts' + cache: 'true' - name: Configure run: qmake porymap.pro @@ -46,21 +39,14 @@ jobs: runs-on: macos-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache + - uses: actions/checkout@v4 - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: version: '6.7.*' modules: 'qtcharts' - cached: ${{ steps.cache-qt.outputs.cache-hit }} + cache: 'true' - name: Configure run: qmake -config release porymap.pro @@ -84,7 +70,7 @@ jobs: run: zip -r porymap-macOS-${{ github.ref_name }}.zip porymap-macOS-${{ github.ref_name }} - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: porymap-macOS-${{ github.ref_name }}.zip @@ -95,7 +81,7 @@ jobs: runs-on: windows-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dsaltares/fetch-gh-release-asset@master if: steps.cache-static-qt.outputs.cache-hit != 'true' @@ -153,7 +139,7 @@ jobs: run: powershell.exe -Command "Compress-Archive -Path porymap-windows-${{ github.ref_name }} -DestinationPath porymap-windows-${{ github.ref_name }}.zip" - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: porymap-windows-${{ github.ref_name }}.zip From 4f6291a3f6cfdd025c0e53ecd27ade2888bd6fe4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Dec 2024 13:25:48 -0500 Subject: [PATCH 119/364] Support non-Windows builds without QtCharts --- include/ui/wildmonchart.h | 1 - porymap.pro | 4 +++- src/mainwindow.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ui/wildmonchart.h b/include/ui/wildmonchart.h index 3be7e468..20840c87 100644 --- a/include/ui/wildmonchart.h +++ b/include/ui/wildmonchart.h @@ -78,7 +78,6 @@ private: // As of writing our static Qt build for Windows doesn't include the QtCharts module, so we dummy the class out here. // The charts module is additionally excluded from Windows in porymap.pro -#define DISABLE_CHARTS_MODULE class WildMonChart : public QWidget { diff --git a/porymap.pro b/porymap.pro index 5734fd73..51eadbb3 100644 --- a/porymap.pro +++ b/porymap.pro @@ -6,8 +6,10 @@ QT += core gui qml network -!win32 { +qtHaveModule(charts) { QT += charts +} else { + warning("Qt module 'charts' not found, disabling chart features.") } greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d9e65eb3..6fb5b899 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -127,7 +127,7 @@ void MainWindow::initWindow() { ui->actionCheck_for_Updates->setVisible(false); #endif -#ifdef DISABLE_CHARTS_MODULE +#ifdef QT_CHARTS_LIB ui->pushButton_SummaryChart->setVisible(false); #endif From 883087d1615b2546f6e05b05ea0a93378ccba077 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Dec 2024 13:38:42 -0500 Subject: [PATCH 120/364] Fix chart button visibility --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6fb5b899..12bbdd6f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -127,7 +127,7 @@ void MainWindow::initWindow() { ui->actionCheck_for_Updates->setVisible(false); #endif -#ifdef QT_CHARTS_LIB +#ifndef QT_CHARTS_LIB ui->pushButton_SummaryChart->setVisible(false); #endif From 64a9e2cacbc93440e82ebb6b159c7abc5433b72e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Dec 2024 14:25:51 -0500 Subject: [PATCH 121/364] Add dividing line for tilesets in Tileset Editor --- CHANGELOG.md | 1 + forms/tileseteditor.ui | 9 +++++++++ include/config.h | 2 ++ include/ui/tileseteditor.h | 1 + include/ui/tileseteditormetatileselector.h | 4 +++- include/ui/tileseteditortileselector.h | 1 + src/config.cpp | 3 +++ src/ui/tileseteditor.cpp | 14 ++++++++++++++ src/ui/tileseteditormetatileselector.cpp | 15 +++++++++++++++ src/ui/tileseteditortileselector.cpp | 11 +++++++++++ 10 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 199bd659..34b75bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add a `Close Project` option - Add charts to the `Wild Pokémon` tab that show species and level distributions. - Add options for customizing the map grid under `View -> Grid Settings`. +- Add an option to display a dividing line between tilesets in the Tileset Editor. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. - Add button to enable editing map groups including renaming groups and rearranging the maps within them. diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index c144e5f2..8623a9bc 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -647,6 +647,7 @@ + @@ -799,6 +800,14 @@ Ctrl+G
+ + + true + + + Show Tileset Divider + +
diff --git a/include/config.h b/include/config.h index 6b64b612..47bc2010 100644 --- a/include/config.h +++ b/include/config.h @@ -68,6 +68,7 @@ public: this->showGrid = false; this->showTilesetEditorMetatileGrid = false; this->showTilesetEditorLayerGrid = true; + this->showTilesetEditorDivider = false; this->monitorFiles = true; this->tilesetCheckerboardFill = true; this->theme = "default"; @@ -119,6 +120,7 @@ public: bool showGrid; bool showTilesetEditorMetatileGrid; bool showTilesetEditorLayerGrid; + bool showTilesetEditorDivider; bool monitorFiles; bool tilesetCheckerboardFill; QString theme; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 32bdc97f..86d53cc2 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -88,6 +88,7 @@ private slots: void on_actionShow_UnusedTiles_toggled(bool checked); void on_actionMetatile_Grid_triggered(bool checked); void on_actionLayer_Grid_triggered(bool checked); + void on_actionShow_Tileset_Divider_triggered(bool checked); void on_actionUndo_triggered(); diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 6b2c9a6f..760da8e4 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -23,7 +23,8 @@ public: QVector usedMetatiles; bool selectorShowUnused = false; bool selectorShowCounts = false; - bool showGrid; + bool showGrid = false; + bool showDivider = false; protected: void mousePressEvent(QGraphicsSceneMouseEvent*); @@ -44,6 +45,7 @@ private: int numRows(int numMetatiles); int numRows(); void drawGrid(); + void drawDivider(); void drawFilters(); void drawUnused(); void drawCounts(); diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index 7e34d52a..aa2a1923 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -33,6 +33,7 @@ public: QVector usedTiles; bool showUnused = false; + bool showDivider = false; protected: void mousePressEvent(QGraphicsSceneMouseEvent*); diff --git a/src/config.cpp b/src/config.cpp index 3d604da6..c0c00194 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -368,6 +368,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->showTilesetEditorMetatileGrid = getConfigBool(key, value); } else if (key == "show_tileset_editor_layer_grid") { this->showTilesetEditorLayerGrid = getConfigBool(key, value); + } else if (key == "show_tileset_editor_divider") { + this->showTilesetEditorDivider = getConfigBool(key, value); } else if (key == "monitor_files") { this->monitorFiles = getConfigBool(key, value); } else if (key == "tileset_checkerboard_fill") { @@ -452,6 +454,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("show_grid", this->showGrid ? "1" : "0"); map.insert("show_tileset_editor_metatile_grid", this->showTilesetEditorMetatileGrid ? "1" : "0"); map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0"); + map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0"); map.insert("monitor_files", this->monitorFiles ? "1" : "0"); map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0"); map.insert("theme", this->theme); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e709bed1..49cfe147 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -99,6 +99,7 @@ void TilesetEditor::initUi() { this->paletteId = ui->spinBox_paletteSelector->value(); this->ui->spinBox_paletteSelector->setMinimum(0); this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); + this->ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); this->setAttributesUi(); this->setMetatileLabelValidator(); @@ -191,6 +192,7 @@ void TilesetEditor::initMetatileSelector() bool showGrid = porymapConfig.showTilesetEditorMetatileGrid; this->ui->actionMetatile_Grid->setChecked(showGrid); this->metatileSelector->showGrid = showGrid; + this->metatileSelector->showDivider = this->ui->actionShow_Tileset_Divider->isChecked(); this->metatilesScene = new QGraphicsScene; this->metatilesScene->addItem(this->metatileSelector); @@ -232,6 +234,8 @@ void TilesetEditor::initTileSelector() connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, this, &TilesetEditor::onSelectedTilesChanged); + this->tileSelector->showDivider = this->ui->actionShow_Tileset_Divider->isChecked(); + this->tilesScene = new QGraphicsScene; this->tilesScene->addItem(this->tileSelector); this->tileSelector->select(0); @@ -1048,6 +1052,16 @@ void TilesetEditor::on_actionLayer_Grid_triggered(bool checked) { porymapConfig.showTilesetEditorLayerGrid = checked; } +void TilesetEditor::on_actionShow_Tileset_Divider_triggered(bool checked) { + this->metatileSelector->showDivider = checked; + this->metatileSelector->draw(); + + this->tileSelector->showDivider = checked; + this->tileSelector->draw(); + + porymapConfig.showTilesetEditorDivider = checked; +} + void TilesetEditor::countMetatileUsage() { // do not double count metatileSelector->usedMetatiles.fill(0); diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 778fde9c..6175a923 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -70,6 +70,7 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet void TilesetEditorMetatileSelector::draw() { this->setPixmap(QPixmap::fromImage(this->buildAllMetatilesImage())); this->drawGrid(); + this->drawDivider(); this->drawSelection(); this->drawFilters(); } @@ -186,6 +187,20 @@ void TilesetEditorMetatileSelector::drawGrid() { this->setPixmap(pixmap); } +void TilesetEditorMetatileSelector::drawDivider() { + if (!this->showDivider) + return; + + const int y = this->numRows(this->primaryTileset->numMetatiles()) * 32; + + QPixmap pixmap = this->pixmap(); + QPainter painter(&pixmap); + painter.setPen(Qt::white); + painter.drawLine(0, y, this->numMetatilesWide * 32, y); + painter.end(); + this->setPixmap(pixmap); +} + void TilesetEditorMetatileSelector::drawFilters() { if (selectorShowUnused) { drawUnused(); diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 7431e928..4cec089e 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -46,6 +46,17 @@ void TilesetEditorTileSelector::draw() { painter.drawImage(origin, tileImage); } + if (this->showDivider) { + int row = this->primaryTileset->tiles.length() / this->numTilesWide; + if (this->primaryTileset->tiles.length() % this->numTilesWide != 0) { + // Round up height for incomplete last row + row++; + } + const int y = row * 16; + painter.setPen(Qt::white); + painter.drawLine(0, y, this->numTilesWide * 16, y); + } + painter.end(); this->setPixmap(QPixmap::fromImage(image)); From 12dba1a8b3283b5a150d57707a4494fae3b9724c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 17 Dec 2024 22:02:46 -0500 Subject: [PATCH 122/364] Add Qt version to About, remove changelog --- forms/aboutporymap.ui | 176 ++++++++++++++++---------------------- include/ui/aboutporymap.h | 4 +- src/ui/aboutporymap.cpp | 17 ++-- 3 files changed, 84 insertions(+), 113 deletions(-) diff --git a/forms/aboutporymap.ui b/forms/aboutporymap.ui index f823396c..7a211e0a 100644 --- a/forms/aboutporymap.ui +++ b/forms/aboutporymap.ui @@ -1,117 +1,89 @@ AboutPorymap - + 0 0 - 582 - 438 + 383 + 121 About Porymap - - - - - - - Arial - 22 - 75 - true - false - true - - - - Porymap - - - Qt::RichText - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - Arial - 12 - - - - Qt::AlignCenter - - - - - - - - Arial - - - - Map editor for pokeemerald, pokefirered and pokeruby. - - - Qt::AlignCenter - - - - - - - <html><head/><body><p>Official Documentation: <a href="https://huderlem.github.io/porymap/"><span style=" text-decoration: underline; color:#0069d9;">https://huderlem.github.io/porymap/</span></a></p></body></html> - - - Qt::AlignCenter - - - true - - - - - - - Qt::Horizontal - - - - - - - true - - - - - - - - - 0 - 0 - 582 - 22 - - - - + + + + + + Arial + 22 + true + false + true + + + + Porymap + + + Qt::TextFormat::RichText + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 0 + + + + + Arial + 12 + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + Arial + + + + Map editor for pokeemerald, pokefirered and pokeruby. + + + Qt::AlignmentFlag::AlignCenter + + + + + + + <html><head/><body><p>Official Documentation: <a href="https://huderlem.github.io/porymap/"><span style=" text-decoration: underline; color:#0069d9;">https://huderlem.github.io/porymap/</span></a></p></body></html> + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + diff --git a/include/ui/aboutporymap.h b/include/ui/aboutporymap.h index 28b06249..148fc8b1 100644 --- a/include/ui/aboutporymap.h +++ b/include/ui/aboutporymap.h @@ -3,13 +3,13 @@ #include #include -#include +#include namespace Ui { class AboutPorymap; } -class AboutPorymap : public QMainWindow +class AboutPorymap : public QDialog { public: explicit AboutPorymap(QWidget *parent = nullptr); diff --git a/src/ui/aboutporymap.cpp b/src/ui/aboutporymap.cpp index 24d76ce5..a422072f 100644 --- a/src/ui/aboutporymap.cpp +++ b/src/ui/aboutporymap.cpp @@ -1,22 +1,21 @@ #include "aboutporymap.h" #include "ui_aboutporymap.h" -#include "log.h" AboutPorymap::AboutPorymap(QWidget *parent) : - QMainWindow(parent), + QDialog(parent), ui(new Ui::AboutPorymap) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - QString versionInfo = QString("Version %1 - %2").arg(QCoreApplication::applicationVersion()).arg(QStringLiteral(__DATE__)); - static const QString commitHash = PORYMAP_LATEST_COMMIT; - if (!commitHash.isEmpty()) - versionInfo.append(QString("\nCommit %1").arg(commitHash)); - - this->ui->label_Version->setText(versionInfo); - this->ui->textBrowser->setSource(QUrl("qrc:/CHANGELOG.md")); + this->ui->label_Version->setText(QString("Version %1%2\nQt %3 (%4)\n%5") + .arg(QCoreApplication::applicationVersion()) + .arg(commitHash.isEmpty() ? "" : QString(" (%1)").arg(commitHash)) + .arg(QStringLiteral(QT_VERSION_STR)) + .arg(QSysInfo::buildCpuArchitecture()) + .arg(QStringLiteral(__DATE__)) + ); } AboutPorymap::~AboutPorymap() From c2b27fd2a1124efddeb0ec4e13613c764582d7f6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Dec 2024 15:04:34 -0500 Subject: [PATCH 123/364] Find git on Windows --- porymap.pro | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/porymap.pro b/porymap.pro index 611b6179..6e117257 100644 --- a/porymap.pro +++ b/porymap.pro @@ -20,10 +20,12 @@ QMAKE_CXXFLAGS += -std=c++17 -Wall QMAKE_TARGET_BUNDLE_PREFIX = com.pret # Get latest commit hash if we can (to display alongside version information). -GIT_PATH = $$system(which git) -!isEmpty(GIT_PATH) { - LATEST_COMMIT = $$system($$GIT_PATH rev-parse --short HEAD 2>/dev/null) +win32 { + LATEST_COMMIT = $$system(git rev-parse --short HEAD 2> nul) +} else { + LATEST_COMMIT = $$system(git rev-parse --short HEAD 2>/dev/null) } + DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\" VERSION = 5.4.1 From 01e586be626d567188ccc540b17c999c512f62a4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Dec 2024 15:57:23 -0500 Subject: [PATCH 124/364] Disable resizing the about window --- src/ui/aboutporymap.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/aboutporymap.cpp b/src/ui/aboutporymap.cpp index a422072f..07654e14 100644 --- a/src/ui/aboutporymap.cpp +++ b/src/ui/aboutporymap.cpp @@ -16,6 +16,8 @@ AboutPorymap::AboutPorymap(QWidget *parent) : .arg(QSysInfo::buildCpuArchitecture()) .arg(QStringLiteral(__DATE__)) ); + + layout()->setSizeConstraint(QLayout::SetFixedSize); } AboutPorymap::~AboutPorymap() From d768075a261910d2139f5aa5227cf5273cd7c109 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 19 Dec 2024 11:44:44 -0500 Subject: [PATCH 125/364] Fix rendering for fully transparent pixels --- src/ui/imageproviders.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 86f0c07a..d4cdb71a 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -43,6 +43,10 @@ QImage getMetatileImage( metatile_image.fill(Qt::magenta); return metatile_image; } + + // The GBA renders transparent pixels using palette 0 color 0. We have access to that color (palettes.value(0).value(0)) + // but all 3 games actually overwrite this color with black when loading the tileset palettes, so we fill the metatile + // image with black so that any pixels we don't render will reveal the correct color. metatile_image.fill(Qt::black); QList> palettes = Tileset::getBlockPalettes(primaryTileset, secondaryTileset, useTruePalettes); @@ -54,7 +58,6 @@ QImage getMetatileImage( for (int y = 0; y < 2; y++) for (int x = 0; x < 2; x++) { int l = layerOrder.size() >= numLayers ? layerOrder[layer] : layer; - int bottomLayer = layerOrder.size() >= numLayers ? layerOrder[0] : 0; // Get the tile to render next Tile tile; @@ -91,12 +94,8 @@ QImage getMetatileImage( QImage tile_image = getTileImage(tile.tileId, primaryTileset, secondaryTileset); if (tile_image.isNull()) { // Some metatiles specify tiles that are outside the valid range. - // These are treated as completely transparent, so they can be skipped without - // being drawn unless they're on the bottom layer, in which case we need - // a placeholder because garbage will be drawn otherwise. - if (l == bottomLayer) { - metatile_painter.fillRect(x * 8, y * 8, 8, 8, palettes.value(0).value(0)); - } + // The way the GBA will render these depends on what's in memory (which Porymap can't know) + // so we treat them as if they were transparent. continue; } @@ -121,12 +120,10 @@ QImage getMetatileImage( } } - // The top layer of the metatile has its first color displayed at transparent. - if (l != bottomLayer) { - QColor color(tile_image.color(0)); - color.setAlpha(0); - tile_image.setColor(0, color.rgba()); - } + // Color 0 is displayed as transparent. + QColor color(tile_image.color(0)); + color.setAlpha(0); + tile_image.setColor(0, color.rgba()); metatile_painter.drawImage(origin, tile_image.mirrored(tile.xflip, tile.yflip)); } From 486d1b7335b63ea75ae86108ceab40fac5274117 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 19 Dec 2024 13:58:13 -0500 Subject: [PATCH 126/364] Add config settings for rendering transparency --- forms/projectsettingseditor.ui | 107 +++++++++++++++++++++++++++---- include/config.h | 8 +++ include/core/tile.h | 2 + include/ui/imageproviders.h | 4 +- src/config.cpp | 12 ++++ src/core/tile.cpp | 29 ++++++--- src/project.cpp | 28 +++++--- src/ui/imageproviders.cpp | 32 ++++----- src/ui/projectsettingseditor.cpp | 18 ++++++ src/ui/tileseteditor.cpp | 8 ++- 10 files changed, 201 insertions(+), 47 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 9aa521a8..fc156a72 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -369,7 +369,7 @@ 0 0 559 - 548 + 560 @@ -602,7 +602,7 @@
- + The mask used to read/write metatile IDs in map data. @@ -616,7 +616,7 @@ - + The mask used to read/write collision values in map data. @@ -630,7 +630,7 @@ - + The mask used to read/write elevation values in map data. @@ -742,7 +742,7 @@ 0 0 559 - 568 + 798 @@ -775,6 +775,86 @@ + + + + Transparent Pixel Rendering + + + + + + Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default) + + + Render as black + + + + + + + Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA) + + + Render using first palette color + + + + + + + + + + Unused Layer Rendering + + + + + + Normal + + + + + + + This raw tile value will be used to fill the unused bottom layer of Normal metatiles + + + + + + + Covered + + + + + + + This raw tile value will be used to fill the unused top layer of Covered metatiles + + + + + + + Split + + + + + + + This raw tile value will be used to fill the unused middle layer of Split metatiles + + + + + + @@ -810,14 +890,14 @@ - + The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. - + The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. @@ -864,7 +944,7 @@ - + The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled. @@ -891,7 +971,7 @@ - + The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. @@ -1549,10 +1629,15 @@
noscrollspinbox.h
- UIntHexSpinBox - QWidget + UIntSpinBox + QAbstractSpinBox
uintspinbox.h
+ + UIntHexSpinBox + UIntSpinBox +
uintspinbox.h
+
diff --git a/include/config.h b/include/config.h index 47bc2010..020808b6 100644 --- a/include/config.h +++ b/include/config.h @@ -301,6 +301,7 @@ public: this->prefabImportPrompted = false; this->tilesetsHaveCallback = true; this->tilesetsHaveIsCompressed = true; + this->setTransparentPixelsBlack = true; this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); @@ -310,6 +311,9 @@ public: this->blockMetatileIdMask = 0x03FF; this->blockCollisionMask = 0x0C00; this->blockElevationMask = 0xF000; + this->unusedTileNormal = 0x3014; + this->unusedTileCovered = 0x0000; + this->unusedTileSplit = 0x0000; this->identifiers.clear(); this->readKeys.clear(); } @@ -362,6 +366,7 @@ public: bool prefabImportPrompted; bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; + bool setTransparentPixelsBlack; int metatileAttributesSize; uint32_t metatileBehaviorMask; uint32_t metatileTerrainTypeMask; @@ -370,6 +375,9 @@ public: uint16_t blockMetatileIdMask; uint16_t blockCollisionMask; uint16_t blockElevationMask; + uint16_t unusedTileNormal; + uint16_t unusedTileCovered; + uint16_t unusedTileSplit; bool mapAllowFlagsEnabled; QString collisionSheetPath; int collisionSheetWidth; diff --git a/include/core/tile.h b/include/core/tile.h index 5d85066a..b58a187d 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -19,6 +19,8 @@ public: uint16_t rawValue() const; static int getIndexInTileset(int); + + static const uint16_t maxValue; }; inline bool operator==(const Tile &a, const Tile &b) { diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index fefa5546..806ffd5b 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -8,8 +8,8 @@ QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); -QImage getMetatileImage(uint16_t, Tileset*, Tileset*, QList, QList, bool useTruePalettes = false); -QImage getMetatileImage(Metatile*, Tileset*, Tileset*, QList, QList, bool useTruePalettes = false); +QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); +QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); QImage getTileImage(uint16_t, Tileset*, Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset); diff --git a/src/config.cpp b/src/config.cpp index c0c00194..6d23977f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -720,6 +720,12 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->blockCollisionMask = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "block_elevation_mask") { this->blockElevationMask = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "unused_tile_normal") { + this->unusedTileNormal = getConfigUint32(key, value, 0, Tile::maxValue); + } else if (key == "unused_tile_covered") { + this->unusedTileCovered = getConfigUint32(key, value, 0, Tile::maxValue); + } else if (key == "unused_tile_split") { + this->unusedTileSplit = getConfigUint32(key, value, 0, Tile::maxValue); } else if (key == "enable_map_allow_flags") { this->mapAllowFlagsEnabled = getConfigBool(key, value); #ifdef CONFIG_BACKWARDS_COMPATABILITY @@ -752,6 +758,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveCallback = getConfigBool(key, value); } else if (key == "tilesets_have_is_compressed") { this->tilesetsHaveIsCompressed = getConfigBool(key, value); + } else if (key == "set_transparent_pixels_black") { + this->setTransparentPixelsBlack = getConfigBool(key, value); } else if (key == "event_icon_path_object") { this->eventIconPaths[Event::Group::Object] = value; } else if (key == "event_icon_path_warp") { @@ -839,6 +847,7 @@ QMap ProjectConfig::getKeyValueMap() { } map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); + map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper()); map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper()); @@ -847,6 +856,9 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("block_metatile_id_mask", "0x" + QString::number(this->blockMetatileIdMask, 16).toUpper()); map.insert("block_collision_mask", "0x" + QString::number(this->blockCollisionMask, 16).toUpper()); map.insert("block_elevation_mask", "0x" + QString::number(this->blockElevationMask, 16).toUpper()); + map.insert("unused_tile_normal", "0x" + QString::number(this->unusedTileNormal, 16).toUpper()); + map.insert("unused_tile_covered", "0x" + QString::number(this->unusedTileCovered, 16).toUpper()); + map.insert("unused_tile_split", "0x" + QString::number(this->unusedTileSplit, 16).toUpper()); map.insert("enable_map_allow_flags", QString::number(this->mapAllowFlagsEnabled)); map.insert("event_icon_path_object", this->eventIconPaths[Event::Group::Object]); map.insert("event_icon_path_warp", this->eventIconPaths[Event::Group::Warp]); diff --git a/src/core/tile.cpp b/src/core/tile.cpp index cc89c2a9..1aab7a9b 100644 --- a/src/core/tile.cpp +++ b/src/core/tile.cpp @@ -1,5 +1,17 @@ #include "tile.h" #include "project.h" +#include "bitpacker.h" + +// Upper limit for raw value (i.e., uint16_t max). +const uint16_t Tile::maxValue = 0xFFFF; + +// At the moment these are fixed, and not exposed to the user. +// We're only using them for convenience when converting between raw values. +// The actual job of clamping Tile's members to correct values is handled by the widths in the bit field. +const BitPacker bitsTileId = BitPacker(0x03FF); +const BitPacker bitsXFlip = BitPacker(0x0400); +const BitPacker bitsYFlip = BitPacker(0x0800); +const BitPacker bitsPalette = BitPacker(0xF000); Tile::Tile() : tileId(0), @@ -16,18 +28,17 @@ { } Tile::Tile(uint16_t raw) : - tileId(raw & 0x3FF), - xflip((raw >> 10) & 1), - yflip((raw >> 11) & 1), - palette((raw >> 12) & 0xF) + tileId(bitsTileId.unpack(raw)), + xflip(bitsXFlip.unpack(raw)), + yflip(bitsYFlip.unpack(raw)), + palette(bitsPalette.unpack(raw)) { } uint16_t Tile::rawValue() const { - return static_cast( - (this->tileId & 0x3FF) - | ((this->xflip & 1) << 10) - | ((this->yflip & 1) << 11) - | ((this->palette & 0xF) << 12)); + return bitsTileId.pack(this->tileId) + | bitsXFlip.pack(this->xflip) + | bitsYFlip.pack(this->yflip) + | bitsPalette.pack(this->palette); } int Tile::getIndexInTileset(int tileId) { diff --git a/src/project.cpp b/src/project.cpp index 43b62ecf..1791bcdf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2096,19 +2096,29 @@ bool Project::readFieldmapProperties() { fileWatcher.addPath(root + "/" + filename); const QMap defines = parser.readCDefinesByName(filename, names); - auto loadDefine = [defines](const QString name, int * dest) { + auto loadDefine = [defines](const QString name, int * dest, int min, int max) { auto it = defines.find(name); if (it != defines.end()) { *dest = it.value(); + if (*dest < min) { + logWarn(QString("Value for tileset property '%1' (%2) is below the minimum (%3). Defaulting to minimum.").arg(name).arg(*dest).arg(min)); + *dest = min; + } else if (*dest > max) { + logWarn(QString("Value for tileset property '%1' (%2) is above the maximum (%3). Defaulting to maximum.").arg(name).arg(*dest).arg(max)); + *dest = max; + } } else { logWarn(QString("Value for tileset property '%1' not found. Using default (%2) instead.").arg(name).arg(*dest)); } }; - loadDefine(numTilesPrimaryName, &Project::num_tiles_primary); - loadDefine(numTilesTotalName, &Project::num_tiles_total); - loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary); - loadDefine(numPalsPrimaryName, &Project::num_pals_primary); - loadDefine(numPalsTotalName, &Project::num_pals_total); + loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter. + loadDefine(numTilesTotalName, &Project::num_tiles_total, 2, 1024); // 1024 is fixed because we store tile IDs in a 10-bit field. + loadDefine(numPalsPrimaryName, &Project::num_pals_primary, 1, Project::num_pals_total - 1); + loadDefine(numTilesPrimaryName, &Project::num_tiles_primary, 1, Project::num_tiles_total - 1); + + // This maximum is overly generous, because until we parse the appropriate masks from the project + // we don't actually know what the maximum number of metatiles is. + loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary, 1, 0xFFFF - 1); auto it = defines.find(maxMapSizeName); if (it != defines.end()) { @@ -3020,12 +3030,12 @@ void Project::applyParsedLimits() { Block::setLayout(); Metatile::setLayout(this); - Project::num_metatiles_primary = qMin(Project::num_metatiles_primary, Block::getMaxMetatileId() + 1); + Project::num_metatiles_primary = qMin(qMax(Project::num_metatiles_primary, 1), Block::getMaxMetatileId() + 1); projectConfig.defaultMetatileId = qMin(projectConfig.defaultMetatileId, Block::getMaxMetatileId()); projectConfig.defaultElevation = qMin(projectConfig.defaultElevation, Block::getMaxElevation()); projectConfig.defaultCollision = qMin(projectConfig.defaultCollision, Block::getMaxCollision()); - projectConfig.collisionSheetHeight = qMin(projectConfig.collisionSheetHeight, Block::getMaxElevation() + 1); - projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1); + projectConfig.collisionSheetHeight = qMin(qMax(projectConfig.collisionSheetHeight, 1), Block::getMaxElevation() + 1); + projectConfig.collisionSheetWidth = qMin(qMax(projectConfig.collisionSheetWidth, 1), Block::getMaxCollision() + 1); } bool Project::hasUnsavedChanges() { diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index d4cdb71a..8fa72699 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -17,8 +17,8 @@ QImage getMetatileImage( uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset, - QList layerOrder, - QList layerOpacity, + const QList &layerOrder, + const QList &layerOpacity, bool useTruePalettes) { Metatile* metatile = Tileset::getMetatile(metatileId, primaryTileset, secondaryTileset); @@ -34,8 +34,8 @@ QImage getMetatileImage( Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset, - QList layerOrder, - QList layerOpacity, + const QList &layerOrder, + const QList &layerOpacity, bool useTruePalettes) { QImage metatile_image(16, 16, QImage::Format_RGBA8888); @@ -44,13 +44,15 @@ QImage getMetatileImage( return metatile_image; } - // The GBA renders transparent pixels using palette 0 color 0. We have access to that color (palettes.value(0).value(0)) - // but all 3 games actually overwrite this color with black when loading the tileset palettes, so we fill the metatile - // image with black so that any pixels we don't render will reveal the correct color. - metatile_image.fill(Qt::black); - QList> palettes = Tileset::getBlockPalettes(primaryTileset, secondaryTileset, useTruePalettes); + // We need to fill the metatile image with something so that if any transparent + // tile pixels line up across layers we will still have something to render. + // The GBA renders transparent pixels using palette 0 color 0. We have this color, + // but all 3 games actually overwrite it with black when loading the tileset palettes, + // so we have a setting to choose between these two behaviors. + metatile_image.fill(projectConfig.setTransparentPixelsBlack ? QColor("black") : QColor(palettes.value(0).value(0))); + QPainter metatile_painter(&metatile_image); const int numLayers = 3; // When rendering, metatiles always have 3 layers uint32_t layerType = metatile->layerType(); @@ -66,25 +68,25 @@ QImage getMetatileImage( tile = metatile->tiles.value(tileOffset + (l * 4)); } else { // "Vanilla" metatiles only have 8 tiles, but render 12. - // The remaining 4 tiles are rendered either as tile 0 or 0x3014 (tile 20, palette 3) depending on layer type. + // The remaining 4 tiles are rendered using user-specified tiles depending on layer type. switch (layerType) { default: case METATILE_LAYER_MIDDLE_TOP: if (l == 0) - tile = Tile(0x3014); + tile = Tile(projectConfig.unusedTileNormal); else // Tiles are on layers 1 and 2 tile = metatile->tiles.value(tileOffset + ((l - 1) * 4)); break; case METATILE_LAYER_BOTTOM_MIDDLE: if (l == 2) - tile = Tile(); + tile = Tile(projectConfig.unusedTileCovered); else // Tiles are on layers 0 and 1 tile = metatile->tiles.value(tileOffset + (l * 4)); break; case METATILE_LAYER_BOTTOM_TOP: if (l == 1) - tile = Tile(); + tile = Tile(projectConfig.unusedTileSplit); else // Tiles are on layers 0 and 2 tile = metatile->tiles.value(tileOffset + ((l == 0 ? 0 : 1) * 4)); break; @@ -101,7 +103,7 @@ QImage getMetatileImage( // Colorize the metatile tiles with its palette. if (tile.palette < palettes.length()) { - QList palette = palettes.value(tile.palette); + const QList palette = palettes.value(tile.palette); for (int j = 0; j < palette.length(); j++) { tile_image.setColor(j, palette.value(j)); } @@ -141,7 +143,7 @@ QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondary return tileset->tiles.value(index, QImage()); } -QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, QList palette) { +QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) { QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset); if (tileImage.isNull()) { tileImage = QImage(8, 8, QImage::Format_RGBA8888); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 346591f4..0e787f5e 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -82,6 +82,8 @@ void ProjectSettingsEditor::connectSignals() { } for (auto checkBox : ui->centralwidget->findChildren()) connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited); + for (auto radioButton : ui->centralwidget->findChildren()) + connect(radioButton, &QRadioButton::toggled, this, &ProjectSettingsEditor::markEdited); for (auto lineEdit : ui->centralwidget->findChildren()) connect(lineEdit, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited); for (auto spinBox : ui->centralwidget->findChildren()) @@ -135,6 +137,9 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue); ui->spinBox_CollisionMask->setMaximum(Block::maxValue); ui->spinBox_ElevationMask->setMaximum(Block::maxValue); + ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue); + ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue); + ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -442,6 +447,12 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_OutputIsCompressed->setChecked(projectConfig.tilesetsHaveIsCompressed); ui->checkBox_DisableWarning->setChecked(porymapConfig.warpBehaviorWarningDisabled); + // Radio buttons + if (projectConfig.setTransparentPixelsBlack) + ui->radioButton_RenderBlack->setChecked(true); + else + ui->radioButton_RenderFirstPalColor->setChecked(true); + // Set spin box values ui->spinBox_Elevation->setValue(projectConfig.defaultElevation); ui->spinBox_Collision->setValue(projectConfig.defaultCollision); @@ -455,6 +466,9 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_MetatileIdMask->setValue(projectConfig.blockMetatileIdMask & ui->spinBox_MetatileIdMask->maximum()); ui->spinBox_CollisionMask->setValue(projectConfig.blockCollisionMask & ui->spinBox_CollisionMask->maximum()); ui->spinBox_ElevationMask->setValue(projectConfig.blockElevationMask & ui->spinBox_ElevationMask->maximum()); + ui->spinBox_UnusedTileNormal->setValue(projectConfig.unusedTileNormal); + ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); + ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -511,6 +525,7 @@ void ProjectSettingsEditor::save() { projectConfig.tilesetsHaveCallback = ui->checkBox_OutputCallback->isChecked(); projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked(); porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked(); + projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); // Save spin box settings projectConfig.defaultElevation = ui->spinBox_Elevation->value(); @@ -525,6 +540,9 @@ void ProjectSettingsEditor::save() { projectConfig.blockMetatileIdMask = ui->spinBox_MetatileIdMask->value(); projectConfig.blockCollisionMask = ui->spinBox_CollisionMask->value(); projectConfig.blockElevationMask = ui->spinBox_ElevationMask->value(); + projectConfig.unusedTileNormal = ui->spinBox_UnusedTileNormal->value(); + projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); + projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 49cfe147..b8da1912 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -99,7 +99,13 @@ void TilesetEditor::initUi() { this->paletteId = ui->spinBox_paletteSelector->value(); this->ui->spinBox_paletteSelector->setMinimum(0); this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); - this->ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); + + // TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8. + // If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing + // metatile groups the user may have designed) so this should be fixed by filling the primary metatiles + // image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is. + this->ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false); + this->ui->actionShow_Tileset_Divider->setVisible(false); this->setAttributesUi(); this->setMetatileLabelValidator(); From ad0b8d6794f4bb63e0e01078b21d3b41e957c5cf Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 21 Dec 2024 17:19:31 -0500 Subject: [PATCH 127/364] Render metatile/collision views by tab --- CHANGELOG.md | 1 + include/mainwindow.h | 2 ++ src/mainwindow.cpp | 13 +++++++++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b75bed..9c5f4af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix a visual issue when quickly dragging map connections around. - Fix map connections rendering incorrectly if their direction name was unknown. - Fix map connections rendering incorrectly if their dimensions were smaller than the border draw distance. +- Fix metatile/collision selection images skewing off-center after opening a map from the Connections tab. - Fix the map list filter retaining text between project open/close. - Fix the map list mishandling value gaps when sorting by Area. - Fix a freeze on startup if project values are defined with mismatched parentheses. diff --git a/include/mainwindow.h b/include/mainwindow.h index 397a1d72..a4d60697 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -351,6 +351,8 @@ private: bool userSetMap(QString); void redrawMapScene(); void refreshMapScene(); + void refreshMetatileViews(); + void refreshCollisionSelector(); void setLayoutOnlyMode(bool layoutOnly); bool checkProjectSanity(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 12bbdd6f..1608c786 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -948,8 +948,6 @@ void MainWindow::redrawMapScene() { } void MainWindow::refreshMapScene() { - on_mainTabBar_tabBarClicked(ui->mainTabBar->currentIndex()); - ui->graphicsView_Map->setScene(editor->scene); ui->graphicsView_Map->setSceneRect(editor->scene->sceneRect()); ui->graphicsView_Map->editor = editor; @@ -971,7 +969,14 @@ void MainWindow::refreshMapScene() { //ui->graphicsView_Collision->setSceneRect(editor->scene_collision_metatiles->sceneRect()); ui->graphicsView_Collision->setFixedSize(editor->movement_permissions_selector_item->pixmap().width() + 2, editor->movement_permissions_selector_item->pixmap().height() + 2); + on_mainTabBar_tabBarClicked(ui->mainTabBar->currentIndex()); +} + +void MainWindow::refreshMetatileViews() { on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); +} + +void MainWindow::refreshCollisionSelector() { on_horizontalSlider_CollisionZoom_valueChanged(ui->horizontalSlider_CollisionZoom->value()); } @@ -2126,8 +2131,10 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) if (index == MapViewTab::Metatiles) { editor->setEditingMetatiles(); + refreshMetatileViews(); } else if (index == MapViewTab::Collision) { editor->setEditingCollision(); + refreshCollisionSelector(); } else if (index == MapViewTab::Prefabs) { editor->setEditingMetatiles(); if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) { @@ -2939,7 +2946,6 @@ void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &ti if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updatePrimaryTileset(tilesetLabel); redrawMapScene(); - on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); markMapEdited(); @@ -2951,7 +2957,6 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updateSecondaryTileset(tilesetLabel); redrawMapScene(); - on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); markMapEdited(); From 298306ce08b0ba6b98d7dce10e53bffbb3c247db Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 22 Dec 2024 16:54:33 -0500 Subject: [PATCH 128/364] Add release build for intel macOS --- .github/workflows/main.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0f50884..1346f310 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,12 @@ jobs: run: make build-macos: - runs-on: macos-latest + strategy: + matrix: + os: [macos-latest, macos-13] + runs-on: ${{ matrix.os }} + env: + BUILD_NAME: porymap-${{ matrix.os }}-${{ github.ref_name }} steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 @@ -61,19 +66,19 @@ jobs: - name: Prep Release Directory if: startsWith(github.ref, 'refs/tags/') run: | - mkdir porymap-macOS-${{ github.ref_name }} - cp porymap.dmg porymap-macOS-${{ github.ref_name }}/porymap.dmg - cp RELEASE-README.txt porymap-macOS-${{ github.ref_name }}/README.txt + mkdir $BUILD_NAME + cp porymap.dmg $BUILD_NAME/porymap.dmg + cp RELEASE-README.txt $BUILD_NAME/README.txt - name: Bundle Release Directory if: startsWith(github.ref, 'refs/tags/') - run: zip -r porymap-macOS-${{ github.ref_name }}.zip porymap-macOS-${{ github.ref_name }} + run: zip -r $BUILD_NAME.zip $BUILD_NAME - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: - files: porymap-macOS-${{ github.ref_name }}.zip + files: $BUILD_NAME.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 0d939772bf451412b76dff113fe5f000a24fe3fb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 20 Dec 2024 14:49:40 -0500 Subject: [PATCH 129/364] Add QMessageBox convenience classes --- include/mainwindow.h | 1 + include/ui/message.h | 60 +++++++++++++++++ porymap.pro | 2 + src/mainwindow.cpp | 149 ++++++++++++------------------------------- src/project.cpp | 11 +++- src/ui/message.cpp | 78 ++++++++++++++++++++++ 6 files changed, 189 insertions(+), 112 deletions(-) create mode 100644 include/ui/message.h create mode 100644 src/ui/message.cpp diff --git a/include/mainwindow.h b/include/mainwindow.h index 0eac8947..5ffc82a7 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -365,6 +365,7 @@ private: QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); + void showRecentError(const QString &baseMessage); void showProjectOpenFailure(); void showMapsExcludedAlert(const QStringList &excludedMapNames); diff --git a/include/ui/message.h b/include/ui/message.h new file mode 100644 index 00000000..8e36372d --- /dev/null +++ b/include/ui/message.h @@ -0,0 +1,60 @@ +#pragma once +#ifndef MESSAGE_H +#define MESSAGE_H + +/* + These classes are thin wrappers around QMessageBox for convenience. + The base Message class is a regular window-modal QMessageBox with "porymap" as the window title. + + QMessageBox's static functions enforce application modality (among other things), which changes the style of the message boxes on macOS. + With these equivalent static functions we have more control over the appearance and behavior of the window, + and we keep the convenience of not needing to provide all the arguments. + + If more control is needed (like adding custom buttons to the window) use the constructors as you would for a normal QMessageBox. +*/ + +#include + +class Message : public QMessageBox { +public: + Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent); +}; + +// Basic error message with an 'Ok' button. +class ErrorMessage : public Message { +public: + ErrorMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + +// Basic warning message with an 'Ok' button. +class WarningMessage : public Message { +public: + WarningMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + +// Basic informational message with a 'Close' button. +class InfoMessage : public Message { +public: + InfoMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + +// Basic question message with a 'Yes' and 'No' button. +class QuestionMessage : public Message { +public: + QuestionMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + +// Error message directing users to their log file. +// Shows the most recent error as detailed text. +class RecentErrorMessage : public ErrorMessage { +public: + RecentErrorMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + + +#endif // MESSAGE_H diff --git a/porymap.pro b/porymap.pro index 99489758..940c0a7f 100644 --- a/porymap.pro +++ b/porymap.pro @@ -77,6 +77,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/filterchildrenproxymodel.cpp \ src/ui/maplistmodels.cpp \ src/ui/maplisttoolbar.cpp \ + src/ui/message.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ src/ui/layoutpixmapitem.cpp \ @@ -183,6 +184,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/filterchildrenproxymodel.h \ include/ui/maplistmodels.h \ include/ui/maplisttoolbar.h \ + include/ui/message.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ include/ui/layoutpixmapitem.h \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 50c8beae..717ca085 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,6 +26,7 @@ #include "newmapdialog.h" #include "newtilesetdialog.h" #include "newnamedialog.h" +#include "message.h" #include #include @@ -36,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -151,6 +151,7 @@ void MainWindow::initWindow() { #endif setWindowDisabled(true); + show(); } void MainWindow::initShortcuts() { @@ -678,14 +679,10 @@ bool MainWindow::checkProjectSanity() { logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root)); - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setText(QString("The selected directory appears to be invalid.")); + ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), this); msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" "Make sure you selected the correct project directory " "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(editor->project->root)); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setDefaultButton(QMessageBox::Ok); auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); msgBox.exec(); if (msgBox.clickedButton() == tryAnyway) { @@ -697,25 +694,18 @@ bool MainWindow::checkProjectSanity() { } void MainWindow::showProjectOpenFailure() { - QString errorMsg = QString("There was an error opening the project. Please see %1 for full error details.").arg(getLogPath()); - QMessageBox error(QMessageBox::Critical, QApplication::applicationName(), errorMsg, QMessageBox::Ok, this); - error.setDetailedText(getMostRecentError()); - error.exec(); + RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this); } // Alert the user that one or more maps have been excluded while loading the project. void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { - QMessageBox msgBox(QMessageBox::Icon::Warning, QApplication::applicationName(), "", QMessageBox::Ok, this); - - QString errorMsg; + RecentErrorMessage msgBox("", this); if (excludedMapNames.length() == 1) { - errorMsg = QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first()); + msgBox.setText(QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first())); } else { - errorMsg = QString("Failed to load the maps listed below. Saving will exclude these maps from your project."); - msgBox.setDetailedText(excludedMapNames.join("\n")); + msgBox.setText(QStringLiteral("Failed to load the maps listed below. Saving will exclude these maps from your project.")); + msgBox.setDetailedText(excludedMapNames.join("\n")); // Overwrites error details text, user will need to check the log. } - errorMsg.append(QString("\n\nPlease see %1 for full error details.").arg(getLogPath())); - msgBox.setText(errorMsg); msgBox.exec(); } @@ -812,22 +802,15 @@ void MainWindow::showFileWatcherWarning(QString filepath) { static bool showing = false; if (showing) return; - QMessageBox notice(this); - notice.setText("File Changed"); - notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?") - .arg(filepath.remove(project->root + "/"))); - notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes); - notice.setDefaultButton(QMessageBox::No); - notice.setIcon(QMessageBox::Question); - + QuestionMessage msgBox(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(filepath.remove(project->root + "/")), this); QCheckBox showAgainCheck("Do not ask again."); - notice.setCheckBox(&showAgainCheck); + msgBox.setCheckBox(&showAgainCheck); showing = true; - int choice = notice.exec(); - if (choice == QMessageBox::Yes) { + auto reply = msgBox.exec(); + if (reply == QMessageBox::Yes) { on_action_Reload_Project_triggered(); - } else if (choice == QMessageBox::No) { + } else if (reply == QMessageBox::No) { if (showAgainCheck.isChecked()) { porymapConfig.monitorFiles = false; if (this->preferenceEditor) @@ -850,14 +833,10 @@ void MainWindow::on_action_Open_Project_triggered() void MainWindow::on_action_Reload_Project_triggered() { // TODO: when undo history is complete show only if has unsaved changes - QMessageBox warning(this); - warning.setText("WARNING"); - warning.setInformativeText("Reloading this project will discard any unsaved changes."); - warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - warning.setDefaultButton(QMessageBox::Cancel); - warning.setIcon(QMessageBox::Warning); - - if (warning.exec() == QMessageBox::Ok) + WarningMessage msgBox(QStringLiteral("Reloading this project will discard any unsaved changes."), this); + msgBox.addButton(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + if (msgBox.exec() == QMessageBox::Ok) openProject(editor->project->root); } @@ -878,23 +857,14 @@ bool MainWindow::userSetMap(QString map_name) { return true; // Already set if (map_name == editor->project->getDynamicMapName()) { - QMessageBox msgBox(QMessageBox::Icon::Warning, - QApplication::applicationName(), - QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name), - QMessageBox::Ok, - this); + WarningMessage msgBox(QString("Cannot open map '%1'.").arg(map_name), this); + msgBox.setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); msgBox.exec(); return false; } if (!setMap(map_name)) { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("There was an error opening map %1.\n\nPlease see %2 for full error details.").arg(map_name).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("There was an error opening map '%1'.").arg(map_name), this); return false; } return true; @@ -950,13 +920,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // Use when the user is specifically requesting a layout to open. bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("There was an error opening layout %1.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("There was an error opening layout '%1'.").arg(layoutId), this); return false; } @@ -1390,7 +1354,7 @@ void MainWindow::onNewTilesetCreated(Tileset *tileset) { // Unlike creating a new map or layout (which immediately opens the new item) // creating a new tileset has no visual feedback that it succeeded, so we show a message. - QMessageBox::information(this, QApplication::applicationName(), QString( "New tileset created at '%1'!").arg(tileset->getExpectedDir())); + InfoMessage::show(QString("New tileset created at '%1'!").arg(tileset->getExpectedDir()), this); // Refresh tileset combo boxes if (!tileset->is_secondary) { @@ -1413,13 +1377,7 @@ void MainWindow::openDuplicateMapDialog(const QString &mapName) { auto dialog = new NewMapDialog(this->editor->project, map, this); dialog->open(); } else { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(mapName).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("Unable to duplicate '%1'.").arg(mapName), this); } } @@ -1440,13 +1398,7 @@ void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { auto dialog = createNewLayoutDialog(layout); dialog->open(); } else { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("Unable to duplicate '%1'.").arg(layoutId), this); } } @@ -1999,8 +1951,7 @@ void MainWindow::addNewEvent(Event::Type type) { updateObjects(); editor->selectMapEvent(object); } else { - QMessageBox msgBox(this); - msgBox.setText("Failed to add new event"); + WarningMessage msgBox(QStringLiteral("Failed to add new event."), this); if (Event::typeToGroup(type) == Event::Group::Object) { msgBox.setInformativeText(QString("The limit for object events (%1) has been reached.\n\n" "This limit can be adjusted with %2 in '%3'.") @@ -2008,8 +1959,6 @@ void MainWindow::addNewEvent(Event::Type type) { .arg(projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count)) .arg(projectConfig.getFilePath(ProjectFilePath::constants_global))); } - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Warning); msgBox.exec(); } } @@ -2509,14 +2458,7 @@ void MainWindow::on_action_Export_Map_Image_triggered() { void MainWindow::on_actionExport_Stitched_Map_Image_triggered() { if (!this->editor->map) { - QMessageBox warning(this); - warning.setText("Notice"); - warning.setInformativeText("Map stitch images are not possible without a map selected."); - warning.setStandardButtons(QMessageBox::Ok); - warning.setDefaultButton(QMessageBox::Cancel); - warning.setIcon(QMessageBox::Warning); - - warning.exec(); + WarningMessage::show(QStringLiteral("Map stitch images are not possible without a map selected."), this); return; } showExportMapImageWindow(ImageExporterMode::Stitch); @@ -2535,13 +2477,7 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() { bool error = false; Layout *mapLayout = AdvanceMapParser::parseLayout(filepath, &error, editor->project); if (error) { - QMessageBox msgBox(this); - msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); - QString message = QString("The .map file could not be processed. View porymap.log for specific errors."); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); + RecentErrorMessage::show(QStringLiteral("Failed to import map from Advance Map 1.92 .map file."), this); delete mapLayout; return; } @@ -2858,8 +2794,7 @@ void MainWindow::on_actionProject_Settings_triggered() { } void MainWindow::onWarpBehaviorWarningClicked() { - static const QString text = QString("Warp Events only function as exits on certain metatiles"); - static const QString informative = QString( + static const QString informative = QStringLiteral( "

" "For instance, most floor metatiles in a cave have the metatile behavior MB_CAVE, but the floor space in front of an exit " "will have MB_SOUTH_ARROW_WARP, which is treated specially in your project's code to allow a Warp Event to warp the player. " @@ -2873,13 +2808,13 @@ void MainWindow::onWarpBehaviorWarningClicked() { "You can disable this warning or edit the list of behaviors that silence this warning under Options -> Project Settings..." "

" ); - QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this); - QPushButton *settings = msgBox.addButton("Open Settings...", QMessageBox::ActionRole); - msgBox.setDefaultButton(QMessageBox::Close); + + InfoMessage msgBox(QStringLiteral("Warp Events only function as exits on certain metatiles"), this); + auto settingsButton = msgBox.addButton("Open Settings...", QMessageBox::ActionRole); msgBox.setTextFormat(Qt::RichText); msgBox.setInformativeText(informative); msgBox.exec(); - if (msgBox.clickedButton() == settings) + if (msgBox.clickedButton() == settingsButton) this->openProjectSettingsEditor(ProjectSettingsEditor::eventsTab); } @@ -3013,12 +2948,7 @@ bool MainWindow::initRegionMapEditor(bool silent) { } bool MainWindow::askToFixRegionMapEditor() { - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setText(QString("There was an error opening the region map data. Please see %1 for full error details.").arg(getLogPath())); - msgBox.setDetailedText(getMostRecentError()); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setDefaultButton(QMessageBox::Ok); + RecentErrorMessage msgBox(QStringLiteral("There was an error opening the region map data."), this); auto reconfigButton = msgBox.addButton("Reconfigure", QMessageBox::ActionRole); msgBox.exec(); if (msgBox.clickedButton() == reconfigButton) { @@ -3088,15 +3018,16 @@ bool MainWindow::closeProject() { return true; if (this->editor->project->hasUnsavedChanges()) { - QMessageBox::StandardButton result = QMessageBox::question( - this, QApplication::applicationName(), "The project has been modified, save changes?", - QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + QuestionMessage msgBox(QStringLiteral("The project has been modified, save changes?"), this); + msgBox.addButton(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Yes); - if (result == QMessageBox::Yes) { + auto reply = msgBox.exec(); + if (reply == QMessageBox::Yes) { editor->saveProject(); - } else if (result == QMessageBox::No) { + } else if (reply == QMessageBox::No) { logWarn("Closing project with unsaved changes."); - } else if (result == QMessageBox::Cancel) { + } else if (reply == QMessageBox::Cancel) { return false; } } diff --git a/src/project.cpp b/src/project.cpp index efc591e4..e0717b36 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -107,9 +107,14 @@ bool Project::load() { && readSongNames() && readMapGroups(); - initNewLayoutSettings(); - initNewMapSettings(); - applyParsedLimits(); + if (success) { + // No need to do this if something failed to load. + // (and in fact we shouldn't, because they contain + // assumptions that some things have loaded correctly). + initNewLayoutSettings(); + initNewMapSettings(); + applyParsedLimits(); + } return success; } diff --git a/src/ui/message.cpp b/src/ui/message.cpp new file mode 100644 index 00000000..d2d91f75 --- /dev/null +++ b/src/ui/message.cpp @@ -0,0 +1,78 @@ +#include "message.h" +#include "log.h" + +#include + +Message::Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent) : + QMessageBox(icon, QApplication::applicationName(), text, buttons, parent) +{ + setWindowModality(Qt::WindowModal); +} + +ErrorMessage::ErrorMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Critical, + message, + QMessageBox::Ok, + parent) +{ + setDefaultButton(QMessageBox::Ok); +} + +WarningMessage::WarningMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Warning, + message, + QMessageBox::Ok, + parent) +{ + setDefaultButton(QMessageBox::Ok); +} + +InfoMessage::InfoMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Information, + message, + QMessageBox::Close, + parent) +{ + setDefaultButton(QMessageBox::Close); +} + +QuestionMessage::QuestionMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Question, + message, + QMessageBox::No | QMessageBox::Yes, + parent) +{ + setDefaultButton(QMessageBox::No); +} + +RecentErrorMessage::RecentErrorMessage(const QString &message, QWidget *parent) : + ErrorMessage(message, parent) +{ + setInformativeText(QString("Please see %1 for full error details.").arg(getLogPath())); + setDetailedText(getMostRecentError()); +} + +int RecentErrorMessage::show(const QString &message, QWidget *parent) { + RecentErrorMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int ErrorMessage::show(const QString &message, QWidget *parent) { + ErrorMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int WarningMessage::show(const QString &message, QWidget *parent) { + WarningMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int QuestionMessage::show(const QString &message, QWidget *parent) { + QuestionMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int InfoMessage::show(const QString &message, QWidget *parent) { + InfoMessage msgBox(message, parent); + return msgBox.exec(); +}; From 40adedef346e9f818016b26c5a71e3bebde28021 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 23 Dec 2024 11:42:57 -0500 Subject: [PATCH 130/364] Fix editor's map/layout clearing if a map/layout fails to load --- src/editor.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index d0a122ec..c22068d2 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1242,10 +1242,10 @@ QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation void Editor::unsetMap() { // disconnect previous map's signals so they are not firing // multiple times if set again in the future - if (map) { - map->pruneEditHistory(); - map->disconnect(this); - for (auto connection : map->getConnections()) + if (this->map) { + this->map->pruneEditHistory(); + this->map->disconnect(this); + for (const auto &connection : this->map->getConnections()) disconnectMapConnection(connection); } clearMapConnections(); @@ -1258,13 +1258,12 @@ bool Editor::setMap(QString map_name) { return false; } - unsetMap(); - Map *loadedMap = project->loadMap(map_name); if (!loadedMap) { return false; } + unsetMap(); this->map = loadedMap; setLayout(map->layout()->id); @@ -1291,13 +1290,17 @@ bool Editor::setLayout(QString layoutId) { return false; } - this->layout = this->project->loadLayout(layoutId); + Layout *loadedLayout = this->project->loadLayout(layoutId); + if (!loadedLayout) { + return false; + } + this->layout = loadedLayout; if (!displayLayout()) { return false; } - editGroup.addStack(&layout->editHistory); + editGroup.addStack(&this->layout->editHistory); map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); From 3ca1ee1650792f297e23f1f1b86951340f40d587 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 28 Dec 2024 00:56:34 -0500 Subject: [PATCH 131/364] Remove config diff noise from hashed containers changing order --- include/config.h | 6 +++--- include/ui/projectsettingseditor.h | 2 +- src/config.cpp | 12 ++++++------ src/ui/projectsettingseditor.cpp | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/include/config.h b/include/config.h index 47bc2010..39ae4cdc 100644 --- a/include/config.h +++ b/include/config.h @@ -337,7 +337,7 @@ public: QString getEventIconPath(Event::Group group); void setPokemonIconPath(const QString &species, const QString &path); QString getPokemonIconPath(const QString &species); - QHash getPokemonIconPaths(); + QMap getPokemonIconPaths(); BaseGameVersion baseGameVersion; QString projectDir; @@ -374,7 +374,7 @@ public: QString collisionSheetPath; int collisionSheetWidth; int collisionSheetHeight; - QSet warpBehaviors; + QList warpBehaviors; protected: virtual QString getConfigFilepath() override; @@ -388,7 +388,7 @@ private: QMap identifiers; QMap filePaths; QMap eventIconPaths; - QHash pokemonIconPaths; + QMap pokemonIconPaths; }; extern ProjectConfig projectConfig; diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index a0a26982..e4a6ae94 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -36,7 +36,7 @@ private: bool projectNeedsReload = false; bool refreshing = false; const QString baseDir; - QHash editedPokemonIconPaths; + QMap editedPokemonIconPaths; QString prevIconSpecies; void initUi(); diff --git a/src/config.cpp b/src/config.cpp index c0c00194..da58ecde 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -16,7 +16,7 @@ #include #include -const QSet defaultWarpBehaviors_RSE = { +const QList defaultWarpBehaviors_RSE = { 0x0E, // MB_MOSSDEEP_GYM_WARP 0x0F, // MB_MT_PYRE_HOLE 0x1B, // MB_STAIRS_OUTSIDE_ABANDONED_SHIP @@ -47,7 +47,7 @@ const QSet defaultWarpBehaviors_RSE = { 0x9D, // MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN }; -const QSet defaultWarpBehaviors_FRLG = { +const QList defaultWarpBehaviors_FRLG = { 0x60, // MB_CAVE_DOOR 0x61, // MB_LADDER 0x62, // MB_EAST_ARROW_WARP @@ -773,9 +773,9 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "warp_behaviors") { this->warpBehaviors.clear(); value.remove(" "); - QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); + const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); for (auto s : behaviorList) - this->warpBehaviors.insert(getConfigUint32(key, s)); + this->warpBehaviors.append(getConfigUint32(key, s)); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -864,7 +864,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth)); map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight)); QStringList warpBehaviorStrs; - for (auto value : this->warpBehaviors) + for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); map.insert("warp_behaviors", warpBehaviorStrs.join(",")); @@ -1006,7 +1006,7 @@ QString ProjectConfig::getPokemonIconPath(const QString &species) { return this->pokemonIconPaths.value(species); } -QHash ProjectConfig::getPokemonIconPaths() { +QMap ProjectConfig::getPokemonIconPaths() { return this->pokemonIconPaths; } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 346591f4..2f5de651 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -475,7 +475,7 @@ void ProjectSettingsEditor::refresh() { // Set warp behaviors QStringList behaviorNames; - for (auto value : projectConfig.warpBehaviors) { + for (const auto &value : projectConfig.warpBehaviors) { if (project->metatileBehaviorMapInverse.contains(value)) behaviorNames.append(project->metatileBehaviorMapInverse.value(value)); } @@ -541,9 +541,9 @@ void ProjectSettingsEditor::save() { // Save warp behaviors projectConfig.warpBehaviors.clear(); - QStringList behaviorNames = this->getWarpBehaviorsList(); + const QStringList behaviorNames = this->getWarpBehaviorsList(); for (auto name : behaviorNames) - projectConfig.warpBehaviors.insert(project->metatileBehaviorMap.value(name)); + projectConfig.warpBehaviors.append(project->metatileBehaviorMap.value(name)); // Save border metatile IDs projectConfig.newMapBorderMetatileIds = this->getBorderMetatileIds(ui->checkBox_EnableCustomBorderSize->isChecked()); From 2ca703e65233e7f7043216fe597207b72ba1efc5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 28 Dec 2024 01:38:13 -0500 Subject: [PATCH 132/364] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5f4af6..e01f6773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add support for defining project values with `enum` where `#define` was expected. - Add button to enable editing map groups including renaming groups and rearranging the maps within them. - Add buttons to hide and show empty folders in each map tree view. +- Add a setting to specify the tile values to use for the unused metatile layer. ### Changed - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. @@ -63,6 +64,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix crash when saving tilesets with fewer palettes than the maximum. - Fix projects not opening on Windows if the project filepath contains certain characters. - Fix exported tile images containing garbage pixels after the end of the tiles. +- Fix fully transparent pixels rendering with the incorrect color. +- Fix the values for some config fields shuffling their order every save. ## [5.4.1] - 2024-03-21 ### Fixed From 90c904ecb92032f3c45ab1c3c9f27b086b47ab1f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 28 Dec 2024 01:51:40 -0500 Subject: [PATCH 133/364] Make it easier to edit MAPSEC names, Area -> Location --- forms/mainwindow.ui | 14 +- forms/mapheaderform.ui | 46 +++-- forms/newlocationdialog.ui | 89 +++++++++ ...{newnamedialog.ui => newmapgroupdialog.ui} | 6 +- forms/regionmapeditor.ui | 179 +++++++++--------- include/core/regionmap.h | 1 - include/mainwindow.h | 9 +- include/project.h | 14 +- include/ui/mapheaderform.h | 12 +- include/ui/maplistmodels.h | 11 +- include/ui/newlocationdialog.h | 34 ++++ .../{newnamedialog.h => newmapgroupdialog.h} | 20 +- include/ui/regionmapeditor.h | 6 +- porymap.pro | 9 +- src/mainwindow.cpp | 115 +++++------ src/project.cpp | 78 +++++--- src/ui/mapheaderform.cpp | 58 ++++-- src/ui/maplistmodels.cpp | 31 ++- src/ui/newlocationdialog.cpp | 79 ++++++++ src/ui/newmapdialog.cpp | 10 +- ...ewnamedialog.cpp => newmapgroupdialog.cpp} | 38 ++-- src/ui/projectsettingseditor.cpp | 2 +- src/ui/regionmapeditor.cpp | 61 ++---- src/ui/regionmapentriespixmapitem.cpp | 2 +- 24 files changed, 593 insertions(+), 331 deletions(-) create mode 100644 forms/newlocationdialog.ui rename forms/{newnamedialog.ui => newmapgroupdialog.ui} (94%) create mode 100644 include/ui/newlocationdialog.h rename include/ui/{newnamedialog.h => newmapgroupdialog.h} (51%) create mode 100644 src/ui/newlocationdialog.cpp rename src/ui/{newnamedialog.cpp => newmapgroupdialog.cpp} (57%) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index f403f198..39f59836 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -95,11 +95,11 @@
- + - Areas + Locations - + 0 @@ -116,10 +116,10 @@ 0 - + - + 0 @@ -3101,7 +3101,7 @@ Ctrl+T - + true @@ -3110,7 +3110,7 @@ :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - Sort by &Area + Sort by &Location diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui index 06541e2c..8faba290 100644 --- a/forms/mapheaderform.ui +++ b/forms/mapheaderform.ui @@ -57,14 +57,14 @@ - + Requires Flash - + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> @@ -74,14 +74,14 @@ - + Weather - + <html><head/><body><p>The default weather on this map.</p></body></html> @@ -94,14 +94,14 @@ - + 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> @@ -114,14 +114,14 @@ - + Battle Scene - + <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> @@ -134,14 +134,14 @@ - + Show Location Name - + <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> @@ -151,14 +151,14 @@ - + Allow Running - + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> @@ -168,14 +168,14 @@ - + Allow Biking - + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> @@ -185,14 +185,14 @@ - + Allow Dig & Escape Rope - + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> @@ -202,20 +202,30 @@ - + Floor Number - + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + + Location Name + + + + + + diff --git a/forms/newlocationdialog.ui b/forms/newlocationdialog.ui new file mode 100644 index 00000000..285e2704 --- /dev/null +++ b/forms/newlocationdialog.ui @@ -0,0 +1,89 @@ + + + NewLocationDialog + + + + 0 + 0 + 252 + 124 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Location ID + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Location Name + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/newnamedialog.ui b/forms/newmapgroupdialog.ui similarity index 94% rename from forms/newnamedialog.ui rename to forms/newmapgroupdialog.ui index fa6cb5ae..d475b665 100644 --- a/forms/newnamedialog.ui +++ b/forms/newmapgroupdialog.ui @@ -1,7 +1,7 @@ - NewNameDialog - + NewMapGroupDialog + 0 @@ -32,7 +32,7 @@ - Name + Map Group Name diff --git a/forms/regionmapeditor.ui b/forms/regionmapeditor.ui index 17cff63e..66787b7b 100644 --- a/forms/regionmapeditor.ui +++ b/forms/regionmapeditor.ui @@ -18,10 +18,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -46,14 +46,14 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContents - Qt::Horizontal + Qt::Orientation::Horizontal @@ -83,7 +83,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus 10 @@ -95,10 +95,10 @@ 30 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::NoTicks + QSlider::TickPosition::NoTicks 1 @@ -108,7 +108,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -123,7 +123,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal false @@ -160,7 +160,7 @@ 0 0 466 - 351 + 336 @@ -182,7 +182,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -207,17 +207,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag
- Qt::Vertical + Qt::Orientation::Vertical @@ -230,7 +230,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -243,7 +243,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -281,7 +281,7 @@ 0 0 466 - 351 + 336 @@ -315,17 +315,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag
- Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +338,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -351,7 +351,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -364,7 +364,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -402,7 +402,7 @@ 0 0 466 - 351 + 336 @@ -436,17 +436,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag
- Qt::Horizontal + Qt::Orientation::Horizontal @@ -459,7 +459,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -472,7 +472,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -485,7 +485,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -529,19 +529,19 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -552,7 +552,7 @@ 8 0 278 - 342 + 327 @@ -563,7 +563,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 0 @@ -583,7 +583,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -596,7 +596,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -618,20 +618,20 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - Qt::Horizontal + Qt::Orientation::Horizontal @@ -648,10 +648,10 @@ - QFrame::Panel + QFrame::Shape::Panel - QFrame::Sunken + QFrame::Shadow::Sunken @@ -681,7 +681,7 @@ - + 15 @@ -719,10 +719,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -747,7 +747,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -760,10 +760,10 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint - + 0 @@ -771,10 +771,10 @@ - <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 display when the player enters it.</p></body></html> + <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 + + false @@ -803,7 +803,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -816,10 +816,10 @@ - + - + @@ -862,10 +862,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -874,15 +874,15 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint - + - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -893,10 +893,10 @@ - + - + @@ -909,7 +909,7 @@ - + true @@ -922,25 +922,25 @@ <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 display when the player enters it.</p></body></html> - - true + + false - - + + Dimensions - + - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -951,10 +951,10 @@ - + - + @@ -966,7 +966,7 @@
- + Location @@ -980,16 +980,6 @@ - - - - Map Name - - - - - -
@@ -1002,7 +992,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1037,7 +1027,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus 10 @@ -1049,10 +1039,10 @@ 30 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::NoTicks + QSlider::TickPosition::NoTicks 1 @@ -1062,7 +1052,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1082,7 +1072,7 @@ 0 0 957 - 22 + 37 @@ -1180,9 +1170,14 @@ NoScrollComboBox - QWidget + QComboBox
noscrollcombobox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 822df79b..18362663 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -33,7 +33,6 @@ struct LayoutSquare struct MapSectionEntry { - QString name = ""; int x = 0; int y = 0; int width = 1; diff --git a/include/mainwindow.h b/include/mainwindow.h index 20789781..1ee612bc 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -190,6 +190,7 @@ private slots: void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); void onNewMapSectionCreated(const QString &idName); + void onMapSectionDisplayNameChanged(const QString &idName, const QString &displayName); void onNewLayoutCreated(Layout *layout); void onNewTilesetCreated(Tileset *tileset); void onMapLoaded(Map *map); @@ -311,8 +312,8 @@ private: QPointer groupListProxyModel = nullptr; QPointer mapGroupModel = nullptr; - QPointer areaListProxyModel = nullptr; - QPointer mapAreaModel = nullptr; + QPointer locationListProxyModel = nullptr; + QPointer mapLocationModel = nullptr; QPointer layoutListProxyModel = nullptr; QPointer layoutTreeModel = nullptr; @@ -357,7 +358,7 @@ private: void openNewLayoutDialog(); void openDuplicateLayoutDialog(const QString &layoutId); void openNewMapGroupDialog(); - void openNewAreaDialog(); + void openNewLocationDialog(); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -446,7 +447,7 @@ struct MapViewTab { struct MapListTab { enum { - Groups = 0, Areas, Layouts + Groups = 0, Locations, Layouts }; }; diff --git a/include/project.h b/include/project.h index fefa1f34..c29a11b4 100644 --- a/include/project.h +++ b/include/project.h @@ -62,6 +62,7 @@ public: QStringList bgEventFacingDirections; QStringList trainerTypes; QStringList globalScriptLabels; + QStringList mapSectionIdNamesSaveOrder; QStringList mapSectionIdNames; QMap regionMapEntries; QMap> metatileLabelsMap; @@ -79,7 +80,6 @@ public: int pokemonMaxLevel; int maxEncounterRate; bool wildEncountersLoaded; - bool saveEmptyMapsec; void set_root(QString); @@ -156,8 +156,10 @@ public: bool readSpeciesIconPaths(); QMap speciesToIconPath; - void addNewMapsec(const QString &name); - void removeMapsec(const QString &name); + void addNewMapsec(const QString &idName); + void removeMapsec(const QString &idName); + QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); } + void setMapsecDisplayName(const QString &idName, const QString &displayName); bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; @@ -253,8 +255,13 @@ public: bool calculateDefaultMapSize(); static int getMaxObjectEvents(); static QString getEmptyMapsecName(); + static QString getMapGroupPrefix(); + + static void numericalModeSort(QStringList &list); private: + QMap mapSectionDisplayNames; + void updateLayout(Layout *); void setNewLayoutBlockdata(Layout *layout); @@ -282,6 +289,7 @@ signals: void tilesetCreated(Tileset *newTileset); void mapGroupAdded(const QString &groupName); void mapSectionAdded(const QString &idName); + void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName); void mapSectionIdNamesChanged(const QStringList &idNames); void mapsExcluded(const QStringList &excludedMapNames); }; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 13e1b67d..8f9adda8 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -25,7 +25,7 @@ public: explicit MapHeaderForm(QWidget *parent = nullptr); ~MapHeaderForm(); - void init(const Project * project); + void setProject(Project * project, bool allowProjectChanges = true); void clear(); void setHeader(MapHeader *header); @@ -34,6 +34,7 @@ public: void setSong(const QString &song); void setLocation(const QString &location); + void setLocationName(const QString &locationName); void setRequiresFlash(bool requiresFlash); void setWeather(const QString &weather); void setType(const QString &type); @@ -46,6 +47,7 @@ public: QString song() const; QString location() const; + QString locationName() const; bool requiresFlash() const; QString weather() const; QString type() const; @@ -56,14 +58,18 @@ public: bool allowsEscaping() const; int floorNumber() const; - void setLocations(QStringList locations); - private: Ui::MapHeaderForm *ui; QPointer m_header = nullptr; + QPointer m_project = nullptr; + bool m_allowProjectChanges = true; + + void setLocations(const QStringList &locations); + void updateLocationName(); void onSongUpdated(const QString &song); void onLocationChanged(const QString &location); + void onLocationNameChanged(const QString &locationName); void onWeatherChanged(const QString &weather); void onTypeChanged(const QString &type); void onBattleSceneChanged(const QString &battleScene); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 53a79f4d..b973f438 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -68,7 +68,8 @@ public: virtual QModelIndex indexOf(const QString &itemName) const; virtual void removeItemAt(const QModelIndex &index); - virtual QStandardItem *getItem(const QModelIndex &index) const; + virtual QStandardItem *itemAt(const QModelIndex &index) const; + virtual QStandardItem *itemAt(const QString &itemName) const; virtual QVariant data(const QModelIndex &index, int role) const override; @@ -124,12 +125,14 @@ signals: -class MapAreaModel : public MapListModel { +class MapLocationModel : public MapListModel { Q_OBJECT public: - MapAreaModel(Project *project, QObject *parent = nullptr); - ~MapAreaModel() {} + MapLocationModel(Project *project, QObject *parent = nullptr); + ~MapLocationModel() {} + + QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override; protected: void removeItem(QStandardItem *item) override; diff --git a/include/ui/newlocationdialog.h b/include/ui/newlocationdialog.h new file mode 100644 index 00000000..33af5c9c --- /dev/null +++ b/include/ui/newlocationdialog.h @@ -0,0 +1,34 @@ +#ifndef NEWLOCATIONDIALOG_H +#define NEWLOCATIONDIALOG_H + +#include +#include +#include + +class Project; + +namespace Ui { +class NewLocationDialog; +} + +class NewLocationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewLocationDialog(Project *project = nullptr, QWidget *parent = nullptr); + ~NewLocationDialog(); + + virtual void accept() override; + +private: + Ui::NewLocationDialog *ui; + QPointer project = nullptr; + const QString namePrefix; + + bool validateIdName(bool allowEmpty = false); + void onIdNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWLOCATIONDIALOG_H diff --git a/include/ui/newnamedialog.h b/include/ui/newmapgroupdialog.h similarity index 51% rename from include/ui/newnamedialog.h rename to include/ui/newmapgroupdialog.h index 4abe02df..cbf8aa34 100644 --- a/include/ui/newnamedialog.h +++ b/include/ui/newmapgroupdialog.h @@ -1,5 +1,5 @@ -#ifndef NEWNAMEDIALOG_H -#define NEWNAMEDIALOG_H +#ifndef NEWMAPGROUPDIALOG_H +#define NEWMAPGROUPDIALOG_H /* This is a generic dialog for requesting a new unique name from the user. @@ -11,30 +11,26 @@ class Project; namespace Ui { -class NewNameDialog; +class NewMapGroupDialog; } -class NewNameDialog : public QDialog +class NewMapGroupDialog : public QDialog { Q_OBJECT public: - explicit NewNameDialog(const QString &label, const QString &prefix = "", Project *project = nullptr, QWidget *parent = nullptr); - ~NewNameDialog(); + explicit NewMapGroupDialog(Project *project = nullptr, QWidget *parent = nullptr); + ~NewMapGroupDialog(); virtual void accept() override; -signals: - void applied(const QString &newName); - private: - Ui::NewNameDialog *ui; + Ui::NewMapGroupDialog *ui; Project *project = nullptr; - const QString namePrefix; bool validateName(bool allowEmpty = false); void onNameChanged(const QString &name); void dialogButtonClicked(QAbstractButton *button); }; -#endif // NEWNAMEDIALOG_H +#endif // NEWMAPGROUPDIALOG_H diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index d5269548..9a838827 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -44,8 +44,6 @@ public: bool reconfigure(); - void setLocations(const QStringList &locations); - QObjectList shortcutableObjects() const; public slots: @@ -118,6 +116,7 @@ private: void displayRegionMapEntryOptions(); void updateRegionMapEntryOptions(QString); void setRegionMap(RegionMap *map); + void setLocations(const QStringList &locations); void restoreWindowState(); void closeEvent(QCloseEvent* event); @@ -135,7 +134,7 @@ private slots: void on_tabWidget_Region_Map_currentChanged(int); void on_pushButton_RM_Options_delete_clicked(); void on_comboBox_RM_ConnectedMap_textActivated(const QString &); - void on_comboBox_RM_Entry_MapSection_textActivated(const QString &); + void on_comboBox_RM_Entry_MapSection_currentTextChanged(const QString &); void on_comboBox_regionSelector_textActivated(const QString &); void on_comboBox_layoutLayer_textActivated(const QString &); void on_spinBox_RM_Entry_x_valueChanged(int); @@ -150,7 +149,6 @@ private slots: void on_checkBox_tileVFlip_stateChanged(int); void on_verticalSlider_Zoom_Map_Image_valueChanged(int); void on_verticalSlider_Zoom_Image_Tiles_valueChanged(int); - void on_lineEdit_RM_MapName_textEdited(const QString &); void onHoveredRegionMapTileChanged(int x, int y); void onHoveredRegionMapTileCleared(); void mouseEvent_region_map(QGraphicsSceneMouseEvent *event, RegionMapPixmapItem *item); diff --git a/porymap.pro b/porymap.pro index f97cd56a..8987b5cc 100644 --- a/porymap.pro +++ b/porymap.pro @@ -104,7 +104,8 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ - src/ui/newnamedialog.cpp \ + src/ui/newlocationdialog.cpp \ + src/ui/newmapgroupdialog.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -212,7 +213,8 @@ HEADERS += include/core/advancemapparser.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ - include/ui/newnamedialog.h \ + include/ui/newlocationdialog.h \ + include/ui/newmapgroupdialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ include/ui/montabwidget.h \ @@ -259,8 +261,9 @@ FORMS += forms/mainwindow.ui \ forms/maplisttoolbar.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ - forms/newnamedialog.ui \ + forms/newlocationdialog.ui \ forms/newmapconnectiondialog.ui \ + forms/newmapgroupdialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ forms/tileseteditor.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 086f114e..fbd7e409 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -25,7 +25,8 @@ #include "filedialog.h" #include "newmapdialog.h" #include "newtilesetdialog.h" -#include "newnamedialog.h" +#include "newmapgroupdialog.h" +#include "newlocationdialog.h" #include "message.h" #include @@ -425,32 +426,32 @@ void MainWindow::initMapList() { // Connect tool bars to lists ui->mapListToolBar_Groups->setList(ui->mapList); - ui->mapListToolBar_Areas->setList(ui->areaList); + ui->mapListToolBar_Locations->setList(ui->locationList); ui->mapListToolBar_Layouts->setList(ui->layoutList); // Left-clicking on items in the map list opens the corresponding map/layout. - connect(ui->mapList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); - connect(ui->areaList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); - connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->mapList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->locationList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); // Right-clicking on items in the map list brings up a context menu. - connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); - connect(ui->areaList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); - connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->locationList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); // Only the groups list allows reorganizing folder contents, editing folder names, etc. - ui->mapListToolBar_Areas->setEditsAllowedButtonVisible(false); + ui->mapListToolBar_Locations->setEditsAllowedButtonVisible(false); ui->mapListToolBar_Layouts->setEditsAllowedButtonVisible(false); // When map list search filter is cleared we want the current map/layout in the editor to be visible in the list. - connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); - connect(ui->mapListToolBar_Areas, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); - connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); + connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Locations, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); // Connect the "add folder" button in each of the map lists - connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewMapGroupDialog); - connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewAreaDialog); - connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); + connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewMapGroupDialog); + connect(ui->mapListToolBar_Locations, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLocationDialog); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); } @@ -628,7 +629,7 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::tilesetCreated, this, &MainWindow::onNewTilesetCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); - connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::setLocationComboBoxes); + connect(project, &Project::mapSectionDisplayNameChanged, this, &MainWindow::onMapSectionDisplayNameChanged); connect(project, &Project::mapsExcluded, this, &MainWindow::showMapsExcludedAlert); this->editor->setProject(project); @@ -1058,7 +1059,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te bool MainWindow::setProjectUI() { Project *project = editor->project; - this->mapHeaderForm->init(project); + this->mapHeaderForm->setProject(project); // Set up project comboboxes const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); @@ -1108,10 +1109,10 @@ bool MainWindow::setProjectUI() { this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this)); connect(this->mapGroupModel, &MapGroupModel::dragMoveCompleted, this->ui->mapList, &MapTree::removeSelected); - this->mapAreaModel = new MapAreaModel(editor->project); - this->areaListProxyModel = new FilterChildrenProxyModel(); - areaListProxyModel->setSourceModel(this->mapAreaModel); - ui->areaList->setModel(areaListProxyModel); + this->mapLocationModel = new MapLocationModel(editor->project); + this->locationListProxyModel = new FilterChildrenProxyModel(); + locationListProxyModel->setSourceModel(this->mapLocationModel); + ui->locationList->setModel(locationListProxyModel); this->layoutTreeModel = new LayoutTreeModel(editor->project); this->layoutListProxyModel = new FilterChildrenProxyModel(); @@ -1143,8 +1144,8 @@ void MainWindow::clearProjectUI() { // Clear map models delete this->mapGroupModel; delete this->groupListProxyModel; - delete this->mapAreaModel; - delete this->areaListProxyModel; + delete this->mapLocationModel; + delete this->locationListProxyModel; delete this->layoutTreeModel; delete this->layoutListProxyModel; resetMapListFilters(); @@ -1197,14 +1198,14 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QAction* addToFolderAction = nullptr; QAction* deleteFolderAction = nullptr; QAction* openItemAction = nullptr; - QAction* copyDisplayNameAction = nullptr; + QAction* copyListNameAction = nullptr; QAction* copyToolTipAction = nullptr; if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); menu.addSeparator(); - copyDisplayNameAction = menu.addAction("Copy Map Name"); + copyListNameAction = menu.addAction("Copy Map Name"); copyToolTipAction = menu.addAction("Copy Map ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { @@ -1219,18 +1220,19 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { deleteFolderAction = menu.addAction("Delete Map Group"); } else if (itemType == "map_section") { // Right-clicking on a MAPSEC folder - addToFolderAction = menu.addAction("Add New Map to Area"); + addToFolderAction = menu.addAction("Add New Map to Location"); menu.addSeparator(); - copyDisplayNameAction = menu.addAction("Copy Area Name"); + copyListNameAction = menu.addAction("Copy Location ID Name"); + copyToolTipAction = menu.addAction("Copy Location In-Game Name"); menu.addSeparator(); - deleteFolderAction = menu.addAction("Delete Area"); + deleteFolderAction = menu.addAction("Delete Location"); if (itemName == this->editor->project->getEmptyMapsecName()) deleteFolderAction->setEnabled(false); // Disallow deleting the default name } else if (itemType == "map_layout") { // Right-clicking on a map layout openItemAction = menu.addAction("Open Layout"); menu.addSeparator(); - copyDisplayNameAction = menu.addAction("Copy Layout Name"); + copyListNameAction = menu.addAction("Copy Layout Name"); copyToolTipAction = menu.addAction("Copy Layout ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { @@ -1263,8 +1265,8 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { openMapListItem(index); }); } - if (copyDisplayNameAction) { - connect(copyDisplayNameAction, &QAction::triggered, [this, selectedItem] { + if (copyListNameAction) { + connect(copyListNameAction, &QAction::triggered, [this, selectedItem] { setClipboardData(selectedItem->text()); }); } @@ -1278,18 +1280,6 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { menu.exec(QCursor::pos()); } -void MainWindow::openNewMapGroupDialog() { - auto dialog = new NewNameDialog("New Group Name", "", this->editor->project, this); - connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); - dialog->open(); -} - -void MainWindow::openNewAreaDialog() { - auto dialog = new NewNameDialog("New Area Name", projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix), this->editor->project, this); - connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); - dialog->open(); -} - void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); @@ -1301,7 +1291,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { // Add new map to the map lists this->mapGroupModel->insertMapItem(newMap->name(), groupName); - this->mapAreaModel->insertMapItem(newMap->name(), newMap->header()->location()); + this->mapLocationModel->insertMapItem(newMap->name(), newMap->header()->location()); this->layoutTreeModel->insertMapItem(newMap->name(), newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps @@ -1344,14 +1334,14 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { } void MainWindow::onNewMapSectionCreated(const QString &idName) { - // Add new map section to the Areas map list view - this->mapAreaModel->insertMapFolderItem(idName); + // Add new map section to the Locations map list view + this->mapLocationModel->insertMapFolderItem(idName); } -void MainWindow::setLocationComboBoxes(const QStringList &locations) { - this->mapHeaderForm->setLocations(locations); - if (this->regionMapEditor) - this->regionMapEditor->setLocations(locations); +void MainWindow::onMapSectionDisplayNameChanged(const QString &idName, const QString &displayName) { + // Update the tool tip in the map list that shows the MAPSEC's in-game name. + QStandardItem *item = this->mapLocationModel->itemAt(idName); + if (item) item->setToolTip(displayName); } void MainWindow::onNewTilesetCreated(Tileset *tileset) { @@ -1371,6 +1361,16 @@ void MainWindow::onNewTilesetCreated(Tileset *tileset) { } } +void MainWindow::openNewMapGroupDialog() { + auto dialog = new NewMapGroupDialog(this->editor->project, this); + dialog->open(); +} + +void MainWindow::openNewLocationDialog() { + auto dialog = new NewLocationDialog(this->editor->project, this); + dialog->open(); +} + void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); dialog->open(); @@ -1505,7 +1505,7 @@ void MainWindow::updateMapList() { activeItemName = this->editor->map->name(); } else { ui->mapList->clearSelection(); - ui->areaList->clearSelection(); + ui->locationList->clearSelection(); if (this->editor->layout) { activeItemName = this->editor->layout->id; @@ -1515,11 +1515,11 @@ void MainWindow::updateMapList() { } this->mapGroupModel->setActiveItem(activeItemName); - this->mapAreaModel->setActiveItem(activeItemName); + this->mapLocationModel->setActiveItem(activeItemName); this->layoutTreeModel->setActiveItem(activeItemName); this->groupListProxyModel->layoutChanged(); - this->areaListProxyModel->layoutChanged(); + this->locationListProxyModel->layoutChanged(); this->layoutListProxyModel->layoutChanged(); } @@ -1813,6 +1813,7 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) clickToolButtonFromEditAction(editor->objectEditAction); } else if (index == MainTab::Connections) { editor->setEditingConnections(); + ui->graphicsView_Connections->setFocus(); // Avoid opening tab with focus on something editable } else if (index == MainTab::WildPokemon) { editor->setEditingEncounters(); } @@ -2706,9 +2707,9 @@ void MainWindow::initTilesetEditor() { MapListToolBar* MainWindow::getCurrentMapListToolBar() { switch (ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: return ui->mapListToolBar_Groups; - case MapListTab::Areas: return ui->mapListToolBar_Areas; - case MapListTab::Layouts: return ui->mapListToolBar_Layouts; + case MapListTab::Groups: return ui->mapListToolBar_Groups; + case MapListTab::Locations: return ui->mapListToolBar_Locations; + case MapListTab::Layouts: return ui->mapListToolBar_Layouts; default: return nullptr; } } @@ -2724,7 +2725,7 @@ MapTree* MainWindow::getCurrentMapList() { // When the search filter is cleared the map lists will (if possible) display the currently-selected map/layout. void MainWindow::resetMapListFilters() { ui->mapListToolBar_Groups->clearFilter(); - ui->mapListToolBar_Areas->clearFilter(); + ui->mapListToolBar_Locations->clearFilter(); ui->mapListToolBar_Layouts->clearFilter(); } diff --git a/src/project.cpp b/src/project.cpp index 83fb39c8..9fff8710 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -727,16 +727,16 @@ void Project::saveRegionMapSections() { const QString emptyMapsecName = getEmptyMapsecName(); OrderedJson::array mapSectionArray; - for (const auto &idName : this->mapSectionIdNames) { - if (!this->saveEmptyMapsec && idName == emptyMapsecName) - continue; - + for (const auto &idName : this->mapSectionIdNamesSaveOrder) { OrderedJson::object mapSectionObj; mapSectionObj["id"] = idName; + if (this->mapSectionDisplayNames.contains(idName)) { + mapSectionObj["name"] = this->mapSectionDisplayNames.value(idName); + } + if (this->regionMapEntries.contains(idName)) { MapSectionEntry entry = this->regionMapEntries.value(idName); - mapSectionObj["name"] = entry.name; mapSectionObj["x"] = entry.x; mapSectionObj["y"] = entry.y; mapSectionObj["width"] = entry.width; @@ -2129,8 +2129,8 @@ bool Project::readTilesetLabels() { } } - this->primaryTilesetLabels.sort(); - this->secondaryTilesetLabels.sort(); + numericalModeSort(this->primaryTilesetLabels); + numericalModeSort(this->secondaryTilesetLabels); bool success = true; if (this->secondaryTilesetLabels.isEmpty()) { @@ -2332,8 +2332,9 @@ bool Project::readFieldmapMasks() { bool Project::readRegionMapSections() { this->mapSectionIdNames.clear(); + this->mapSectionIdNamesSaveOrder.clear(); + this->mapSectionDisplayNames.clear(); this->regionMapEntries.clear(); - this->saveEmptyMapsec = false; const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); @@ -2371,17 +2372,15 @@ bool Project::readRegionMapSections() { } this->mapSectionIdNames.append(idName); - if (idName == defaultName) { - // The default map section (MAPSEC_NONE) isn't normally present in the region map sections data file. - // We append this name to mapSectionIdNames ourselves if it isn't present. - // We need to record whether we found it in the data file, so that we can preserve the data when we save the file later. - this->saveEmptyMapsec = true; - } + this->mapSectionIdNamesSaveOrder.append(idName); + + if (mapSectionObj.contains("name")) + this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj["name"])); // Map sections may have additional data indicating their position on the region map. // If they have this data, we can add them to the region map entry list. bool hasRegionMapData = true; - static const QSet regionMapFieldNames = { "name", "x", "y", "width", "height" }; + static const QSet regionMapFieldNames = { "x", "y", "width", "height" }; for (auto fieldName : regionMapFieldNames) { if (!mapSectionObj.contains(fieldName)) { hasRegionMapData = false; @@ -2392,7 +2391,6 @@ bool Project::readRegionMapSections() { continue; MapSectionEntry entry; - entry.name = ParseUtil::jsonToQString(mapSectionObj["name"]); entry.x = ParseUtil::jsonToInt(mapSectionObj["x"]); entry.y = ParseUtil::jsonToInt(mapSectionObj["y"]); entry.width = ParseUtil::jsonToInt(mapSectionObj["width"]); @@ -2405,6 +2403,7 @@ bool Project::readRegionMapSections() { if (!this->mapSectionIdNames.contains(defaultName)) { this->mapSectionIdNames.append(defaultName); } + numericalModeSort(this->mapSectionIdNames); return true; } @@ -2413,29 +2412,47 @@ QString Project::getEmptyMapsecName() { return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); } +QString Project::getMapGroupPrefix() { + // We could expose this to users, but it's never enforced so it probably won't affect anyone. + return QStringLiteral("gMapGroup_"); +} + // This function assumes a valid and unique name -void Project::addNewMapsec(const QString &name) { - if (this->mapSectionIdNames.last() == getEmptyMapsecName()) { +void Project::addNewMapsec(const QString &idName) { + if (this->mapSectionIdNamesSaveOrder.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. - this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); + this->mapSectionIdNamesSaveOrder.insert(this->mapSectionIdNames.length() - 1, idName); } else { - this->mapSectionIdNames.append(name); + this->mapSectionIdNamesSaveOrder.append(idName); } + + this->mapSectionIdNames.append(idName); + numericalModeSort(this->mapSectionIdNames); + this->hasUnsavedDataChanges = true; - emit mapSectionAdded(name); + emit mapSectionAdded(idName); emit mapSectionIdNamesChanged(this->mapSectionIdNames); } -void Project::removeMapsec(const QString &name) { - if (!this->mapSectionIdNames.contains(name) || name == getEmptyMapsecName()) +void Project::removeMapsec(const QString &idName) { + if (!this->mapSectionIdNames.contains(idName) || idName == getEmptyMapsecName()) return; - this->mapSectionIdNames.removeOne(name); + this->mapSectionIdNames.removeOne(idName); + this->mapSectionIdNamesSaveOrder.removeOne(idName); this->hasUnsavedDataChanges = true; emit mapSectionIdNamesChanged(this->mapSectionIdNames); } +void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { + if (this->mapSectionDisplayNames[idName] == displayName) + return; + this->mapSectionDisplayNames[idName] = displayName; + this->hasUnsavedDataChanges = true; + emit mapSectionDisplayNameChanged(idName, displayName); +} + // Read the constants to preserve any "unused" heal locations when writing the file later bool Project::readHealLocationConstants() { this->healLocationNameToValue.clear(); @@ -2704,7 +2721,7 @@ bool Project::readSongNames() { // Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically. // The default song should be the first in the list, not the first alphabetically, so save that before sorting. this->defaultSong = this->songNames.value(0, "0"); - this->songNames.sort(); + numericalModeSort(this->songNames); return true; } @@ -3139,3 +3156,14 @@ bool Project::hasUnsavedChanges() { } return false; } + +// TODO: This belongs in a more general utility file, once we have one. +// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. +// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable +// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). +// We can use QCollator to sort these lists with better handling for numbers. +void Project::numericalModeSort(QStringList &list) { + QCollator collator; + collator.setNumericMode(true); + std::sort(list.begin(), list.end(), collator); +} diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 4bea0bef..09cb22c7 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -12,18 +12,24 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) ui->spinBox_FloorNumber->setMinimum(INT_MIN); ui->spinBox_FloorNumber->setMaximum(INT_MAX); - // When the UI is updated, sync those changes to the tracked MapHeader (if there is one) + // The layout for this UI keeps fields at their size hint, which is a little short for the line edit. + ui->lineEdit_LocationName->setMinimumWidth(ui->comboBox_Location->sizeHint().width()); + connect(ui->comboBox_Song, &QComboBox::currentTextChanged, this, &MapHeaderForm::onSongUpdated); connect(ui->comboBox_Location, &QComboBox::currentTextChanged, this, &MapHeaderForm::onLocationChanged); connect(ui->comboBox_Weather, &QComboBox::currentTextChanged, this, &MapHeaderForm::onWeatherChanged); connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged); connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged); + connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged); connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged); connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); + connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); + + connect(ui->lineEdit_LocationName, &QLineEdit::textChanged, this, &MapHeaderForm::onLocationNameChanged); } MapHeaderForm::~MapHeaderForm() @@ -31,35 +37,39 @@ MapHeaderForm::~MapHeaderForm() delete ui; } -void MapHeaderForm::init(const Project * project) { +void MapHeaderForm::setProject(Project * project, bool allowChanges) { clear(); - if (!project) + if (m_project) { + m_project->disconnect(this); + } + m_project = project; + m_allowProjectChanges = allowChanges; + + if (!m_project) return; // Populate combo boxes const QSignalBlocker b_Song(ui->comboBox_Song); ui->comboBox_Song->clear(); - ui->comboBox_Song->addItems(project->songNames); + ui->comboBox_Song->addItems(m_project->songNames); const QSignalBlocker b_Weather(ui->comboBox_Weather); ui->comboBox_Weather->clear(); - ui->comboBox_Weather->addItems(project->weatherNames); + ui->comboBox_Weather->addItems(m_project->weatherNames); const QSignalBlocker b_Type(ui->comboBox_Type); ui->comboBox_Type->clear(); - ui->comboBox_Type->addItems(project->mapTypes); + ui->comboBox_Type->addItems(m_project->mapTypes); const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); ui->comboBox_BattleScene->clear(); - ui->comboBox_BattleScene->addItems(project->mapBattleScenes); + ui->comboBox_BattleScene->addItems(m_project->mapBattleScenes); - QStringList locations = project->mapSectionIdNames; - locations.sort(); const QSignalBlocker b_Locations(ui->comboBox_Location); ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(locations); + ui->comboBox_Location->addItems(m_project->mapSectionIdNames); // Hide config-specific settings @@ -74,12 +84,13 @@ void MapHeaderForm::init(const Project * project) { bool floorNumEnabled = projectConfig.floorNumberEnabled; ui->spinBox_FloorNumber->setVisible(floorNumEnabled); ui->label_FloorNumber->setVisible(floorNumEnabled); + + // If the project changes any of the displayed data, update it accordingly. + connect(m_project, &Project::mapSectionIdNamesChanged, this, &MapHeaderForm::setLocations); + connect(m_project, &Project::mapSectionDisplayNameChanged, this, &MapHeaderForm::updateLocationName); } -// Unlike other combo boxes in the map header form, locations can be added or removed externally. -void MapHeaderForm::setLocations(QStringList locations) { - locations.sort(); - +void MapHeaderForm::setLocations(const QStringList &locations) { const QSignalBlocker b(ui->comboBox_Location); const QString before = ui->comboBox_Location->currentText(); ui->comboBox_Location->clear(); @@ -136,6 +147,7 @@ void MapHeaderForm::setHeaderData(const MapHeader &header) { setAllowsBiking(header.allowsBiking()); setAllowsEscaping(header.allowsEscaping()); setFloorNumber(header.floorNumber()); + updateLocationName(); } MapHeader MapHeaderForm::headerData() const { @@ -158,9 +170,14 @@ MapHeader MapHeaderForm::headerData() const { return header; } +void MapHeaderForm::updateLocationName() { + setLocationName(m_project ? m_project->getMapsecDisplayName(location()) : QString()); +} + // Set data in UI void MapHeaderForm::setSong(const QString &song) { ui->comboBox_Song->setCurrentText(song); } void MapHeaderForm::setLocation(const QString &location) { ui->comboBox_Location->setCurrentText(location); } +void MapHeaderForm::setLocationName(const QString &locationName) { ui->lineEdit_LocationName->setText(locationName); } void MapHeaderForm::setRequiresFlash(bool requiresFlash) { ui->checkBox_RequiresFlash->setChecked(requiresFlash); } void MapHeaderForm::setWeather(const QString &weather) { ui->comboBox_Weather->setCurrentText(weather); } void MapHeaderForm::setType(const QString &type) { ui->comboBox_Type->setCurrentText(type); } @@ -174,6 +191,7 @@ void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_F // Read data from UI QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); } QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); } +QString MapHeaderForm::locationName() const { return ui->lineEdit_LocationName->text(); } bool MapHeaderForm::requiresFlash() const { return ui->checkBox_RequiresFlash->isChecked(); } QString MapHeaderForm::weather() const { return ui->comboBox_Weather->currentText(); } QString MapHeaderForm::type() const { return ui->comboBox_Type->currentText(); } @@ -186,7 +204,6 @@ int MapHeaderForm::floorNumber() const { return ui->spinBox_FloorNumber-> // Send changes in UI to tracked MapHeader (if there is one) void MapHeaderForm::onSongUpdated(const QString &song) { if (m_header) m_header->setSong(song); } -void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); } void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); } void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); } void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); } @@ -196,3 +213,14 @@ void MapHeaderForm::onAllowRunningChanged(int selected) { if (m_hea void MapHeaderForm::onAllowBikingChanged(int selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } void MapHeaderForm::onAllowEscapingChanged(int selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } +void MapHeaderForm::onLocationChanged(const QString &location) { + if (m_header) m_header->setLocation(location); + updateLocationName(); +} +void MapHeaderForm::onLocationNameChanged(const QString &locationName) { + if (m_project && m_allowProjectChanges) { + // The location name is actually part of the project, not the map header. + // If the field is changed in the UI we can push these changes to the project. + m_project->setMapsecDisplayName(location(), locationName); + } +} diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a46dfc58..e74d0a93 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -56,7 +56,7 @@ MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemMod this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); } -QStandardItem *MapListModel::getItem(const QModelIndex &index) const { +QStandardItem *MapListModel::itemAt(const QModelIndex &index) const { if (index.isValid()) { QStandardItem *item = static_cast(index.internalPointer()); if (item) @@ -65,6 +65,13 @@ QStandardItem *MapListModel::getItem(const QModelIndex &index) const { return this->root; } +QStandardItem *MapListModel::itemAt(const QString &itemName) const { + QModelIndex index = this->indexOf(itemName); + if (!index.isValid()) + return nullptr; + return this->itemAt(index)->child(index.row(), index.column()); +} + QModelIndex MapListModel::indexOf(const QString &itemName) const { if (this->mapItems.contains(itemName)) return this->mapItems.value(itemName)->index(); @@ -76,7 +83,7 @@ QModelIndex MapListModel::indexOf(const QString &itemName) const { } void MapListModel::removeItemAt(const QModelIndex &index) { - QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); + QStandardItem *item = this->itemAt(index)->child(index.row(), index.column()); if (!item) return; @@ -153,7 +160,7 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); - const QStandardItem *item = this->getItem(index)->child(row, col); + const QStandardItem *item = this->itemAt(index)->child(row, col); const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); @@ -179,7 +186,7 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); - editor->setPlaceholderText("gMapGroup_"); + editor->setPlaceholderText(Project::getMapGroupPrefix()); editor->setValidator(new IdentifierValidator(parent)); editor->setFrame(false); return editor; @@ -388,13 +395,13 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); - const QStandardItem *item = this->getItem(index)->child(row, col); + const QStandardItem *item = this->itemAt(index)->child(row, col); const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); if (role == Qt::DisplayRole) { if (type == "map_name") { - return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name; + return QString("[%1.%2] ").arg(this->itemAt(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name; } else if (type == this->folderTypeName) { return name; @@ -417,7 +424,7 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int -MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(project, parent) { +MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListModel(project, parent) { this->folderTypeName = "map_section"; for (const auto &idName : this->project->mapSectionIdNames) { @@ -431,11 +438,17 @@ MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(pro sort(0, Qt::AscendingOrder); } -void MapAreaModel::removeItem(QStandardItem *item) { +void MapLocationModel::removeItem(QStandardItem *item) { this->project->removeMapsec(item->data(MapListUserRoles::NameRole).toString()); this->removeRow(item->row()); } +QStandardItem *MapLocationModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) { + folder = MapListModel::createMapFolderItem(folderName, folder); + folder->setToolTip(this->project->getMapsecDisplayName(folderName)); + return folder; +} + LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) { @@ -477,7 +490,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); - const QStandardItem *item = this->getItem(index)->child(row, col); + const QStandardItem *item = this->itemAt(index)->child(row, col); const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); diff --git a/src/ui/newlocationdialog.cpp b/src/ui/newlocationdialog.cpp new file mode 100644 index 00000000..d0586d99 --- /dev/null +++ b/src/ui/newlocationdialog.cpp @@ -0,0 +1,79 @@ +#include "newlocationdialog.h" +#include "ui_newlocationdialog.h" +#include "project.h" +#include "validator.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewLocationDialog::NewLocationDialog(Project* project, QWidget *parent) : + QDialog(parent), + ui(new Ui::NewLocationDialog), + namePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + this->project = project; + + ui->lineEdit_IdName->setValidator(new IdentifierValidator(namePrefix, this)); + ui->lineEdit_IdName->setText(namePrefix); + + connect(ui->lineEdit_IdName, &QLineEdit::textChanged, this, &NewLocationDialog::onIdNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLocationDialog::dialogButtonClicked); + + adjustSize(); +} + +NewLocationDialog::~NewLocationDialog() +{ + delete ui; +} + +void NewLocationDialog::onIdNameChanged(const QString &idName) { + validateIdName(true); + + // Extract a presumed display name from the ID name + QString displayName = idName; + if (displayName.startsWith(namePrefix)) + displayName.remove(0, namePrefix.length()); + displayName.replace("_", " "); + ui->lineEdit_DisplayName->setText(displayName); +} + +bool NewLocationDialog::validateIdName(bool allowEmpty) { + const QString name = ui->lineEdit_IdName->text(); + + QString errorText; + if (name.isEmpty() || name == namePrefix) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_IdName->text()); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_IdName->text()).arg(name); + } + + bool isValid = errorText.isEmpty(); + ui->label_IdNameError->setText(errorText); + ui->label_IdNameError->setVisible(!isValid); + ui->lineEdit_IdName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewLocationDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewLocationDialog::accept() { + if (!validateIdName()) + return; + + const QString idName = ui->lineEdit_IdName->text(); + const QString displayName = ui->lineEdit_DisplayName->text(); + + this->project->addNewMapsec(idName); + this->project->setMapsecDisplayName(idName, displayName); + + QDialog::accept(); +} diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 2d1fa46f..fda3d59a 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -53,7 +53,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); - this->headerForm->init(project); + this->headerForm->setProject(project, false); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -77,13 +77,13 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL case MapListTab::Groups: ui->comboBox_Group->setTextItem(mapListItem); break; - case MapListTab::Areas: + case MapListTab::Locations: this->headerForm->setLocation(mapListItem); break; case MapListTab::Layouts: // We specifically lock the layout ID because otherwise the setting would be overwritten when // the user changes the map name (which will normally automatically update the layout ID to match). - // For the Group/Area settings above we don't care if the user changes them afterwards. + // For the Group/Location settings above we don't care if the user changes them afterwards. ui->comboBox_LayoutID->setTextItem(mapListItem); ui->comboBox_LayoutID->setDisabled(true); break; @@ -252,5 +252,9 @@ void NewMapDialog::accept() { return; } ui->label_GenericError->setVisible(false); + + // If the location name field was changed, update the project for that too. + this->project->setMapsecDisplayName(this->headerForm->location(), this->headerForm->locationName()); + QDialog::accept(); } diff --git a/src/ui/newnamedialog.cpp b/src/ui/newmapgroupdialog.cpp similarity index 57% rename from src/ui/newnamedialog.cpp rename to src/ui/newmapgroupdialog.cpp index 842bf4b7..13461b68 100644 --- a/src/ui/newnamedialog.cpp +++ b/src/ui/newmapgroupdialog.cpp @@ -1,48 +1,43 @@ -#include "newnamedialog.h" -#include "ui_newnamedialog.h" +#include "newmapgroupdialog.h" +#include "ui_newmapgroupdialog.h" #include "project.h" -#include "imageexport.h" #include "validator.h" const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewNameDialog::NewNameDialog(const QString &label, const QString &prefix, Project* project, QWidget *parent) : +NewMapGroupDialog::NewMapGroupDialog(Project* project, QWidget *parent) : QDialog(parent), - ui(new Ui::NewNameDialog), - namePrefix(prefix) + ui(new Ui::NewMapGroupDialog) { setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); this->project = project; - if (!label.isEmpty()) - ui->label_Name->setText(label); + ui->lineEdit_Name->setValidator(new IdentifierValidator(this)); + ui->lineEdit_Name->setText(Project::getMapGroupPrefix()); - ui->lineEdit_Name->setValidator(new IdentifierValidator(namePrefix, this)); - ui->lineEdit_Name->setText(namePrefix); - - connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewNameDialog::onNameChanged); - connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewNameDialog::dialogButtonClicked); + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewMapGroupDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapGroupDialog::dialogButtonClicked); adjustSize(); } -NewNameDialog::~NewNameDialog() +NewMapGroupDialog::~NewMapGroupDialog() { delete ui; } -void NewNameDialog::onNameChanged(const QString &) { +void NewMapGroupDialog::onNameChanged(const QString &) { validateName(true); } -bool NewNameDialog::validateName(bool allowEmpty) { +bool NewMapGroupDialog::validateName(bool allowEmpty) { const QString name = ui->lineEdit_Name->text(); QString errorText; - if (name.isEmpty() || name == namePrefix) { + if (name.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (this->project && !this->project->isIdentifierUnique(name)) { + } else if (!this->project->isIdentifierUnique(name)) { errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } @@ -53,7 +48,7 @@ bool NewNameDialog::validateName(bool allowEmpty) { return isValid; } -void NewNameDialog::dialogButtonClicked(QAbstractButton *button) { +void NewMapGroupDialog::dialogButtonClicked(QAbstractButton *button) { auto role = ui->buttonBox->buttonRole(button); if (role == QDialogButtonBox::RejectRole){ reject(); @@ -62,10 +57,11 @@ void NewNameDialog::dialogButtonClicked(QAbstractButton *button) { } } -void NewNameDialog::accept() { +void NewMapGroupDialog::accept() { if (!validateName()) return; - emit applied(ui->lineEdit_Name->text()); + this->project->addNewMapGroup(ui->lineEdit_Name->text()); + QDialog::accept(); } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 9324c123..016480a7 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -293,7 +293,7 @@ QStringList ProjectSettingsEditor::getWarpBehaviorsList() { void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) { list.removeDuplicates(); - list.sort(); + Project::numericalModeSort(list); ui->textEdit_WarpBehaviors->setText(list.join("\n")); } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index f0415726..af0e220b 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -28,6 +28,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->setAttribute(Qt::WA_DeleteOnClose); this->ui->setupUi(this); this->project = project; + connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations); this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); this->initShortcuts(); this->restoreWindowState(); @@ -728,8 +729,12 @@ void RegionMapEditor::displayRegionMapEntryOptions() { void RegionMapEditor::updateRegionMapEntryOptions(QString section) { if (!this->region_map->layoutEnabled()) return; + const QSignalBlocker b_X(this->ui->spinBox_RM_Entry_x); + const QSignalBlocker b_Y(this->ui->spinBox_RM_Entry_y); + const QSignalBlocker b_W(this->ui->spinBox_RM_Entry_width); + const QSignalBlocker b_H(this->ui->spinBox_RM_Entry_height); + bool enabled = (section != this->region_map->default_map_section) && this->region_map_entries.contains(section); - this->ui->lineEdit_RM_MapName->setEnabled(enabled); this->ui->spinBox_RM_Entry_x->setEnabled(enabled); this->ui->spinBox_RM_Entry_y->setEnabled(enabled); this->ui->spinBox_RM_Entry_width->setEnabled(enabled); @@ -737,56 +742,31 @@ void RegionMapEditor::updateRegionMapEntryOptions(QString section) { this->ui->pushButton_entryActivate->setEnabled(section != this->region_map->default_map_section); this->ui->pushButton_entryActivate->setText(enabled ? "Remove" : "Add"); - this->ui->lineEdit_RM_MapName->blockSignals(true); - this->ui->spinBox_RM_Entry_x->blockSignals(true); - this->ui->spinBox_RM_Entry_y->blockSignals(true); - this->ui->spinBox_RM_Entry_width->blockSignals(true); - this->ui->spinBox_RM_Entry_height->blockSignals(true); - this->ui->comboBox_RM_Entry_MapSection->setCurrentText(section); this->activeEntry = section; this->region_map_entries_item->currentSection = section; MapSectionEntry entry = enabled ? this->region_map_entries[section] : MapSectionEntry(); - this->ui->lineEdit_RM_MapName->setText(entry.name); this->ui->spinBox_RM_Entry_x->setValue(entry.x); this->ui->spinBox_RM_Entry_y->setValue(entry.y); this->ui->spinBox_RM_Entry_width->setValue(entry.width); this->ui->spinBox_RM_Entry_height->setValue(entry.height); - - this->ui->lineEdit_RM_MapName->blockSignals(false); - this->ui->spinBox_RM_Entry_x->blockSignals(false); - this->ui->spinBox_RM_Entry_y->blockSignals(false); - this->ui->spinBox_RM_Entry_width->blockSignals(false); - this->ui->spinBox_RM_Entry_height->blockSignals(false); } void RegionMapEditor::on_pushButton_entryActivate_clicked() { QString section = this->ui->comboBox_RM_Entry_MapSection->currentText(); if (section == this->region_map->default_map_section) return; + MapSectionEntry oldEntry = this->region_map->getEntry(section); if (this->region_map_entries.contains(section)) { // disable - MapSectionEntry oldEntry = this->region_map->getEntry(section); - this->region_map->removeEntry(section); - MapSectionEntry newEntry = this->region_map->getEntry(section); - RemoveEntry *commit = new RemoveEntry(this->region_map, section, oldEntry, newEntry); - this->region_map->editHistory.push(commit); - updateRegionMapEntryOptions(section); - - this->ui->pushButton_entryActivate->setText("Add"); + this->region_map->editHistory.push(new RemoveEntry(this->region_map, section, oldEntry, MapSectionEntry())); } else { // enable - MapSectionEntry oldEntry = this->region_map->getEntry(section); - MapSectionEntry entry = MapSectionEntry(); - entry.valid = true; - this->region_map->setEntry(section, entry); - MapSectionEntry newEntry = this->region_map->getEntry(section); - AddEntry *commit = new AddEntry(this->region_map, section, oldEntry, newEntry); - this->region_map->editHistory.push(commit); - updateRegionMapEntryOptions(section); - - this->ui->pushButton_entryActivate->setText("Remove"); + MapSectionEntry newEntry = MapSectionEntry(); + newEntry.valid = true; + this->region_map->editHistory.push(new AddEntry(this->region_map, section, oldEntry, newEntry)); } + updateRegionMapEntryOptions(section); } void RegionMapEditor::displayRegionMapTileSelector() { @@ -932,7 +912,7 @@ void RegionMapEditor::on_tabWidget_Region_Map_currentChanged(int index) { break; case 2: this->ui->verticalSlider_Zoom_Image_Tiles->setVisible(false); - on_comboBox_RM_Entry_MapSection_textActivated(ui->comboBox_RM_Entry_MapSection->currentText()); + on_comboBox_RM_Entry_MapSection_currentTextChanged(ui->comboBox_RM_Entry_MapSection->currentText()); break; } } @@ -943,7 +923,7 @@ void RegionMapEditor::on_comboBox_RM_ConnectedMap_textActivated(const QString &m onRegionMapLayoutSelectedTileChanged(this->currIndex);// re-draw layout image } -void RegionMapEditor::on_comboBox_RM_Entry_MapSection_textActivated(const QString &text) { +void RegionMapEditor::on_comboBox_RM_Entry_MapSection_currentTextChanged(const QString &text) { this->activeEntry = text; this->region_map_entries_item->currentSection = text; updateRegionMapEntryOptions(text); @@ -1046,15 +1026,6 @@ void RegionMapEditor::on_spinBox_RM_LayoutHeight_valueChanged(int value) { } } -void RegionMapEditor::on_lineEdit_RM_MapName_textEdited(const QString &text) { - if (!this->region_map_entries.contains(activeEntry)) return; - MapSectionEntry oldEntry = this->region_map_entries[activeEntry]; - this->region_map_entries[activeEntry].name = text; - MapSectionEntry newEntry = this->region_map_entries[activeEntry]; - EditEntry *commit = new EditEntry(this->region_map, activeEntry, oldEntry, newEntry); - this->region_map->editHistory.push(commit); -} - void RegionMapEditor::on_pushButton_RM_Options_delete_clicked() { int index = this->region_map->tilemapToLayoutIndex(this->currIndex); QList oldLayout = this->region_map->getLayout(this->region_map->getLayer()); @@ -1147,7 +1118,7 @@ void RegionMapEditor::on_action_Swap_triggered() { connect(&buttonBox, &QDialogButtonBox::accepted, [&popup, &oldSecBox, &newSecBox, &beforeSection, &afterSection](){ beforeSection = oldSecBox->currentText(); afterSection = newSecBox->currentText(); - if (!beforeSection.isEmpty() && !afterSection.isEmpty()) { + if (!beforeSection.isEmpty() && !afterSection.isEmpty() && beforeSection != afterSection) { popup.accept(); } }); @@ -1186,7 +1157,7 @@ void RegionMapEditor::on_action_Replace_triggered() { connect(&buttonBox, &QDialogButtonBox::accepted, [&popup, &oldSecBox, &newSecBox, &beforeSection, &afterSection](){ beforeSection = oldSecBox->currentText(); afterSection = newSecBox->currentText(); - if (!beforeSection.isEmpty() && !afterSection.isEmpty()) { + if (!beforeSection.isEmpty() && !afterSection.isEmpty() && beforeSection != afterSection) { popup.accept(); } }); diff --git a/src/ui/regionmapentriespixmapitem.cpp b/src/ui/regionmapentriespixmapitem.cpp index ace3c38d..8ac888a0 100644 --- a/src/ui/regionmapentriespixmapitem.cpp +++ b/src/ui/regionmapentriespixmapitem.cpp @@ -8,7 +8,7 @@ void RegionMapEntriesPixmapItem::draw() { int entry_x, entry_y, entry_w, entry_h; - if (!entry.valid || entry.name == region_map->default_map_section) { + if (!entry.valid || currentSection == region_map->default_map_section) { entry_x = entry_y = 0; entry_w = entry_h = 1; } else { From f4c0cb2d2dacd4c2aed36a2b18b2768a6dc94568 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 13:24:09 -0500 Subject: [PATCH 134/364] Use placeholder text for new map/layout names --- forms/newlayoutdialog.ui | 6 ++++++ forms/newmapdialog.ui | 3 +++ include/project.h | 2 -- src/core/maplayout.cpp | 1 + src/project.cpp | 24 ++---------------------- src/ui/newlayoutdialog.cpp | 2 +- src/ui/newmapdialog.cpp | 5 ++--- 7 files changed, 15 insertions(+), 28 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index 42c7733c..980b5351 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -68,6 +68,9 @@ <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> + + LAYOUT_MY_NEW_LAYOUT + true @@ -92,6 +95,9 @@ <html><head/><body><p>The name of the new layout. The name cannot be the same as any other existing layout.</p></body></html> + + MyNewLayout + true diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 6b32810a..604800f8 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -185,6 +185,9 @@ <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + MyNewMap + true diff --git a/include/project.h b/include/project.h index c29a11b4..9fb4e2ed 100644 --- a/include/project.h +++ b/include/project.h @@ -133,8 +133,6 @@ public: NewMapSettings newMapSettings; Layout::Settings newLayoutSettings; - QString getNewMapName() const; - QString getNewLayoutName() const; void initNewMapSettings(); void initNewLayoutSettings(); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 26d4cb89..e2549897 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,6 +32,7 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } +// When we create a layout automatically for a new map we add this suffix to differentiate the layout name from the map name. QString Layout::defaultSuffix() { return "_Layout"; } diff --git a/src/project.cpp b/src/project.cpp index 9fff8710..f8ccd768 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1995,28 +1995,8 @@ QString Project::toUniqueIdentifier(const QString &identifier) const { return uniqueIdentifier; } -QString Project::getNewMapName() const { - // Ensure default name/ID doesn't already exist. - int suffix = 1; - QString newMapName; - do { - newMapName = QString("NewMap%1").arg(suffix++); - } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(Map::mapConstantFromName(newMapName))); - return newMapName; -} - -QString Project::getNewLayoutName() const { - // Ensure default name/ID doesn't already exist. - int suffix = 1; - QString newLayoutName; - do { - newLayoutName = QString("NewLayout%1").arg(suffix++); - } while (!isIdentifierUnique(newLayoutName) || !isIdentifierUnique(Layout::layoutConstantFromName(newLayoutName))); - return newLayoutName; -} - void Project::initNewMapSettings() { - this->newMapSettings.name = getNewMapName(); + this->newMapSettings.name = QString(); this->newMapSettings.group = this->groupNames.at(0); this->newMapSettings.canFlyTo = false; @@ -2044,7 +2024,7 @@ void Project::initNewMapSettings() { } void Project::initNewLayoutSettings() { - this->newLayoutSettings.name = getNewLayoutName(); + this->newLayoutSettings.name = QString(); this->newLayoutSettings.id = Layout::layoutConstantFromName(this->newLayoutSettings.name); this->newLayoutSettings.width = getDefaultMapDimension(); this->newLayoutSettings.height = getDefaultMapDimension(); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 226df897..537a3092 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -28,7 +28,7 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { settings->name = project->toUniqueIdentifier(this->layoutToCopy->name); } else { - settings->name = project->getNewLayoutName(); + settings->name = QString(); } // Generate a unique Layout constant settings->id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index fda3d59a..3ce36869 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -35,9 +35,8 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare settings->name = project->toUniqueIdentifier(this->mapToCopy->name()); } else { - // Not duplicating a map, get a generic new map name. - // The rest of the settings are preserved in the project between sessions. - settings->name = project->getNewMapName(); + // Clear the previously-used map name. The rest of the settings are preserved between sessions. + settings->name = QString(); } // Generate a unique Layout constant settings->layout.id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); From cfb6f70580b5f4ca743a46394852347c0a44967b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 14:38:49 -0500 Subject: [PATCH 135/364] Fix freeze when creating a new tileset --- include/ui/newtilesetdialog.h | 3 +++ src/mainwindow.cpp | 10 ++++++---- src/ui/newtilesetdialog.cpp | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index 374c85a7..2a5ad14e 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -21,6 +21,9 @@ public: virtual void accept() override; +signals: + void applied(Tileset *tileset); + private: Ui::NewTilesetDialog *ui; Project *project = nullptr; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fbd7e409..16d55c2d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1347,10 +1347,6 @@ void MainWindow::onMapSectionDisplayNameChanged(const QString &idName, const QSt void MainWindow::onNewTilesetCreated(Tileset *tileset) { logInfo(QString("Created a new tileset named %1.").arg(tileset->name)); - // Unlike creating a new map or layout (which immediately opens the new item) - // creating a new tileset has no visual feedback that it succeeded, so we show a message. - InfoMessage::show(QString("New tileset created at '%1'!").arg(tileset->getExpectedDir()), this); - // Refresh tileset combo boxes if (!tileset->is_secondary) { int index = this->editor->project->primaryTilesetLabels.indexOf(tileset->name); @@ -1409,6 +1405,12 @@ void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { void MainWindow::on_actionNew_Tileset_triggered() { auto dialog = new NewTilesetDialog(editor->project, this); + connect(dialog, &NewTilesetDialog::applied, [this](Tileset *tileset) { + // Unlike creating a new map or layout (which immediately opens the new item) + // creating a new tileset has no visual feedback that it succeeded, so we show a message. + // It's important that we do this after the dialog has closed (sheet modal dialogs on macOS don't seem to play nice together). + InfoMessage::show(QString("New tileset created at '%1'!").arg(tileset->getExpectedDir()), this); + }); dialog->open(); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 049b317d..968b3b88 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -76,4 +76,5 @@ void NewTilesetDialog::accept() { ui->label_GenericError->setVisible(false); QDialog::accept(); + emit applied(tileset); } From fe8f978a6bdce960794c99baefbaaca0961ceb5f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 14:46:49 -0500 Subject: [PATCH 136/364] Fix map symbol editing regression, save new map/layout dialog geometry --- include/config.h | 2 ++ src/config.cpp | 6 ++++++ src/ui/maplistmodels.cpp | 1 - src/ui/newlayoutdialog.cpp | 9 ++++++++- src/ui/newmapdialog.cpp | 2 ++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/config.h b/include/config.h index 083fea78..926e93c6 100644 --- a/include/config.h +++ b/include/config.h @@ -137,6 +137,8 @@ public: QVersionNumber lastUpdateCheckVersion; QMap rateLimitTimes; QByteArray wildMonChartGeometry; + QByteArray newMapDialogGeometry; + QByteArray newLayoutDialogGeometry; protected: virtual QString getConfigFilepath() override; diff --git a/src/config.cpp b/src/config.cpp index c25eb239..ce78d8b9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -349,6 +349,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->customScriptsEditorState = bytesFromString(value); } else if (key == "wild_mon_chart_geometry") { this->wildMonChartGeometry = bytesFromString(value); + } else if (key == "new_map_dialog_geometry") { + this->newMapDialogGeometry = bytesFromString(value); + } else if (key == "new_layout_dialog_geometry") { + this->newLayoutDialogGeometry = bytesFromString(value); } else if (key == "metatiles_zoom") { this->metatilesZoom = getConfigInteger(key, value, 10, 100, 30); } else if (key == "collision_zoom") { @@ -441,6 +445,8 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("custom_scripts_editor_geometry", stringFromByteArray(this->customScriptsEditorGeometry)); map.insert("custom_scripts_editor_state", stringFromByteArray(this->customScriptsEditorState)); map.insert("wild_mon_chart_geometry", stringFromByteArray(this->wildMonChartGeometry)); + map.insert("new_map_dialog_geometry", stringFromByteArray(this->newMapDialogGeometry)); + map.insert("new_layout_dialog_geometry", stringFromByteArray(this->newLayoutDialogGeometry)); map.insert("mirror_connecting_maps", this->mirrorConnectingMaps ? "1" : "0"); map.insert("show_dive_emerge_maps", this->showDiveEmergeMaps ? "1" : "0"); map.insert("dive_emerge_map_opacity", QString::number(this->diveEmergeMapOpacity)); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index e74d0a93..d08bb2cf 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -104,7 +104,6 @@ QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); - map->setEditable(this->editable); // Will override flags if necessary map->setToolTip(this->project->mapNamesToMapConstants.value(mapName)); this->mapItems.insert(mapName, map); return map; diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 537a3092..779a4694 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -42,11 +42,18 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked); refresh(); - adjustSize(); + + if (porymapConfig.newLayoutDialogGeometry.isEmpty()){ + // On first display resize to fit contents a little better + adjustSize(); + } else { + restoreGeometry(porymapConfig.newLayoutDialogGeometry); + } } NewLayoutDialog::~NewLayoutDialog() { + porymapConfig.newLayoutDialogGeometry = saveGeometry(); saveSettings(); delete ui; } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 3ce36869..521878c5 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -63,6 +63,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); + restoreGeometry(porymapConfig.newMapDialogGeometry); } // Adding new map to an existing map list folder. Initialize settings accordingly. @@ -91,6 +92,7 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL NewMapDialog::~NewMapDialog() { + porymapConfig.newMapDialogGeometry = saveGeometry(); saveSettings(); delete ui; } From 07e4d24b988174de16965d26bf0fe3641963ac80 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 15:19:21 -0500 Subject: [PATCH 137/364] Enforce layout settings for duplicate maps --- src/ui/newmapdialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 521878c5..d54e85be 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -213,6 +213,12 @@ void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) // Changing the layout ID to an existing layout updates the layout settings to match. const Layout *layout = this->project->mapLayouts.value(text); + if (!layout && this->mapToCopy) { + // When duplicating a map, if a new layout ID is specified the settings will be updated + // to match the layout of the map we're duplicating. + layout = this->mapToCopy->layout(); + } + if (layout) { ui->newLayoutForm->setSettings(layout->settings()); ui->newLayoutForm->setDisabled(true); From a00636260c823f1558f5c261b3bc25df005da185 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 15:29:11 -0500 Subject: [PATCH 138/364] Remove old layout suffix function --- src/core/maplayout.cpp | 5 ----- src/project.cpp | 2 +- src/ui/newmapdialog.cpp | 3 ++- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index e2549897..94e6c291 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,11 +32,6 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -// When we create a layout automatically for a new map we add this suffix to differentiate the layout name from the map name. -QString Layout::defaultSuffix() { - return "_Layout"; -} - 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])"); diff --git a/src/project.cpp b/src/project.cpp index f8ccd768..9b8025b2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2001,7 +2001,7 @@ void Project::initNewMapSettings() { this->newMapSettings.canFlyTo = false; this->newMapSettings.layout.folderName = this->newMapSettings.name; - this->newMapSettings.layout.name = QString("%1%2").arg(this->newMapSettings.name).arg(Layout::defaultSuffix()); + this->newMapSettings.layout.name = QString(); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); this->newMapSettings.layout.width = getDefaultMapDimension(); this->newMapSettings.layout.height = getDefaultMapDimension(); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d54e85be..caa1e839 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -124,12 +124,13 @@ void NewMapDialog::saveSettings() { settings->header = this->headerForm->headerData(); // This dialog doesn't give users the option to give new layouts a name, we generate one using the map name. + // We instead add a "_Layout" suffix to differentiate the layout name from the map name. // (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for // something the majority of users creating a map won't need. If they want to give a specific name to a layout // they can create the layout first, then create a new map that uses that layout.) const Layout *layout = this->project->mapLayouts.value(settings->layout.id); if (!layout) { - const QString newLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + const QString newLayoutName = settings->name + QStringLiteral("_Layout"); settings->layout.name = this->project->toUniqueIdentifier(newLayoutName); } else { // Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway. From 0cf7a45890c1e814b4099105a9f5b71cd6bcf169 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 15:49:16 -0500 Subject: [PATCH 139/364] Don't automatically create empty MAPSEC display names --- src/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index 9b8025b2..3abfcfbf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2426,7 +2426,7 @@ void Project::removeMapsec(const QString &idName) { } void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { - if (this->mapSectionDisplayNames[idName] == displayName) + if (this->mapSectionDisplayNames.value(idName) == displayName) return; this->mapSectionDisplayNames[idName] = displayName; this->hasUnsavedDataChanges = true; From de78a1172dda0ec56fbf437dbebe58fe7f366e58 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 23 Jan 2025 15:03:14 -0500 Subject: [PATCH 140/364] Update changelog --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e01f6773..66d26e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ## [Unreleased] ### Added -- Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access. - Add the ability to edit layouts with no corresponding map. +- Add ``Duplicate Map`` / ``Duplicate Layout`` options, accessible by right-clicking a map or layout in the map list. +- Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access. - Add a `Close Project` option - Add charts to the `Wild Pokémon` tab that show species and level distributions. - Add options for customizing the map grid under `View -> Grid Settings`. @@ -21,6 +22,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add a setting to specify the tile values to use for the unused metatile layer. ### Changed +- Redesigned the new map dialog, including better error checking and a collapsible section for header data. +- Map groups and ``MAPSEC`` names specified when creating a new map will be added automatically if they don't already exist. - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. - Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions. - A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening. @@ -30,6 +33,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - The triple-layer metatiles setting can now be set automatically using a project constant. - `Export Map Stitch Image` now shows a preview of the full image, not just the current map. - Maps and layouts were internally separated. +- Unrecognized map names in Event or Connections data will no longer be overwritten. +- Map names and ``MAP_NAME`` constants are no longer required to match. +- Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. @@ -66,6 +72,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix exported tile images containing garbage pixels after the end of the tiles. - Fix fully transparent pixels rendering with the incorrect color. - Fix the values for some config fields shuffling their order every save. +- Fix some problems with tileset detection when importing maps from AdvanceMap. +- Fix certain input fields allowing invalid identifiers, like names starting with numbers. ## [5.4.1] - 2024-03-21 ### Fixed From 3bbb81e436f361d68bde9da7b2c73a5d0643dffe Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 25 Jan 2025 21:50:45 -0500 Subject: [PATCH 141/364] Add search feature to wild pokemon tab --- docsrc/manual/project-files.rst | 1 + forms/mainwindow.ui | 25 +++-- forms/wildmonsearch.ui | 94 +++++++++++++++++ include/config.h | 1 + include/core/wildmoninfo.h | 15 ++- include/mainwindow.h | 4 + include/project.h | 2 + include/ui/encountertablemodel.h | 25 ++--- include/ui/montabwidget.h | 3 + include/ui/wildmonsearch.h | 47 +++++++++ porymap.pro | 9 +- resources/icons/magnifier.ico | Bin 0 -> 1912 bytes resources/images.qrc | 1 + src/config.cpp | 1 + src/core/wildmoninfo.cpp | 44 +++++++- src/editor.cpp | 2 +- src/mainwindow.cpp | 59 ++++++----- src/project.cpp | 51 ++++++++-- src/ui/encountertabledelegates.cpp | 26 +---- src/ui/encountertablemodel.cpp | 94 +++++++---------- src/ui/montabwidget.cpp | 13 ++- src/ui/wildmonsearch.cpp | 158 +++++++++++++++++++++++++++++ 22 files changed, 520 insertions(+), 155 deletions(-) create mode 100644 forms/wildmonsearch.ui create mode 100644 include/ui/wildmonsearch.h create mode 100755 resources/icons/magnifier.ico create mode 100644 src/ui/wildmonsearch.cpp diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 67bba635..fc720c22 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -122,6 +122,7 @@ In addition to these files, there are some specific symbol and macro names that ``define_map_section_prefix``, ``MAPSEC_``, expected prefix for location macro names ``define_map_section_empty``, ``NONE``, macro name after prefix for empty region map sections ``define_species_prefix``, ``SPECIES_``, expected prefix for species macro names + ``define_species_empty``, ``NONE``, macro name after prefix for the default species ``regex_behaviors``, ``\bMB_``, regex to find metatile behavior macro names ``regex_obj_event_gfx``, ``\bOBJ_EVENT_GFX_``, regex to find Object Event graphics ID macro names ``regex_items``, ``\bITEM_(?!(B_)?USE_)``, regex to find item macro names diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 39f59836..dfea9752 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1740,7 +1740,7 @@ 0 0 100 - 30 + 16
@@ -1834,7 +1834,7 @@ 0 0 100 - 30 + 16 @@ -1928,7 +1928,7 @@ 0 0 100 - 30 + 16 @@ -2028,7 +2028,7 @@ 0 0 100 - 30 + 16 @@ -2122,7 +2122,7 @@ 0 0 100 - 30 + 16 @@ -2700,8 +2700,8 @@ 0 0 - 100 - 30 + 204 + 16 @@ -2845,6 +2845,17 @@
+ + + + ... + + + + :/icons/magnifier.ico:/icons/magnifier.ico + + +
diff --git a/forms/wildmonsearch.ui b/forms/wildmonsearch.ui new file mode 100644 index 00000000..117f7242 --- /dev/null +++ b/forms/wildmonsearch.ui @@ -0,0 +1,94 @@ + + + WildMonSearch + + + + 0 + 0 + 547 + 329 + + + + Wild Pokémon Search + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + :/images/pokemon_icon_placeholder.png + + + Qt::AlignmentFlag::AlignCenter + + + + + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + + + + 4 + + + + + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
+ + + + +
diff --git a/include/config.h b/include/config.h index 926e93c6..502ee1ec 100644 --- a/include/config.h +++ b/include/config.h @@ -221,6 +221,7 @@ enum ProjectIdentifier { define_map_section_prefix, define_map_section_empty, define_species_prefix, + define_species_empty, regex_behaviors, regex_obj_event_gfx, regex_items, diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index d0aa29dc..3c94fb17 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -5,10 +5,14 @@ #include #include "orderedmap.h" -struct WildPokemon { - int minLevel = 5; - int maxLevel = 5; - QString species = "SPECIES_NONE"; // TODO: Use define_species_prefix +class WildPokemon { +public: + WildPokemon(); + WildPokemon(int minLevel, int maxLevel, const QString &species); + + int minLevel; + int maxLevel; + QString species; }; struct WildMonInfo { @@ -30,7 +34,8 @@ struct EncounterField { typedef QVector EncounterFields; void setDefaultEncounterRate(QString fieldName, int rate); -WildMonInfo getDefaultMonInfo(EncounterField field); +WildMonInfo getDefaultMonInfo(const EncounterField &field); +QVector getWildEncounterPercentages(const EncounterField &field); void combineEncounters(WildMonInfo &to, WildMonInfo from); #endif // GUARD_WILDMONINFO_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 1ee612bc..cf37a298 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -28,6 +28,7 @@ #include "gridsettings.h" #include "customscriptseditor.h" #include "wildmonchart.h" +#include "wildmonsearch.h" #include "updatepromoter.h" #include "aboutporymap.h" #include "mapheaderform.h" @@ -282,6 +283,7 @@ private slots: void on_pushButton_DeleteWildMonGroup_clicked(); void on_pushButton_SummaryChart_clicked(); void on_pushButton_ConfigureEncountersJSON_clicked(); + void on_toolButton_WildMonSearch_clicked(); void on_pushButton_CreatePrefab_clicked(); void on_spinBox_SelectedElevation_valueChanged(int elevation); void on_spinBox_SelectedCollision_valueChanged(int collision); @@ -294,6 +296,7 @@ private slots: void reloadScriptEngine(); void on_actionShow_Grid_triggered(); void on_actionGrid_Settings_triggered(); + void openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName); public: Ui::MainWindow *ui; @@ -321,6 +324,7 @@ private: QPointer networkAccessManager = nullptr; QPointer aboutWindow = nullptr; QPointer wildMonChart = nullptr; + QPointer wildMonSearch = nullptr; QAction *undoAction = nullptr; QAction *redoAction = nullptr; diff --git a/include/project.h b/include/project.h index 9fb4e2ed..aff38f03 100644 --- a/include/project.h +++ b/include/project.h @@ -152,6 +152,7 @@ public: QVector extraEncounterGroups; bool readSpeciesIconPaths(); + QPixmap getSpeciesIcon(const QString &species) const; QMap speciesToIconPath; void addNewMapsec(const QString &idName); @@ -238,6 +239,7 @@ public: static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); + static QString getEmptySpeciesName(); static int getNumTilesPrimary(); static int getNumTilesTotal(); static int getNumMetatilesPrimary(); diff --git a/include/ui/encountertablemodel.h b/include/ui/encountertablemodel.h index b2a39ad8..6fe54a91 100644 --- a/include/ui/encountertablemodel.h +++ b/include/ui/encountertablemodel.h @@ -14,7 +14,7 @@ class EncounterTableModel : public QAbstractTableModel { Q_OBJECT public: - EncounterTableModel(WildMonInfo monInfo, EncounterFields allFields, int fieldIndex, QObject *parent = nullptr); + EncounterTableModel(const WildMonInfo &monInfo, const EncounterField &field, QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; @@ -28,22 +28,17 @@ public: Slot, Group, Species, MinLevel, MaxLevel, EncounterChance, SlotRatio, EncounterRate, Count }; - WildMonInfo encounterData() const { return this->monInfo; } - EncounterField encounterField() const { return this->encounterFields.at(this->fieldIndex); } - QList percentages() const { return this->slotPercentages; } - void resize(int rows, int cols); + WildMonInfo encounterData() const { return m_monInfo; } + EncounterField encounterField() const { return m_encounterField; } + QList percentages() const { return m_slotPercentages; } private: - WildMonInfo monInfo; - EncounterFields encounterFields; - int fieldIndex; - - int numRows = 0; - int numCols = 0; - - QVector slotRatios; - QList groupNames; - QList slotPercentages; + int m_numRows = 0; + int m_numCols = 0; + WildMonInfo m_monInfo; + EncounterField m_encounterField; + QMap m_groupNames; + QList m_slotPercentages; signals: void edited(); diff --git a/include/ui/montabwidget.h b/include/ui/montabwidget.h index 6d916068..8292d28e 100644 --- a/include/ui/montabwidget.h +++ b/include/ui/montabwidget.h @@ -27,6 +27,8 @@ public: void copy(int index); void paste(int index); + void setCurrentField(const QString &fieldName); + public slots: void setTabActive(int index, bool active = true); void deactivateTab(int tabIndex); @@ -38,6 +40,7 @@ private: QVector activeTabs; QVector addDeleteTabButtons; QVector copyTabButtons; + QMap fieldNameToIndex; Editor *editor; }; diff --git a/include/ui/wildmonsearch.h b/include/ui/wildmonsearch.h new file mode 100644 index 00000000..49e04b57 --- /dev/null +++ b/include/ui/wildmonsearch.h @@ -0,0 +1,47 @@ +#ifndef WILDMONSEARCH_H +#define WILDMONSEARCH_H + +#include + +class Project; + +namespace Ui { +class WildMonSearch; +} + +class WildMonSearch : public QDialog +{ + Q_OBJECT + +public: + explicit WildMonSearch(Project *project, QWidget *parent = nullptr); + ~WildMonSearch(); + + void refresh(); + +signals: + void openWildMonTableRequested(const QString &mapName, const QString &groupName, const QString &fieldName); + +private: + struct RowData { + QString mapName; + QString groupName; + QString fieldName; + QString levelRange; + QString chance; + }; + + Ui::WildMonSearch *ui; + Project *const project; + QMap> percentageStrings; + QMap> resultsCache; + + void addTableEntry(const RowData &rowData); + QList search(const QString &species) const; + void updatePercentageStrings(); + void updateResults(const QString &species); + void cellDoubleClicked(int row, int column); + +}; + +#endif // WILDMONSEARCH_H diff --git a/porymap.pro b/porymap.pro index 8987b5cc..b6758c2e 100644 --- a/porymap.pro +++ b/porymap.pro @@ -139,7 +139,8 @@ SOURCES += src/core/advancemapparser.cpp \ src/log.cpp \ src/ui/uintspinbox.cpp \ src/ui/updatepromoter.cpp \ - src/ui/wildmonchart.cpp + src/ui/wildmonchart.cpp \ + src/ui/wildmonsearch.cpp HEADERS += include/core/advancemapparser.h \ include/core/block.h \ @@ -251,7 +252,8 @@ HEADERS += include/core/advancemapparser.h \ include/log.h \ include/ui/uintspinbox.h \ include/ui/updatepromoter.h \ - include/ui/wildmonchart.h + include/ui/wildmonchart.h \ + include/ui/wildmonsearch.h FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ @@ -281,7 +283,8 @@ FORMS += forms/mainwindow.ui \ forms/customscriptseditor.ui \ forms/customscriptslistitem.ui \ forms/updatepromoter.ui \ - forms/wildmonchart.ui + forms/wildmonchart.ui \ + forms/wildmonsearch.ui RESOURCES += \ resources/images.qrc \ diff --git a/resources/icons/magnifier.ico b/resources/icons/magnifier.ico new file mode 100755 index 0000000000000000000000000000000000000000..9cbc3618f5ad94df90ca1cc1d726a76c87687874 GIT binary patch literal 1912 zcmV-;2Z#8HP)RhC_%=of2~iV`G2#OyRH72DiQe4g{&Ce1iA4XnF?W*F$L##h`JC_X{LY!>48y?x^Wv`d z+4}Yk3+d(?}b7ky!ZZQjEx2F{nw_A+cSlj0Jv-U-9^nUt^OxhuR?x) z0m9=kJiB=h&W1JQ7nQ)BTLeMmV6$-q!b1*?;Ns9Z%y4q}@9L#+=VT!m9K{Elx1hJ@ zTTkcCFGn%~5Uk%~!@T)DOIn-F;A0UP5B~crIP%KSx}Y3OYFwx)ln{z*7*FV?4F;V! zQ!yQ92G8MvcXr~lHFuh8nGac(dkCPO{3fRYurU=LeeCf%uh;wf1NYy@1jaKT_90;} z!gI@R#636VL8PolV=BUmhDd@Sk~+ZCLY{}1;|3?#(RJuF+7?uzq@)zzA3f$NEb@GI z;)mn@sQ`GULU~2Srp1dEQfv*okDbK@nZwOB<(OOQ#E6{4sG^!dqnd#j#a2u+pinK? z?Hnq+r5H6tbR9iw`e448&q)ej6~H0bY`0aEmC<4qT|fHa%JraimK#BqHD#=*6pVt$ zsjL#rs2Jm9y1)|vnaHEkTZXQal(43P`C>kq?@RzBs%poy;$ni=aB^6IBP$zK1#Hh% zL@9WTg0rmSlY+;o5VIs30oWiCK=JefS{oxDDokJGllji12zf)-q0vJ60ZDS0l^&*o zlM|`nEU+0IDS#xM9fyNY9ZA{>UDKgz9KkrC(H1ZW=F>`W>QlhxfzB4tVo|h{e4JTq zvZ|Yzr)5uPFG)Ojo7EEtP*pjW2CWrM6?INWrZZ-uNZ1067TYKb>hqMnj+mN~J{6eG z6y+n4+#<;?2q}Q1s$1{2#c@M%mg$4}Vm>nk(4*tyeZDh;AT_b5R0L-e2tdK`coG4U zAWVC}Up6%iOEpHz6dMjCu(-@l0KmZDAms7LK2w$q01~H8{j{aO{}2q$h6ay;>28Vk z-v+;|(h<~2U?Wv*X2BUqQcp-wZ%9y~YV`T<7Ztb&VxEU0(&^Z@?*P8-?tU8?sK5Y} zu5Z5HAMghT4jwuT)!^}XO`Igoh6<7DLN}{iqrfZ}3!bFA<$`ASvdx`#9?S8fX~0^KwH}i?#9{m&wstE^DSDTO$ER%Eh@{&Y-je6 z-ygW$J98%T2x6`)3>QtnsP4pY+>K}=3z3)uLD`OcL&XAj7nCWK zdoymmbt$r(E>zbwtlzzB`=+Zwq>mw+T7g}=IyBx|}HLoMFmZ zpETmBDrm1cUVtG;IDF(gba(gQ`s-_{jk_R6W2mmFg$IYx^zP#LEo%<2j+mJ8g@i1d zYivE{emi#_M~)nU5>M!*Wu-iOi4m!j z@`4k~TIL|n{u^*=E&BEXO)qp`5rVtI|Ez}>lFgVktG2eWspTn`%Qc_%^~nIN!XuH$ z;qE=V-}CtfMqgRGuCu(V3WAi2o227dZ2uT&`4rSF2;v2T(60#bXK%%#r3QO1D?412jAkAq!fyblr<;--8$!r z_3SjVs%>xF)?4d7kBU5#P0d-Y2V#^JY0?8u!2}jOX<|JlAfSH>i&bqRtbe}`~LK(=5g(=35bWn;Om`>=suej!G989GDHbk yPWo#l*?CK)#6Jy?hRC*hOS0a=M00RJDa7Sg}Qvxjj0000icons/help.ico icons/link_broken.ico icons/link.ico + icons/magnifier.ico icons/map_edited.ico icons/map_opened.ico icons/map.ico diff --git a/src/config.cpp b/src/config.cpp index ce78d8b9..2356f7a9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -113,6 +113,7 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}}, {ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}}, {ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}}, + {ProjectIdentifier::define_species_empty, {"define_species_empty", "NONE"}}, // Regex {ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}}, {ProjectIdentifier::regex_obj_event_gfx, {"regex_obj_event_gfx", "\\bOBJ_EVENT_GFX_"}}, diff --git a/src/core/wildmoninfo.cpp b/src/core/wildmoninfo.cpp index ecad908f..5222b9e6 100644 --- a/src/core/wildmoninfo.cpp +++ b/src/core/wildmoninfo.cpp @@ -1,12 +1,22 @@ #include "wildmoninfo.h" #include "montabwidget.h" +#include "project.h" + +WildPokemon::WildPokemon(int minLevel, int maxLevel, const QString &species) + : minLevel(minLevel), + maxLevel(maxLevel), + species(species) +{} + +WildPokemon::WildPokemon() : WildPokemon(5, 5, Project::getEmptySpeciesName()) +{} QMap defaultEncounterRates; void setDefaultEncounterRate(QString fieldName, int rate) { defaultEncounterRates[fieldName] = rate; } -WildMonInfo getDefaultMonInfo(EncounterField field) { +WildMonInfo getDefaultMonInfo(const EncounterField &field) { WildMonInfo newInfo; newInfo.active = true; newInfo.encounterRate = defaultEncounterRates.value(field.name, 1); @@ -18,6 +28,38 @@ WildMonInfo getDefaultMonInfo(EncounterField field) { return newInfo; } +QVector getWildEncounterPercentages(const EncounterField &field) { + QVector percentages(field.encounterRates.size(), 0); + + if (!field.groups.empty()) { + // This encounter field is broken up into groups (e.g. for fishing rod types). + // Each group's percentages will be relative to the group total, not the overall total. + for (auto groupKeyPair : field.groups) { + int groupTotal = 0; + for (int slot : groupKeyPair.second) { + groupTotal += field.encounterRates.value(slot, 0); + } + if (groupTotal != 0) { + for (int slot : groupKeyPair.second) { + percentages[slot] = static_cast(field.encounterRates.value(slot, 0)) / static_cast(groupTotal); + } + } + } + } else { + // This encounter field has a single group, percentages are relative to the overall total. + int groupTotal = 0; + for (int chance : field.encounterRates) { + groupTotal += chance; + } + if (groupTotal != 0) { + for (int slot = 0; slot < percentages.count(); slot++) { + percentages[slot] = static_cast(field.encounterRates.value(slot, 0)) / static_cast(groupTotal); + } + } + } + return percentages; +} + void combineEncounters(WildMonInfo &to, WildMonInfo from) { to.encounterRate = from.encounterRate; diff --git a/src/editor.cpp b/src/editor.cpp index c22068d2..0b3350e3 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -302,7 +302,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { form.addRow(new QLabel("Group Base Label:"), lineEdit); lineEdit->setValidator(new IdentifierValidator(lineEdit)); connect(lineEdit, &QLineEdit::textChanged, [this, &lineEdit, &buttonBox](QString text){ - if (this->project->encounterGroupLabels.contains(text)) { + if (!this->project->isIdentifierUnique(text)) { lineEdit->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); buttonBox.button(QDialogButtonBox::Ok)->setDisabled(true); } else { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 16d55c2d..bf9a91e8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2537,6 +2537,25 @@ void MainWindow::on_pushButton_SummaryChart_clicked() { openSubWindow(this->wildMonChart); } +void MainWindow::on_toolButton_WildMonSearch_clicked() { + if (!this->wildMonSearch) { + this->wildMonSearch = new WildMonSearch(this->editor->project, this); + connect(this->wildMonSearch, &WildMonSearch::openWildMonTableRequested, this, &MainWindow::openWildMonTable); + connect(this->editor, &Editor::wildMonTableEdited, this->wildMonSearch, &WildMonSearch::refresh); + } + openSubWindow(this->wildMonSearch); +} + +void MainWindow::openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName) { + if (userSetMap(mapName)) { + // Switch to the correct main tab, wild encounter group, and wild encounter type tab. + on_mainTabBar_tabBarClicked(MainTab::WildPokemon); + ui->comboBox_EncounterGroupLabel->setCurrentText(groupName); + QWidget *w = ui->stackedWidget_WildMons->currentWidget(); + if (w) static_cast(w)->setCurrentField(fieldName); + } +} + void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() { editor->configureEncounterJSON(this); } @@ -2984,33 +3003,21 @@ void MainWindow::clearOverlay() { // delete is happening too late and some of the pointers haven't been cleared by the time we need them to, // so we nullify them all here anyway. bool MainWindow::closeSupplementaryWindows() { - if (this->tilesetEditor && !this->tilesetEditor->close()) - return false; - this->tilesetEditor = nullptr; + #define SAFE_CLOSE(window) \ + do { \ + if ((window) && !(window)->close()) \ + return false; \ + window = nullptr; \ + } while (0); - if (this->regionMapEditor && !this->regionMapEditor->close()) - return false; - this->regionMapEditor = nullptr; - - if (this->mapImageExporter && !this->mapImageExporter->close()) - return false; - this->mapImageExporter = nullptr; - - if (this->shortcutsEditor && !this->shortcutsEditor->close()) - return false; - this->shortcutsEditor = nullptr; - - if (this->preferenceEditor && !this->preferenceEditor->close()) - return false; - this->preferenceEditor = nullptr; - - if (this->customScriptsEditor && !this->customScriptsEditor->close()) - return false; - this->customScriptsEditor = nullptr; - - if (this->wildMonChart && !this->wildMonChart->close()) - return false; - this->wildMonChart = nullptr; + SAFE_CLOSE(this->tilesetEditor); + SAFE_CLOSE(this->regionMapEditor); + SAFE_CLOSE(this->mapImageExporter); + SAFE_CLOSE(this->shortcutsEditor); + SAFE_CLOSE(this->preferenceEditor); + SAFE_CLOSE(this->customScriptsEditor); + SAFE_CLOSE(this->wildMonChart); + SAFE_CLOSE(this->wildMonSearch); if (this->projectSettingsEditor) this->projectSettingsEditor->closeQuietly(); this->projectSettingsEditor = nullptr; diff --git a/src/project.cpp b/src/project.cpp index 3abfcfbf..712ee87f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1721,11 +1721,11 @@ bool Project::readWildMonData() { for (OrderedJson subObjectRef : wildMonObj["wild_encounter_groups"].array_items()) { OrderedJson::object subObject = subObjectRef.object_items(); if (!subObject["for_maps"].bool_value()) { - extraEncounterGroups.push_back(subObject); + this->extraEncounterGroups.push_back(subObject); continue; } - for (OrderedJson field : subObject["fields"].array_items()) { + for (const OrderedJson &field : subObject["fields"].array_items()) { EncounterField encounterField; OrderedJson::object fieldObj = field.object_items(); encounterField.name = fieldObj["type"].string_value(); @@ -1744,17 +1744,17 @@ bool Project::readWildMonData() { } } encounterRateFrequencyMaps.insert(encounterField.name, QMap()); - wildMonFields.append(encounterField); + this->wildMonFields.append(encounterField); } auto encounters = subObject["encounters"].array_items(); - for (auto encounter : encounters) { + for (const auto &encounter : encounters) { OrderedJson::object encounterObj = encounter.object_items(); QString mapConstant = encounterObj["map"].string_value(); WildPokemonHeader header; - for (EncounterField monField : wildMonFields) { + for (const EncounterField &monField : this->wildMonFields) { QString field = monField.name; if (!encounterObj[field].is_null()) { OrderedJson::object encounterFieldObj = encounterObj[field].object_items(); @@ -1776,8 +1776,8 @@ bool Project::readWildMonData() { } } } - wildMonData[mapConstant].insert({encounterObj["base_label"].string_value(), header}); - encounterGroupLabels.append(encounterObj["base_label"].string_value()); + this->wildMonData[mapConstant].insert({encounterObj["base_label"].string_value(), header}); + this->encounterGroupLabels.append(encounterObj["base_label"].string_value()); } } @@ -1975,6 +1975,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { } if (identifier == getEmptyMapDefineName()) return false; + if (this->encounterGroupLabels.contains(identifier)) + return false; return true; } @@ -2981,6 +2983,31 @@ bool Project::readSpeciesIconPaths() { return true; } +QPixmap Project::getSpeciesIcon(const QString &species) const { + QPixmap pixmap; + if (!QPixmapCache::find(species, &pixmap)) { + // Prefer path from config. If not present, use the path parsed from project files + QString path = projectConfig.getPokemonIconPath(species); + if (path.isEmpty()) { + path = this->speciesToIconPath.value(species); + } else { + path = Project::getExistingFilepath(path); + } + + QImage img(path); + if (img.isNull()) { + // No icon for this species, use placeholder + static const QPixmap placeholder = QPixmap(QStringLiteral(":images/pokemon_icon_placeholder.png")); + pixmap = placeholder; + } else { + img.setColor(0, qRgba(0, 0, 0, 0)); + pixmap = QPixmap::fromImage(img).copy(0, 0, 32, 32); + QPixmapCache::insert(species, pixmap); + } + } + return pixmap; +} + int Project::getNumTilesPrimary() { return Project::num_tiles_primary; @@ -3067,19 +3094,21 @@ int Project::getMaxObjectEvents() } QString Project::getEmptyMapDefineName() { - const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_empty); + return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_empty); } QString Project::getDynamicMapDefineName() { - const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); + return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); } QString Project::getDynamicMapName() { return projectConfig.getIdentifier(ProjectIdentifier::symbol_dynamic_map_name); } +QString Project::getEmptySpeciesName() { + return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty); +} + // If the provided filepath is an absolute path to an existing file, return filepath. // If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path. // Otherwise return empty string. diff --git a/src/ui/encountertabledelegates.cpp b/src/ui/encountertabledelegates.cpp index 230abc7b..4ffe9e0d 100644 --- a/src/ui/encountertabledelegates.cpp +++ b/src/ui/encountertabledelegates.cpp @@ -12,30 +12,8 @@ SpeciesComboDelegate::SpeciesComboDelegate(Project *project, QObject *parent) : } void SpeciesComboDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QString species = index.data(Qt::DisplayRole).toString(); - - QPixmap pm; - if (!QPixmapCache::find(species, &pm)) { - // Prefer path from config. If not present, use the path parsed from project files - QString path = projectConfig.getPokemonIconPath(species); - if (path.isEmpty()) { - path = this->project->speciesToIconPath.value(species); - } else { - path = Project::getExistingFilepath(path); - } - - QImage img(path); - if (img.isNull()) { - // No icon for this species, use placeholder - pm = QPixmap(":images/pokemon_icon_placeholder.png"); - } else { - img.setColor(0, qRgba(0, 0, 0, 0)); - pm = QPixmap::fromImage(img); - } - QPixmapCache::insert(species, pm); - } - QPixmap monIcon = pm.copy(0, 0, 32, 32); - + const QString species = index.data(Qt::DisplayRole).toString(); + const QPixmap monIcon = this->project->getSpeciesIcon(species); painter->drawText(QRect(option.rect.topLeft() + QPoint(36, 0), option.rect.bottomRight()), Qt::AlignLeft | Qt::AlignVCenter, species); painter->drawPixmap(QRect(option.rect.topLeft(), QSize(32, 32)), monIcon, monIcon.rect()); } diff --git a/src/ui/encountertablemodel.cpp b/src/ui/encountertablemodel.cpp index f7e73996..d3a6f2d3 100644 --- a/src/ui/encountertablemodel.cpp +++ b/src/ui/encountertablemodel.cpp @@ -3,52 +3,27 @@ -EncounterTableModel::EncounterTableModel(WildMonInfo info, EncounterFields fields, int index, QObject *parent) : QAbstractTableModel(parent) { - this->fieldIndex = index; - this->encounterFields = fields; - this->monInfo = info; - - this->resize(this->monInfo.wildPokemon.size(), ColumnType::Count); - - for (int r = 0; r < this->numRows; r++) { - this->groupNames.append(QString()); - this->slotPercentages.append(0.0); - this->slotRatios.append(fields[fieldIndex].encounterRates.value(r, 0)); - } - - if (!this->encounterFields[this->fieldIndex].groups.empty()) { - for (auto groupKeyPair : fields[fieldIndex].groups) { - int groupTotal = 0; - for (int i : groupKeyPair.second) { - this->groupNames[i] = groupKeyPair.first; - groupTotal += this->slotRatios[i]; - } - for (int i : groupKeyPair.second) { - this->slotPercentages[i] = static_cast(this->slotRatios[i]) / static_cast(groupTotal); - } - } - } else { - int groupTotal = 0; - for (int chance : this->encounterFields[this->fieldIndex].encounterRates) { - groupTotal += chance; - } - for (int i = 0; i < this->slotPercentages.count(); i++) { - this->slotPercentages[i] = static_cast(this->slotRatios[i]) / static_cast(groupTotal); +EncounterTableModel::EncounterTableModel(const WildMonInfo &info, const EncounterField &field, QObject *parent) + : QAbstractTableModel(parent), + m_numRows(info.wildPokemon.size()), + m_numCols(ColumnType::Count), + m_monInfo(info), + m_encounterField(field), + m_slotPercentages(getWildEncounterPercentages(field)) +{ + for (const auto &groupKeyPair : m_encounterField.groups) { + for (const auto &slot : groupKeyPair.second) { + m_groupNames.insert(slot, groupKeyPair.first); } } } -void EncounterTableModel::resize(int rows, int cols) { - this->numRows = rows; - this->numCols = cols; -} - int EncounterTableModel::rowCount(const QModelIndex &) const { - return this->numRows; + return m_numRows; } int EncounterTableModel::columnCount(const QModelIndex &) const { - return this->numCols; + return m_numCols; } QVariant EncounterTableModel::data(const QModelIndex &index, int role) const { @@ -61,26 +36,26 @@ QVariant EncounterTableModel::data(const QModelIndex &index, int role) const { return row; case ColumnType::Group: - return this->groupNames[row]; + return m_groupNames.value(row); case ColumnType::Species: - return this->monInfo.wildPokemon[row].species; + return m_monInfo.wildPokemon.value(row).species; case ColumnType::MinLevel: - return this->monInfo.wildPokemon[row].minLevel; + return m_monInfo.wildPokemon.value(row).minLevel; case ColumnType::MaxLevel: - return this->monInfo.wildPokemon[row].maxLevel; + return m_monInfo.wildPokemon.value(row).maxLevel; case ColumnType::EncounterChance: - return QString::number(this->slotPercentages[row] * 100.0, 'f', 2) + "%"; + return QString::number(m_slotPercentages.value(row, 0) * 100, 'f', 2) + "%"; case ColumnType::SlotRatio: - return this->slotRatios[row]; + return m_encounterField.encounterRates.value(row); case ColumnType::EncounterRate: if (row == 0) { - return this->monInfo.encounterRate; + return m_monInfo.encounterRate; } else { return QVariant(); } @@ -92,17 +67,17 @@ QVariant EncounterTableModel::data(const QModelIndex &index, int role) const { else if (role == Qt::EditRole) { switch (col) { case ColumnType::Species: - return this->monInfo.wildPokemon[row].species; + return m_monInfo.wildPokemon.value(row).species; case ColumnType::MinLevel: - return this->monInfo.wildPokemon[row].minLevel; + return m_monInfo.wildPokemon.value(row).minLevel; case ColumnType::MaxLevel: - return this->monInfo.wildPokemon[row].maxLevel; + return m_monInfo.wildPokemon.value(row).maxLevel; case ColumnType::EncounterRate: if (row == 0) { - return this->monInfo.encounterRate; + return m_monInfo.encounterRate; } else { return QVariant(); } @@ -149,12 +124,13 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu int row = index.row(); int col = index.column(); + auto wildMon = &m_monInfo.wildPokemon[row]; switch (col) { case ColumnType::Species: { QString species = value.toString(); - if (this->monInfo.wildPokemon[row].species != species) { - this->monInfo.wildPokemon[row].species = species; + if (wildMon->species != species) { + wildMon->species = species; emit edited(); } break; @@ -162,9 +138,9 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu case ColumnType::MinLevel: { int minLevel = value.toInt(); - if (this->monInfo.wildPokemon[row].minLevel != minLevel) { - this->monInfo.wildPokemon[row].minLevel = minLevel; - this->monInfo.wildPokemon[row].maxLevel = qMax(minLevel, this->monInfo.wildPokemon[row].maxLevel); + if (wildMon->minLevel != minLevel) { + wildMon->minLevel = minLevel; + wildMon->maxLevel = qMax(minLevel, wildMon->maxLevel); emit edited(); } break; @@ -172,9 +148,9 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu case ColumnType::MaxLevel: { int maxLevel = value.toInt(); - if (this->monInfo.wildPokemon[row].maxLevel != maxLevel) { - this->monInfo.wildPokemon[row].maxLevel = maxLevel; - this->monInfo.wildPokemon[row].minLevel = qMin(maxLevel, this->monInfo.wildPokemon[row].minLevel); + if (wildMon->maxLevel != maxLevel) { + wildMon->maxLevel = maxLevel; + wildMon->minLevel = qMin(maxLevel, wildMon->minLevel); emit edited(); } break; @@ -182,8 +158,8 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu case ColumnType::EncounterRate: { int encounterRate = value.toInt(); - if (this->monInfo.encounterRate != encounterRate) { - this->monInfo.encounterRate = encounterRate; + if (m_monInfo.encounterRate != encounterRate) { + m_monInfo.encounterRate = encounterRate; emit edited(); } break; diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp index 7b196c75..3a04c1b7 100644 --- a/src/ui/montabwidget.cpp +++ b/src/ui/montabwidget.cpp @@ -29,6 +29,8 @@ void MonTabWidget::populate() { copyTabButtons.resize(fields.size()); copyTabButtons.fill(nullptr); + this->fieldNameToIndex.clear(); + int index = 0; for (EncounterField field : fields) { QTableView *table = new QTableView(this); @@ -45,7 +47,7 @@ void MonTabWidget::populate() { connect(buttonCopy, &QPushButton::clicked, [=]() {actionCopyTab(index); }); copyTabButtons[index] = buttonCopy; this->tabBar()->setTabButton(index, QTabBar::LeftSide, buttonCopy); - + this->fieldNameToIndex.insert(field.name, index); index++; } } @@ -111,7 +113,7 @@ void MonTabWidget::deactivateTab(int tabIndex) { EncounterTableModel *oldModel = static_cast(speciesTable->model()); WildMonInfo monInfo = oldModel->encounterData(); monInfo.active = false; - EncounterTableModel *newModel = new EncounterTableModel(monInfo, editor->project->wildMonFields, tabIndex, this); + EncounterTableModel *newModel = new EncounterTableModel(monInfo, editor->project->wildMonFields[tabIndex], this); speciesTable->setModel(newModel); setTabActive(tabIndex, false); @@ -120,7 +122,7 @@ void MonTabWidget::deactivateTab(int tabIndex) { void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo) { QTableView *speciesTable = tableAt(tabIndex); - EncounterTableModel *model = new EncounterTableModel(monInfo, editor->project->wildMonFields, tabIndex, this); + EncounterTableModel *model = new EncounterTableModel(monInfo, editor->project->wildMonFields[tabIndex], this); connect(model, &EncounterTableModel::edited, editor, &Editor::saveEncounterTabData); connect(model, &EncounterTableModel::edited, editor, &Editor::wildMonTableEdited); speciesTable->setModel(model); @@ -167,3 +169,8 @@ void MonTabWidget::setTabActive(int index, bool active) { this->copyTabButtons[index]->show(); } } + +void MonTabWidget::setCurrentField(const QString &fieldName) { + int index = this->fieldNameToIndex.value(fieldName, -1); + if (index >= 0) setCurrentIndex(index); +} diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp new file mode 100644 index 00000000..684d2947 --- /dev/null +++ b/src/ui/wildmonsearch.cpp @@ -0,0 +1,158 @@ +#include "wildmonsearch.h" +#include "ui_wildmonsearch.h" +#include "project.h" + +enum ResultsColumn { + Group, + Field, + Level, + Chance, +}; + +enum ResultsDataRole { + MapName = Qt::UserRole, +}; + +WildMonSearch::WildMonSearch(Project *project, QWidget *parent) : + QDialog(parent), + ui(new Ui::WildMonSearch), + project(project) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + // Set up species combo box + ui->comboBox_Search->addItems(project->speciesToIconPath.keys()); + ui->comboBox_Search->setCurrentText(QString()); + ui->comboBox_Search->lineEdit()->setPlaceholderText(Project::getEmptySpeciesName()); + connect(ui->comboBox_Search, &QComboBox::currentTextChanged, this, &WildMonSearch::updateResults); + + // Set up table header + static const QStringList labels = {"Group", "Field", "Level", "Chance"}; + ui->table_Results->setHorizontalHeaderLabels(labels); + ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Group, QHeaderView::Stretch); + ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Field, QHeaderView::ResizeToContents); + ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Level, QHeaderView::ResizeToContents); + ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Chance, QHeaderView::ResizeToContents); + + // Table is read-only + ui->table_Results->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->table_Results->setSelectionMode(QAbstractItemView::NoSelection); + + connect(ui->table_Results, &QTableWidget::cellDoubleClicked, this, &WildMonSearch::cellDoubleClicked); + + refresh(); +} + +WildMonSearch::~WildMonSearch() { + delete ui; +} + +void WildMonSearch::refresh() { + this->resultsCache.clear(); + updatePercentageStrings(); + updateResults(ui->comboBox_Search->currentText()); +} + +void WildMonSearch::addTableEntry(const RowData &rowData) { + int row = ui->table_Results->rowCount(); + ui->table_Results->insertRow(row); + + auto groupItem = new QTableWidgetItem(rowData.groupName); + groupItem->setData(ResultsDataRole::MapName, rowData.mapName); + + ui->table_Results->setItem(row, ResultsColumn::Group, groupItem); + ui->table_Results->setItem(row, ResultsColumn::Field, new QTableWidgetItem(rowData.fieldName)); + ui->table_Results->setItem(row, ResultsColumn::Level, new QTableWidgetItem(rowData.levelRange)); + ui->table_Results->setItem(row, ResultsColumn::Chance, new QTableWidgetItem(rowData.chance)); +} + +QList WildMonSearch::search(const QString &species) const { + QList results; + for (const auto &keyPair : this->project->wildMonData) { + QString mapConstant = keyPair.first; + for (const auto &grouplLabelPair : this->project->wildMonData[mapConstant]) { + QString groupName = grouplLabelPair.first; + WildPokemonHeader encounterHeader = this->project->wildMonData[mapConstant][groupName]; + for (const auto &fieldNamePair : encounterHeader.wildMons) { + QString fieldName = fieldNamePair.first; + WildMonInfo monInfo = encounterHeader.wildMons[fieldName]; + for (int slot = 0; slot < monInfo.wildPokemon.length(); slot++) { + const WildPokemon wildMon = monInfo.wildPokemon.at(slot); + if (wildMon.species == species) { + RowData rowData; + rowData.groupName = groupName; + rowData.fieldName = fieldName; + rowData.mapName = this->project->mapConstantsToMapNames.value(mapConstant, mapConstant); + + // If min and max level are the same display a single number, otherwise display a level range. + rowData.levelRange = (wildMon.minLevel == wildMon.maxLevel) ? QString::number(wildMon.minLevel) + : QString("%1-%2").arg(wildMon.minLevel).arg(wildMon.maxLevel); + rowData.chance = this->percentageStrings[fieldName][slot]; + results.append(rowData); + } + } + } + } + } + return results; +} + +void WildMonSearch::updatePercentageStrings() { + this->percentageStrings.clear(); + for (const EncounterField &monField : this->project->wildMonFields) { + QMap slotToPercentString; + auto percentages = getWildEncounterPercentages(monField); + for (int i = 0; i < percentages.length(); i++) { + slotToPercentString.insert(i, QString::number(percentages.at(i) * 100, 'f', 2) + "%"); + } + this->percentageStrings[monField.name] = slotToPercentString; + } +} + +void WildMonSearch::updateResults(const QString &species) { + ui->speciesIcon->setPixmap(this->project->getSpeciesIcon(species)); + + ui->table_Results->clearContents(); + ui->table_Results->setRowCount(0); + + // Note: Per Qt docs, sorting should be disabled while populating the table to avoid it interfering with insertion order. + ui->table_Results->setSortingEnabled(false); + + if (ui->comboBox_Search->findText(species) < 0) + return; // Not a species name, no need to search wild encounter data. + + const QList results = this->resultsCache.value(species, search(species)); + if (results.isEmpty()) { + static const RowData noResults = { + .groupName = QStringLiteral("Species not found."), + .fieldName = QStringLiteral("--"), + .levelRange = QStringLiteral("--"), + .chance = QStringLiteral("--"), + }; + addTableEntry(noResults); + } else { + for (const auto &entry : results) { + addTableEntry(entry); + } + } + + // TODO: This does a lexical sort... We might need custom item delegates to get proper numerical sorting in this table. + ui->table_Results->setSortingEnabled(true); + + this->resultsCache.insert(species, results); +} + +// Double-clicking row data opens the corresponding map/table on the Wild Pokémon tab. +void WildMonSearch::cellDoubleClicked(int row, int) { + auto groupItem = ui->table_Results->item(row, ResultsColumn::Group); + auto fieldItem = ui->table_Results->item(row, ResultsColumn::Field); + if (!groupItem || !fieldItem) + return; + + const QString mapName = groupItem->data(ResultsDataRole::MapName).toString(); + if (mapName.isEmpty()) + return; + + emit openWildMonTableRequested(mapName, groupItem->text(), fieldItem->text()); +} From 5a6d5ea929737bb0c68fb07e32af4beb560758ee Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 29 Jan 2025 13:48:03 -0500 Subject: [PATCH 142/364] Fix some issues with map connections after layout split --- include/core/map.h | 2 +- include/editor.h | 5 +- include/ui/connectionpixmapitem.h | 14 +++--- include/ui/connectionslistitem.h | 3 +- src/core/map.cpp | 2 +- src/editor.cpp | 83 ++++--------------------------- src/ui/connectionpixmapitem.cpp | 43 ++++++++++------ src/ui/connectionslistitem.cpp | 5 ++ 8 files changed, 57 insertions(+), 100 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 9cc2a69b..b223536d 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -91,7 +91,7 @@ public: void removeConnection(MapConnection *); void addConnection(MapConnection *); void loadConnection(MapConnection *); - QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr); + QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr) const; QPixmap renderConnection(const QString &direction, Layout *fromLayout = nullptr); QUndoStack* editHistory() const { return m_editHistory; } diff --git a/include/editor.h b/include/editor.h index f5e94d28..434415e9 100644 --- a/include/editor.h +++ b/include/editor.h @@ -135,7 +135,7 @@ public: QList borderItems; QGraphicsItemGroup *mapGrid = nullptr; - MapRuler *map_ruler = nullptr; + QPointer map_ruler = nullptr; MovableRect *playerViewRect = nullptr; CursorTileRect *cursorMapTileRect = nullptr; @@ -221,10 +221,7 @@ private: void clearMapGrid(); void clearWildMonTables(); void updateBorderVisibility(); - void disconnectMapConnection(MapConnection *connection); - QPoint getConnectionOrigin(MapConnection *connection); void removeConnectionPixmap(MapConnection *connection); - void updateConnectionPixmap(ConnectionPixmapItem *connectionItem); void displayConnection(MapConnection *connection); void displayDivingConnection(MapConnection *connection); void setDivingMapName(QString mapName, QString direction); diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 183f2d79..26b83aa6 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -10,22 +10,20 @@ class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - ConnectionPixmapItem(MapConnection* connection, int originX, int originY); - ConnectionPixmapItem(MapConnection* connection, QPoint origin); + ConnectionPixmapItem(MapConnection* connection); const QPointer connection; - void setOrigin(int x, int y); - void setOrigin(QPoint pos); - void setEditable(bool editable); bool getEditable(); void setSelected(bool selected); - void updatePos(); void render(bool ignoreCache = false); +signals: + void positionChanged(qreal x, qreal y); + private: QPixmap basePixmap; qreal originX; @@ -36,6 +34,10 @@ private: static const int mWidth = 16; static const int mHeight = 16; + void updatePos(); + void updateOrigin(); + void refresh(); + protected: virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index 7ba6a9d8..b63922a9 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -23,7 +23,6 @@ public: explicit ConnectionsListItem(QWidget *parent, MapConnection *connection, const QStringList &mapNames); ~ConnectionsListItem(); - void updateUI(); void setSelected(bool selected); private: @@ -33,6 +32,8 @@ private: bool isSelected = false; unsigned actionId = 0; + void updateUI(); + protected: virtual void mousePressEvent(QMouseEvent*) override; virtual void focusInEvent(QFocusEvent*) override; diff --git a/src/core/map.cpp b/src/core/map.cpp index a42faa27..aac20138 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -90,7 +90,7 @@ int Map::getBorderHeight() const { // Get the portion of the map that can be rendered when rendered as a map connection. // Cardinal connections render the nearest segment of their map and within the bounds of the border draw distance, // Dive/Emerge connections are rendered normally within the bounds of their parent map. -QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) { +QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) const { int x = 0, y = 0; int w = getWidth(), h = getHeight(); diff --git a/src/editor.cpp b/src/editor.cpp index c22068d2..9dd9d190 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -779,14 +779,6 @@ void Editor::updateEncounterFields(EncounterFields newFields) { project->wildMonFields = newFields; } -void Editor::disconnectMapConnection(MapConnection *connection) { - // Disconnect MapConnection's signals used by the display. - // It'd be nice if we could just 'connection->disconnect(this)' but that doesn't account for lambda functions. - QObject::disconnect(connection, &MapConnection::targetMapNameChanged, nullptr, nullptr); - QObject::disconnect(connection, &MapConnection::directionChanged, nullptr, nullptr); - QObject::disconnect(connection, &MapConnection::offsetChanged, nullptr, nullptr); -} - void Editor::displayConnection(MapConnection *connection) { if (!connection) return; @@ -797,13 +789,13 @@ void Editor::displayConnection(MapConnection *connection) { } // Create connection image - ConnectionPixmapItem *pixmapItem = new ConnectionPixmapItem(connection, getConnectionOrigin(connection)); - pixmapItem->render(); + auto pixmapItem = new ConnectionPixmapItem(connection); scene->addItem(pixmapItem); maskNonVisibleConnectionTiles(); + connect(pixmapItem, &ConnectionPixmapItem::positionChanged, this, &Editor::maskNonVisibleConnectionTiles); // Create item for the list panel - ConnectionsListItem *listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, pixmapItem->connection, project->mapNames); + auto listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, pixmapItem->connection, project->mapNames); ui->layout_ConnectionsList->insertWidget(ui->layout_ConnectionsList->count() - 1, listItem); // Insert above the vertical spacer // Double clicking the pixmap or clicking the list item's map button opens the connected map @@ -822,25 +814,6 @@ void Editor::displayConnection(MapConnection *connection) { setSelectedConnectionItem(pixmapItem); }); - // Sync edits to 'offset' between the list UI and the pixmap - connect(connection, &MapConnection::offsetChanged, [=](int, int) { - listItem->updateUI(); - pixmapItem->updatePos(); - maskNonVisibleConnectionTiles(); - }); - - // Sync edits to 'direction' between the list UI and the pixmap - connect(connection, &MapConnection::directionChanged, [=](QString, QString) { - listItem->updateUI(); - updateConnectionPixmap(pixmapItem); - }); - - // Sync edits to 'map' between the list UI and the pixmap - connect(connection, &MapConnection::targetMapNameChanged, [=](QString, QString) { - listItem->updateUI(); - updateConnectionPixmap(pixmapItem); - }); - // When the pixmap is deleted, remove its associated list item connect(pixmapItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater); @@ -875,8 +848,6 @@ void Editor::removeConnectionPixmap(MapConnection *connection) { if (!connection) return; - disconnectMapConnection(connection); - if (MapConnection::isDiving(connection->direction())) { removeDivingMapPixmap(connection); return; @@ -1011,39 +982,6 @@ void Editor::updateDivingMapsVisibility() { } } -// Get the 'origin' point for the connection's pixmap, i.e. where it should be positioned in the editor when connection->offset() == 0. -// This differs depending on the connection's direction and the dimensions of its target map or parent map. -QPoint Editor::getConnectionOrigin(MapConnection *connection) { - if (!connection) - return QPoint(0, 0); - - Map *parentMap = connection->parentMap(); - Map *targetMap = connection->targetMap(); - const QString direction = connection->direction(); - int x = 0, y = 0; - - if (direction == "right") { - if (parentMap) x = parentMap->getWidth(); - } else if (direction == "down") { - if (parentMap) y = parentMap->getHeight(); - } else if (direction == "left") { - if (targetMap) x = -targetMap->getConnectionRect(direction).width(); - } else if (direction == "up") { - if (targetMap) y = -targetMap->getConnectionRect(direction).height(); - } - return QPoint(x * 16, y * 16); -} - -void Editor::updateConnectionPixmap(ConnectionPixmapItem *pixmapItem) { - if (!pixmapItem) - return; - - pixmapItem->setOrigin(getConnectionOrigin(pixmapItem->connection)); - pixmapItem->render(true); // Full render to reflect map changes - - maskNonVisibleConnectionTiles(); -} - void Editor::setSelectedConnectionItem(ConnectionPixmapItem *pixmapItem) { if (!pixmapItem || pixmapItem == selected_connection_item) return; @@ -1245,8 +1183,6 @@ void Editor::unsetMap() { if (this->map) { this->map->pruneEditHistory(); this->map->disconnect(this); - for (const auto &connection : this->map->getConnections()) - disconnectMapConnection(connection); } clearMapConnections(); @@ -1546,7 +1482,10 @@ void Editor::clearMap() { map = nullptr; // These are normally preserved between map displays, we only delete them now. - delete scene; + if (scene) { + scene->removeItem(this->map_ruler); + delete scene; + } delete metatile_selector_item; delete movement_permissions_selector_item; } @@ -1575,9 +1514,10 @@ bool Editor::displayLayout() { scene->installEventFilter(filter); connect(filter, &MapSceneEventFilter::wheelZoom, this, &Editor::onWheelZoom); scene->installEventFilter(this->map_ruler); + this->map_ruler->setZValue(1000); + scene->addItem(this->map_ruler); } - clearConnectionMask(); displayMetatileSelector(); displayMapMetatiles(); displayMovementPermissionSelector(); @@ -1586,9 +1526,7 @@ bool Editor::displayLayout() { displayCurrentMetatilesSelection(); displayMapBorder(); displayMapGrid(); - - this->map_ruler->setZValue(1000); - scene->addItem(this->map_ruler); + maskNonVisibleConnectionTiles(); if (map_item) { map_item->setVisible(false); @@ -1638,7 +1576,6 @@ void Editor::clearMapMetatiles() { if (map_item && scene) { scene->removeItem(map_item); delete map_item; - scene->removeItem(this->map_ruler); } } diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index d3ff13ae..e22b0fab 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -4,19 +4,25 @@ #include -ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection, int x, int y) +ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection) : QGraphicsPixmapItem(connection->getPixmap()), connection(connection) { this->setEditable(true); setFlag(ItemIsFocusable, true); this->basePixmap = pixmap(); - this->setOrigin(x, y); + refresh(); + + // If the connection changes externally we want to update the pixmap to reflect the change. + connect(connection, &MapConnection::offsetChanged, this, &ConnectionPixmapItem::updatePos); + connect(connection, &MapConnection::directionChanged, this, &ConnectionPixmapItem::refresh); + connect(connection, &MapConnection::targetMapNameChanged, this, &ConnectionPixmapItem::refresh); } -ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection, QPoint pos) - : ConnectionPixmapItem(connection, pos.x(), pos.y()) -{} +void ConnectionPixmapItem::refresh() { + updateOrigin(); + render(true); +} // Render additional visual effects on top of the base map image. void ConnectionPixmapItem::render(bool ignoreCache) { @@ -77,8 +83,6 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari // If connection->offset changed externally we call this to correct our position. void ConnectionPixmapItem::updatePos() { - const QSignalBlocker blocker(this); - qreal x = this->originX; qreal y = this->originY; @@ -89,17 +93,28 @@ void ConnectionPixmapItem::updatePos() { } this->setPos(x, y); + emit positionChanged(x, y); } -// Set the pixmap's external origin point, i.e. the pixmap's position when connection->offset == 0 -void ConnectionPixmapItem::setOrigin(int x, int y) { - this->originX = x; - this->originY = y; +void ConnectionPixmapItem::updateOrigin() { + const Map *parentMap = connection->parentMap(); + const Map *targetMap = connection->targetMap(); + const QString direction = connection->direction(); + int x = 0, y = 0; + + if (direction == "right") { + if (parentMap) x = parentMap->getWidth(); + } else if (direction == "down") { + if (parentMap) y = parentMap->getHeight(); + } else if (direction == "left") { + if (targetMap) x = -targetMap->getConnectionRect(direction).width(); + } else if (direction == "up") { + if (targetMap) y = -targetMap->getConnectionRect(direction).height(); + } + this->originX = x * this->mWidth; + this->originY = y * this->mHeight; updatePos(); } -void ConnectionPixmapItem::setOrigin(QPoint pos) { - this->setOrigin(pos.x(), pos.y()); -} void ConnectionPixmapItem::setEditable(bool editable) { setFlag(ItemIsMovable, editable); diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index dcff253c..5b21a12c 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -38,6 +38,11 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec // Distinguish between move actions for the edit history connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); + // If the connection changes externally we want to update to reflect the change. + connect(connection, &MapConnection::offsetChanged, this, &ConnectionsListItem::updateUI); + connect(connection, &MapConnection::directionChanged, this, &ConnectionsListItem::updateUI); + connect(connection, &MapConnection::targetMapNameChanged, this, &ConnectionsListItem::updateUI); + this->connection = connection; this->map = connection->parentMap(); this->updateUI(); From 9bdf3966794d49ce0d372455dfc5382efd6837f6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 30 Jan 2025 11:53:00 -0500 Subject: [PATCH 143/364] Fix some lexical sorting of names with numbers --- include/ui/filterchildrenproxymodel.h | 21 +++++++++++++++++---- include/ui/maplistmodels.h | 1 - include/ui/wildmonsearch.h | 15 +++++++++++++++ src/mainwindow.cpp | 4 ++++ src/ui/filterchildrenproxymodel.cpp | 19 ++++++++++++++----- src/ui/maplistmodels.cpp | 11 ----------- src/ui/wildmonsearch.cpp | 8 ++++---- 7 files changed, 54 insertions(+), 25 deletions(-) diff --git a/include/ui/filterchildrenproxymodel.h b/include/ui/filterchildrenproxymodel.h index 507693b3..93934a7e 100644 --- a/include/ui/filterchildrenproxymodel.h +++ b/include/ui/filterchildrenproxymodel.h @@ -3,15 +3,28 @@ #include -class FilterChildrenProxyModel : public QSortFilterProxyModel +class NumericSortProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit FilterChildrenProxyModel(QObject *parent = nullptr); - void setHideEmpty(bool hidden) { this->hideEmpty = hidden; } + explicit NumericSortProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}; + protected: - bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; +}; + +class FilterChildrenProxyModel : public NumericSortProxyModel +{ + Q_OBJECT + +public: + explicit FilterChildrenProxyModel(QObject *parent = nullptr) : NumericSortProxyModel(parent) {}; + void setHideEmpty(bool hidden) { this->hideEmpty = hidden; } + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override; + private: bool hideEmpty = false; }; diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index b973f438..681f0955 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -79,7 +79,6 @@ protected: QString activeItemName; QString folderTypeName; - bool sortingEnabled = false; bool editable = false; QIcon mapGrayIcon; diff --git a/include/ui/wildmonsearch.h b/include/ui/wildmonsearch.h index 49e04b57..ccf0fe94 100644 --- a/include/ui/wildmonsearch.h +++ b/include/ui/wildmonsearch.h @@ -2,9 +2,24 @@ #define WILDMONSEARCH_H #include +#include +#include class Project; +class NumericSortTableItem : public QTableWidgetItem +{ +public: + explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {}; + +protected: + virtual bool operator<(const QTableWidgetItem &other) const override { + QCollator collator; + collator.setNumericMode(true); + return collator.compare(text(), other.text()) < 0; + } +}; + namespace Ui { class WildMonSearch; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bf9a91e8..3348796a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1113,11 +1113,15 @@ bool MainWindow::setProjectUI() { this->locationListProxyModel = new FilterChildrenProxyModel(); locationListProxyModel->setSourceModel(this->mapLocationModel); ui->locationList->setModel(locationListProxyModel); + ui->locationList->setSortingEnabled(true); + ui->locationList->sortByColumn(0, Qt::SortOrder::AscendingOrder); this->layoutTreeModel = new LayoutTreeModel(editor->project); this->layoutListProxyModel = new FilterChildrenProxyModel(); this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); + ui->layoutList->setSortingEnabled(true); + ui->layoutList->sortByColumn(0, Qt::SortOrder::AscendingOrder); return true; } diff --git a/src/ui/filterchildrenproxymodel.cpp b/src/ui/filterchildrenproxymodel.cpp index 99464ae6..06d7a184 100644 --- a/src/ui/filterchildrenproxymodel.cpp +++ b/src/ui/filterchildrenproxymodel.cpp @@ -1,10 +1,6 @@ #include "filterchildrenproxymodel.h" -FilterChildrenProxyModel::FilterChildrenProxyModel(QObject *parent) : - QSortFilterProxyModel(parent) -{ - -} +#include bool FilterChildrenProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { @@ -42,3 +38,16 @@ bool FilterChildrenProxyModel::filterAcceptsRow(int source_row, const QModelInde // parent call for initial behaviour return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } + +bool NumericSortProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { + QVariant l = (source_left.model() ? source_left.model()->data(source_left, sortRole()) : QVariant()); + QVariant r = (source_right.model() ? source_right.model()->data(source_right, sortRole()) : QVariant()); + + if (l.canConvert() && r.canConvert()) { + // We need to override lexical comparison of strings to do a numeric sort. + QCollator collator; + collator.setNumericMode(true); + return collator.compare(l.toString(), r.toString()) < 0; + } + return QSortFilterProxyModel::lessThan(source_left, source_right); +} diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index d08bb2cf..0d42b896 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -135,9 +135,6 @@ QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString if (folder) { folder->appendRow(map); } - - if (this->sortingEnabled) - this->sort(0, Qt::AscendingOrder); return map; } @@ -147,8 +144,6 @@ QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) { QStandardItem *item = createMapFolderItem(folderName); this->root->appendRow(item); - if (this->sortingEnabled) - this->sort(0, Qt::AscendingOrder); return item; } @@ -432,9 +427,6 @@ MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListM for (const auto &mapName : this->project->mapNames) { insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); } - - this->sortingEnabled = true; - sort(0, Qt::AscendingOrder); } void MapLocationModel::removeItem(QStandardItem *item) { @@ -459,9 +451,6 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListMod for (const auto &mapName : this->project->mapNames) { insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); } - - this->sortingEnabled = true; - sort(0, Qt::AscendingOrder); } void LayoutTreeModel::removeItem(QStandardItem *) { diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index 684d2947..71e5ceed 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -58,13 +58,13 @@ void WildMonSearch::addTableEntry(const RowData &rowData) { int row = ui->table_Results->rowCount(); ui->table_Results->insertRow(row); - auto groupItem = new QTableWidgetItem(rowData.groupName); + auto groupItem = new NumericSortTableItem(rowData.groupName); groupItem->setData(ResultsDataRole::MapName, rowData.mapName); ui->table_Results->setItem(row, ResultsColumn::Group, groupItem); - ui->table_Results->setItem(row, ResultsColumn::Field, new QTableWidgetItem(rowData.fieldName)); - ui->table_Results->setItem(row, ResultsColumn::Level, new QTableWidgetItem(rowData.levelRange)); - ui->table_Results->setItem(row, ResultsColumn::Chance, new QTableWidgetItem(rowData.chance)); + ui->table_Results->setItem(row, ResultsColumn::Field, new NumericSortTableItem(rowData.fieldName)); + ui->table_Results->setItem(row, ResultsColumn::Level, new NumericSortTableItem(rowData.levelRange)); + ui->table_Results->setItem(row, ResultsColumn::Chance, new NumericSortTableItem(rowData.chance)); } QList WildMonSearch::search(const QString &species) const { From 748e51c3d5cd3e8631611bdd12360ee0c81a3085 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 30 Jan 2025 12:12:23 -0500 Subject: [PATCH 144/364] Remove old comment --- src/ui/wildmonsearch.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index 71e5ceed..f43a0dab 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -137,7 +137,6 @@ void WildMonSearch::updateResults(const QString &species) { } } - // TODO: This does a lexical sort... We might need custom item delegates to get proper numerical sorting in this table. ui->table_Results->setSortingEnabled(true); this->resultsCache.insert(species, results); From fe38e4259161632bccf16391d1a01fda0f911c1b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 10 Oct 2024 21:14:03 -0400 Subject: [PATCH 145/364] Port changes from Custom Attributes redesign --- forms/customattributesdialog.ui | 172 ++++++++++++++ forms/customattributesframe.ui | 89 +++++++ forms/mainwindow.ui | 158 +++++-------- include/core/events.h | 10 +- include/editor.h | 2 +- include/mainwindow.h | 5 +- include/ui/customattributesdialog.h | 34 +++ include/ui/customattributesframe.h | 36 +++ include/ui/customattributestable.h | 43 +++- include/ui/eventframes.h | 2 + porymap.pro | 10 +- src/core/events.cpp | 68 +++--- src/editor.cpp | 7 +- src/mainwindow.cpp | 36 +-- src/project.cpp | 18 +- src/ui/customattributesdialog.cpp | 101 ++++++++ src/ui/customattributesframe.cpp | 39 ++++ src/ui/customattributestable.cpp | 345 +++++++++++++++------------- src/ui/eventframes.cpp | 15 +- 19 files changed, 823 insertions(+), 367 deletions(-) create mode 100644 forms/customattributesdialog.ui create mode 100644 forms/customattributesframe.ui create mode 100644 include/ui/customattributesdialog.h create mode 100644 include/ui/customattributesframe.h create mode 100644 src/ui/customattributesdialog.cpp create mode 100644 src/ui/customattributesframe.cpp diff --git a/forms/customattributesdialog.ui b/forms/customattributesdialog.ui new file mode 100644 index 00000000..b1f1ee4b --- /dev/null +++ b/forms/customattributesdialog.ui @@ -0,0 +1,172 @@ + + + CustomAttributesDialog + + + + 0 + 0 + 410 + 192 + + + + Add New Custom Attribute + + + + + + + 0 + + + 0 + + + + + Name + + + + + + + The key name for the new JSON field + + + true + + + + + + + Type + + + + + + + The data type for the new JSON field + + + + + + + Value + + + + + + + + 0 + 0 + + + + The value for the new JSON field + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
+ + +
diff --git a/forms/customattributesframe.ui b/forms/customattributesframe.ui new file mode 100644 index 00000000..87619e63 --- /dev/null +++ b/forms/customattributesframe.ui @@ -0,0 +1,89 @@ + + + CustomAttributesFrame + + + + 0 + 0 + 400 + 300 + + + + + + + Custom Attributes + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add + + + + + + + false + + + Delete + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + CustomAttributesTable + QTableWidget +
customattributestable.h
+
+
+ + +
diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index dfea9752..4a9e439a 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2244,113 +2244,69 @@
- + - QFrame::Shape::StyledPanel + QFrame::Shape::NoFrame - QFrame::Shadow::Raised + QFrame::Shadow::Plain - - - - - Custom Fields - - - - - - - QFrame::Shape::NoFrame - - - QFrame::Shadow::Plain - - - - 0 + + true + + + + + 0 + 0 + 1011 + 806 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + - - 0 + + QFrame::Shape::StyledPanel - - 0 + + QFrame::Shadow::Raised - - 0 + + + + + + Qt::Orientation::Vertical - - - - Add - - - - - - - Delete - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - Custom fields will be added to the map.json file for the current map. - - - false - - - false - - - true - - - false - - - true - - - false - - - - Type + + + 1 + 767 + - - - - Key - - - - - Value - - - - - + + + +
@@ -3349,6 +3305,12 @@
maplisttoolbar.h
1 + + CustomAttributesFrame + QFrame +
customattributesframe.h
+ 1 +
diff --git a/include/core/events.h b/include/core/events.h index c0a5a625..081ff506 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -160,10 +160,10 @@ public: virtual void setDefaultValues(Project *project); virtual QSet getExpectedFields() = 0; - void readCustomValues(QJsonObject values); - void addCustomValuesTo(OrderedJson::object *obj); - const QMap getCustomValues() { return this->customValues; } - void setCustomValues(const QMap newCustomValues) { this->customValues = newCustomValues; } + void readCustomAttributes(const QJsonObject &json); + void addCustomAttributesTo(OrderedJson::object *obj) const; + const QMap getCustomAttributes() const { return this->customAttributes; } + void setCustomAttributes(const QMap newCustomAttributes) { this->customAttributes = newCustomAttributes; } virtual void loadPixmap(Project *project); @@ -206,7 +206,7 @@ protected: int spriteHeight = 16; bool usingSprite = false; - QMap customValues; + QMap customAttributes; QPixmap pixmap; DraggablePixmapItem *pixmapItem = nullptr; diff --git a/include/editor.h b/include/editor.h index 434415e9..81bf1611 100644 --- a/include/editor.h +++ b/include/editor.h @@ -107,7 +107,7 @@ public: void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false); void updateSecondaryTileset(QString tilesetLabel, bool forceLoad = false); void toggleBorderVisibility(bool visible, bool enableScriptCallback = true); - void updateCustomMapHeaderValues(QTableWidget *); + void updateCustomMapAttributes(); DraggablePixmapItem *addMapEvent(Event *event); bool eventLimitReached(Map *, Event::Type); diff --git a/include/mainwindow.h b/include/mainwindow.h index cf37a298..42d346f3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -274,9 +274,6 @@ private slots: void on_actionAbout_Porymap_triggered(); void on_actionOpen_Log_File_triggered(); void on_actionOpen_Config_Folder_triggered(); - void on_pushButton_AddCustomHeaderField_clicked(); - void on_pushButton_DeleteCustomHeaderField_clicked(); - void on_tableWidget_CustomHeaderFields_cellChanged(int row, int column); void on_horizontalSlider_MetatileZoom_valueChanged(int value); void on_horizontalSlider_CollisionZoom_valueChanged(int value); void on_pushButton_NewWildMonGroup_clicked(); @@ -406,7 +403,7 @@ private: Event::Group getEventGroupFromTabWidget(QWidget *tab); bool closeSupplementaryWindows(); void setWindowDisabled(bool); - + void resetMapCustomAttributesTable(); void initTilesetEditor(); bool initRegionMapEditor(bool silent = false); bool askToFixRegionMapEditor(); diff --git a/include/ui/customattributesdialog.h b/include/ui/customattributesdialog.h new file mode 100644 index 00000000..7048e16c --- /dev/null +++ b/include/ui/customattributesdialog.h @@ -0,0 +1,34 @@ +#ifndef CUSTOMATTRIBUTESDIALOG_H +#define CUSTOMATTRIBUTESDIALOG_H + +#include +#include + +#include "customattributestable.h" + +namespace Ui { +class CustomAttributesDialog; +} + +class CustomAttributesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CustomAttributesDialog(CustomAttributesTable *table); + ~CustomAttributesDialog(); + +private: + Ui::CustomAttributesDialog *ui; + CustomAttributesTable *const m_table; + + void setInputType(int inputType); + void onNameChanged(const QString &); + bool validateName(bool allowEmpty = false); + void clickedButton(QAbstractButton *button); + void addNewAttribute(); + QVariant getValue() const; +}; + + +#endif // CUSTOMATTRIBUTESDIALOG_H diff --git a/include/ui/customattributesframe.h b/include/ui/customattributesframe.h new file mode 100644 index 00000000..f19d0601 --- /dev/null +++ b/include/ui/customattributesframe.h @@ -0,0 +1,36 @@ +#ifndef CUSTOMATTRIBUTESFRAME_H +#define CUSTOMATTRIBUTESFRAME_H + +/* + The frame containing the Custom Attributes table and its Add/Delete buttons. + Shared by the map's Header tab and Events. +*/ + +#include "customattributestable.h" + +#include +#include + +namespace Ui { +class CustomAttributesFrame; +} + +class CustomAttributesFrame : public QFrame +{ + Q_OBJECT + +public: + explicit CustomAttributesFrame(QWidget *parent = nullptr); + ~CustomAttributesFrame(); + + CustomAttributesTable* table() const; + +private: + Ui::CustomAttributesFrame *ui; + + void addAttribute(); + void deleteAttribute(); + void updateDeleteButton(); +}; + +#endif // CUSTOMATTRIBUTESFRAME_H diff --git a/include/ui/customattributestable.h b/include/ui/customattributestable.h index f17f4877..21cac4de 100644 --- a/include/ui/customattributestable.h +++ b/include/ui/customattributestable.h @@ -1,25 +1,44 @@ #ifndef CUSTOMATTRIBUTESTABLE_H #define CUSTOMATTRIBUTESTABLE_H -#include "events.h" #include -#include +#include #include -class CustomAttributesTable : public QFrame +class CustomAttributesTable : public QTableWidget { -public: - explicit CustomAttributesTable(Event *event, QWidget *parent = nullptr); - ~CustomAttributesTable(); + Q_OBJECT - static const QMap getAttributes(QTableWidget * table); - static QJsonValue pickType(QWidget * parent, bool * ok = nullptr); - static void addAttribute(QTableWidget * table, QString key, QJsonValue value, bool isNew = false); - static bool deleteSelectedAttributes(QTableWidget * table); +public: + explicit CustomAttributesTable(QWidget *parent = nullptr); + ~CustomAttributesTable() {}; + + QMap getAttributes() const; + void setAttributes(const QMap &attributes); + + void addNewAttribute(const QString &key, const QJsonValue &value); + bool deleteSelectedAttributes(); + + bool isEmpty() const; + bool isSelectionEmpty() const; + + QSet keys() const { return m_keys; } + QSet restrictedKeys() const { return m_restrictedKeys; } + void setRestrictedKeys(const QSet &keys) { m_restrictedKeys = keys; } + +signals: + void edited(); + +protected: + virtual void resizeEvent(QResizeEvent *event) override; private: - Event *event; - QTableWidget *table; + QSet m_keys; // All keys currently in the table + QSet m_restrictedKeys; // All keys not allowed in the table + + QPair getAttribute(int row) const; + int addAttribute(const QString &key, const QJsonValue &value); + void removeAttribute(const QString &key); void resizeVertically(); }; diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index c11cf8e6..2ad55750 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -52,6 +52,8 @@ public: QFrame *frame_contents; QVBoxLayout *layout_contents; + CustomAttributesFrame *custom_attributes; + protected: bool populated = false; bool initialized = false; diff --git a/porymap.pro b/porymap.pro index b6758c2e..76d19d21 100644 --- a/porymap.pro +++ b/porymap.pro @@ -67,6 +67,8 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/aboutporymap.cpp \ src/ui/colorinputwidget.cpp \ src/ui/connectionslistitem.cpp \ + src/ui/customattributesdialog.cpp \ + src/ui/customattributestable.cpp \ src/ui/customscriptseditor.cpp \ src/ui/customscriptslistitem.cpp \ src/ui/divingmappixmapitem.cpp \ @@ -83,7 +85,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/regionmaplayoutpixmapitem.cpp \ src/ui/regionmapentriespixmapitem.cpp \ src/ui/cursortilerect.cpp \ - src/ui/customattributestable.cpp \ + src/ui/customattributesframe.cpp \ src/ui/eventframes.cpp \ src/ui/eventfilters.cpp \ src/ui/filterchildrenproxymodel.cpp \ @@ -176,6 +178,8 @@ HEADERS += include/core/advancemapparser.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ include/ui/connectionslistitem.h \ + include/ui/customattributesdialog.h \ + include/ui/customattributestable.h \ include/ui/customscriptseditor.h \ include/ui/customscriptslistitem.h \ include/ui/divingmappixmapitem.h \ @@ -192,7 +196,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/regionmaplayoutpixmapitem.h \ include/ui/regionmapentriespixmapitem.h \ include/ui/cursortilerect.h \ - include/ui/customattributestable.h \ + include/ui/customattributesframe.h \ include/ui/eventframes.h \ include/ui/eventfilters.h \ include/ui/filterchildrenproxymodel.h \ @@ -258,6 +262,7 @@ HEADERS += include/core/advancemapparser.h \ FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ forms/connectionslistitem.ui \ + forms/customattributesframe.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ @@ -282,6 +287,7 @@ FORMS += forms/mainwindow.ui \ forms/projectsettingseditor.ui \ forms/customscriptseditor.ui \ forms/customscriptslistitem.ui \ + forms/customattributesdialog.ui \ forms/updatepromoter.ui \ forms/wildmonchart.ui \ forms/wildmonsearch.ui diff --git a/src/core/events.cpp b/src/core/events.cpp index c0cf7b7a..2da13da0 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -53,20 +53,20 @@ void Event::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); } -void Event::readCustomValues(QJsonObject values) { - this->customValues.clear(); - QSet expectedFields = this->getExpectedFields(); - for (QString key : values.keys()) { - if (!expectedFields.contains(key)) { - this->customValues[key] = values[key]; +void Event::readCustomAttributes(const QJsonObject &json) { + this->customAttributes.clear(); + const QSet expectedFields = this->getExpectedFields(); + for (auto i = json.constBegin(); i != json.constEnd(); i++) { + if (!expectedFields.contains(i.key())) { + this->customAttributes[i.key()] = i.value(); } } } -void Event::addCustomValuesTo(OrderedJson::object *obj) { - for (QString key : this->customValues.keys()) { - if (!obj->contains(key)) { - (*obj)[key] = OrderedJson::fromQJsonValue(this->customValues[key]); +void Event::addCustomAttributesTo(OrderedJson::object *obj) const { + for (auto i = this->customAttributes.constBegin(); i != this->customAttributes.constEnd(); i++) { + if (!obj->contains(i.key())) { + (*obj)[i.key()] = OrderedJson::fromQJsonValue(i.value()); } } } @@ -197,7 +197,7 @@ Event *ObjectEvent::duplicate() { copy->setSightRadiusBerryTreeID(this->getSightRadiusBerryTreeID()); copy->setScript(this->getScript()); copy->setFlag(this->getFlag()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -227,7 +227,7 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID(); objectJson["script"] = this->getScript(); objectJson["flag"] = this->getFlag(); - this->addCustomValuesTo(&objectJson); + this->addCustomAttributesTo(&objectJson); return objectJson; } @@ -245,7 +245,7 @@ bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { this->setScript(ParseUtil::jsonToQString(json["script"])); this->setFlag(ParseUtil::jsonToQString(json["flag"])); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -371,7 +371,7 @@ Event *CloneObjectEvent::duplicate() { copy->setGfx(this->getGfx()); copy->setTargetID(this->getTargetID()); copy->setTargetMap(this->getTargetMap()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -394,7 +394,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["target_local_id"] = this->getTargetID(); const QString mapName = this->getTargetMap(); cloneJson["target_map"] = project->mapNamesToMapConstants.value(mapName, mapName); - this->addCustomValuesTo(&cloneJson); + this->addCustomAttributesTo(&cloneJson); return cloneJson; } @@ -411,7 +411,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -478,7 +478,7 @@ Event *WarpEvent::duplicate() { copy->setDestinationMap(this->getDestinationMap()); copy->setDestinationWarpID(this->getDestinationWarpID()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -501,7 +501,7 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["dest_map"] = project->mapNamesToMapConstants.value(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); - this->addCustomValuesTo(&warpJson); + this->addCustomAttributesTo(&warpJson); return warpJson; } @@ -518,7 +518,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -560,7 +560,7 @@ Event *TriggerEvent::duplicate() { copy->setScriptVarValue(this->getScriptVarValue()); copy->setScriptLabel(this->getScriptLabel()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -584,7 +584,7 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { triggerJson["var_value"] = this->getScriptVarValue(); triggerJson["script"] = this->getScriptLabel(); - this->addCustomValuesTo(&triggerJson); + this->addCustomAttributesTo(&triggerJson); return triggerJson; } @@ -597,7 +597,7 @@ bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"])); this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -634,7 +634,7 @@ Event *WeatherTriggerEvent::duplicate() { copy->setElevation(this->getElevation()); copy->setWeather(this->getWeather()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -656,7 +656,7 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { weatherJson["elevation"] = this->getElevation(); weatherJson["weather"] = this->getWeather(); - this->addCustomValuesTo(&weatherJson); + this->addCustomAttributesTo(&weatherJson); return weatherJson; } @@ -667,7 +667,7 @@ bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { this->setElevation(ParseUtil::jsonToInt(json["elevation"])); this->setWeather(ParseUtil::jsonToQString(json["weather"])); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -701,7 +701,7 @@ Event *SignEvent::duplicate() { copy->setFacingDirection(this->getFacingDirection()); copy->setScriptLabel(this->getScriptLabel()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -724,7 +724,7 @@ OrderedJson::object SignEvent::buildEventJson(Project *) { signJson["player_facing_dir"] = this->getFacingDirection(); signJson["script"] = this->getScriptLabel(); - this->addCustomValuesTo(&signJson); + this->addCustomAttributesTo(&signJson); return signJson; } @@ -736,7 +736,7 @@ bool SignEvent::loadFromJson(QJsonObject json, Project *) { this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"])); this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -774,7 +774,7 @@ Event *HiddenItemEvent::duplicate() { copy->setQuantity(this->getQuantity()); copy->setQuantity(this->getQuantity()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -803,7 +803,7 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { hiddenItemJson["underfoot"] = this->getUnderfoot(); } - this->addCustomValuesTo(&hiddenItemJson); + this->addCustomAttributesTo(&hiddenItemJson); return hiddenItemJson; } @@ -821,7 +821,7 @@ bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"])); } - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } @@ -867,7 +867,7 @@ Event *SecretBaseEvent::duplicate() { copy->setElevation(this->getElevation()); copy->setBaseID(this->getBaseID()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -889,7 +889,7 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { secretBaseJson["elevation"] = this->getElevation(); secretBaseJson["secret_base_id"] = this->getBaseID(); - this->addCustomValuesTo(&secretBaseJson); + this->addCustomAttributesTo(&secretBaseJson); return secretBaseJson; } @@ -900,7 +900,7 @@ bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { this->setElevation(ParseUtil::jsonToInt(json["elevation"])); this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"])); - this->readCustomValues(json); + this->readCustomAttributes(json); return true; } diff --git a/src/editor.cpp b/src/editor.cpp index f3fb1c99..3c7fee27 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -10,7 +10,7 @@ #include "editcommands.h" #include "config.h" #include "scripting.h" -#include "customattributestable.h" +#include "customattributesframe.h" #include "validator.h" #include #include @@ -52,6 +52,7 @@ Editor::Editor(Ui::MainWindow* ui) connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this, &Editor::openMapScripts); connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this, &Editor::openProjectInTextEditor); connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::toggleGrid); + connect(ui->mapCustomAttributesFrame->table(), &CustomAttributesTable::edited, this, &Editor::updateCustomMapAttributes); } Editor::~Editor() @@ -1975,9 +1976,9 @@ void Editor::updateBorderVisibility() { } } -void Editor::updateCustomMapHeaderValues(QTableWidget *table) +void Editor::updateCustomMapAttributes() { - map->setCustomAttributes(CustomAttributesTable::getAttributes(table)); + map->setCustomAttributes(ui->mapCustomAttributesFrame->table()->getAttributes()); map->modify(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bf9a91e8..d0ba8040 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -7,7 +7,7 @@ #include "eventframes.h" #include "bordermetatilespixmapitem.h" #include "currentselectedmetatilespixmapitem.h" -#include "customattributestable.h" +#include "customattributesframe.h" #include "scripting.h" #include "adjustingstackedwidget.h" #include "draggablepixmapitem.h" @@ -1033,16 +1033,7 @@ void MainWindow::displayMapProperties() { ui->comboBox_PrimaryTileset->setCurrentText(editor->map->layout()->tileset_primary_label); ui->comboBox_SecondaryTileset->setCurrentText(editor->map->layout()->tileset_secondary_label); - - // Custom fields table. -/* // TODO: Re-enable - ui->tableWidget_CustomHeaderFields->blockSignals(true); - ui->tableWidget_CustomHeaderFields->setRowCount(0); - for (auto it = map->customHeaders.begin(); it != map->customHeaders.end(); it++) - CustomAttributesTable::addAttribute(ui->tableWidget_CustomHeaderFields, it.key(), it.value()); - ui->tableWidget_CustomHeaderFields->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); - ui->tableWidget_CustomHeaderFields->blockSignals(false); -*/ + ui->mapCustomAttributesFrame->table()->setAttributes(editor->map->customAttributes()); } void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { @@ -1119,6 +1110,8 @@ bool MainWindow::setProjectUI() { this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); + ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->topLevelMapFields); + return true; } @@ -2867,27 +2860,6 @@ void MainWindow::reloadScriptEngine() { Scripting::cb_MapOpened(editor->map->name()); // TODO: API should have equivalent for layout } -void MainWindow::on_pushButton_AddCustomHeaderField_clicked() -{ - bool ok; - QJsonValue value = CustomAttributesTable::pickType(this, &ok); - if (ok){ - CustomAttributesTable::addAttribute(this->ui->tableWidget_CustomHeaderFields, "", value, true); - this->editor->updateCustomMapHeaderValues(this->ui->tableWidget_CustomHeaderFields); - } -} - -void MainWindow::on_pushButton_DeleteCustomHeaderField_clicked() -{ - if (CustomAttributesTable::deleteSelectedAttributes(this->ui->tableWidget_CustomHeaderFields)) - this->editor->updateCustomMapHeaderValues(this->ui->tableWidget_CustomHeaderFields); -} - -void MainWindow::on_tableWidget_CustomHeaderFields_cellChanged(int, int) -{ - this->editor->updateCustomMapHeaderValues(this->ui->tableWidget_CustomHeaderFields); -} - void MainWindow::on_horizontalSlider_MetatileZoom_valueChanged(int value) { porymapConfig.metatilesZoom = value; double scale = pow(3.0, static_cast(value - 30) / 30.0); diff --git a/src/project.cpp b/src/project.cpp index 712ee87f..9e0a8c3a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -353,14 +353,13 @@ bool Project::loadMapData(Map* map) { } } - // Check for custom fields -/* // TODO: Re-enable - for (QString key : mapObj.keys()) { - if (!this->topLevelMapFields.contains(key)) { - map->customHeaders.insert(key, mapObj[key]); + QMap customAttributes; + for (auto i = mapObj.constBegin(); i != mapObj.constEnd(); i++) { + if (!this->topLevelMapFields.contains(i.key())) { + customAttributes.insert(i.key(), i.value()); } } -*/ + map->setCustomAttributes(customAttributes); return true; } @@ -1338,11 +1337,10 @@ void Project::saveMap(Map *map) { } // Custom header fields. -/* // TODO: Re-enable - for (QString key : map->customHeaders.keys()) { - mapObj[key] = OrderedJson::fromQJsonValue(map->customHeaders[key]); + const auto customAttributes = map->customAttributes(); + for (auto i = customAttributes.constBegin(); i != customAttributes.constEnd(); i++) { + mapObj[i.key()] = OrderedJson::fromQJsonValue(i.value()); } -*/ OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); diff --git a/src/ui/customattributesdialog.cpp b/src/ui/customattributesdialog.cpp new file mode 100644 index 00000000..7b4a4b84 --- /dev/null +++ b/src/ui/customattributesdialog.cpp @@ -0,0 +1,101 @@ +#include "customattributesdialog.h" +#include "ui_customattributesdialog.h" + +#include + +static int curInputType = 0; + +CustomAttributesDialog::CustomAttributesDialog(CustomAttributesTable *table) : + QDialog(table), + ui(new Ui::CustomAttributesDialog), + m_table(table) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + // Type combo box + ui->comboBox_Type->addItems({"String", "Number", "Boolean"}); + ui->comboBox_Type->setEditable(false); + + // When the value type is changed, update the value input widget + connect(ui->comboBox_Type, QOverload::of(&QComboBox::currentIndexChanged), this, &CustomAttributesDialog::setInputType); + ui->comboBox_Type->setCurrentIndex(curInputType); + + ui->spinBox_Value->setMinimum(INT_MIN); + ui->spinBox_Value->setMaximum(INT_MAX); + + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &CustomAttributesDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CustomAttributesDialog::clickedButton); + + adjustSize(); +} + +CustomAttributesDialog::~CustomAttributesDialog() { + delete ui; +} + +void CustomAttributesDialog::setInputType(int inputType) { + if (inputType < 0 || inputType >= ui->stackedWidget_Value->count()) + return; + + ui->stackedWidget_Value->setCurrentIndex(inputType); + + // Preserve input widget for later dialogs + curInputType = inputType; +} + +void CustomAttributesDialog::onNameChanged(const QString &) { + validateName(true); +} + +bool CustomAttributesDialog::validateName(bool allowEmpty) { + const 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 (m_table->restrictedKeys().contains(name)) { + errorText = QString("The name '%1' is reserved, please choose a different name.").arg(name); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? "QLineEdit { background-color: rgba(255, 0, 0, 25%) }" : ""); + return isValid; +} + +QVariant CustomAttributesDialog::getValue() const { + QVariant value; + auto widget = ui->stackedWidget_Value->currentWidget(); + if (widget == ui->page_String) { + value = QVariant(ui->lineEdit_Value->text()); + } else if (widget == ui->page_Number) { + value = QVariant(ui->spinBox_Value->value()); + } else if (widget == ui->page_Boolean) { + value = QVariant(ui->checkBox_Value->isChecked()); + } + return value; +} + +void CustomAttributesDialog::addNewAttribute() { + m_table->addNewAttribute(ui->lineEdit_Name->text(), QJsonValue::fromVariant(getValue())); +} + +void CustomAttributesDialog::clickedButton(QAbstractButton *button) { + auto buttonRole = ui->buttonBox->buttonRole(button); + if (buttonRole == QDialogButtonBox::AcceptRole && validateName()) { + const QString key = ui->lineEdit_Name->text(); + if (m_table->keys().contains(key)) { + // Warn user if key name would overwrite an existing custom attribute + const QString msg = QString("Overwrite value for existing attribute '%1'?").arg(key); + if (QMessageBox::warning(this, "Warning", msg, QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel){ + return; + } + } + addNewAttribute(); + done(QDialog::Accepted); + } else if (buttonRole == QDialogButtonBox::RejectRole) { + done(QDialog::Rejected); + } +} diff --git a/src/ui/customattributesframe.cpp b/src/ui/customattributesframe.cpp new file mode 100644 index 00000000..e9cb6663 --- /dev/null +++ b/src/ui/customattributesframe.cpp @@ -0,0 +1,39 @@ +#include "customattributesframe.h" +#include "ui_customattributesframe.h" +#include "customattributesdialog.h" +#include +#include +#include + +CustomAttributesFrame::CustomAttributesFrame(QWidget *parent) : + QFrame(parent), + ui(new Ui::CustomAttributesFrame) +{ + ui->setupUi(this); + + connect(ui->button_Add, &QPushButton::clicked, this, &CustomAttributesFrame::addAttribute); + connect(ui->button_Delete, &QPushButton::clicked, this, &CustomAttributesFrame::deleteAttribute); + connect(ui->tableWidget, &CustomAttributesTable::itemSelectionChanged, this, &CustomAttributesFrame::updateDeleteButton); + connect(ui->tableWidget, &CustomAttributesTable::edited, this, &CustomAttributesFrame::updateDeleteButton); +} + +CustomAttributesFrame::~CustomAttributesFrame() { + delete ui; +} + +CustomAttributesTable* CustomAttributesFrame::table() const { + return ui->tableWidget; +} + +void CustomAttributesFrame::addAttribute() { + auto dialog = new CustomAttributesDialog(ui->tableWidget); + dialog->open(); +} + +void CustomAttributesFrame::deleteAttribute() { + ui->tableWidget->deleteSelectedAttributes(); +} + +void CustomAttributesFrame::updateDeleteButton() { + ui->button_Delete->setDisabled(ui->tableWidget->isSelectionEmpty()); +} diff --git a/src/ui/customattributestable.cpp b/src/ui/customattributestable.cpp index d87b3f8a..65381443 100644 --- a/src/ui/customattributestable.cpp +++ b/src/ui/customattributestable.cpp @@ -1,196 +1,176 @@ #include "customattributestable.h" #include "parseutil.h" -#include +#include "noscrollspinbox.h" #include -#include -#include -#include #include -#include -CustomAttributesTable::CustomAttributesTable(Event *event, QWidget *parent) : - QFrame(parent) +enum Column { + Key, + Value, + Count +}; + +enum DataRole { + JsonType = Qt::UserRole, + OriginalValue, +}; + +CustomAttributesTable::CustomAttributesTable(QWidget *parent) : + QTableWidget(parent) { - this->event = event; + this->setColumnCount(Column::Count); + this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + this->setHorizontalHeaderLabels(QStringList({"Key", "Value"})); + this->horizontalHeader()->setStretchLastSection(true); + this->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + this->horizontalHeader()->setVisible(false); + this->verticalHeader()->setVisible(false); - QVBoxLayout *layout = new QVBoxLayout(this); - QLabel *label = new QLabel("Custom Attributes"); - layout->addWidget(label); + connect(this, &QTableWidget::cellChanged, this, &CustomAttributesTable::edited); - QFrame *buttonsFrame = new QFrame(this); - buttonsFrame->setLayout(new QHBoxLayout()); - QPushButton *addButton = new QPushButton(this); - QPushButton *deleteButton = new QPushButton(this); - addButton->setText("Add"); - deleteButton->setText("Delete"); - buttonsFrame->layout()->addWidget(addButton); - buttonsFrame->layout()->addWidget(deleteButton); - buttonsFrame->layout()->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Fixed)); - buttonsFrame->layout()->setContentsMargins(0, 0, 0, 0); - layout->addWidget(buttonsFrame); - - this->table = new QTableWidget(this); - this->table->setColumnCount(3); - this->table->setHorizontalHeaderLabels(QStringList({"Type", "Key", "Value"})); - this->table->horizontalHeader()->setStretchLastSection(true); - layout->addWidget(this->table); - - QMap customValues = this->event->getCustomValues(); - for (auto it = customValues.begin(); it != customValues.end(); it++) - CustomAttributesTable::addAttribute(this->table, it.key(), it.value()); - - connect(addButton, &QPushButton::clicked, [=]() { - bool ok; - QJsonValue value = CustomAttributesTable::pickType(this, &ok); - if (ok){ - CustomAttributesTable::addAttribute(this->table, "", value, true); - this->event->setCustomValues(CustomAttributesTable::getAttributes(this->table)); - this->resizeVertically(); + // Key cells are uneditable, but users should be allowed to select one and press delete to remove the row. + // Adding the "Selectable" flag to the Key cell changes its appearance to match the Value cell, which + // makes it confusing that you can't edit the Key cell. To keep the uneditable appearance and allow + // deleting rows by selecting Key cells, we select the full row when a Key cell is selected. + connect(this, &QTableWidget::cellPressed, [this](int row, int column) { + if (column == Column::Key) { + this->selectRow(row); } }); - - connect(deleteButton, &QPushButton::clicked, [=]() { - if (CustomAttributesTable::deleteSelectedAttributes(this->table)) { - this->event->setCustomValues(CustomAttributesTable::getAttributes(this->table)); - this->resizeVertically(); - } - }); - - connect(this->table, &QTableWidget::cellChanged, [=]() { - this->event->setCustomValues(CustomAttributesTable::getAttributes(this->table)); - }); - - this->resizeVertically(); } -CustomAttributesTable::~CustomAttributesTable() -{ -} - -void CustomAttributesTable::resizeVertically() { - int horizontalHeaderHeight = this->table->horizontalHeader()->height(); - int rowHeight = 0; - for (int i = 0; i < this->table->rowCount(); i++) { - rowHeight += this->table->rowHeight(0); - } - int totalHeight = horizontalHeaderHeight + rowHeight; - if (this->table->rowCount() == 0) { - totalHeight += 1; - } else { - totalHeight += 2; - } - this->table->setMinimumHeight(totalHeight); - this->table->setMaximumHeight(totalHeight); -} - -const QMap CustomAttributesTable::getAttributes(QTableWidget * table) { +QMap CustomAttributesTable::getAttributes() const { QMap fields; - if (!table) return fields; - - for (int row = 0; row < table->rowCount(); row++) { - QString key = ""; - QTableWidgetItem *typeItem = table->item(row, 0); - QTableWidgetItem *keyItem = table->item(row, 1); - QTableWidgetItem *valueItem = table->item(row, 2); - - if (keyItem) key = keyItem->text(); - if (key.isEmpty() || !typeItem || !valueItem) - continue; - - // Read from the table data which JSON type to save the value as - QJsonValue::Type type = static_cast(typeItem->data(Qt::UserRole).toInt()); - QJsonValue value; - switch (type) - { - case QJsonValue::String: - value = QJsonValue(valueItem->text()); - break; - case QJsonValue::Double: - value = QJsonValue(valueItem->text().toInt()); - break; - case QJsonValue::Bool: - value = QJsonValue(valueItem->checkState() == Qt::Checked); - break; - default: - // All other types will just be preserved - value = valueItem->data(Qt::UserRole).toJsonValue(); - break; - } - fields[key] = value; + for (int row = 0; row < this->rowCount(); row++) { + auto keyValuePair = this->getAttribute(row); + if (!keyValuePair.first.isEmpty()) + fields[keyValuePair.first] = keyValuePair.second; } return fields; } -QJsonValue CustomAttributesTable::pickType(QWidget * parent, bool * ok) { - const QMap valueTypes = { - {"String", QJsonValue(QString(""))}, - {"Number", QJsonValue(0)}, - {"Boolean", QJsonValue(false)}, - }; - QStringList typeNames = valueTypes.keys(); - QString selection = QInputDialog::getItem(parent, "", "Choose Value Type", typeNames, typeNames.indexOf("String"), false, ok); - return valueTypes.value(selection); +QPair CustomAttributesTable::getAttribute(int row) const { + auto keyItem = this->item(row, Column::Key); + if (!keyItem) + return {}; + + // Read from the table data which JSON type to save the value as + QJsonValue::Type type = static_cast(keyItem->data(DataRole::JsonType).toInt()); + + QJsonValue value; + if (type == QJsonValue::String) { + value = QJsonValue(this->item(row, Column::Value)->text()); + } else if (type == QJsonValue::Double) { + auto spinBox = static_cast(this->cellWidget(row, Column::Value)); + value = QJsonValue(spinBox->value()); + } else if (type == QJsonValue::Bool) { + value = QJsonValue(this->item(row, Column::Value)->checkState() == Qt::Checked); + } else { + // All other types will just be preserved + value = this->item(row, Column::Value)->data(DataRole::OriginalValue).toJsonValue(); + } + + return {keyItem->text(), value}; } -void CustomAttributesTable::addAttribute(QTableWidget * table, QString key, QJsonValue value, bool isNew) { - if (!table) return; - QTableWidgetItem * valueItem; +int CustomAttributesTable::addAttribute(const QString &key, const QJsonValue &value) { + // Stop 'edited' signals from being emitted before we finish creating the new table data. + const QSignalBlocker blocker(this); + + // Certain key names cannot be used (if they would overwrite a field used outside this table) + if (m_restrictedKeys.contains(key)) + return -1; + + // Overwrite existing key (if present) + if (m_keys.contains(key)) + this->removeAttribute(key); + + // Add new row + int rowIndex = this->rowCount(); + this->insertRow(rowIndex); + QJsonValue::Type type = value.type(); - switch (type) - { - case QJsonValue::String: - case QJsonValue::Double: - valueItem = new QTableWidgetItem(ParseUtil::jsonToQString(value)); + + // Add key name to table + auto keyItem = new QTableWidgetItem(key); + keyItem->setFlags(Qt::ItemIsEnabled); + keyItem->setData(DataRole::JsonType, type); // Record the type for writing to the file + keyItem->setTextAlignment(Qt::AlignCenter); + keyItem->setToolTip(key); // Display name as tool tip in case it's too long to see in the cell + this->setItem(rowIndex, Column::Key, keyItem); + + // Add value to table + switch (type) { + case QJsonValue::String: { + // Add a regular cell item for editing text + this->setItem(rowIndex, Column::Value, new QTableWidgetItem(ParseUtil::jsonToQString(value))); break; - case QJsonValue::Bool: - valueItem = new QTableWidgetItem(""); + } case QJsonValue::Double: { + // Add a spin box for editing number values + auto spinBox = new NoScrollSpinBox(this); + spinBox->setMinimum(INT_MIN); + spinBox->setMaximum(INT_MAX); + spinBox->setValue(ParseUtil::jsonToInt(value)); + // This connection will be handled by QTableWidget::cellChanged for other cell types + connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, &CustomAttributesTable::edited); + this->setCellWidget(rowIndex, Column::Value, spinBox); + break; + } case QJsonValue::Bool: { + // Add a checkable cell item for editing bools + auto valueItem = new QTableWidgetItem(""); valueItem->setCheckState(value.toBool() ? Qt::Checked : Qt::Unchecked); valueItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); + this->setItem(rowIndex, Column::Value, valueItem); break; - default: - valueItem = new QTableWidgetItem("This value cannot be edited from this table"); - valueItem->setFlags(Qt::ItemIsSelectable); - valueItem->setData(Qt::UserRole, value); // Preserve the value for writing to the file + } default: { + // Arrays, objects, or null/undefined values cannot be edited + auto valueItem = new QTableWidgetItem("This value cannot be edited from this table"); + valueItem->setFlags(Qt::NoItemFlags); + valueItem->setData(DataRole::OriginalValue, value); // Preserve the value for writing to the file + this->setItem(rowIndex, Column::Value, valueItem); break; - } + }} + m_keys.insert(key); - const QHash typeToName = { - {QJsonValue::Bool, "Bool"}, - {QJsonValue::Double, "Number"}, - {QJsonValue::String, "String"}, - {QJsonValue::Array, "Array"}, - {QJsonValue::Object, "Object"}, - {QJsonValue::Null, "Null"}, - {QJsonValue::Undefined, "Null"}, - }; - QTableWidgetItem * typeItem = new QTableWidgetItem(typeToName[type]); - typeItem->setFlags(Qt::ItemIsEnabled); - typeItem->setData(Qt::UserRole, type); // Record the type for writing to the file - typeItem->setTextAlignment(Qt::AlignCenter); + return rowIndex; +} - int rowIndex = table->rowCount(); - table->insertRow(rowIndex); - table->setItem(rowIndex, 0, typeItem); - table->setItem(rowIndex, 1, new QTableWidgetItem(key)); - table->setItem(rowIndex, 2, valueItem); +// For the user adding an attribute by interacting with the table +void CustomAttributesTable::addNewAttribute(const QString &key, const QJsonValue &value) { + int row = this->addAttribute(key, value); + if (row < 0) return; + this->resizeVertically(); + this->selectRow(row); + emit this->edited(); +} - if (isNew) { - valueItem->setText(""); // Erase the "0" in new numbers - table->selectRow(rowIndex); +// For programmatically populating the table +void CustomAttributesTable::setAttributes(const QMap &attributes) { + m_keys.clear(); + this->setRowCount(0); // Clear old values + for (auto it = attributes.cbegin(); it != attributes.cend(); it++) + this->addAttribute(it.key(), it.value()); + this->resizeVertically(); +} + +void CustomAttributesTable::removeAttribute(const QString &key) { + for (int row = 0; row < this->rowCount(); row++) { + auto keyItem = this->item(row, Column::Key); + if (keyItem && keyItem->text() == key) { + m_keys.remove(key); + this->removeRow(row); + break; + } } } -bool CustomAttributesTable::deleteSelectedAttributes(QTableWidget * table) { - if (!table) +bool CustomAttributesTable::deleteSelectedAttributes() { + if (this->isEmpty()) return false; - int rowCount = table->rowCount(); - if (rowCount <= 0) - return false; - - QModelIndexList indexList = table->selectionModel()->selectedIndexes(); + QModelIndexList indexList = this->selectionModel()->selectedIndexes(); QList persistentIndexes; - for (QModelIndex index : indexList) { + for (const auto &index : indexList) { QPersistentModelIndex persistentIndex(index); persistentIndexes.append(persistentIndex); } @@ -198,12 +178,51 @@ bool CustomAttributesTable::deleteSelectedAttributes(QTableWidget * table) { if (persistentIndexes.isEmpty()) return false; - for (QPersistentModelIndex index : persistentIndexes) { - table->removeRow(index.row()); + for (const auto &index : persistentIndexes) { + auto row = index.row(); + auto item = this->item(row, Column::Key); + if (item) m_keys.remove(item->text()); + this->removeRow(row); } + this->resizeVertically(); - if (table->rowCount() > 0) { - table->selectRow(0); + if (this->rowCount() > 0) { + this->selectRow(0); } + emit this->edited(); return true; } + +void CustomAttributesTable::resizeVertically() { + int height = 0; + if (this->isEmpty()) { + // Hide header when table is empty + this->horizontalHeader()->setVisible(false); + } else { + for (int i = 0; i < this->rowCount(); i++) + height += this->rowHeight(i); + + // Account for header and horizontal scroll bar + this->horizontalHeader()->setVisible(true); + height += this->horizontalHeader()->height(); + if (this->horizontalScrollBar()->isVisible()) + height += this->horizontalScrollBar()->height(); + height += 2; // Border + } + + this->setMinimumHeight(height); + this->setMaximumHeight(height); +} + +void CustomAttributesTable::resizeEvent(QResizeEvent *event) { + QTableWidget::resizeEvent(event); + this->resizeVertically(); +} + +bool CustomAttributesTable::isEmpty() const { + return this->rowCount() <= 0; +} + +bool CustomAttributesTable::isSelectionEmpty() const { + return this->selectedIndexes().isEmpty(); +} diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index cb06fea1..61794c41 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -1,5 +1,5 @@ #include "eventframes.h" -#include "customattributestable.h" +#include "customattributesframe.h" #include "editcommands.h" #include "draggablepixmapitem.h" @@ -97,8 +97,9 @@ void EventFrame::setup() { } void EventFrame::initCustomAttributesTable() { - CustomAttributesTable *customAttributes = new CustomAttributesTable(this->event, this); - this->layout_contents->addWidget(customAttributes); + this->custom_attributes = new CustomAttributesFrame(this); + this->custom_attributes->table()->setRestrictedKeys(this->event->getExpectedFields()); + this->layout_contents->addWidget(this->custom_attributes); } void EventFrame::connectSignals(MainWindow *) { @@ -128,6 +129,12 @@ void EventFrame::connectSignals(MainWindow *) { this->event->setZ(value); this->event->modify(); }); + + this->custom_attributes->disconnect(); + connect(this->custom_attributes->table(), &CustomAttributesTable::edited, [this]() { + this->event->setCustomAttributes(this->custom_attributes->table()->getAttributes()); + this->event->modify(); + }); } void EventFrame::initialize() { @@ -139,6 +146,8 @@ void EventFrame::initialize() { this->spinner_y->setValue(this->event->getY()); this->spinner_z->setValue(this->event->getZ()); + this->custom_attributes->table()->setAttributes(this->event->getCustomAttributes()); + this->label_icon->setPixmap(this->event->getPixmap()); } From ecb2825e7d13befa1df495a8f28fd20f0c425af8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 31 Jan 2025 11:11:20 -0500 Subject: [PATCH 146/364] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d26e1b..84361c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add ``Duplicate Map`` / ``Duplicate Layout`` options, accessible by right-clicking a map or layout in the map list. - Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access. - Add a `Close Project` option -- Add charts to the `Wild Pokémon` tab that show species and level distributions. +- Add a search button to the `Wild Pokémon` tab that shows the encounter data for a species across all maps. +- Add charts to the `Wild Pokémon` tab that show species and level distributions for the current map. - Add options for customizing the map grid under `View -> Grid Settings`. - Add an option to display a dividing line between tilesets in the Tileset Editor. - An alert will be displayed when attempting to open a seemingly invalid project. From 2aa2f8dbd4fff1f289403f9cedd5bcb40f2883db Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 5 Feb 2025 11:05:24 -0500 Subject: [PATCH 147/364] Fix shortcuts editor crash --- CHANGELOG.md | 1 + include/ui/shortcutseditor.h | 4 ++-- src/ui/shortcutseditor.cpp | 12 ++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84361c22..b3b225d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix the values for some config fields shuffling their order every save. - Fix some problems with tileset detection when importing maps from AdvanceMap. - Fix certain input fields allowing invalid identifiers, like names starting with numbers. +- Fix crash in the Shortcuts Editor when applying changes after closing certain windows. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/include/ui/shortcutseditor.h b/include/ui/shortcutseditor.h index 31b0279e..e44c8896 100644 --- a/include/ui/shortcutseditor.h +++ b/include/ui/shortcutseditor.h @@ -35,9 +35,9 @@ signals: private: Ui::ShortcutsEditor *ui; QWidget *main_container; - QMultiMap labels_objects; + QMultiMap> labels_objects; QHash contexts_layouts; - QHash multiKeyEdits_objects; + QHash> multiKeyEdits_objects; void parseObjectList(const QObjectList &objectList); QString getLabel(const QObject *object) const; diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index 36bc30d0..d0ef943d 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -2,6 +2,7 @@ #include "ui_shortcutseditor.h" #include "config.h" #include "multikeyedit.h" +#include "message.h" #include "log.h" #include @@ -46,6 +47,15 @@ void ShortcutsEditor::setShortcutableObjects(const QObjectList &shortcutableObje void ShortcutsEditor::saveShortcuts() { QMultiMap objects_keySequences; for (auto it = multiKeyEdits_objects.cbegin(); it != multiKeyEdits_objects.cend(); ++it) { + if (!it.value()) { + // Some shortcuts cannot be saved. Pointers in the object map can become null if they are + // deleted externally while the shortcuts editor is open. Ideally we should try to restore + // the original object so the shortcut can be saved. Alternatively this could generally be + // prevented by making the shortcuts editor modal. For now, saving these shortcuts is skipped, + // and we warn the user that this happened. + ErrorMessage::show(QStringLiteral("Some shortcuts failed to save. Please close the Shortcuts Editor and retry."), this); + return; + } if (it.key()->keySequences().isEmpty()) objects_keySequences.insert(it.value(), QKeySequence()); for (auto keySequence : it.key()->keySequences()) @@ -60,6 +70,7 @@ void ShortcutsEditor::saveShortcuts() { // Restores default shortcuts but doesn't save until Apply or OK is clicked. void ShortcutsEditor::resetShortcuts() { for (auto it = multiKeyEdits_objects.begin(); it != multiKeyEdits_objects.end(); ++it) { + if (!it.value()) continue; it.key()->blockSignals(true); const auto defaults = shortcutsConfig.defaultShortcuts(it.value()); it.key()->setKeySequences(defaults); @@ -90,6 +101,7 @@ bool ShortcutsEditor::stringPropertyIsNotEmpty(const QObject *object, const char void ShortcutsEditor::populateMainContainer() { for (auto object : labels_objects) { + if (!object) continue; const auto shortcutContext = getShortcutContext(object); if (!contexts_layouts.contains(shortcutContext)) addNewContextGroup(shortcutContext); From 05c07e5a007a64f39743f0d6ed1a0b87d5cb18bf Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 5 Feb 2025 13:48:38 -0500 Subject: [PATCH 148/364] Keep metatile images on separate rows, fix metatile usage count --- CHANGELOG.md | 2 + include/ui/metatileselector.h | 1 + include/ui/tileseteditormetatileselector.h | 1 + src/project.cpp | 10 +- src/ui/metatileselector.cpp | 16 ++- src/ui/tileseteditor.cpp | 33 +++--- src/ui/tileseteditormetatileselector.cpp | 112 ++++++++++----------- 7 files changed, 85 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b225d6..41e7f5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Unrecognized map names in Event or Connections data will no longer be overwritten. - Map names and ``MAP_NAME`` constants are no longer required to match. - Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. +- Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. @@ -76,6 +77,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix some problems with tileset detection when importing maps from AdvanceMap. - Fix certain input fields allowing invalid identifiers, like names starting with numbers. - Fix crash in the Shortcuts Editor when applying changes after closing certain windows. +- Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index ae11c58e..fba0993d 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -79,6 +79,7 @@ private: bool positionIsValid(const QPoint &pos) const; bool selectionIsValid(); void hoverChanged(); + int numPrimaryMetatilesRounded() const; signals: void hoveredMetatileSelectionChanged(uint16_t); diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 760da8e4..afc77ffe 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -51,6 +51,7 @@ private: void drawCounts(); QImage buildAllMetatilesImage(); QImage buildImage(int metatileIdStart, int numMetatiles); + int numPrimaryMetatilesRounded() const; signals: void hoveredMetatileChanged(uint16_t); diff --git a/src/project.cpp b/src/project.cpp index 712ee87f..5912ad4c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1056,7 +1056,7 @@ bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); - logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_primary_label).arg(defaultTileset)); + logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset)); layout->tileset_primary_label = defaultTileset; layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { @@ -1068,7 +1068,7 @@ bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_secondary = getTileset(layout->tileset_secondary_label); if (!layout->tileset_secondary) { QString defaultTileset = this->getDefaultSecondaryTilesetLabel(); - logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_secondary_label).arg(defaultTileset)); + logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset)); layout->tileset_secondary_label = defaultTileset; layout->tileset_secondary = getTileset(layout->tileset_secondary_label); if (!layout->tileset_secondary) { @@ -1137,7 +1137,8 @@ bool Project::loadBlockdata(Layout *layout) { layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) { - logWarn(QString("Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata.") + logWarn(QString("%1 blockdata length %2 does not match dimensions %3x%4 (should be %5). Resizing blockdata.") + .arg(layout->name) .arg(layout->blockdata.count()) .arg(layout->getWidth()) .arg(layout->getHeight()) @@ -1174,7 +1175,8 @@ bool Project::loadLayoutBorder(Layout *layout) { int borderLength = layout->getBorderWidth() * layout->getBorderHeight(); if (layout->border.count() != borderLength) { - logWarn(QString("Layout border blockdata length %1 must be %2. Resizing border blockdata.") + logWarn(QString("%1 border blockdata length %2 must be %3. Resizing border blockdata.") + .arg(layout->name) .arg(layout->border.count()) .arg(borderLength)); layout->border.resize(borderLength); diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 1214e289..b8bafefc 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -9,12 +9,17 @@ QPoint MetatileSelector::getSelectionDimensions() { return SelectablePixmapItem::getSelectionDimensions(); } +int MetatileSelector::numPrimaryMetatilesRounded() const { + // We round up the number of primary metatiles to keep the tilesets on separate rows. + return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; +} + void MetatileSelector::draw() { if (!this->primaryTileset || !this->secondaryTileset) { this->setPixmap(QPixmap()); } - int primaryLength = this->primaryTileset->numMetatiles(); + int primaryLength = this->numPrimaryMetatilesRounded(); int length_ = primaryLength + this->secondaryTileset->numMetatiles(); int height_ = length_ / this->numMetatilesWide; if (length_ % this->numMetatilesWide != 0) { @@ -149,7 +154,7 @@ void MetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void MetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPoint pos = this->getCellPos(event->pos()); - if (!positionIsValid(pos) || this->cellPos == pos) + if (this->cellPos == pos) return; this->cellPos = pos; @@ -199,10 +204,11 @@ void MetatileSelector::updateExternalSelectedMetatiles() { uint16_t MetatileSelector::getMetatileId(int x, int y) const { int index = y * this->numMetatilesWide + x; - if (index < this->primaryTileset->numMetatiles()) { + int numPrimary = this->numPrimaryMetatilesRounded(); + if (index < numPrimary) { return static_cast(index); } else { - return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles()); + return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary); } } @@ -215,7 +221,7 @@ QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) { int index = metatileId < Project::getNumMetatilesPrimary() ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles(); + : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e05750dc..23942a8a 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -29,13 +29,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) this->tileYFlip = ui->checkBox_yFlip->isChecked(); this->paletteId = ui->spinBox_paletteSelector->value(); - // TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8. - // If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing - // metatile groups the user may have designed) so this should be fixed by filling the primary metatiles - // image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is. - ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false); - ui->actionShow_Tileset_Divider->setVisible(false); - + ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->spinBox_paletteSelector->setMinimum(0); ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this)); @@ -759,7 +753,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() { QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowTitle("Change Number of Metatiles"); - dialog.setWindowModality(Qt::NonModal); + dialog.setWindowModality(Qt::WindowModal); QFormLayout form(&dialog); @@ -1051,20 +1045,19 @@ void TilesetEditor::countMetatileUsage() { // do not double count metatileSelector->usedMetatiles.fill(0); - for (auto layout : this->project->mapLayouts.values()) { - bool usesPrimary = false; - bool usesSecondary = false; + for (auto layout : this->project->mapLayouts) { + // It's possible for a layout's tileset labels to change if they are invalid, + // so we need to load all the tilesets even if they aren't the tileset we're looking for. + // Otherwise the metatile usage counts may change because the layouts with invalid tilesets + // were updated to use a tileset we were looking for. + this->project->loadLayoutTilesets(layout); - if (layout->tileset_primary_label == this->primaryTileset->name) { - usesPrimary = true; - } - - if (layout->tileset_secondary_label == this->secondaryTileset->name) { - usesSecondary = true; - } + bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name); + bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name); if (usesPrimary || usesSecondary) { - this->project->loadLayout(layout); + if (!this->project->loadLayout(layout)) + continue; // for each block in the layout, mark in the vector that it is used for (int i = 0; i < layout->blockdata.length(); i++) { @@ -1097,9 +1090,9 @@ void TilesetEditor::countTileUsage() { QSet secondaryTilesets; for (auto layout : this->project->mapLayouts.values()) { + this->project->loadLayoutTilesets(layout); if (layout->tileset_primary_label == this->primaryTileset->name || layout->tileset_secondary_label == this->secondaryTileset->name) { - this->project->loadLayoutTilesets(layout); // need to check metatiles if (layout->tileset_primary && layout->tileset_secondary) { primaryTilesets.insert(layout->tileset_primary); diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 6175a923..07e50bba 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -22,11 +22,16 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) { } int TilesetEditorMetatileSelector::numRows() { - return this->numRows(this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles()); + return this->numRows(this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles()); +} + +int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const { + // We round up the number of primary metatiles to keep the tilesets on separate rows. + return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; } QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() { - return this->buildImage(0, this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles()); + return this->buildImage(0, this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles()); } QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() { @@ -39,11 +44,11 @@ QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() { QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) { int numMetatilesHigh = this->numRows(numMetatiles); - int numPrimary = this->primaryTileset->numMetatiles(); + int numPrimary = this->numPrimaryMetatilesRounded(); int maxPrimary = Project::getNumMetatilesPrimary(); bool includesPrimary = metatileIdStart < maxPrimary; - QImage image(this->numMetatilesWide * 32, numMetatilesHigh * 32, QImage::Format_RGBA8888); + QImage image(this->numMetatilesWide * this->cellWidth, numMetatilesHigh * this->cellHeight, QImage::Format_RGBA8888); image.fill(Qt::magenta); QPainter painter(&image); for (int i = 0; i < numMetatiles; i++) { @@ -57,10 +62,10 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet this->layout->metatileLayerOrder, this->layout->metatileLayerOpacity, true) - .scaled(32, 32); + .scaled(this->cellWidth, this->cellHeight); int map_y = i / this->numMetatilesWide; int map_x = i % this->numMetatilesWide; - QPoint metatile_origin = QPoint(map_x * 32, map_y * 32); + QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight); painter.drawImage(metatile_origin, metatile_image); } painter.end(); @@ -107,10 +112,11 @@ uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() { uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) { int index = y * this->numMetatilesWide + x; - if (index < this->primaryTileset->numMetatiles()) { + int numPrimary = numPrimaryMetatilesRounded(); + if (index < numPrimary) { return static_cast(index); } else { - return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles()); + return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary); } } @@ -156,7 +162,7 @@ QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) { } int index = metatileId < Project::getNumMetatilesPrimary() ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles(); + : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } @@ -176,12 +182,12 @@ void TilesetEditorMetatileSelector::drawGrid() { const int numColumns = this->numMetatilesWide; const int numRows = this->numRows(); for (int column = 1; column < numColumns; column++) { - int x = column * 32; - painter.drawLine(x, 0, x, numRows * 32); + int x = column * this->cellWidth; + painter.drawLine(x, 0, x, numRows * this->cellHeight); } for (int row = 1; row < numRows; row++) { - int y = row * 32; - painter.drawLine(0, y, numColumns * 32, y); + int y = row * this->cellHeight; + painter.drawLine(0, y, numColumns * this->cellWidth, y); } painter.end(); this->setPixmap(pixmap); @@ -191,12 +197,12 @@ void TilesetEditorMetatileSelector::drawDivider() { if (!this->showDivider) return; - const int y = this->numRows(this->primaryTileset->numMetatiles()) * 32; + const int y = this->numRows(this->numPrimaryMetatilesRounded()) * this->cellHeight; QPixmap pixmap = this->pixmap(); QPainter painter(&pixmap); painter.setPen(Qt::white); - painter.drawLine(0, y, this->numMetatilesWide * 32, y); + painter.drawLine(0, y, this->numMetatilesWide * this->cellWidth, y); painter.end(); this->setPixmap(pixmap); } @@ -212,7 +218,7 @@ void TilesetEditorMetatileSelector::drawFilters() { void TilesetEditorMetatileSelector::drawUnused() { // setup the circle with a line through it image to layer above unused metatiles - QPixmap redX(32, 32); + QPixmap redX(this->cellWidth, this->cellHeight); redX.fill(Qt::transparent); QPen whitePen(Qt::white); @@ -223,21 +229,21 @@ void TilesetEditorMetatileSelector::drawUnused() { QPainter oPainter(&redX); oPainter.setPen(whitePen); - oPainter.drawEllipse(QRect(1, 1, 30, 30)); + oPainter.drawEllipse(QRect(1, 1, this->cellWidth - 2, this->cellHeight - 2)); oPainter.setPen(pinkPen); - oPainter.drawEllipse(QRect(2, 2, 28, 28)); - oPainter.drawEllipse(QRect(3, 3, 26, 26)); + oPainter.drawEllipse(QRect(2, 2, this->cellWidth - 4, this->cellHeight - 4)); + oPainter.drawEllipse(QRect(3, 3, this->cellWidth - 6, this->cellHeight - 6)); oPainter.setPen(whitePen); - oPainter.drawEllipse(QRect(4, 4, 24, 24)); + oPainter.drawEllipse(QRect(4, 4, this->cellHeight - 8, this->cellHeight - 8)); whitePen.setWidth(5); oPainter.setPen(whitePen); - oPainter.drawLine(0, 0, 31, 31); + oPainter.drawLine(0, 0, this->cellWidth - 1, this->cellHeight - 1); pinkPen.setWidth(3); oPainter.setPen(pinkPen); - oPainter.drawLine(2, 2, 29, 29); + oPainter.drawLine(2, 2, this->cellWidth - 3, this->cellHeight - 3); oPainter.end(); @@ -247,19 +253,13 @@ void TilesetEditorMetatileSelector::drawUnused() { QPainter unusedPainter(&metatilesPixmap); unusedPainter.setOpacity(0.5); - int primaryLength = this->primaryTileset->numMetatiles(); - int length_ = primaryLength + this->secondaryTileset->numMetatiles(); - - for (int i = 0; i < length_; i++) { - int tile = i; - if (i >= primaryLength) { - tile += Project::getNumMetatilesPrimary() - primaryLength; - } - if (!usedMetatiles[tile]) { - unusedPainter.drawPixmap((i % 8) * 32, (i / 8) * 32, redX); - } + for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) { + if (this->usedMetatiles.at(metatileId) || !Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + continue; + // Adjust position from center to top-left corner + QPoint pos = getMetatileIdCoordsOnWidget(metatileId) - QPoint(this->cellWidth / 2, this->cellHeight / 2); + unusedPainter.drawPixmap(pos.x(), pos.y(), redX); } - unusedPainter.end(); this->setPixmap(metatilesPixmap); @@ -268,38 +268,28 @@ void TilesetEditorMetatileSelector::drawUnused() { void TilesetEditorMetatileSelector::drawCounts() { QPen blackPen(Qt::black); blackPen.setWidth(1); - - QPixmap metatilesPixmap = this->pixmap(); - - QPainter countPainter(&metatilesPixmap); - countPainter.setPen(blackPen); - - for (int tile = 0; tile < this->usedMetatiles.size(); tile++) { - int count = usedMetatiles[tile]; - QString countText = QString::number(count); - if (count > 1000) countText = ">1k"; - countPainter.drawText((tile % 8) * 32, (tile / 8) * 32 + 32, countText); - } - - // write in white and black for contrast QPen whitePen(Qt::white); whitePen.setWidth(1); - countPainter.setPen(whitePen); - int primaryLength = this->primaryTileset->numMetatiles(); - int length_ = primaryLength + this->secondaryTileset->numMetatiles(); + QPixmap metatilesPixmap = this->pixmap(); + QPainter countPainter(&metatilesPixmap); - for (int i = 0; i < length_; i++) { - int tile = i; - if (i >= primaryLength) { - tile += Project::getNumMetatilesPrimary() - primaryLength; - } - int count = usedMetatiles[tile]; - QString countText = QString::number(count); - if (count > 1000) countText = ">1k"; - countPainter.drawText((i % 8) * 32 + 1, (i / 8) * 32 + 32 - 1, countText); + for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) { + if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + continue; + + int count = this->usedMetatiles.at(metatileId); + QString countText = (count > 1000) ? QStringLiteral(">1k") : QString::number(count); + + // Adjust position from center to bottom-left corner + QPoint pos = getMetatileIdCoordsOnWidget(metatileId) + QPoint(-(this->cellWidth / 2), this->cellHeight / 2); + + // write in black and white for contrast + countPainter.setPen(blackPen); + countPainter.drawText(pos.x(), pos.y(), countText); + countPainter.setPen(whitePen); + countPainter.drawText(pos.x() + 1, pos.y() - 1, countText); } - countPainter.end(); this->setPixmap(metatilesPixmap); From 52a900b85d4495a2cdf1d7b9461a0faf3a30fa24 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 5 Feb 2025 15:31:14 -0500 Subject: [PATCH 149/364] Hide map settings when exporting layout images --- forms/mapimageexporter.ui | 2 +- src/ui/mapimageexporter.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/forms/mapimageexporter.ui b/forms/mapimageexporter.ui index 933aab67..322793c9 100644 --- a/forms/mapimageexporter.ui +++ b/forms/mapimageexporter.ui @@ -29,7 +29,7 @@ - + Map diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 4596ba36..0237795a 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -55,6 +55,12 @@ MapImageExporter::MapImageExporter(QWidget *parent, Editor *editor, ImageExporte ui->comboBox_MapSelection->addItems(editor->project->mapNames); ui->comboBox_MapSelection->setCurrentText(m_map->name()); ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + } else { + // Some settings only apply to maps. When exporting an image in layout-only mode we hide them. + ui->comboBox_MapSelection->setVisible(false); + ui->label_MapSelection->setVisible(false); + ui->groupBox_Events->setVisible(false); + ui->groupBox_Connections->setVisible(false); } connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage); From be02424f1c1d60c0c721640c3334a77a6e67b73f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 5 Feb 2025 15:59:13 -0500 Subject: [PATCH 150/364] Reduce diff noise when saving maps --- CHANGELOG.md | 1 + src/lib/orderedjson.cpp | 4 ++++ src/project.cpp | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b225d6..0c1c70be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - `Export Map Stitch Image` now shows a preview of the full image, not just the current map. - Maps and layouts were internally separated. - Unrecognized map names in Event or Connections data will no longer be overwritten. +- Reduced diff noise when saving maps. - Map names and ``MAP_NAME`` constants are no longer required to match. - Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index d7dd35e8..e1a600aa 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -116,6 +116,10 @@ static void dump(const QString &value, QString &out, int *indent, bool isKey = f static void dump(const Json::array &values, QString &out, int *indent) { bool first = true; if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); + if (values.empty()) { + out += "[]"; + return; + } out += "[\n"; *indent += 1; for (const auto &value : values) { diff --git a/src/project.cpp b/src/project.cpp index 712ee87f..94fe5099 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1297,7 +1297,7 @@ void Project::saveMap(Map *map) { } mapObj["connections"] = connectionsArr; } else { - mapObj["connections"] = QJsonValue::Null; + mapObj["connections"] = OrderedJson(); } if (map->sharedEventsMap().isEmpty()) { From 7173503f5905dfa5c6cb04c4b8ed83952d3f31b8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 5 Feb 2025 16:17:06 -0500 Subject: [PATCH 151/364] Fix Qt5 build, misc warnings --- include/core/tileset.h | 2 +- include/lib/fex/array_value.h | 1 + include/ui/encountertablemodel.h | 4 ++-- include/ui/mapheaderform.h | 4 ++-- include/ui/newlocationdialog.h | 2 +- src/core/tileset.cpp | 3 ++- src/ui/wildmonchart.cpp | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/core/tileset.h b/include/core/tileset.h index 999e731d..32d18858 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -80,7 +80,7 @@ public: Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); } void clearMetatiles(); - void resizeMetatiles(unsigned int newNumMetatiles); + void resizeMetatiles(int newNumMetatiles); int numMetatiles() const { return m_metatiles.length(); } private: diff --git a/include/lib/fex/array_value.h b/include/lib/fex/array_value.h index 642cc129..3339d21a 100644 --- a/include/lib/fex/array_value.h +++ b/include/lib/fex/array_value.h @@ -96,6 +96,7 @@ namespace fex case Type::kValuePair: return pair_.first + " = " + pair_.second->ToString() + "\n"; } + return ""; } static ArrayValue Empty() diff --git a/include/ui/encountertablemodel.h b/include/ui/encountertablemodel.h index 6fe54a91..157e0771 100644 --- a/include/ui/encountertablemodel.h +++ b/include/ui/encountertablemodel.h @@ -30,7 +30,7 @@ public: WildMonInfo encounterData() const { return m_monInfo; } EncounterField encounterField() const { return m_encounterField; } - QList percentages() const { return m_slotPercentages; } + QVector percentages() const { return m_slotPercentages; } private: int m_numRows = 0; @@ -38,7 +38,7 @@ private: WildMonInfo m_monInfo; EncounterField m_encounterField; QMap m_groupNames; - QList m_slotPercentages; + QVector m_slotPercentages; signals: void edited(); diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 8f9adda8..79f4f6c8 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -9,9 +9,9 @@ #include #include -#include "mapheader.h" -class Project; +#include "mapheader.h" +#include "project.h" namespace Ui { class MapHeaderForm; diff --git a/include/ui/newlocationdialog.h b/include/ui/newlocationdialog.h index 33af5c9c..a98b2d61 100644 --- a/include/ui/newlocationdialog.h +++ b/include/ui/newlocationdialog.h @@ -5,7 +5,7 @@ #include #include -class Project; +#include "project.h" namespace Ui { class NewLocationDialog; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index e79e7706..20590369 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -83,7 +83,8 @@ void Tileset::addMetatile(Metatile* metatile) { m_metatiles.append(metatile); } -void Tileset::resizeMetatiles(unsigned int newNumMetatiles) { +void Tileset::resizeMetatiles(int newNumMetatiles) { + if (newNumMetatiles < 0) newNumMetatiles = 0; while (m_metatiles.length() > newNumMetatiles) { delete m_metatiles.takeLast(); } diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 80c41f81..c27e7968 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -109,7 +109,7 @@ void WildMonChart::readTable() { } // Read data from the table, combining data for duplicate entries - const QList tableFrequencies = this->table->percentages(); + const QVector tableFrequencies = this->table->percentages(); const QVector tablePokemon = this->table->encounterData().wildPokemon; const int numRows = qMin(tableFrequencies.length(), tablePokemon.length()); const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); From 54e41b0c201b1e23c6fffccaa95382a9f2990329 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 12 Nov 2024 01:09:43 -0500 Subject: [PATCH 152/364] redesign layout dimension change window --- forms/resizelayoutpopup.ui | 276 +++++++++++++++++++++++++++++++++ include/core/editcommands.h | 11 +- include/core/maplayout.h | 1 + include/ui/movablerect.h | 64 ++++++-- include/ui/noscrollspinbox.h | 2 + include/ui/resizelayoutpopup.h | 106 +++++++++++++ porymap.pro | 7 +- src/core/editcommands.cpp | 21 +-- src/core/maplayout.cpp | 28 ++++ src/mainwindow.cpp | 96 +++--------- src/ui/movablerect.cpp | 158 ++++++++++++++++++- src/ui/noscrollspinbox.cpp | 5 + src/ui/resizelayoutpopup.cpp | 186 ++++++++++++++++++++++ 13 files changed, 854 insertions(+), 107 deletions(-) create mode 100644 forms/resizelayoutpopup.ui create mode 100644 include/ui/resizelayoutpopup.h create mode 100644 src/ui/resizelayoutpopup.cpp diff --git a/forms/resizelayoutpopup.ui b/forms/resizelayoutpopup.ui new file mode 100644 index 00000000..c98fe10f --- /dev/null +++ b/forms/resizelayoutpopup.ui @@ -0,0 +1,276 @@ + + + ResizeLayoutPopup + + + + 0 + 0 + 598 + 378 + + + + Dialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Resize Layout + + + Qt::AlignCenter + + + 8 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + true + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Width + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 64 + 0 + + + + + + + + Height + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 64 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Border Width + + + + + + + + 64 + 0 + + + + + + + + Border Height + + + + + + + + 64 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + + + buttonBox + accepted() + ResizeLayoutPopup + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ResizeLayoutPopup + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 5bf99784..83e33d41 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -8,6 +8,7 @@ #include #include #include +#include class Map; class Layout; @@ -203,7 +204,12 @@ private: /// Implements a command to commit a map or border resize action. class ResizeLayout : public QUndoCommand { public: - ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, + // ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, + // const Blockdata &oldMetatiles, const Blockdata &newMetatiles, + // QSize oldBorderDimensions, QSize newBorderDimensions, + // const Blockdata &oldBorder, const Blockdata &newBorder, + // QUndoCommand *parent = nullptr); + ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -220,8 +226,7 @@ private: int oldLayoutWidth; int oldLayoutHeight; - int newLayoutWidth; - int newLayoutHeight; + QMargins newLayoutMargins; int oldBorderWidth; int oldBorderHeight; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 1579b4ad..f8ebe905 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -103,6 +103,7 @@ public: void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); + void adjustDimensions(QMargins margins, bool setNewBlockdata = true, bool enableScriptCallback = false); void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index efd85847..c5ca00ab 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -5,12 +5,13 @@ #include #include -class MovableRect : public QGraphicsItem + + +class MovableRect : public QGraphicsRectItem { public: MovableRect(bool *enabled, int width, int height, QRgb color); - QRectF boundingRect() const override - { + QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, -penWidth, @@ -18,21 +19,62 @@ public: 20 * 8 + penWidth * 2); } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override - { + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { if (!(*enabled)) return; painter->setPen(this->color); - painter->drawRect(x() - 2, y() - 2, this->width + 3, this->height + 3); + painter->drawRect(this->rect().x() - 2, this->rect().y() - 2, this->rect().width() + 3, this->rect().height() + 3); painter->setPen(QColor(0, 0, 0)); - painter->drawRect(x() - 3, y() - 3, this->width + 5, this->height + 5); - painter->drawRect(x() - 1, y() - 1, this->width + 1, this->height + 1); + painter->drawRect(this->rect().x() - 3, this->rect().y() - 3, this->rect().width() + 5, this->rect().height() + 5); + painter->drawRect(this->rect().x() - 1, this->rect().y() - 1, this->rect().width() + 1, this->rect().height() + 1); } void updateLocation(int x, int y); bool *enabled; -private: - int width; - int height; + +protected: QRgb color; }; + + +/// A MovableRect with the addition of being resizable. +class ResizableRect : public QObject, public MovableRect +{ + Q_OBJECT +public: + ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color); + + QRectF boundingRect() const override { + return QRectF(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + } + + QPainterPath shape() const override { + QPainterPath path; + path.addRect(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + path.addRect(this->rect() - QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + return path; + } + + void updatePosFromRect(QRect newPos); + +protected: + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + +private: + enum class Edge { None, Left, Right, Top, Bottom, TopLeft, BottomLeft, TopRight, BottomRight }; + ResizableRect::Edge detectEdge(int x, int y); + + // Variables for keeping state of original rect while resizing + ResizableRect::Edge clickedEdge = ResizableRect::Edge::None; + QPointF clickedPos = QPointF(); + QRect clickedRect; + + int lineWidth = 8; + +signals: + void rectUpdated(QRect rect); +}; + #endif // MOVABLERECT_H diff --git a/include/ui/noscrollspinbox.h b/include/ui/noscrollspinbox.h index 0cc11043..0615da5a 100644 --- a/include/ui/noscrollspinbox.h +++ b/include/ui/noscrollspinbox.h @@ -12,6 +12,8 @@ public: void wheelEvent(QWheelEvent *event) override; void focusOutEvent(QFocusEvent *event) override; + void setLineEditEnabled(bool enabled); + unsigned getActionId(); private: diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h new file mode 100644 index 00000000..de48c993 --- /dev/null +++ b/include/ui/resizelayoutpopup.h @@ -0,0 +1,106 @@ +#ifndef RESIZELAYOUTPOPUP_H +#define RESIZELAYOUTPOPUP_H + +#include +#include +#include +#include +#include + +class ResizableRect; +class Editor; +namespace Ui { + class ResizeLayoutPopup; +} + + + +/// Custom scene that paints its background a gray checkered pattern. +/// Additionally there is a definable "valid" area which will paint the checkerboard green inside. +class CheckeredBgScene : public QGraphicsScene { + Q_OBJECT + +public: + CheckeredBgScene(QObject *parent = nullptr); + void setValidRect(int x, int y, int width, int height) { + this->validRect = QRect(x * this->gridSize, y * this->gridSize, width * this->gridSize, height * this->gridSize); + } + void setValidRect(QRect rect) { + this->validRect = rect; + } + +protected: + void drawBackground(QPainter *painter, const QRectF &rect) override; + +private: + int gridSize = 16; // virtual pixels + QRect validRect = QRect(); +}; + + + +/// PixmapItem subclass which allows for creating a boundary which determine whether +/// the pixmap paints normally or with a black tint. +/// This item is movable and snaps on a 16x16 grid. +class BoundedPixmapItem : public QGraphicsPixmapItem { +public: + BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = nullptr); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override; + + void setBoundary(ResizableRect *rect) { this->boundary = rect; } + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + +private: + ResizableRect *boundary = nullptr; + QPointF clickedPos = QPointF(); +}; + + + +/// The main (modal) dialog window for resizing layout and border dimensions. +/// The dialog itself is minimal, and is connected to the parent widget's geometry. +class ResizeLayoutPopup : public QDialog +{ + Q_OBJECT + +public: + ResizeLayoutPopup(QWidget *parent, Editor *editor); + ~ResizeLayoutPopup(); + + void setupLayoutView(); + + void resetPosition(); + + QMargins getResult(); + QSize getBorderResult(); + +protected: + void moveEvent(QMoveEvent *) override { + // Prevent the dialog from being moved + this->resetPosition(); + } + + void resizeEvent(QResizeEvent *) override { + // Prevent the dialog from being resized + this->resetPosition(); + } + +private slots: + void on_spinBox_width_valueChanged(int value); + void on_spinBox_height_valueChanged(int value); + +private: + QWidget *parent = nullptr; + Editor *editor = nullptr; + + Ui::ResizeLayoutPopup *ui; + + ResizableRect *outline = nullptr; + BoundedPixmapItem *layoutPixmap = nullptr; + + QPointer scene = nullptr; +}; + +#endif // RESIZELAYOUTPOPUP_H diff --git a/porymap.pro b/porymap.pro index 76d19d21..aa3d7c0b 100644 --- a/porymap.pro +++ b/porymap.pro @@ -35,6 +35,7 @@ DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" SOURCES += src/core/advancemapparser.cpp \ src/core/block.cpp \ + src/ui/resizelayoutpopup.cpp \ src/core/bitpacker.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ @@ -257,7 +258,8 @@ HEADERS += include/core/advancemapparser.h \ include/ui/uintspinbox.h \ include/ui/updatepromoter.h \ include/ui/wildmonchart.h \ - include/ui/wildmonsearch.h + include/ui/wildmonsearch.h \ + include/ui/resizelayoutpopup.h FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ @@ -290,7 +292,8 @@ FORMS += forms/mainwindow.ui \ forms/customattributesdialog.ui \ forms/updatepromoter.ui \ forms/wildmonchart.ui \ - forms/wildmonsearch.ui + forms/wildmonsearch.ui \ + forms/resizelayoutpopup.ui RESOURCES += \ resources/images.qrc \ diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index c500c8c0..17cec2df 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -177,7 +177,7 @@ bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, +ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -189,8 +189,7 @@ ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newL this->oldLayoutWidth = oldLayoutDimensions.width(); this->oldLayoutHeight = oldLayoutDimensions.height(); - this->newLayoutWidth = newLayoutDimensions.width(); - this->newLayoutHeight = newLayoutDimensions.height(); + this->newLayoutMargins = newLayoutMargins; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -210,12 +209,14 @@ void ResizeLayout::redo() { if (!layout) return; - layout->blockdata = newMetatiles; - layout->setDimensions(newLayoutWidth, newLayoutHeight, false, true); - layout->border = newBorder; layout->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); + layout->width = oldLayoutWidth; + layout->height = oldLayoutHeight; + layout->adjustDimensions(this->newLayoutMargins, false, true); + layout->blockdata = newMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); @@ -225,12 +226,14 @@ void ResizeLayout::redo() { void ResizeLayout::undo() { if (!layout) return; - layout->blockdata = oldMetatiles; - layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false, true); - layout->border = oldBorder; layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); + layout->width = oldLayoutWidth + newLayoutMargins.left() + newLayoutMargins.right(); + layout->height = oldLayoutHeight + newLayoutMargins.top() + newLayoutMargins.bottom(); + layout->adjustDimensions(-this->newLayoutMargins, false, true); + layout->blockdata = oldMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 94e6c291..4e8a08a1 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -176,6 +176,34 @@ void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bo emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); } +void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata, bool enableScriptCallback) { + int oldWidth = this->width; + int oldHeight = this->height; + int newWidth = this->width + margins.left() + margins.right(); + int newHeight = this->height + margins.top() + margins.bottom(); + + if (setNewBlockdata) { + // Fill new blockdata TODO: replace old functions, scripting support, undo etc + Blockdata newBlockdata; + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if ((x < margins.left()) || (x >= newWidth - margins.right()) || (y < margins.top()) || (y >= newHeight - margins.bottom())) { + newBlockdata.append(0); + } else { + int index = (y - margins.top()) * oldWidth + (x - margins.left()); + newBlockdata.append(this->blockdata.value(index)); + } + } + this->blockdata = newBlockdata; + } + + this->width = newWidth; + this->height = newHeight; + + emit layoutChanged(this); + emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); +} + void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { if (setNewBlockdata) { setNewBorderDimensionsBlockdata(newWidth, newHeight); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9b742967..24d11190 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -23,6 +23,7 @@ #include "newmapconnectiondialog.h" #include "config.h" #include "filedialog.h" +#include "resizelayoutpopup.h" #include "newmapdialog.h" #include "newtilesetdialog.h" #include "newmapgroupdialog.h" @@ -2601,88 +2602,31 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & void MainWindow::on_pushButton_ChangeDimensions_clicked() { if (!editor || !editor->layout) return; - QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - dialog.setWindowTitle("Change Map Dimensions"); - dialog.setWindowModality(Qt::NonModal); - - QFormLayout form(&dialog); - - QSpinBox *widthSpinBox = new QSpinBox(); - QSpinBox *heightSpinBox = new QSpinBox(); - QSpinBox *bwidthSpinBox = new QSpinBox(); - QSpinBox *bheightSpinBox = new QSpinBox(); - widthSpinBox->setMinimum(1); - heightSpinBox->setMinimum(1); - bwidthSpinBox->setMinimum(1); - bheightSpinBox->setMinimum(1); - widthSpinBox->setMaximum(editor->project->getMaxMapWidth()); - heightSpinBox->setMaximum(editor->project->getMaxMapHeight()); - bwidthSpinBox->setMaximum(MAX_BORDER_WIDTH); - bheightSpinBox->setMaximum(MAX_BORDER_HEIGHT); - widthSpinBox->setValue(editor->layout->getWidth()); - heightSpinBox->setValue(editor->layout->getHeight()); - bwidthSpinBox->setValue(editor->layout->getBorderWidth()); - bheightSpinBox->setValue(editor->layout->getBorderHeight()); - if (projectConfig.useCustomBorderSize) { - form.addRow(new QLabel("Map Width"), widthSpinBox); - form.addRow(new QLabel("Map Height"), heightSpinBox); - form.addRow(new QLabel("Border Width"), bwidthSpinBox); - form.addRow(new QLabel("Border Height"), bheightSpinBox); - } else { - form.addRow(new QLabel("Width"), widthSpinBox); - form.addRow(new QLabel("Height"), heightSpinBox); - } - - QLabel *errorLabel = new QLabel(); - errorLabel->setStyleSheet("QLabel { color: red }"); - errorLabel->setVisible(false); - - QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - form.addRow(&buttonBox); - connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &errorLabel, this](){ - // Ensure width and height are an acceptable size. - // The maximum number of metatiles in a map is the following: - // max = (width + 15) * (height + 14) - // This limit can be found in fieldmap.c in pokeruby/pokeemerald/pokefirered. - int numMetatiles = editor->project->getMapDataSize(widthSpinBox->value(), heightSpinBox->value()); - int maxMetatiles = editor->project->getMaxMapDataSize(); - if (numMetatiles <= maxMetatiles) { - dialog.accept(); - } else { - QString errorText = QString("Error: The specified width and height are too large.\n" - "The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) - .arg(widthSpinBox->value()) - .arg(heightSpinBox->value()) - .arg(numMetatiles); - errorLabel->setText(errorText); - errorLabel->setVisible(true); - } - }); - connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - - form.addRow(errorLabel); - - if (dialog.exec() == QDialog::Accepted) { - Layout *layout = editor->layout; - Blockdata oldMetatiles = layout->blockdata; - Blockdata oldBorder = layout->border; - QSize oldMapDimensions(layout->getWidth(), layout->getHeight()); + ResizeLayoutPopup popup(this->ui->graphicsView_Map, this->editor); + popup.show(); + popup.setupLayoutView(); + if (popup.exec() == QDialog::Accepted) { + Layout *layout = this->editor->layout; + QMargins result = popup.getResult(); + QSize borderResult = popup.getBorderResult(); + QSize oldLayoutDimensions(layout->getWidth(), layout->getHeight()); QSize oldBorderDimensions(layout->getBorderWidth(), layout->getBorderHeight()); - QSize newMapDimensions(widthSpinBox->value(), heightSpinBox->value()); - QSize newBorderDimensions(bwidthSpinBox->value(), bheightSpinBox->value()); - if (oldMapDimensions != newMapDimensions || oldBorderDimensions != newBorderDimensions) { - layout->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); - layout->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); - editor->layout->editHistory.push(new ResizeLayout(layout, - oldMapDimensions, newMapDimensions, + if (!result.isNull() || (borderResult != oldBorderDimensions)) { + Blockdata oldMetatiles = layout->blockdata; + Blockdata oldBorder = layout->border; + + layout->adjustDimensions(result); + layout->setBorderDimensions(borderResult.width(), borderResult.height(), true, true); + layout->editHistory.push(new ResizeLayout(layout, + oldLayoutDimensions, result, oldMetatiles, layout->blockdata, - oldBorderDimensions, newBorderDimensions, + oldBorderDimensions, borderResult, oldBorder, layout->border )); } } + + return; } void MainWindow::on_checkBox_smartPaths_stateChanged(int selected) diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 55327dba..e9e373e0 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -1,17 +1,163 @@ +#include +#include + #include "movablerect.h" MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color) + : QGraphicsRectItem(0, 0, width, height) { this->enabled = enabled; - this->width = width; - this->height = height; this->color = color; this->setVisible(*enabled); } -void MovableRect::updateLocation(int x, int y) -{ - this->setX((x * 16) - this->width / 2 + 8); - this->setY((y * 16) - this->height / 2 + 8); +/// Center rect on grid position (x, y) +void MovableRect::updateLocation(int x, int y) { + this->setRect((x * 16) - this->rect().width() / 2 + 8, (y * 16) - this->rect().height() / 2 + 8, this->rect().width(), this->rect().height()); this->setVisible(*this->enabled); } + +/****************************************************************************** + ************************************************************************ + ******************************************************************************/ + +int roundUp(int numToRound, int multiple) { + return (numToRound + multiple - 1) & -multiple; +} + +ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) + : QObject(parent), + MovableRect(enabled, width * 16, height * 16, color) +{ + setZValue(0xFFFFFFFF); // ensure on top of view + setAcceptHoverEvents(true); + setFlags(this->flags() | QGraphicsItem::ItemIsMovable); +} + +ResizableRect::Edge ResizableRect::detectEdge(int x, int y) { + QRectF edge = this->boundingRect(); + if (x <= edge.left() + this->lineWidth) { + if (y >= edge.top() + 2 * this->lineWidth) { + if (y <= edge.bottom() - 2 * this->lineWidth) { + return ResizableRect::Edge::Left; + } + else { + return ResizableRect::Edge::BottomLeft; + } + } + else { + return ResizableRect::Edge::TopLeft; + } + } + else if (x >= edge.right() - this->lineWidth) { + if (y >= edge.top() + 2 * this->lineWidth) { + if (y <= edge.bottom() - 2 * this->lineWidth) { + return ResizableRect::Edge::Right; + } + else { + return ResizableRect::Edge::BottomRight; + } + } + else { + return ResizableRect::Edge::TopRight; + } + } + else { + if (y <= edge.top() + this->lineWidth) { + return ResizableRect::Edge::Top; + } + else if (y >= edge.bottom() - this->lineWidth) { + return ResizableRect::Edge::Bottom; + } + } + return ResizableRect::Edge::None; +} + +void ResizableRect::updatePosFromRect(QRect newRect) { + prepareGeometryChange(); + this->setRect(newRect); + emit this->rectUpdated(newRect); +} + +void ResizableRect::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { + switch (this->detectEdge(event->pos().x(), event->pos().y())) { + case ResizableRect::Edge::None: + default: + break; + case ResizableRect::Edge::Left: + case ResizableRect::Edge::Right: + this->setCursor(Qt::SizeHorCursor); + break; + case ResizableRect::Edge::Top: + case ResizableRect::Edge::Bottom: + this->setCursor(Qt::SizeVerCursor); + break; + case ResizableRect::Edge::TopRight: + case ResizableRect::Edge::BottomLeft: + this->setCursor(Qt::SizeBDiagCursor); + break; + case ResizableRect::Edge::TopLeft: + case ResizableRect::Edge::BottomRight: + this->setCursor(Qt::SizeFDiagCursor); + break; + } +} + +void ResizableRect::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { + this->unsetCursor(); +} + +void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) { + int x = event->pos().x(); + int y = event->pos().y(); + this->clickedPos = event->scenePos(); + this->clickedRect = this->rect().toAlignedRect(); + this->clickedEdge = this->detectEdge(x, y); +} + +void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + int dx = roundUp(event->scenePos().x() - this->clickedPos.x(), 16); + int dy = roundUp(event->scenePos().y() - this->clickedPos.y(), 16); + + QRect resizedRect = this->clickedRect; + + switch (this->clickedEdge) { + case ResizableRect::Edge::None: + default: + return; + case ResizableRect::Edge::Left: + resizedRect.adjust(dx, 0, 0, 0); + break; + case ResizableRect::Edge::Right: + resizedRect.adjust(0, 0, dx, 0); + break; + case ResizableRect::Edge::Top: + resizedRect.adjust(0, dy, 0, 0); + break; + case ResizableRect::Edge::Bottom: + resizedRect.adjust(0, 0, 0, dy); + break; + case ResizableRect::Edge::TopRight: + resizedRect.adjust(0, dy, dx, 0); + break; + case ResizableRect::Edge::BottomLeft: + resizedRect.adjust(dx, 0, 0, dy); + break; + case ResizableRect::Edge::TopLeft: + resizedRect.adjust(dx, dy, 0, 0); + break; + case ResizableRect::Edge::BottomRight: + resizedRect.adjust(0, 0, dx, dy); + break; + } + + // lower bounds limits + if (resizedRect.width() < 16) + resizedRect.setWidth(16); + if (resizedRect.height() < 16) + resizedRect.setHeight(16); + + // TODO: upper bound limits + + this->updatePosFromRect(resizedRect); +} diff --git a/src/ui/noscrollspinbox.cpp b/src/ui/noscrollspinbox.cpp index f8d1d444..3493d4ee 100644 --- a/src/ui/noscrollspinbox.cpp +++ b/src/ui/noscrollspinbox.cpp @@ -1,5 +1,6 @@ #include "noscrollspinbox.h" #include +#include unsigned actionId = 0xffff; @@ -25,6 +26,10 @@ void NoScrollSpinBox::focusOutEvent(QFocusEvent *event) { QSpinBox::focusOutEvent(event); } +void NoScrollSpinBox::setLineEditEnabled(bool enabled) { + this->lineEdit()->setReadOnly(!enabled); +} + unsigned NoScrollSpinBox::getActionId() { return actionId; } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp new file mode 100644 index 00000000..c6c48db7 --- /dev/null +++ b/src/ui/resizelayoutpopup.cpp @@ -0,0 +1,186 @@ +#include "resizelayoutpopup.h" +#include "editor.h" +#include "movablerect.h" +#include "config.h" + +#include "ui_resizelayoutpopup.h" + +// TODO: put this in a util file or something +extern int roundUp(int, int); + +CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { } + +void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { + QRect r = rect.toRect(); + int xMin = r.left() - r.left() % this->gridSize - this->gridSize; + int yMin = r.top() - r.top() % this->gridSize - this->gridSize; + int xMax = r.right() - r.right() % this->gridSize + this->gridSize; + int yMax = r.bottom() - r.bottom() % this->gridSize + this->gridSize; + + // draw grid 16x16 from top to bottom of scene + QColor paintColor(0x00ff00); + for (int x = xMin, xTile = 0; x <= xMax; x += this->gridSize, xTile++) { + for (int y = yMin, yTile = 0; y <= yMax; y += this->gridSize, yTile++) { + if (!((xTile ^ yTile) & 1)) { // tile numbers have same parity (evenness) + if (this->validRect.contains(x, y)) // check if inside validRect + paintColor = QColor(132, 217, 165); // green light color + else + paintColor = 0xbcbcbc; // normal light color + } + else { + if (this->validRect.contains(x, y)) // check if inside validRect + paintColor = QColor(76, 178, 121); // green dark color + else + paintColor = 0x969696; // normal dark color + } + painter->fillRect(QRect(x, y, this->gridSize, this->gridSize), paintColor); + } + } +} + +/****************************************************************************** + ************************************************************************ + ******************************************************************************/ + +BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) { + setFlags(this->flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable); +} + +void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * item, QWidget *widget) { + // Draw the pixmap darkened in the background + painter->fillRect(this->boundingRect().toAlignedRect(), QColor(0x444444)); + painter->setCompositionMode(QPainter::CompositionMode_Multiply); + painter->drawPixmap(this->boundingRect().toAlignedRect(), this->pixmap()); + + // draw the normal pixmap on top, cropping to validRect as needed + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + QRect intersection = this->mapRectFromScene(this->boundary->rect()).toAlignedRect() & this->boundingRect().toAlignedRect(); + QPixmap cropped = this->pixmap().copy(intersection); + painter->drawPixmap(intersection, cropped); +} + +QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { + if (change == ItemPositionChange && scene()) { + QPointF newPos = value.toPointF(); + return QPointF(roundUp(newPos.x(), 16), roundUp(newPos.y(), 16)); + } + else + return QGraphicsItem::itemChange(change, value); +} + +/****************************************************************************** + ************************************************************************ + ******************************************************************************/ + +ResizeLayoutPopup::ResizeLayoutPopup(QWidget *parent, Editor *editor) : + QDialog(parent), + parent(parent), + editor(editor), + ui(new Ui::ResizeLayoutPopup) +{ + ui->setupUi(this); + this->resetPosition(); + this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint); + this->setWindowModality(Qt::ApplicationModal); + + this->scene = new CheckeredBgScene(this); + //this->ui->graphicsView->setAlignment(Qt::AlignTop|Qt::AlignLeft); + this->ui->graphicsView->setScene(this->scene); + this->ui->graphicsView->setRenderHints(QPainter::Antialiasing); + this->ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); +} + +ResizeLayoutPopup::~ResizeLayoutPopup() +{ + delete ui; +} + +/// Reset position of the dialog to cover the MainWindow's layout metatile scene +void ResizeLayoutPopup::resetPosition() { + this->setGeometry(QRect(parent->mapToGlobal(QPoint(0, 0)), parent->size())); +} + +/// Custom scene contains +/// (1) pixmap representing the current layout / not resizable / drag-movable +/// (1) layout outline / resizable / not movable +void ResizeLayoutPopup::setupLayoutView() { + if (!this->editor || !this->editor->layout) return; + // TODO: this should be a more robust check probably + + // Border stuff + bool bordersEnabled = projectConfig.useCustomBorderSize; + if (bordersEnabled) { + this->ui->spinBox_borderWidth->setMinimum(1); + this->ui->spinBox_borderHeight->setMinimum(1); + this->ui->spinBox_borderWidth->setMaximum(MAX_BORDER_WIDTH); + this->ui->spinBox_borderHeight->setMaximum(MAX_BORDER_HEIGHT); + this->ui->spinBox_borderWidth->setLineEditEnabled(false); + this->ui->spinBox_borderHeight->setLineEditEnabled(false); + } else { + this->ui->frame_border->setVisible(false); + } + this->ui->spinBox_borderWidth->setValue(this->editor->layout->getBorderWidth()); + this->ui->spinBox_borderHeight->setValue(this->editor->layout->getBorderHeight()); + + // Layout stuff + QPixmap pixmap = this->editor->layout->pixmap; + this->layoutPixmap = new BoundedPixmapItem(pixmap); + this->scene->addItem(layoutPixmap); + int maxWidth = this->editor->project->getMaxMapWidth(); + int maxHeight = this->editor->project->getMaxMapHeight(); + QGraphicsRectItem *cover = new QGraphicsRectItem(-maxWidth * 8, -maxHeight * 8, maxWidth * 16, maxHeight * 16); + this->scene->addItem(cover); + + this->ui->spinBox_width->setMinimum(1); + this->ui->spinBox_width->setMaximum(maxWidth); + this->ui->spinBox_height->setMinimum(1); + this->ui->spinBox_height->setMaximum(maxHeight); + + this->ui->spinBox_width->setLineEditEnabled(false); + this->ui->spinBox_height->setLineEditEnabled(false); + + static bool layoutSizeRectVisible = true; + + this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); + connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ + this->scene->setValidRect(rect); + this->ui->spinBox_width->setValue(rect.width() / 16); + this->ui->spinBox_height->setValue(rect.height() / 16); + }); + scene->addItem(outline); + + layoutPixmap->setBoundary(outline); + this->outline->rectUpdated(outline->rect().toAlignedRect()); + + this->ui->graphicsView->scale(0.5, 0.5); + this->ui->graphicsView->centerOn(layoutPixmap); + // this->ui->graphicsView->fitInView(cover->rect(), Qt::KeepAspectRatio); +} + +void ResizeLayoutPopup::on_spinBox_width_valueChanged(int value) { + if (!this->outline) return; + QRectF rect = this->outline->rect(); + this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), value * 16, rect.height())); +} + +void ResizeLayoutPopup::on_spinBox_height_valueChanged(int value) { + if (!this->outline) return; + QRectF rect = this->outline->rect(); + this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), rect.width(), value * 16)); +} + +/// Result is the number of metatiles to add (or subtract) to each side of the map after dimension changes +QMargins ResizeLayoutPopup::getResult() { + QMargins result = QMargins(); + + result.setLeft(this->layoutPixmap->x() - this->outline->rect().left()); + result.setTop(this->layoutPixmap->y() - this->outline->rect().top()); + result.setRight(this->outline->rect().right() - (this->layoutPixmap->x() + this->layoutPixmap->pixmap().width())); + result.setBottom(this->outline->rect().bottom() - (this->layoutPixmap->y() + this->layoutPixmap->pixmap().height())); + + return result / 16; +} + +QSize ResizeLayoutPopup::getBorderResult() { + return QSize(this->ui->spinBox_borderWidth->value(), this->ui->spinBox_borderHeight->value()); +} From e4a4fb5ab1a01926736632acd3754d8ccb411594 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 12 Nov 2024 12:30:56 -0500 Subject: [PATCH 153/364] add limits for resizing layouts --- include/core/editcommands.h | 5 ----- include/ui/movablerect.h | 3 +++ src/ui/movablerect.cpp | 27 +++++++++++++++++++-------- src/ui/resizelayoutpopup.cpp | 5 +++-- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 83e33d41..eabfacc0 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -204,11 +204,6 @@ private: /// Implements a command to commit a map or border resize action. class ResizeLayout : public QUndoCommand { public: - // ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, - // const Blockdata &oldMetatiles, const Blockdata &newMetatiles, - // QSize oldBorderDimensions, QSize newBorderDimensions, - // const Blockdata &oldBorder, const Blockdata &newBorder, - // QUndoCommand *parent = nullptr); ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index c5ca00ab..6f53e729 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -55,6 +55,7 @@ public: } void updatePosFromRect(QRect newPos); + void setLimit(QRect limit) { this->limit = limit; } protected: void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; @@ -71,6 +72,8 @@ private: QPointF clickedPos = QPointF(); QRect clickedRect; + QRect limit = QRect(); + int lineWidth = 8; signals: diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index e9e373e0..6cb3c7e0 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -151,13 +151,24 @@ void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { break; } - // lower bounds limits - if (resizedRect.width() < 16) - resizedRect.setWidth(16); - if (resizedRect.height() < 16) - resizedRect.setHeight(16); + // lower bounds limits, smallest possible size is 16x16 square + if (resizedRect.width() < 16) { + if (dx < 0) { // right sided adjustment made + resizedRect.setWidth(16); + } else { // left sided adjustment slightly more complicated + int dxMax = this->clickedRect.right() - this->clickedRect.left() - 16; + resizedRect.adjust(dxMax - dx, 0, 0, 0); + } + } + if (resizedRect.height() < 16) { + if (dy < 0) { // bottom + resizedRect.setHeight(16); + } else { // top + int dyMax = this->clickedRect.bottom() - this->clickedRect.top() - 16; + resizedRect.adjust(0, dyMax - dy, 0, 0); + } + } - // TODO: upper bound limits - - this->updatePosFromRect(resizedRect); + // Upper bounds: clip resized to limit rect + this->updatePosFromRect(resizedRect & this->limit); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index c6c48db7..2ed540bc 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -22,13 +22,13 @@ void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { for (int x = xMin, xTile = 0; x <= xMax; x += this->gridSize, xTile++) { for (int y = yMin, yTile = 0; y <= yMax; y += this->gridSize, yTile++) { if (!((xTile ^ yTile) & 1)) { // tile numbers have same parity (evenness) - if (this->validRect.contains(x, y)) // check if inside validRect + if (this->validRect.contains(x, y)) paintColor = QColor(132, 217, 165); // green light color else paintColor = 0xbcbcbc; // normal light color } else { - if (this->validRect.contains(x, y)) // check if inside validRect + if (this->validRect.contains(x, y)) paintColor = QColor(76, 178, 121); // green dark color else paintColor = 0x969696; // normal dark color @@ -142,6 +142,7 @@ void ResizeLayoutPopup::setupLayoutView() { static bool layoutSizeRectVisible = true; this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); + this->outline->setLimit(cover->rect().toAlignedRect()); connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ this->scene->setValidRect(rect); this->ui->spinBox_width->setValue(rect.width() / 16); From 035c326348319367caa787a62af35c9f65e64331 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 12 Nov 2024 12:49:37 -0500 Subject: [PATCH 154/364] reset button working for change dimension dialog --- include/ui/resizelayoutpopup.h | 2 ++ src/ui/resizelayoutpopup.cpp | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h index de48c993..4f6b51f7 100644 --- a/include/ui/resizelayoutpopup.h +++ b/include/ui/resizelayoutpopup.h @@ -6,6 +6,7 @@ #include #include #include +#include class ResizableRect; class Editor; @@ -90,6 +91,7 @@ protected: private slots: void on_spinBox_width_valueChanged(int value); void on_spinBox_height_valueChanged(int value); + void on_buttonBox_clicked(QAbstractButton *button); private: QWidget *parent = nullptr; diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 2ed540bc..8aa2f706 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -84,7 +84,6 @@ ResizeLayoutPopup::ResizeLayoutPopup(QWidget *parent, Editor *editor) : this->setWindowModality(Qt::ApplicationModal); this->scene = new CheckeredBgScene(this); - //this->ui->graphicsView->setAlignment(Qt::AlignTop|Qt::AlignLeft); this->ui->graphicsView->setScene(this->scene); this->ui->graphicsView->setRenderHints(QPainter::Antialiasing); this->ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); @@ -100,12 +99,18 @@ void ResizeLayoutPopup::resetPosition() { this->setGeometry(QRect(parent->mapToGlobal(QPoint(0, 0)), parent->size())); } +void ResizeLayoutPopup::on_buttonBox_clicked(QAbstractButton *button) { + if(button == this->ui->buttonBox->button(QDialogButtonBox::Reset) ) { + this->scene->clear(); + setupLayoutView(); + } +} + /// Custom scene contains /// (1) pixmap representing the current layout / not resizable / drag-movable /// (1) layout outline / resizable / not movable void ResizeLayoutPopup::setupLayoutView() { if (!this->editor || !this->editor->layout) return; - // TODO: this should be a more robust check probably // Border stuff bool bordersEnabled = projectConfig.useCustomBorderSize; @@ -153,9 +158,9 @@ void ResizeLayoutPopup::setupLayoutView() { layoutPixmap->setBoundary(outline); this->outline->rectUpdated(outline->rect().toAlignedRect()); - this->ui->graphicsView->scale(0.5, 0.5); + // TODO: is this an ideal size for all maps, or should this adjust based on starting dimensions? + this->ui->graphicsView->setTransform(QTransform::fromScale(0.5, 0.5)); this->ui->graphicsView->centerOn(layoutPixmap); - // this->ui->graphicsView->fitInView(cover->rect(), Qt::KeepAspectRatio); } void ResizeLayoutPopup::on_spinBox_width_valueChanged(int value) { From 1e7d5144b95c19e544eaf99c0864ead265857863 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 6 Feb 2025 12:26:54 -0500 Subject: [PATCH 155/364] add back metatile limit upper bound check and popup --- include/ui/resizelayoutpopup.h | 1 + src/ui/movablerect.cpp | 5 +++-- src/ui/resizelayoutpopup.cpp | 27 +++++++++++++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h index 4f6b51f7..1f79580e 100644 --- a/include/ui/resizelayoutpopup.h +++ b/include/ui/resizelayoutpopup.h @@ -29,6 +29,7 @@ public: void setValidRect(QRect rect) { this->validRect = rect; } + QRect getValidRect() { return this->validRect; } protected: void drawBackground(QPainter *painter, const QRectF &rect) override; diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 6cb3c7e0..ca64feda 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "movablerect.h" @@ -151,7 +152,7 @@ void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { break; } - // lower bounds limits, smallest possible size is 16x16 square + // Lower limits: smallest possible size is 16x16 square if (resizedRect.width() < 16) { if (dx < 0) { // right sided adjustment made resizedRect.setWidth(16); @@ -169,6 +170,6 @@ void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } } - // Upper bounds: clip resized to limit rect + // Upper limits: clip resized to limit rect this->updatePosFromRect(resizedRect & this->limit); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 8aa2f706..f73e18f9 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -89,8 +89,7 @@ ResizeLayoutPopup::ResizeLayoutPopup(QWidget *parent, Editor *editor) : this->ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } -ResizeLayoutPopup::~ResizeLayoutPopup() -{ +ResizeLayoutPopup::~ResizeLayoutPopup() { delete ui; } @@ -149,7 +148,31 @@ void ResizeLayoutPopup::setupLayoutView() { this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); this->outline->setLimit(cover->rect().toAlignedRect()); connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ + // Note: this extra limit check needs access to the project values, so it is done here and not ResizableRect::mouseMoveEvent + // Upper limits: maximum metatiles in a map formula: + // max = (width + 15) * (height + 14) + // This limit can be found in fieldmap.c in pokeruby/pokeemerald/pokefirered. + int numMetatiles = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); + int maxMetatiles = editor->project->getMaxMapDataSize(); + if (numMetatiles > maxMetatiles) { + QString errorText = QString("The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" + "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") + .arg(maxMetatiles) + .arg(rect.width() / 16) + .arg(rect.height() / 16) + .arg(numMetatiles); + QMessageBox warning; + warning.setIcon(QMessageBox::Warning); + warning.setText("The specified width and height are too large."); + warning.setInformativeText(errorText); + warning.setStandardButtons(QMessageBox::Ok); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + // adjust rect to last accepted size + rect = this->scene->getValidRect(); + } this->scene->setValidRect(rect); + this->outline->setRect(rect); this->ui->spinBox_width->setValue(rect.width() / 16); this->ui->spinBox_height->setValue(rect.height() / 16); }); From 51773926d21a2d1a6b74d666b896ec27f861cee9 Mon Sep 17 00:00:00 2001 From: garak Date: Thu, 6 Feb 2025 12:55:51 -0500 Subject: [PATCH 156/364] resolve some goofiness --- include/core/maplayout.h | 2 +- include/ui/movablerect.h | 2 +- src/core/editcommands.cpp | 4 ++-- src/core/maplayout.cpp | 4 ++-- src/mainwindow.cpp | 2 -- src/ui/movablerect.cpp | 2 +- src/ui/resizelayoutpopup.cpp | 6 +++--- 7 files changed, 10 insertions(+), 12 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index f8ebe905..da58c4b7 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -103,7 +103,7 @@ public: void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); - void adjustDimensions(QMargins margins, bool setNewBlockdata = true, bool enableScriptCallback = false); + void adjustDimensions(QMargins margins, bool setNewBlockdata = true); void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 6f53e729..56798a0c 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -59,7 +59,7 @@ public: protected: void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 17cec2df..61410dc4 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -214,7 +214,7 @@ void ResizeLayout::redo() { layout->width = oldLayoutWidth; layout->height = oldLayoutHeight; - layout->adjustDimensions(this->newLayoutMargins, false, true); + layout->adjustDimensions(this->newLayoutMargins); layout->blockdata = newMetatiles; layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); @@ -231,7 +231,7 @@ void ResizeLayout::undo() { layout->width = oldLayoutWidth + newLayoutMargins.left() + newLayoutMargins.right(); layout->height = oldLayoutHeight + newLayoutMargins.top() + newLayoutMargins.bottom(); - layout->adjustDimensions(-this->newLayoutMargins, false, true); + layout->adjustDimensions(-this->newLayoutMargins); layout->blockdata = oldMetatiles; layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 4e8a08a1..35fcc2f7 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -176,14 +176,14 @@ void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bo emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); } -void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata, bool enableScriptCallback) { +void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { int oldWidth = this->width; int oldHeight = this->height; int newWidth = this->width + margins.left() + margins.right(); int newHeight = this->height + margins.top() + margins.bottom(); if (setNewBlockdata) { - // Fill new blockdata TODO: replace old functions, scripting support, undo etc + // Fill new blockdata Blockdata newBlockdata; for (int y = 0; y < newHeight; y++) for (int x = 0; x < newWidth; x++) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 24d11190..84bd4190 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2625,8 +2625,6 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { )); } } - - return; } void MainWindow::on_checkBox_smartPaths_stateChanged(int selected) diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index ca64feda..ba323185 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -104,7 +104,7 @@ void ResizableRect::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { } } -void ResizableRect::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { +void ResizableRect::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { this->unsetCursor(); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index f73e18f9..12855843 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -46,7 +46,7 @@ BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *paren setFlags(this->flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable); } -void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * item, QWidget *widget) { +void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { // Draw the pixmap darkened in the background painter->fillRect(this->boundingRect().toAlignedRect(), QColor(0x444444)); painter->setCompositionMode(QPainter::CompositionMode_Multiply); @@ -140,8 +140,8 @@ void ResizeLayoutPopup::setupLayoutView() { this->ui->spinBox_height->setMinimum(1); this->ui->spinBox_height->setMaximum(maxHeight); - this->ui->spinBox_width->setLineEditEnabled(false); - this->ui->spinBox_height->setLineEditEnabled(false); + //this->ui->spinBox_width->setLineEditEnabled(false); + //this->ui->spinBox_height->setLineEditEnabled(false); static bool layoutSizeRectVisible = true; From 287e65b514b28a5154490ea6344d63f38fece322 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 6 Feb 2025 13:03:48 -0500 Subject: [PATCH 157/364] stop disabling spinboxes in resize popup --- src/ui/resizelayoutpopup.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 12855843..45559b70 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -118,8 +118,6 @@ void ResizeLayoutPopup::setupLayoutView() { this->ui->spinBox_borderHeight->setMinimum(1); this->ui->spinBox_borderWidth->setMaximum(MAX_BORDER_WIDTH); this->ui->spinBox_borderHeight->setMaximum(MAX_BORDER_HEIGHT); - this->ui->spinBox_borderWidth->setLineEditEnabled(false); - this->ui->spinBox_borderHeight->setLineEditEnabled(false); } else { this->ui->frame_border->setVisible(false); } @@ -140,9 +138,6 @@ void ResizeLayoutPopup::setupLayoutView() { this->ui->spinBox_height->setMinimum(1); this->ui->spinBox_height->setMaximum(maxHeight); - //this->ui->spinBox_width->setLineEditEnabled(false); - //this->ui->spinBox_height->setLineEditEnabled(false); - static bool layoutSizeRectVisible = true; this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); From 6b9a4d73e9e453b9547bb8877d655471b525346d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 4 Feb 2025 16:34:21 -0500 Subject: [PATCH 158/364] Read/write heal locations using new JSON data --- docsrc/manual/project-files.rst | 11 +- forms/mainwindow.ui | 10 +- forms/preferenceeditor.ui | 10 + forms/projectsettingseditor.ui | 10 +- include/config.h | 11 +- include/core/events.h | 135 ++++++------ include/core/heallocation.h | 28 --- include/core/map.h | 2 +- include/core/parseutil.h | 11 +- include/editor.h | 1 - include/lib/orderedjson.h | 3 + include/project.h | 24 +-- include/ui/eventframes.h | 3 +- porymap.pro | 2 - src/config.cpp | 12 +- src/core/events.cpp | 124 ++++++++--- src/core/heallocation.cpp | 39 ---- src/core/map.cpp | 2 +- src/core/parseutil.cpp | 12 +- src/editor.cpp | 121 ++++++----- src/mainwindow.cpp | 50 ++--- src/project.cpp | 360 +++++++++---------------------- src/ui/draggablepixmapitem.cpp | 7 + src/ui/eventframes.cpp | 56 +++-- src/ui/neweventtoolbutton.cpp | 6 +- src/ui/prefab.cpp | 3 - src/ui/preferenceeditor.cpp | 2 + src/ui/projectsettingseditor.cpp | 6 +- src/ui/regionmapeditor.cpp | 3 - 29 files changed, 447 insertions(+), 617 deletions(-) delete mode 100644 include/core/heallocation.h delete mode 100644 src/core/heallocation.cpp diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index fc720c22..5510c61d 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -36,12 +36,12 @@ The filepath that Porymap expects for each file can be overridden on the ``Files data/tilesets/metatiles.inc, yes, yes, ``tilesets_metatiles_asm``, only if ``tilesets_headers`` can't be found data/tilesets/[primary|secondary]/\*, yes, yes, ``data_tilesets_folders``, default tileset data location src/data/wild_encounters.json, yes, yes, ``json_wild_encounters``, optional (only required to use Wild Pokémon tab) + src/data/heal_locations.json, yes, yes, ``json_heal_locations``, src/data/object_events/object_event_graphics_info_pointers.h, yes, no, ``data_obj_event_gfx_pointers``, src/data/object_events/object_event_graphics_info.h, yes, no, ``data_obj_event_gfx_info``, src/data/object_events/object_event_pic_tables.h, yes, no, ``data_obj_event_pic_tables``, src/data/object_events/object_event_graphics.h, yes, no, ``data_obj_event_gfx``, src/data/graphics/pokemon.h, yes, no, ``data_pokemon_gfx``, for pokemon sprite icons - src/data/heal_locations.h, yes, yes, ``data_heal_locations``, src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, include/constants/global.h, yes, no, ``constants_global``, reads ``define_obj_event_count`` @@ -50,7 +50,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files include/constants/vars.h, yes, no, ``constants_vars``, for Trigger events include/constants/weather.h, yes, no, ``constants_weather``, for map weather and Weather Triggers include/constants/songs.h, yes, no, ``constants_songs``, for map music - include/constants/heal_locations.h, yes, yes, ``constants_heal_locations``, include/constants/pokemon.h, yes, no, ``constants_pokemon``, reads ``define_min_level`` and ``define_max_level`` include/constants/map_types.h, yes, no, ``constants_map_types``, include/constants/trainer_types.h, yes, no, ``constants_trainer_types``, for Object events @@ -85,11 +84,6 @@ In addition to these files, there are some specific symbol and macro names that ``symbol_obj_event_gfx_pointers``, ``gObjectEventGraphicsInfoPointers``, to map Object Event graphics IDs to graphics data ``symbol_pokemon_icon_table``, ``gMonIconTable``, to map species constants to icon images ``symbol_wild_encounters``, ``gWildMonHeaders``, output as the ``label`` property for the top-level wild ecounters JSON object - ``symbol_heal_locations_type``, ``struct HealLocation``, the type for the Heal Locations table - ``symbol_heal_locations``, ``sHealLocations``, the default Heal Locations table name when ``Respawn Map/NPC`` is disabled - ``symbol_spawn_points``, ``sSpawnPoints``, the default Heal Locations table name when ``Respawn Map/NPC`` is enabled - ``symbol_spawn_maps``, ``u16 sWhiteoutRespawnHealCenterMapIdxs``, the type and table name for Heal Location ``Respawn Map`` values - ``symbol_spawn_npcs``, ``u8 sWhiteoutRespawnHealerNpcIds``, the type and table name for Heal Location ``Respawn NPC`` values ``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab ``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names ``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic`` @@ -114,8 +108,7 @@ In addition to these files, there are some specific symbol and macro names that ``define_attribute_terrain``, ``METATILE_ATTRIBUTE_TERRAIN``, name used to extract setting from ``symbol_attribute_table`` ``define_attribute_encounter``, ``METATILE_ATTRIBUTE_ENCOUNTER_TYPE``, name used to extract setting from ``symbol_attribute_table`` ``define_metatile_label_prefix``, ``METATILE_``, expected prefix for metatile label macro names - ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, output as prefix for Heal Location IDs if ``Respawn Map/NPC`` is disabled - ``define_spawn_prefix``, ``SPAWN_``, output as prefix for Heal Location IDs if ``Respawn Map/NPC`` is enabled + ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, default prefix for heal location macro names ``define_map_prefix``, ``MAP_``, expected prefix for map macro names ``define_map_dynamic``, ``DYNAMIC``, macro name after prefix for Dynamic maps ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 4a9e439a..a7a6d112 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2042,9 +2042,9 @@
- + - Healspots + Heal Locations @@ -2091,7 +2091,7 @@
- + Qt::Orientation::Horizontal @@ -2106,7 +2106,7 @@ - + QFrame::Shape::NoFrame @@ -2116,7 +2116,7 @@ Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop - + 0 diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index a7009e6e..2863a541 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -54,6 +54,16 @@ + + + + If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. + + + Disable warning when deleting events with IDs + + + diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index fc156a72..da006eff 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1119,9 +1119,9 @@ - + - The icon that will be used to represent Healspot events + The icon that will be used to represent Heal Location events true @@ -1136,9 +1136,9 @@ - + - Healspots + Heal Locations @@ -1224,7 +1224,7 @@ - + ... diff --git a/include/config.h b/include/config.h index 502ee1ec..d4bb055e 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,7 @@ public: this->paletteEditorBitDepth = 24; this->projectSettingsTab = 0; this->warpBehaviorWarningDisabled = false; + this->eventDeleteWarningDisabled = false; this->checkForUpdates = true; this->lastUpdateCheckTime = QDateTime(); this->lastUpdateCheckVersion = porymapVersion; @@ -132,6 +133,7 @@ public: int paletteEditorBitDepth; int projectSettingsTab; bool warpBehaviorWarningDisabled; + bool eventDeleteWarningDisabled; bool checkForUpdates; QDateTime lastUpdateCheckTime; QVersionNumber lastUpdateCheckVersion; @@ -184,11 +186,6 @@ enum ProjectIdentifier { symbol_obj_event_gfx_pointers, symbol_pokemon_icon_table, symbol_wild_encounters, - symbol_heal_locations_type, - symbol_heal_locations, - symbol_spawn_points, - symbol_spawn_maps, - symbol_spawn_npcs, symbol_attribute_table, symbol_tilesets_prefix, symbol_dynamic_map_name, @@ -214,7 +211,6 @@ enum ProjectIdentifier { define_attribute_encounter, define_metatile_label_prefix, define_heal_locations_prefix, - define_spawn_prefix, define_map_prefix, define_map_dynamic, define_map_empty, @@ -247,6 +243,7 @@ enum ProjectFilePath { json_map_groups, json_layouts, json_wild_encounters, + json_heal_locations, json_region_map_entries, json_region_porymap_cfg, tilesets_headers, @@ -260,14 +257,12 @@ enum ProjectFilePath { data_obj_event_pic_tables, data_obj_event_gfx, data_pokemon_gfx, - data_heal_locations, constants_global, constants_items, constants_flags, constants_vars, constants_weather, constants_songs, - constants_heal_locations, constants_pokemon, constants_map_types, constants_trainer_types, diff --git a/include/core/events.h b/include/core/events.h index 081ff506..bdf9d5a2 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -10,7 +10,6 @@ #include #include "orderedjson.h" -using OrderedJson = poryjson::Json; class Project; @@ -125,7 +124,7 @@ public: // standard public methods public: - virtual Event *duplicate() = 0; + virtual Event *duplicate() const = 0; void setMap(Map *newMap) { this->map = newMap; } Map *getMap() const { return this->map; } @@ -155,7 +154,7 @@ public: Event::Type getEventType() const { return this->eventType; } virtual OrderedJson::object buildEventJson(Project *project) = 0; - virtual bool loadFromJson(QJsonObject json, Project *project) = 0; + virtual bool loadFromJson(const QJsonObject &json, Project *project) = 0; virtual void setDefaultValues(Project *project); @@ -168,10 +167,10 @@ public: virtual void loadPixmap(Project *project); void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } - QPixmap getPixmap() { return this->pixmap; } + QPixmap getPixmap() const { return this->pixmap; } void setPixmapItem(DraggablePixmapItem *item); - DraggablePixmapItem *getPixmapItem() { return this->pixmapItem; } + DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; } void setUsingSprite(bool newUsingSprite) { this->usingSprite = newUsingSprite; } bool getUsingSprite() const { return this->usingSprite; } @@ -184,6 +183,9 @@ public: int getEventIndex(); + void setIdName(QString newIdName) { this->idName = newIdName; } + QString getIdName() const { return this->idName; } + static QString eventGroupToString(Event::Group group); static QString eventTypeToString(Event::Type type); static Event::Type eventTypeFromString(QString type); @@ -206,6 +208,11 @@ protected: int spriteHeight = 16; bool usingSprite = false; + // Some events can have an associated #define name that should be unique to this event. + // e.g. object events can have a 'LOCALID', or Heal Locations have a 'HEAL_LOCATION' id. + // When deleting events like this we want to warn the user that the #define may also be deleted. + QString idName; + QMap customAttributes; QPixmap pixmap; @@ -227,14 +234,14 @@ public: } virtual ~ObjectEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); } virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -243,28 +250,28 @@ public: virtual void loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } - QString getGfx() { return this->gfx; } + QString getGfx() const { return this->gfx; } void setMovement(QString newMovement) { this->movement = newMovement; } - QString getMovement() { return this->movement; } + QString getMovement() const { return this->movement; } void setRadiusX(int newRadiusX) { this->radiusX = newRadiusX; } - int getRadiusX() { return this->radiusX; } + int getRadiusX() const { return this->radiusX; } void setRadiusY(int newRadiusY) { this->radiusY = newRadiusY; } - int getRadiusY() { return this->radiusY; } + int getRadiusY() const { return this->radiusY; } void setTrainerType(QString newTrainerType) { this->trainerType = newTrainerType; } - QString getTrainerType() { return this->trainerType; } + QString getTrainerType() const { return this->trainerType; } void setSightRadiusBerryTreeID(QString newValue) { this->sightRadiusBerryTreeID = newValue; } - QString getSightRadiusBerryTreeID() { return this->sightRadiusBerryTreeID; } + QString getSightRadiusBerryTreeID() const { return this->sightRadiusBerryTreeID; } void setScript(QString newScript) { this->script = newScript; } - QString getScript() { return this->script; } + QString getScript() const { return this->script; } void setFlag(QString newFlag) { this->flag = newFlag; } - QString getFlag() { return this->flag; } + QString getFlag() const { return this->flag; } public: void setFrameFromMovement(QString movement); @@ -300,12 +307,12 @@ public: } virtual ~CloneObjectEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -314,10 +321,10 @@ public: virtual void loadPixmap(Project *project) override; void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } - QString getTargetMap() { return this->targetMap; } + QString getTargetMap() const { return this->targetMap; } void setTargetID(int newTargetID) { this->targetID = newTargetID; } - int getTargetID() { return this->targetID; } + int getTargetID() const { return this->targetID; } private: QString targetMap; @@ -338,22 +345,22 @@ public: } virtual ~WarpEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setDestinationMap(QString newDestinationMap) { this->destinationMap = newDestinationMap; } - QString getDestinationMap() { return this->destinationMap; } + QString getDestinationMap() const { return this->destinationMap; } void setDestinationWarpID(QString newDestinationWarpID) { this->destinationWarpID = newDestinationWarpID; } - QString getDestinationWarpID() { return this->destinationWarpID; } + QString getDestinationWarpID() const { return this->destinationWarpID; } void setWarningEnabled(bool enabled); @@ -373,12 +380,12 @@ public: CoordEvent() : Event() {} virtual ~CoordEvent() {} - virtual Event *duplicate() override = 0; + virtual Event *duplicate() const override = 0; virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -399,27 +406,27 @@ public: } virtual ~TriggerEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); } virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; } - QString getScriptVar() { return this->scriptVar; } + QString getScriptVar() const { return this->scriptVar; } void setScriptVarValue(QString newScriptVarValue) { this->scriptVarValue = newScriptVarValue; } - QString getScriptVarValue() { return this->scriptVarValue; } + QString getScriptVarValue() const { return this->scriptVarValue; } void setScriptLabel(QString newScriptLabel) { this->scriptLabel = newScriptLabel; } - QString getScriptLabel() { return this->scriptLabel; } + QString getScriptLabel() const { return this->scriptLabel; } private: QString scriptVar; @@ -441,19 +448,19 @@ public: } virtual ~WeatherTriggerEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setWeather(QString newWeather) { this->weather = newWeather; } - QString getWeather() { return this->weather; } + QString getWeather() const { return this->weather; } private: QString weather; @@ -472,12 +479,12 @@ public: } virtual ~BGEvent() {} - virtual Event *duplicate() override = 0; + virtual Event *duplicate() const override = 0; virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -497,24 +504,24 @@ public: } virtual ~SignEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); } virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; } - QString getFacingDirection() { return this->facingDirection; } + QString getFacingDirection() const { return this->facingDirection; } void setScriptLabel(QString newScriptLabel) { this->scriptLabel = newScriptLabel; } - QString getScriptLabel() { return this->scriptLabel; } + QString getScriptLabel() const { return this->scriptLabel; } private: QString facingDirection; @@ -534,28 +541,28 @@ public: } virtual ~HiddenItemEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setItem(QString newItem) { this->item = newItem; } - QString getItem() { return this->item; } + QString getItem() const { return this->item; } void setFlag(QString newFlag) { this->flag = newFlag; } - QString getFlag() { return this->flag; } + QString getFlag() const { return this->flag; } void setQuantity(int newQuantity) { this->quantity = newQuantity; } - int getQuantity() { return this->quantity; } + int getQuantity() const { return this->quantity; } void setUnderfoot(bool newUnderfoot) { this->underfoot = newUnderfoot; } - bool getUnderfoot() { return this->underfoot; } + bool getUnderfoot() const { return this->underfoot; } private: QString item; @@ -579,19 +586,19 @@ public: } virtual ~SecretBaseEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setBaseID(QString newBaseID) { this->baseID = newBaseID; } - QString getBaseID() { return this->baseID; } + QString getBaseID() const { return this->baseID; } private: QString baseID; @@ -611,38 +618,26 @@ public: } virtual ~HealLocationEvent() {} - virtual Event *duplicate() override { return nullptr; } + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject, Project *) override { return false; } + virtual bool loadFromJson(const QJsonObject &, Project *) override; virtual void setDefaultValues(Project *project) override; - virtual QSet getExpectedFields() override { return QSet(); } + virtual QSet getExpectedFields() override; - void setIndex(int newIndex) { this->index = newIndex; } - int getIndex() { return this->index; } + void setRespawnMapName(QString newRespawnMapName) { this->respawnMapName = newRespawnMapName; } + QString getRespawnMapName() const { return this->respawnMapName; } - void setLocationName(QString newLocationName) { this->locationName = newLocationName; } - QString getLocationName() { return this->locationName; } - - void setIdName(QString newIdName) { this->idName = newIdName; } - QString getIdName() { return this->idName; } - - void setRespawnMap(QString newRespawnMap) { this->respawnMap = newRespawnMap; } - QString getRespawnMap() { return this->respawnMap; } - - void setRespawnNPC(uint8_t newRespawnNPC) { this->respawnNPC = newRespawnNPC; } - uint8_t getRespawnNPC() { return this->respawnNPC; } + void setRespawnNPC(QString newRespawnNPC) { this->respawnNPC = newRespawnNPC; } + QString getRespawnNPC() const { return this->respawnNPC; } private: - int index = -1; - QString locationName; - QString idName; - QString respawnMap; - uint8_t respawnNPC = 0; + QString respawnMapName; + QString respawnNPC; }; @@ -656,7 +651,7 @@ public: virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); }; virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); }; - QStringList getScripts() { return this->scripts; } + QStringList getScripts() const { return this->scripts; } private: QStringList scripts; diff --git a/include/core/heallocation.h b/include/core/heallocation.h deleted file mode 100644 index 533f44b5..00000000 --- a/include/core/heallocation.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#ifndef HEALLOCATION_H -#define HEALLOCATION_H - -#include -#include - -class Event; - -class HealLocation { - -public: - HealLocation()=default; - HealLocation(QString, QString, int, int16_t, int16_t, QString = "", uint8_t = 1); - friend QDebug operator<<(QDebug debug, const HealLocation &hl); - -public: - QString idName; - QString mapName; - int index; - int16_t x; - int16_t y; - QString respawnMap; - uint8_t respawnNPC; - static HealLocation fromEvent(Event *); -}; - -#endif // HEALLOCATION_H diff --git a/include/core/map.h b/include/core/map.h index b223536d..a25db5e9 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -84,7 +84,7 @@ public: void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); - int getIndexOfEvent(Event *) const; + int getIndexOfEvent(const Event *) const; void deleteConnections(); QList getConnections() const; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index d184646b..b220597a 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -2,7 +2,6 @@ #ifndef PARSEUTIL_H #define PARSEUTIL_H -#include "heallocation.h" #include "log.h" #include "orderedjson.h" #include "orderedmap.h" @@ -78,11 +77,11 @@ public: static QString removeLineComments(QString text, const QStringList &commentSymbols); static QStringList splitShellCommand(QStringView command); - static int gameStringToInt(QString gameString, bool * ok = nullptr); - static bool gameStringToBool(QString gameString, bool * ok = nullptr); - static QString jsonToQString(QJsonValue value, bool * ok = nullptr); - static int jsonToInt(QJsonValue value, bool * ok = nullptr); - static bool jsonToBool(QJsonValue value, bool * ok = nullptr); + static int gameStringToInt(const QString &gameString, bool * ok = nullptr); + static bool gameStringToBool(const QString &gameString, bool * ok = nullptr); + static QString jsonToQString(const QJsonValue &value, bool * ok = nullptr); + static int jsonToInt(const QJsonValue &value, bool * ok = nullptr); + static bool jsonToBool(const QJsonValue &value, bool * ok = nullptr); private: QString root; diff --git a/include/editor.h b/include/editor.h index 81bf1611..104cda9a 100644 --- a/include/editor.h +++ b/include/editor.h @@ -46,7 +46,6 @@ public: public: Ui::MainWindow* ui; - QObject *parent = nullptr; QPointer project = nullptr; QPointer map = nullptr; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index c13ec13c..386937f9 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -247,4 +247,7 @@ protected: } // namespace poryjson +using OrderedJson = poryjson::Json; +using OrderedJsonDoc = poryjson::JsonDoc; + #endif // ORDERED_JSON_H diff --git a/include/project.h b/include/project.h index aff38f03..fcdde462 100644 --- a/include/project.h +++ b/include/project.h @@ -4,7 +4,6 @@ #include "map.h" #include "blockdata.h" -#include "heallocation.h" #include "wildmoninfo.h" #include "parseutil.h" #include "orderedjson.h" @@ -35,8 +34,8 @@ public: QStringList mapNames; QStringList groupNames; QMap groupNameToMapNames; - QList healLocations; - QMap healLocationNameToValue; + QStringList healLocationSaveOrder; + QMap> healLocations; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; QMap mapNameToLayoutId; @@ -87,15 +86,7 @@ public: void clearTilesetCache(); void clearMapLayouts(); void clearEventGraphics(); - - struct DataQualifiers - { - bool isStatic; - bool isConst; - }; - DataQualifiers getDataQualifiers(QString, QString); - DataQualifiers healLocationDataQualifiers; - QString healLocationsTableName; + void clearHealLocations(); bool sanityCheck(); bool load(); @@ -142,7 +133,8 @@ public: bool isIdentifierUnique(const QString &identifier) const; bool isValidNewIdentifier(QString identifier) const; QString toUniqueIdentifier(const QString &identifier) const; - QString getProjectTitle(); + QString getProjectTitle() const; + QString getNewHealLocationName(const Map* map) const; bool readWildMonData(); tsl::ordered_map> wildMonData; @@ -187,7 +179,7 @@ public: void saveMapGroups(); void saveRegionMapSections(); void saveWildMonData(); - void saveHealLocations(Map*); + void saveHealLocations(); void saveTilesets(Tileset*, Tileset*); void saveTilesetMetatileLabels(Tileset*, Tileset*); void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); @@ -207,7 +199,6 @@ public: bool readBgEventFacingDirections(); bool readTrainerTypes(); bool readMetatileBehaviors(); - bool readHealLocationConstants(); bool readHealLocations(); bool readMiscellaneousConstants(); bool readEventScriptLabels(); @@ -267,9 +258,6 @@ private: void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); - void saveHealLocationsData(Map *map); - void saveHealLocationsConstants(); - void ignoreWatchedFileTemporarily(QString filepath); static int num_tiles_primary; diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 2ad55750..cfde161c 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -267,10 +267,11 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_id; QFrame *hideable_respawn_map; QFrame *hideable_respawn_npc; NoScrollComboBox *combo_respawn_map; - NoScrollSpinBox *spinner_respawn_npc; + NoScrollComboBox *combo_respawn_npc; private: HealLocationEvent *healLocation; diff --git a/porymap.pro b/porymap.pro index 76d19d21..f3599c1a 100644 --- a/porymap.pro +++ b/porymap.pro @@ -39,7 +39,6 @@ SOURCES += src/core/advancemapparser.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ src/core/filedialog.cpp \ - src/core/heallocation.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ src/core/mapconnection.cpp \ @@ -150,7 +149,6 @@ HEADERS += include/core/advancemapparser.h \ include/core/blockdata.h \ include/core/events.h \ include/core/filedialog.h \ - include/core/heallocation.h \ include/core/history.h \ include/core/imageexport.h \ include/core/map.h \ diff --git a/src/config.cpp b/src/config.cpp index 2356f7a9..2511aa65 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -75,11 +75,6 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::symbol_obj_event_gfx_pointers, {"symbol_obj_event_gfx_pointers", "gObjectEventGraphicsInfoPointers"}}, {ProjectIdentifier::symbol_pokemon_icon_table, {"symbol_pokemon_icon_table", "gMonIconTable"}}, {ProjectIdentifier::symbol_wild_encounters, {"symbol_wild_encounters", "gWildMonHeaders"}}, - {ProjectIdentifier::symbol_heal_locations_type, {"symbol_heal_locations_type", "struct HealLocation"}}, - {ProjectIdentifier::symbol_heal_locations, {"symbol_heal_locations", "sHealLocations"}}, - {ProjectIdentifier::symbol_spawn_points, {"symbol_spawn_points", "sSpawnPoints"}}, - {ProjectIdentifier::symbol_spawn_maps, {"symbol_spawn_maps", "u16 sWhiteoutRespawnHealCenterMapIdxs"}}, - {ProjectIdentifier::symbol_spawn_npcs, {"symbol_spawn_npcs", "u8 sWhiteoutRespawnHealerNpcIds"}}, {ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}}, {ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}}, {ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}}, @@ -106,7 +101,6 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}}, {ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}}, {ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}}, - {ProjectIdentifier::define_spawn_prefix, {"define_spawn_prefix", "SPAWN_"}}, {ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}}, {ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "DYNAMIC"}}, {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, @@ -140,6 +134,7 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::json_map_groups, { "json_map_groups", "data/maps/map_groups.json"}}, {ProjectFilePath::json_layouts, { "json_layouts", "data/layouts/layouts.json"}}, {ProjectFilePath::json_wild_encounters, { "json_wild_encounters", "src/data/wild_encounters.json"}}, + {ProjectFilePath::json_heal_locations, { "json_heal_locations", "src/data/heal_locations.json"}}, {ProjectFilePath::json_region_map_entries, { "json_region_map_entries", "src/data/region_map/region_map_sections.json"}}, {ProjectFilePath::json_region_porymap_cfg, { "json_region_porymap_cfg", "src/data/region_map/porymap_config.json"}}, {ProjectFilePath::tilesets_headers, { "tilesets_headers", "src/data/tilesets/headers.h"}}, @@ -153,14 +148,12 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::data_obj_event_pic_tables, { "data_obj_event_pic_tables", "src/data/object_events/object_event_pic_tables.h"}}, {ProjectFilePath::data_obj_event_gfx, { "data_obj_event_gfx", "src/data/object_events/object_event_graphics.h"}}, {ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}}, - {ProjectFilePath::data_heal_locations, { "data_heal_locations", "src/data/heal_locations.h"}}, {ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}}, {ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}}, {ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}}, {ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}}, {ProjectFilePath::constants_weather, { "constants_weather", "include/constants/weather.h"}}, {ProjectFilePath::constants_songs, { "constants_songs", "include/constants/songs.h"}}, - {ProjectFilePath::constants_heal_locations, { "constants_heal_locations", "include/constants/heal_locations.h"}}, {ProjectFilePath::constants_pokemon, { "constants_pokemon", "include/constants/pokemon.h"}}, {ProjectFilePath::constants_map_types, { "constants_map_types", "include/constants/map_types.h"}}, {ProjectFilePath::constants_trainer_types, { "constants_trainer_types", "include/constants/trainer_types.h"}}, @@ -399,6 +392,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->projectSettingsTab = getConfigInteger(key, value, 0); } else if (key == "warp_behavior_warning_disabled") { this->warpBehaviorWarningDisabled = getConfigBool(key, value); + } else if (key == "event_delete_warning_disabled") { + this->eventDeleteWarningDisabled = getConfigBool(key, value); } else if (key == "check_for_updates") { this->checkForUpdates = getConfigBool(key, value); } else if (key == "last_update_check_time") { @@ -475,6 +470,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth)); map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); + map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); map.insert("check_for_updates", QString::number(this->checkForUpdates)); map.insert("last_update_check_time", this->lastUpdateCheckTime.toUTC().toString()); map.insert("last_update_check_version", this->lastUpdateCheckVersion.toString()); diff --git a/src/core/events.cpp b/src/core/events.cpp index 2da13da0..87421035 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -86,7 +86,7 @@ QString Event::eventGroupToString(Event::Group group) { case Event::Group::Bg: return "BG"; case Event::Group::Heal: - return "Healspot"; + return "Heal Location"; default: return ""; } @@ -111,7 +111,7 @@ QString Event::eventTypeToString(Event::Type type) { case Event::Type::SecretBase: return "event_secret_base"; case Event::Type::HealLocation: - return "event_healspot"; + return "event_heal_location"; default: return ""; } @@ -134,7 +134,7 @@ Event::Type Event::eventTypeFromString(QString type) { return Event::Type::HiddenItem; } else if (type == "event_secret_base") { return Event::Type::SecretBase; - } else if (type == "event_healspot") { + } else if (type == "event_heal_location") { return Event::Type::HealLocation; } else { return Event::Type::None; @@ -183,7 +183,7 @@ void Event::setIcons() { } -Event *ObjectEvent::duplicate() { +Event *ObjectEvent::duplicate() const { ObjectEvent *copy = new ObjectEvent(); copy->setX(this->getX()); @@ -232,7 +232,7 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { return objectJson; } -bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { +bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -362,7 +362,7 @@ void ObjectEvent::setFrameFromMovement(QString facingDir) { -Event *CloneObjectEvent::duplicate() { +Event *CloneObjectEvent::duplicate() const { CloneObjectEvent *copy = new CloneObjectEvent(); copy->setX(this->getX()); @@ -399,7 +399,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { return cloneJson; } -bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { +bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); @@ -469,7 +469,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { -Event *WarpEvent::duplicate() { +Event *WarpEvent::duplicate() const { WarpEvent *copy = new WarpEvent(); copy->setX(this->getX()); @@ -506,7 +506,7 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { return warpJson; } -bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { +bool WarpEvent::loadFromJson(const QJsonObject &json, Project *project) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -550,7 +550,7 @@ void WarpEvent::setWarningEnabled(bool enabled) { -Event *TriggerEvent::duplicate() { +Event *TriggerEvent::duplicate() const { TriggerEvent *copy = new TriggerEvent(); copy->setX(this->getX()); @@ -589,7 +589,7 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { return triggerJson; } -bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { +bool TriggerEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -626,7 +626,7 @@ QSet TriggerEvent::getExpectedFields() { -Event *WeatherTriggerEvent::duplicate() { +Event *WeatherTriggerEvent::duplicate() const { WeatherTriggerEvent *copy = new WeatherTriggerEvent(); copy->setX(this->getX()); @@ -661,7 +661,7 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { return weatherJson; } -bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { +bool WeatherTriggerEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -692,7 +692,7 @@ QSet WeatherTriggerEvent::getExpectedFields() { -Event *SignEvent::duplicate() { +Event *SignEvent::duplicate() const { SignEvent *copy = new SignEvent(); copy->setX(this->getX()); @@ -729,7 +729,7 @@ OrderedJson::object SignEvent::buildEventJson(Project *) { return signJson; } -bool SignEvent::loadFromJson(QJsonObject json, Project *) { +bool SignEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -763,7 +763,7 @@ QSet SignEvent::getExpectedFields() { -Event *HiddenItemEvent::duplicate() { +Event *HiddenItemEvent::duplicate() const { HiddenItemEvent *copy = new HiddenItemEvent(); copy->setX(this->getX()); @@ -808,7 +808,7 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { return hiddenItemJson; } -bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { +bool HiddenItemEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -859,7 +859,7 @@ QSet HiddenItemEvent::getExpectedFields() { -Event *SecretBaseEvent::duplicate() { +Event *SecretBaseEvent::duplicate() const { SecretBaseEvent *copy = new SecretBaseEvent(); copy->setX(this->getX()); @@ -894,7 +894,7 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { return secretBaseJson; } -bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { +bool SecretBaseEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); @@ -925,6 +925,20 @@ QSet SecretBaseEvent::getExpectedFields() { +Event *HealLocationEvent::duplicate() const { + HealLocationEvent *copy = new HealLocationEvent(); + + copy->setX(this->getX()); + copy->setY(this->getY()); + copy->setIdName(this->getIdName()); + copy->setRespawnMapName(this->getRespawnMapName()); + copy->setRespawnNPC(this->getRespawnNPC()); + + copy->setCustomAttributes(this->getCustomAttributes()); + + return copy; +} + EventFrame *HealLocationEvent::createEventFrame() { if (!this->eventFrame) { this->eventFrame = new HealLocationFrame(this); @@ -933,22 +947,62 @@ EventFrame *HealLocationEvent::createEventFrame() { return this->eventFrame; } -OrderedJson::object HealLocationEvent::buildEventJson(Project *) { - return OrderedJson::object(); -} +OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { + OrderedJson::object healLocationJson; -void HealLocationEvent::setDefaultValues(Project *) { - this->setElevation(projectConfig.defaultElevation); - if (!this->map) - return; - - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix - : ProjectIdentifier::define_heal_locations_prefix); - this->setLocationName(this->map->constantName()); - this->setIdName(prefix + this->map->constantName()); - if (respawnEnabled) { - this->setRespawnMap(this->map->name()); - this->setRespawnNPC(1); + healLocationJson["id"] = this->getIdName(); + // This field doesn't need to be stored in the Event itself, so it's output only. + healLocationJson["map"] = this->getMap() ? this->getMap()->constantName() : QString(); + healLocationJson["x"] = this->getX(); + healLocationJson["y"] = this->getY(); + if (projectConfig.healLocationRespawnDataEnabled) { + const QString mapName = this->getRespawnMapName(); + healLocationJson["respawn_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + healLocationJson["respawn_npc"] = this->getRespawnNPC(); } + + this->addCustomAttributesTo(&healLocationJson); + + return healLocationJson; +} + +bool HealLocationEvent::loadFromJson(const QJsonObject &json, Project *project) { + this->setX(ParseUtil::jsonToInt(json["x"])); + this->setY(ParseUtil::jsonToInt(json["y"])); + this->setIdName(ParseUtil::jsonToQString(json["id"])); + + if (projectConfig.healLocationRespawnDataEnabled) { + // Log a warning if "respawn_map" isn't a known map ID, but don't overwrite user data. + const QString mapConstant = ParseUtil::jsonToQString(json["respawn_map"]); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Unknown Respawn Map constant '%1'.").arg(mapConstant)); + this->setRespawnMapName(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); + this->setRespawnNPC(ParseUtil::jsonToQString(json["respawn_npc"])); + } + + this->readCustomAttributes(json); + return true; +} + +void HealLocationEvent::setDefaultValues(Project *project) { + if (this->map) { + this->setIdName(project->getNewHealLocationName(this->map)); + this->setRespawnMapName(this->map->name()); + } + this->setRespawnNPC(QString::number(0 + this->getIndexOffset(Event::Group::Object))); +} + +const QSet expectedHealLocationFields = { + "id", + "map" +}; + +QSet HealLocationEvent::getExpectedFields() { + QSet expectedFields = expectedHealLocationFields; + if (projectConfig.healLocationRespawnDataEnabled) { + expectedFields.insert("respawn_map"); + expectedFields.insert("respawn_npc"); + } + expectedFields << "x" << "y"; + return expectedFields; } diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp deleted file mode 100644 index d4f9a2c8..00000000 --- a/src/core/heallocation.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "heallocation.h" -#include "config.h" -#include "events.h" -#include "map.h" - -// TODO: Remove - -HealLocation::HealLocation(QString id, QString map, - int i, int16_t x, int16_t y, - QString respawnMap, uint8_t respawnNPC) { - this->idName = id; - this->mapName = map; - this->index = i; - this->x = x; - this->y = y; - this->respawnMap = respawnMap; - this->respawnNPC = respawnNPC; -} - -HealLocation HealLocation::fromEvent(Event *fromEvent) { - HealLocationEvent *event = dynamic_cast(fromEvent); - - HealLocation healLocation; - healLocation.idName = event->getIdName(); - healLocation.mapName = event->getLocationName(); - healLocation.index = event->getIndex(); - healLocation.x = event->getX(); - healLocation.y = event->getY(); - if (projectConfig.healLocationRespawnDataEnabled) { - healLocation.respawnNPC = event->getRespawnNPC(); - healLocation.respawnMap = Map::mapConstantFromName(event->getRespawnMap(), false); - } - return healLocation; -} - -QDebug operator<<(QDebug debug, const HealLocation &healLocation) { - debug << "HealLocation_" + healLocation.mapName << "(" << healLocation.x << ',' << healLocation.y << ")"; - return debug; -} diff --git a/src/core/map.cpp b/src/core/map.cpp index aac20138..e784ba5f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -230,7 +230,7 @@ void Map::addEvent(Event *event) { if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event); } -int Map::getIndexOfEvent(Event *event) const { +int Map::getIndexOfEvent(const Event *event) const { return m_events.value(event->getEventGroup()).indexOf(event); } diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index a4a2018d..d67989ae 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -31,8 +31,6 @@ static const QMap globalDefineValues = { {"UINT_MAX", UINT_MAX}, }; -using OrderedJson = poryjson::Json; - ParseUtil::ParseUtil() { } void ParseUtil::set_root(const QString &dir) { @@ -580,7 +578,7 @@ QMap ParseUtil::readNamedIndexCArray(const QString &filename, return map; } -int ParseUtil::gameStringToInt(QString gameString, bool * ok) { +int ParseUtil::gameStringToInt(const QString &gameString, bool * ok) { if (ok) *ok = true; if (QString::compare(gameString, "TRUE", Qt::CaseInsensitive) == 0) return 1; @@ -589,7 +587,7 @@ int ParseUtil::gameStringToInt(QString gameString, bool * ok) { return gameString.toInt(ok, 0); } -bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { +bool ParseUtil::gameStringToBool(const QString &gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } @@ -705,7 +703,7 @@ bool ParseUtil::ensureFieldsExist(const QJsonObject &obj, const QList & // QJsonValues are strictly typed, and so will not attempt any implicit conversions. // The below functions are for attempting to convert a JSON value read from the user's // project to a QString, int, or bool (whichever Porymap expects). -QString ParseUtil::jsonToQString(QJsonValue value, bool * ok) { +QString ParseUtil::jsonToQString(const QJsonValue &value, bool * ok) { if (ok) *ok = true; switch (value.type()) { @@ -718,7 +716,7 @@ QString ParseUtil::jsonToQString(QJsonValue value, bool * ok) { return QString(); } -int ParseUtil::jsonToInt(QJsonValue value, bool * ok) { +int ParseUtil::jsonToInt(const QJsonValue &value, bool * ok) { if (ok) *ok = true; switch (value.type()) { @@ -731,7 +729,7 @@ int ParseUtil::jsonToInt(QJsonValue value, bool * ok) { return 0; } -bool ParseUtil::jsonToBool(QJsonValue value, bool * ok) { +bool ParseUtil::jsonToBool(const QJsonValue &value, bool * ok) { if (ok) *ok = true; switch (value.type()) { diff --git a/src/editor.cpp b/src/editor.cpp index 3c7fee27..ceb7aa67 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -12,6 +12,7 @@ #include "scripting.h" #include "customattributesframe.h" #include "validator.h" +#include "message.h" #include #include #include @@ -1376,13 +1377,11 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i if (this->selected_events->size() > 0) eventType = this->selected_events->first()->event->getEventType(); - if (eventType != Event::Type::HealLocation) { - DraggablePixmapItem *newEvent = addNewEvent(eventType); - if (newEvent) { - newEvent->move(pos.x(), pos.y()); - emit objectsChanged(); - selectMapEvent(newEvent); - } + DraggablePixmapItem *newEvent = addNewEvent(eventType); + if (newEvent) { + newEvent->move(pos.x(), pos.y()); + emit objectsChanged(); + selectMapEvent(newEvent); } } } else if (objectEditAction == EditAction::Select) { @@ -2112,15 +2111,7 @@ void Editor::duplicateSelectedEvents() { logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::eventTypeToString(eventType))); continue; } - if (eventType == Event::Type::HealLocation) { - logWarn("Skipping duplication, event is a heal location."); - continue; - } Event *duplicate = original->duplicate(); - if (!duplicate) { - logError("Encountered a problem duplicating an event."); - continue; - } duplicate->setX(duplicate->getX() + 1); duplicate->setY(duplicate->getY() + 1); selectedEvents.append(duplicate); @@ -2138,13 +2129,6 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { event->setMap(this->map); event->setDefaultValues(this->project); - - if (type == Event::Type::HealLocation) { - HealLocation healLocation = HealLocation::fromEvent(event); - project->healLocations.append(healLocation); - ((HealLocationEvent *)event)->setIndex(project->healLocations.length()); - } - map->commit(new EventCreate(this, map, event)); return event->getPixmapItem(); } @@ -2162,42 +2146,73 @@ void Editor::deleteSelectedEvents() { if (!this->selected_events || this->selected_events->length() == 0 || !this->map || this->editMode != EditMode::Events) return; - DraggablePixmapItem *nextSelectedEvent = nullptr; - QList selectedEvents; - int numDeleted = 0; + QList eventsToDelete; + bool skipWarning = porymapConfig.eventDeleteWarningDisabled; for (DraggablePixmapItem *item : *this->selected_events) { - Event::Group event_group = item->event->getEventGroup(); - if (event_group != Event::Group::Heal) { - numDeleted++; - item->event->setPixmapItem(item); - selectedEvents.append(item->event); - } - else { // don't allow deletion of heal locations - logWarn(QString("Cannot delete event of type '%1'").arg(Event::eventTypeToString(item->event->getEventType()))); - } - } - if (numDeleted) { - // Get the index for the event that should be selected after this event has been deleted. - // Select event at next smallest index when deleting a single event. - // If deleting multiple events, just let editor work out next selected. - if (numDeleted == 1) { - Event::Group event_group = selectedEvents[0]->getEventGroup(); - int index = this->map->getIndexOfEvent(selectedEvents[0]); - if (index != this->map->getNumEvents(event_group) - 1) - index++; - else - index--; - Event *event = this->map->getEvent(event_group, index); - for (QGraphicsItem *child : this->events_group->childItems()) { - DraggablePixmapItem *event_item = static_cast(child); - if (event_item->event == event) { - nextSelectedEvent = event_item; - break; + Event* event = item->event; + const QString idName = event->getIdName(); + if (skipWarning || idName.isEmpty()) { + eventsToDelete.append(event); + } else { + // If an event with a ID #define is deleted, its ID is also deleted (by the user's project, not Porymap). + // Warn the user about this and give them a chance to abort. + WarningMessage msgBox(QStringLiteral("Deleting this event may also delete the constant listed below. This can stop your project from compiling.\n\n" + "Are you sure you want to delete this event?"), + ui->graphicsView_Map); + msgBox.setInformativeText(idName); + msgBox.setIconPixmap(event->getPixmap()); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + msgBox.addButton(QStringLiteral("Delete"), QMessageBox::DestructiveRole); + msgBox.setCheckBox(new QCheckBox(QStringLiteral("Don't warn me again"))); + + QAbstractButton* deleteAllButton = nullptr; + if (this->selected_events->length() > 1) { + deleteAllButton = msgBox.addButton(QStringLiteral("Delete All"), QMessageBox::DestructiveRole); + msgBox.addButton(QStringLiteral("Skip"), QMessageBox::NoRole); + } + + msgBox.exec(); + auto clickedButton = msgBox.clickedButton(); + auto clickedRole = msgBox.buttonRole(clickedButton); + porymapConfig.eventDeleteWarningDisabled = msgBox.checkBox()->isChecked(); + if (clickedRole == QMessageBox::DestructiveRole) { + // Confirmed deleting this event. + eventsToDelete.append(event); + if (deleteAllButton && clickedButton == deleteAllButton) { + // Confirmed deleting all events, no more warning. + skipWarning = true; } + } else if (clickedRole == QMessageBox::NoRole) { + // Declined deleting this event. + continue; + } else if (clickedRole == QMessageBox::RejectRole) { + // Canceled delete. + return; } } - this->map->commit(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); + // TODO: Are we just calling this to invalidate connections? + event->setPixmapItem(item); } + if (eventsToDelete.isEmpty()) + return; + + // Get the index for the event that should be selected after this event has been deleted. + // Select event at next smallest index when deleting a single event. + // If deleting multiple events, just let editor work out next selected. + Event *nextSelectedEvent = nullptr; + if (eventsToDelete.length() == 1) { + const Event *eventToDelete = eventsToDelete.first(); + Event::Group event_group = eventToDelete->getEventGroup(); + int index = this->map->getIndexOfEvent(eventToDelete); + if (index != this->map->getNumEvents(event_group) - 1) + index++; + else + index--; + nextSelectedEvent = this->map->getEvent(event_group, index); + } + + this->map->commit(new EventDelete(this, this->map, eventsToDelete, nextSelectedEvent)); } void Editor::openMapScripts() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d0ba8040..5e05517e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -56,9 +56,6 @@ #define RELEASE_PLATFORM #endif -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - MainWindow::MainWindow(QWidget *parent) : @@ -857,6 +854,11 @@ bool MainWindow::userSetMap(QString map_name) { if (editor->map && editor->map->name() == map_name) return true; // Already set + if (map_name.isEmpty()) { + WarningMessage::show(QStringLiteral("Cannot open map with empty name."), this); + return false; + } + if (map_name == editor->project->getDynamicMapName()) { WarningMessage msgBox(QString("Cannot open map '%1'.").arg(map_name), this); msgBox.setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); @@ -1276,6 +1278,10 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); + if (newMap->needsHealLocation()) { + addNewEvent(Event::Type::HealLocation); + } + // TODO: Creating a new map shouldn't be automatically saved. // For one, it takes away the option to discard the new map. // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. @@ -1297,12 +1303,6 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } - if (newMap->needsHealLocation()) { - addNewEvent(Event::Type::HealLocation); - editor->project->saveHealLocations(newMap); - editor->save(); - } - userSetMap(newMap->name()); } @@ -1606,13 +1606,6 @@ void MainWindow::copy() { for (auto item : events) { Event *event = item->event; - - if (event->getEventType() == Event::Type::HealLocation) { - // no copy on heal locations - logWarn(QString("Copying heal location events is not allowed.")); - continue; - } - OrderedJson::object eventContainer; eventContainer["event_type"] = Event::eventTypeToString(event->getEventType()); OrderedJson::object eventJson = event->buildEventJson(editor->project); @@ -1732,10 +1725,6 @@ void MainWindow::paste() { logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString)); continue; } - if (type == Event::Type::HealLocation) { - logWarn(QString("Cannot paste events of type '%1'").arg(typeString)); - continue; - } Event *pasteEvent = Event::create(type); if (!pasteEvent) @@ -1981,7 +1970,7 @@ void MainWindow::displayEventTabs() { tryAddEventTab(ui->tab_Warps); tryAddEventTab(ui->tab_Triggers); tryAddEventTab(ui->tab_BGs); - tryAddEventTab(ui->tab_Healspots); + tryAddEventTab(ui->tab_HealLocations); } void MainWindow::updateObjects() { @@ -2075,9 +2064,9 @@ void MainWindow::updateSelectedObjects() { break; } case Event::Group::Heal: { - scrollTarget = ui->scrollArea_Healspots; - target = ui->scrollAreaWidgetContents_Healspots; - ui->tabWidget_EventType->setCurrentWidget(ui->tab_Healspots); + scrollTarget = ui->scrollArea_HealLocations; + target = ui->scrollAreaWidgetContents_HealLocations; + ui->tabWidget_EventType->setCurrentWidget(ui->tab_HealLocations); QSignalBlocker b(this->ui->spinner_HealID); this->ui->spinner_HealID->setMinimum(event_offs); @@ -2142,11 +2131,11 @@ void MainWindow::updateSelectedObjects() { Event::Group MainWindow::getEventGroupFromTabWidget(QWidget *tab) { static const QMap tabToGroup = { - {ui->tab_Objects, Event::Group::Object}, - {ui->tab_Warps, Event::Group::Warp}, - {ui->tab_Triggers, Event::Group::Coord}, - {ui->tab_BGs, Event::Group::Bg}, - {ui->tab_Healspots, Event::Group::Heal}, + {ui->tab_Objects, Event::Group::Object}, + {ui->tab_Warps, Event::Group::Warp}, + {ui->tab_Triggers, Event::Group::Coord}, + {ui->tab_BGs, Event::Group::Bg}, + {ui->tab_HealLocations, Event::Group::Heal}, }; return tabToGroup.value(tab, Event::Group::None); } @@ -2169,6 +2158,9 @@ void MainWindow::eventTabChanged(int index) { case Event::Group::Bg: ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newSignAction); break; + case Event::Group::Heal: + ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newHealLocationAction); + break; default: break; } diff --git a/src/project.cpp b/src/project.cpp index 9e0a8c3a..690ecdcc 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -23,9 +23,6 @@ #include #include -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - int Project::num_tiles_primary = 512; int Project::num_tiles_total = 1024; int Project::num_metatiles_primary = 512; @@ -47,6 +44,7 @@ Project::~Project() clearTilesetCache(); clearMapLayouts(); clearEventGraphics(); + clearHealLocations(); } void Project::set_root(QString dir) { @@ -97,7 +95,6 @@ bool Project::load() { && readFieldmapMasks() && readTilesetLabels() && readTilesetMetatileLabels() - && readHealLocations() && readMiscellaneousConstants() && readSpeciesIconPaths() && readWildMonData() @@ -105,7 +102,8 @@ bool Project::load() { && readObjEventGfxConstants() && readEventGraphics() && readSongNames() - && readMapGroups(); + && readMapGroups() + && readHealLocations(); if (success) { // No need to do this if something failed to load. @@ -118,7 +116,7 @@ bool Project::load() { return success; } -QString Project::getProjectTitle() { +QString Project::getProjectTitle() const { if (!root.isNull()) { return root.section('/', -1); } else { @@ -182,7 +180,6 @@ void Project::initTopLevelMapFields() { "warp_events", "coord_events", "bg_events", - "heal_locations", "shared_events_map", "shared_scripts_map", }; @@ -316,30 +313,11 @@ bool Project::loadMapData(Map* map) { } } - -/* TODO: Re-enable - const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - for (auto it = healLocations.begin(); it != healLocations.end(); it++) { - HealLocation loc = *it; - //if TRUE map is flyable / has healing location - if (loc.mapName == Map::mapConstantFromName(map->name, false)) { - HealLocationEvent *heal = new HealLocationEvent(); - heal->setMap(map); - heal->setX(loc.x); - heal->setY(loc.y); - heal->setElevation(projectConfig.defaultElevation); - heal->setLocationName(loc.mapName); - heal->setIdName(loc.idName); - heal->setIndex(loc.index); - if (projectConfig.healLocationRespawnDataEnabled) { - heal->setRespawnMap(mapConstantsToMapNames.value(QString(mapPrefix + loc.respawnMap))); - heal->setRespawnNPC(loc.respawnNPC); - } - map->events[Event::Group::Heal].append(heal); - map->ownedEvents.append(heal); - } + // Heal locations are global. Populate the Map's heal location events using our global array. + const QList hlEvents = this->healLocations.value(map->constantName()); + for (const auto &event : hlEvents) { + map->addEvent(event->duplicate()); } -*/ map->deleteConnections(); QJsonArray connectionsArr = mapObj["connections"].toArray(); @@ -846,134 +824,67 @@ void Project::saveWildMonData() { wildEncountersFile.close(); } -void Project::saveHealLocations(Map *map) { - this->saveHealLocationsData(map); - this->saveHealLocationsConstants(); +// For a map with a constant of 'MAP_FOO', returns a unique 'HEAL_LOCATION_FOO'. +// Because of how event ID names are checked it doesn't guarantee that the name +// won't be in-use by some map that hasn't been loaded yet. +QString Project::getNewHealLocationName(const Map* map) const { + if (!map) return QString(); + + QString idName = map->constantName(); + const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + if (idName.startsWith(mapPrefix)) { + idName.remove(0, mapPrefix.length()); + } + return toUniqueIdentifier(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + idName); } -// Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h -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 - 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; +void Project::saveHealLocations() { + const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_heal_locations)); + QFile file(filepath); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing").arg(filepath)); + return; + } + + // Build the JSON data for output. + QMap> idNameToJson; + for (auto i = this->healLocations.constBegin(); i != this->healLocations.constEnd(); i++) { + const QString mapConstant = i.key(); + for (const auto &event : i.value()) { + // Heal location events don't need to track the "map" field, we're already tracking it either with + // the keys in the healLocations map or by virtue of the event being added to a particular Map object. + // The global JSON data needs this field, so we add it back here. + auto eventJson = event->buildEventJson(this); + eventJson["map"] = mapConstant; + idNameToJson[event->getIdName()].append(eventJson); } } - - // Find any duplicate constant names - QMap healLocationsDupes; - QSet healLocationsUnique; - for (auto hl : this->healLocations) { - QString idName = hl.idName; - if (healLocationsUnique.contains(idName)) - healLocationsDupes[idName] = 1; - else - healLocationsUnique.insert(idName); - } - - // Create the definition text for each data table - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString qualifiers = QString(healLocationDataQualifiers.isStatic ? "static " : "") - + QString(healLocationDataQualifiers.isConst ? "const " : ""); - - QString locationTableText = QString("%1%2 %3[] =\n{\n").arg(qualifiers) - .arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations_type)) - .arg(this->healLocationsTableName); - QString respawnMapTableText, respawnNPCTableText; - if (respawnEnabled) { - respawnMapTableText = QString("\n%1%2[][2] =\n{\n").arg(qualifiers).arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_spawn_maps)); - respawnNPCTableText = QString("\n%1%2[] =\n{\n").arg(qualifiers).arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_spawn_npcs)); - } - - // Populate the data tables with the heal location data - int i = 0; - const QString emptyMapName = projectConfig.getIdentifier(ProjectIdentifier::define_map_empty); - for (auto hl : this->healLocations) { - // Add numbered suffix for duplicate constants - if (healLocationsDupes.keys().contains(hl.idName)) { - QString duplicateName = hl.idName; - hl.idName += QString("_%1").arg(healLocationsDupes[duplicateName]); - healLocationsDupes[duplicateName]++; - this->healLocations[i].idName = hl.idName; // Update the name for writing constants later + // We store Heal Locations in a QMap with map name keys. This makes retrieval by map name easy, + // but it will sort them alphabetically. Project::healLocationSaveOrder lets us better support + // the (perhaps unlikely) user who has designed something with assumptions about the order of the data. + // This also avoids a bunch of diff noise on the first save from Porymap reordering the data. + OrderedJson::array eventJsonArr; + for (const auto &idName : this->healLocationSaveOrder) { + if (!idNameToJson.value(idName).isEmpty()) { + eventJsonArr.push_back(idNameToJson[idName].takeFirst()); + } + } + // Save any heal locations that weren't covered above (should be any new data). + for (auto i = idNameToJson.constBegin(); i != idNameToJson.constEnd(); i++) { + for (const auto &object : i.value()) { + eventJsonArr.push_back(object); } - - // Add entry to map/coords table - QString mapName = !hl.mapName.isEmpty() ? hl.mapName : emptyMapName; - locationTableText += QString(" [%1 - 1] = {MAP_GROUP(%2), MAP_NUM(%2), %3, %4},\n") - .arg(hl.idName) - .arg(mapName) - .arg(hl.x) - .arg(hl.y); - - // Add entry to respawn map and npc tables - if (respawnEnabled) { - mapName = !hl.respawnMap.isEmpty() ? hl.respawnMap : emptyMapName; - respawnMapTableText += QString(" [%1 - 1] = {MAP_GROUP(%2), MAP_NUM(%2)},\n") - .arg(hl.idName) - .arg(mapName); - - respawnNPCTableText += QString(" [%1 - 1] = %2,\n") - .arg(hl.idName) - .arg(hl.respawnNPC); - } - i++; } - const QString tableEnd = QString("};\n"); - QString text = locationTableText + tableEnd; - if (respawnEnabled) - text += respawnMapTableText + tableEnd + respawnNPCTableText + tableEnd; - QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::data_heal_locations); + OrderedJson::object object; + object["heal_locations"] = eventJsonArr; + ignoreWatchedFileTemporarily(filepath); - saveTextFile(filepath, text); - */ -} - -// Saves heal location defines in root + /include/constants/heal_locations.h -void Project::saveHealLocationsConstants() { - // Get existing defines, and create an inverted map so they'll be in sorted order for printing - int nextDefineValue = 1; - QMap valuesToNames = QMap(); - QStringList defineNames = this->healLocationNameToValue.keys(); - QList defineValues = this->healLocationNameToValue.values(); - for (auto name : defineNames) { - int value = this->healLocationNameToValue.value(name); - if (valuesToNames.contains(value)) { - do { // Redefine duplicate as first available value - value = nextDefineValue++; - } while (defineValues.contains(value)); - } - valuesToNames.insert(value, name); - } - - // Check for new id names in the heal locations list - for (auto hl : this->healLocations) { - if (this->healLocationNameToValue.contains(hl.idName)) - continue; - int value; - do { // Give new heal location first available value - value = nextDefineValue++; - } while (valuesToNames.contains(value)); - valuesToNames.insert(value, hl.idName); - } - - // Include guards - const QString guardName = "GUARD_CONSTANTS_HEAL_LOCATIONS_H"; - QString constantsText = QString("#ifndef %1\n#define %1\n\n").arg(guardName); - - // List defines in ascending order - QMap::const_iterator i; - for (i = valuesToNames.constBegin(); i != valuesToNames.constEnd(); i++) - constantsText += QString("#define %1 %2\n").arg(i.value()).arg(i.key()); - - constantsText += QString("\n#endif // %1\n").arg(guardName); - - QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::constants_heal_locations); - ignoreWatchedFileTemporarily(filepath); - saveTextFile(filepath, constantsText); + OrderedJson json(object); + OrderedJsonDoc jsonDoc(&json); + jsonDoc.dump(&file); + file.close(); } void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { @@ -1336,6 +1247,16 @@ void Project::saveMap(Map *map) { mapObj["shared_scripts_map"] = map->sharedScriptsMap(); } + // Update the global heal locations array using the Map's heal location events. + // This won't get saved to disc until Project::saveHealLocations is called. + QList hlEvents; + for (const auto &event : map->getEvents(Event::Group::Heal)) { + auto hl = static_cast(event); + hlEvents.append(static_cast(hl->duplicate())); + } + qDeleteAll(this->healLocations[map->constantName()]); + this->healLocations[map->constantName()] = hlEvents; + // Custom header fields. const auto customAttributes = map->customAttributes(); for (auto i = customAttributes.constBegin(); i != customAttributes.constEnd(); i++) { @@ -1348,7 +1269,6 @@ void Project::saveMap(Map *map) { mapFile.close(); saveLayout(map->layout()); - saveHealLocations(map); map->setClean(); } @@ -1380,6 +1300,7 @@ void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); saveRegionMapSections(); + saveHealLocations(); saveWildMonData(); saveConfig(); this->hasUnsavedDataChanges = false; @@ -1975,6 +1896,15 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return false; if (this->encounterGroupLabels.contains(identifier)) return false; + // Check event IDs + for (const auto &map : this->mapCache) { + auto events = map->getEvents(); + for (const auto &event : events) { + QString idName = event->getIdName(); + if (!idName.isEmpty() && idName == identifier) + return false; + } + } return true; } @@ -2034,18 +1964,6 @@ void Project::initNewLayoutSettings() { this->newLayoutSettings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); } -Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) { - Project::DataQualifiers qualifiers; - - QRegularExpression regex(QString("\\s*(?static\\s*)?(?const\\s*)?[A-Za-z0-9_\\s]*\\b%1\\b").arg(label)); - QRegularExpressionMatch match = regex.match(text); - - qualifiers.isStatic = match.captured("static").isNull() ? false : true; - qualifiers.isConst = match.captured("const").isNull() ? false : true; - - return qualifiers; -} - QString Project::getDefaultPrimaryTilesetLabel() const { QString defaultLabel = projectConfig.defaultPrimaryTileset; if (!this->primaryTilesetLabels.contains(defaultLabel)) { @@ -2433,110 +2351,40 @@ void Project::setMapsecDisplayName(const QString &idName, const QString &display emit mapSectionDisplayNameChanged(idName, displayName); } -// Read the constants to preserve any "unused" heal locations when writing the file later -bool Project::readHealLocationConstants() { - this->healLocationNameToValue.clear(); - const QStringList regexList = { - QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix)), - QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_spawn_prefix)) - }; - QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_heal_locations); - fileWatcher.addPath(root + "/" + constantsFilename); - this->healLocationNameToValue = parser.readCDefinesByRegex(constantsFilename, regexList); - // No need to check if empty, not finding any heal location constants is ok - return true; +void Project::clearHealLocations() { + for (auto &events : this->healLocations) { + qDeleteAll(events); + } + this->healLocations.clear(); + this->healLocationSaveOrder.clear(); } -// TODO: Simplify using the new C struct parsing functions (and indexed array parsing functions) bool Project::readHealLocations() { - this->healLocations.clear(); + clearHealLocations(); - if (!this->readHealLocationConstants()) + QJsonDocument doc; + const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_heal_locations); + const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath); + if (!parser.tryParseJsonFile(&doc, filepath)) { + logError(QString("Failed to read heal locations from '%1'").arg(baseFilepath)); return false; - - QString filename = projectConfig.getFilePath(ProjectFilePath::data_heal_locations); - fileWatcher.addPath(root + "/" + filename); - QString text = parser.readTextFile(root + "/" + filename); - - // Strip comments - static const QRegularExpression re_comments("//.*?(\r\n?|\n)|/\\*.*?\\*/", QRegularExpression::DotMatchesEverythingOption); - text.replace(re_comments, ""); - - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - - // Search for the name of the main Heal Locations table - const QRegularExpression tableNameExpr(QString("%1\\s+(?[A-Za-z0-9_]+)\\[").arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations_type))); - const QRegularExpressionMatch tableNameMatch = tableNameExpr.match(text); - if (tableNameMatch.hasMatch()) { - // Found table name, record it and its qualifiers for output when saving. - this->healLocationsTableName = tableNameMatch.captured("name"); - this->healLocationDataQualifiers = this->getDataQualifiers(text, this->healLocationsTableName); - } else { - // No table name found, initialize default name for output when saving. - this->healLocationsTableName = respawnEnabled ? projectConfig.getIdentifier(ProjectIdentifier::symbol_spawn_points) - : projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations); - this->healLocationDataQualifiers = { .isStatic = true, .isConst = true }; } + fileWatcher.addPath(filepath); - // Create regex pattern for the constants (ex: "SPAWN_PALLET_TOWN" or "HEAL_LOCATION_PETALBURG_CITY") - const QString spawnPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_spawn_prefix); - const QString healLocPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix); - const QRegularExpression constantsExpr(QString("\\b(%1|%2)[A-Za-z0-9_]+").arg(spawnPrefix).arg(healLocPrefix)); - - // Find all the unique heal location constants used in the data tables. - // Porymap doesn't care whether or not a constant appeared in the heal locations constants file. - // Any data entry without a designated initializer using one of these constants will be silently discarded. - // Any data entry that repeats a designated initializer will also be discarded. - QStringList constants = QStringList(); - QRegularExpressionMatchIterator constantsMatch = constantsExpr.globalMatch(text); - while (constantsMatch.hasNext()) - constants << constantsMatch.next().captured(); - constants.removeDuplicates(); - - // Pattern for a map value pair (ex: "MAP_GROUP(PALLET_TOWN), MAP_NUM(PALLET_TOWN)") - const QString mapPattern = "MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\1)[\\s\\)]+"; - // Pattern for an x, y number pair - const QString coordPattern = "\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"; - - for (const auto &idName : constants) { - // Create regex pattern for e.g. "SPAWN_PALLET_TOWN - 1] = " - const QString initializerPattern = QString("%1\\s*-\\s*1\\s*\\]\\s*=\\s*").arg(idName); - - // Expression for location data, e.g. "SPAWN_PALLET_TOWN - 1] = {MAP_GROUP(PALLET_TOWN), MAP_NUM(PALLET_TOWN), x, y}" - QRegularExpression locationRegex(QString("%1\\{%2,%3}").arg(initializerPattern).arg(mapPattern).arg(coordPattern)); - QRegularExpressionMatch match = locationRegex.match(text); - - // Read location data - HealLocation healLocation; - if (match.hasMatch()) { - QString mapName = match.captured("map"); - int x = match.captured("x").toInt(nullptr, 0); - int y = match.captured("y").toInt(nullptr, 0); - healLocation = HealLocation(idName, mapName, this->healLocations.size() + 1, x, y); - } else { - // This heal location has data, but is missing from the location table and won't be displayed by Porymap. - // Add a dummy entry, and preserve the rest of its data for the user anyway - healLocation = HealLocation(idName, "", this->healLocations.size() + 1, 0, 0); + QJsonArray healLocations = doc.object()["heal_locations"].toArray(); + for (int i = 0; i < healLocations.size(); i++) { + QJsonObject healLocationObj = healLocations.at(i).toObject(); + static const QString mapField = QStringLiteral("map"); + if (!healLocationObj.contains(mapField)) { + logWarn(QString("Ignoring data for heal location %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(mapField)); + continue; } - // Read respawn data - if (respawnEnabled) { - // Expression for respawn map data, e.g. "SPAWN_PALLET_TOWN - 1] = {MAP_GROUP(PALLET_TOWN_PLAYERS_HOUSE_1F), MAP_NUM(PALLET_TOWN_PLAYERS_HOUSE_1F)}" - QRegularExpression respawnMapRegex(QString("%1\\{%2}").arg(initializerPattern).arg(mapPattern)); - match = respawnMapRegex.match(text); - if (match.hasMatch()) - healLocation.respawnMap = match.captured("map"); - - // Expression for respawn npc data, e.g. "SPAWN_PALLET_TOWN - 1] = 1" - QRegularExpression respawnNPCRegex(QString("%1(?[0-9]+)").arg(initializerPattern)); - match = respawnNPCRegex.match(text); - if (match.hasMatch()) - healLocation.respawnNPC = match.captured("npc").toInt(nullptr, 0); - } - - this->healLocations.append(healLocation); + auto event = new HealLocationEvent(); + event->loadFromJson(healLocationObj, this); + this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); + this->healLocationSaveOrder.append(event->getIdName()); } - // No need to check if empty, not finding any heal locations is ok return true; } diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 78a00bb6..03f1bb99 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -107,6 +107,7 @@ void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { } } +// Events with properties that specify a map will open that map when double-clicked. void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { Event::Type eventType = this->event->getEventType(); if (eventType == Event::Type::Warp) { @@ -126,4 +127,10 @@ void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { QString destMap = editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); emit editor->warpEventDoubleClicked(destMap, 0, Event::Group::Warp); } + else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { + HealLocationEvent *heal = dynamic_cast(this->event); + const QString localIdName = heal->getRespawnNPC(); + int localId = 0; // TODO: Get value from localIdName + emit editor->warpEventDoubleClicked(heal->getRespawnMapName(), localId, Event::Group::Object); + } } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 61794c41..40a545ab 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -947,6 +947,14 @@ void HealLocationFrame::setup() { this->hideable_label_z->setVisible(false); this->spinner_z->setVisible(false); + // ID + QFormLayout *l_form_id = new QFormLayout(); + this->line_edit_id = new QLineEdit(this); + this->line_edit_id->setToolTip("The unique identifier for this heal location."); + this->line_edit_id->setPlaceholderText(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + "MY_MAP"); + l_form_id->addRow("ID", this->line_edit_id); + this->layout_contents->addLayout(l_form_id); + // respawn map combo this->hideable_respawn_map = new QFrame; QFormLayout *l_form_respawn_map = new QFormLayout(hideable_respawn_map); @@ -960,10 +968,10 @@ void HealLocationFrame::setup() { this->hideable_respawn_npc = new QFrame; QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc); l_form_respawn_npc->setContentsMargins(0, 0, 0, 0); - this->spinner_respawn_npc = new NoScrollSpinBox(hideable_respawn_npc); - this->spinner_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" + this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc); + this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" "upon respawning after whiteout."); - l_form_respawn_npc->addRow("Respawn NPC", this->spinner_respawn_npc); + l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); this->layout_contents->addWidget(hideable_respawn_npc); // custom attributes @@ -975,19 +983,23 @@ void HealLocationFrame::connectSignals(MainWindow *window) { EventFrame::connectSignals(window); - if (projectConfig.healLocationRespawnDataEnabled) { - this->combo_respawn_map->disconnect(); - connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->healLocation->setRespawnMap(text); - this->healLocation->modify(); - }); + this->line_edit_id->disconnect(); + connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { + this->healLocation->setIdName(text); + this->healLocation->modify(); + }); - this->spinner_respawn_npc->disconnect(); - connect(this->spinner_respawn_npc, QOverload::of(&QSpinBox::valueChanged), [this](int value) { - this->healLocation->setRespawnNPC(value); - this->healLocation->modify(); - }); - } + this->combo_respawn_map->disconnect(); + connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { + this->healLocation->setRespawnMapName(text); + this->healLocation->modify(); + }); + + this->combo_respawn_npc->disconnect(); + connect(this->combo_respawn_npc, &QComboBox::currentTextChanged, [this](const QString &text) { + this->healLocation->setRespawnNPC(text); + this->healLocation->modify(); + }); } void HealLocationFrame::initialize() { @@ -996,12 +1008,11 @@ void HealLocationFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - if (respawnEnabled) { - this->combo_respawn_map->setTextItem(this->healLocation->getRespawnMap()); - this->spinner_respawn_npc->setValue(this->healLocation->getRespawnNPC()); - } + this->line_edit_id->setText(this->healLocation->getIdName()); + this->combo_respawn_map->setTextItem(this->healLocation->getRespawnMapName()); + this->combo_respawn_npc->setTextItem(this->healLocation->getRespawnNPC()); + bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; this->hideable_respawn_map->setVisible(respawnEnabled); this->hideable_respawn_npc->setVisible(respawnEnabled); } @@ -1012,6 +1023,7 @@ void HealLocationFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - if (projectConfig.healLocationRespawnDataEnabled) - this->combo_respawn_map->addItems(project->mapNames); + this->combo_respawn_map->addItems(project->mapNames); + // TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map + // Same for warp IDs. } diff --git a/src/ui/neweventtoolbutton.cpp b/src/ui/neweventtoolbutton.cpp index b569cec9..dee658e4 100644 --- a/src/ui/neweventtoolbutton.cpp +++ b/src/ui/neweventtoolbutton.cpp @@ -26,11 +26,9 @@ void NewEventToolButton::init() this->newWarpAction->setIcon(QIcon(":/icons/add.ico")); connect(this->newWarpAction, &QAction::triggered, this, &NewEventToolButton::newWarp); - /* // disable this functionality for now this->newHealLocationAction = new QAction("New Heal Location", this); this->newHealLocationAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newHealLocationAction, SIGNAL(triggered(bool)), this, SLOT(newHealLocation())); - */ + connect(this->newHealLocationAction, &QAction::triggered, this, &NewEventToolButton::newHealLocation); this->newTriggerAction = new QAction("New Trigger", this); this->newTriggerAction->setIcon(QIcon(":/icons/add.ico")); @@ -56,7 +54,7 @@ void NewEventToolButton::init() alignMenu->addAction(this->newObjectAction); alignMenu->addAction(this->newCloneObjectAction); alignMenu->addAction(this->newWarpAction); - //alignMenu->addAction(this->newHealLocationAction); + alignMenu->addAction(this->newHealLocationAction); alignMenu->addAction(this->newTriggerAction); alignMenu->addAction(this->newWeatherTriggerAction); alignMenu->addAction(this->newSignAction); diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index e1fc83e9..6a406072 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -16,9 +16,6 @@ #include #include -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - const QString defaultFilepath = "prefabs.json"; void Prefab::loadPrefabs() { diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index a51b614d..c8d4a323 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -50,6 +50,7 @@ void PreferenceEditor::updateFields() { ui->checkBox_MonitorProjectFiles->setChecked(porymapConfig.monitorFiles); ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch); ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates); + ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled); } void PreferenceEditor::saveFields() { @@ -64,6 +65,7 @@ void PreferenceEditor::saveFields() { porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked(); porymapConfig.reopenOnLaunch = ui->checkBox_OpenRecentProject->isChecked(); porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked(); + porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked(); porymapConfig.save(); emit preferencesSaved(); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 016480a7..1bb17307 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -61,7 +61,7 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_WarpsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_WarpsIcon); }); connect(ui->button_TriggersIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_TriggersIcon); }); connect(ui->button_BGsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_BGsIcon); }); - connect(ui->button_HealspotsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealspotsIcon); }); + connect(ui->button_HealLocationsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealLocationsIcon); }); connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); }); @@ -476,7 +476,7 @@ void ProjectSettingsEditor::refresh() { ui->lineEdit_WarpsIcon->setText(projectConfig.getEventIconPath(Event::Group::Warp)); ui->lineEdit_TriggersIcon->setText(projectConfig.getEventIconPath(Event::Group::Coord)); ui->lineEdit_BGsIcon->setText(projectConfig.getEventIconPath(Event::Group::Bg)); - ui->lineEdit_HealspotsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal)); + ui->lineEdit_HealLocationsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal)); for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren()) lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName())); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) @@ -546,7 +546,7 @@ void ProjectSettingsEditor::save() { projectConfig.setEventIconPath(Event::Group::Warp, ui->lineEdit_WarpsIcon->text()); projectConfig.setEventIconPath(Event::Group::Coord, ui->lineEdit_TriggersIcon->text()); projectConfig.setEventIconPath(Event::Group::Bg, ui->lineEdit_BGsIcon->text()); - projectConfig.setEventIconPath(Event::Group::Heal, ui->lineEdit_HealspotsIcon->text()); + projectConfig.setEventIconPath(Event::Group::Heal, ui->lineEdit_HealLocationsIcon->text()); for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren()) projectConfig.setFilePath(lineEdit->objectName(), lineEdit->text()); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index af0e220b..21b5c76d 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -18,9 +18,6 @@ #include #include -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : QMainWindow(parent), ui(new Ui::RegionMapEditor) From 8892c642f19614e21b1c3a33e84dd8e11094f707 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 12:39:58 -0500 Subject: [PATCH 159/364] Remove redundant reload warning --- src/mainwindow.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 84bd4190..bb158751 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -834,12 +834,7 @@ void MainWindow::on_action_Open_Project_triggered() } void MainWindow::on_action_Reload_Project_triggered() { - // TODO: when undo history is complete show only if has unsaved changes - WarningMessage msgBox(QStringLiteral("Reloading this project will discard any unsaved changes."), this); - msgBox.addButton(QMessageBox::Cancel); - msgBox.setDefaultButton(QMessageBox::Cancel); - if (msgBox.exec() == QMessageBox::Ok) - openProject(editor->project->root); + openProject(editor->project->root); } void MainWindow::on_action_Close_Project_triggered() { From c212afcc2009aa1c7bb6e115e9417459f75c9171 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 13:00:10 -0500 Subject: [PATCH 160/364] Update changelog --- CHANGELOG.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1c70be..189b6b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ## [Unreleased] ### Added -- Add the ability to edit layouts with no corresponding map. -- Add ``Duplicate Map`` / ``Duplicate Layout`` options, accessible by right-clicking a map or layout in the map list. -- Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access. +- Redesigned the map list, adding new features including opening/editing layouts with no associated map, duplicating maps or layouts (accessible via right-click), editing the names of map groups, rearranging maps and map groups, and hiding empty folders. +- Add a drop-down for changing the layout of the currently opened map. +- Redesigned the Connections tab, adding new features including the option to open or display diving maps and a list UI for easier edit access. - Add a `Close Project` option - Add a search button to the `Wild Pokémon` tab that shows the encounter data for a species across all maps. - Add charts to the `Wild Pokémon` tab that show species and level distributions for the current map. @@ -18,11 +18,11 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add an option to display a dividing line between tilesets in the Tileset Editor. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. -- Add button to enable editing map groups including renaming groups and rearranging the maps within them. - Add buttons to hide and show empty folders in each map tree view. - Add a setting to specify the tile values to use for the unused metatile layer. ### Changed +- `Change Dimensions` now has an interactive resizing rectangle. - Redesigned the new map dialog, including better error checking and a collapsible section for header data. - Map groups and ``MAPSEC`` names specified when creating a new map will be added automatically if they don't already exist. - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. @@ -33,7 +33,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. - `Export Map Stitch Image` now shows a preview of the full image, not just the current map. -- Maps and layouts were internally separated. +- `Custom Attributes` tables now display numbers using spin boxes. The `type` column was removed, because `value`'s type is now obvious. - Unrecognized map names in Event or Connections data will no longer be overwritten. - Reduced diff noise when saving maps. - Map names and ``MAP_NAME`` constants are no longer required to match. @@ -51,7 +51,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix the `Edit History` window not raising to the front when reactivated. - New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded. - Fix invalid species names clearing from wild pokémon data when revisited. -- Fix editing wild pokémon data not marking the map as edited. +- Fix editing wild pokémon data not marking the map as unsaved. +- Fix editing an event's `Custom Attributes` not marking the map as unsaved. - Fix changes to map connections not marking connected maps as unsaved. - Fix numerous issues related to connecting a map to itself. - Fix incorrect map connections getting selected when opening a map by double-clicking a map connection. @@ -74,6 +75,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix exported tile images containing garbage pixels after the end of the tiles. - Fix fully transparent pixels rendering with the incorrect color. - Fix the values for some config fields shuffling their order every save. +- Fix `key`s in `Custom Attributes` disappearing if given an empty name or the name of an existing field. - Fix some problems with tileset detection when importing maps from AdvanceMap. - Fix certain input fields allowing invalid identifiers, like names starting with numbers. - Fix crash in the Shortcuts Editor when applying changes after closing certain windows. From 184f04202fef4e6a83e3d465d5f80fb0aaf6ef8d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 13:22:21 -0500 Subject: [PATCH 161/364] Fix cursor and improperly-disabled edits when switching to Events tab --- src/editor.cpp | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 3c7fee27..ce8752c3 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -134,36 +134,26 @@ void Editor::setEditorView() { return; } + map_item->setEditsEnabled(this->editMode != EditMode::Connections); map_item->draw(); collision_item->draw(); current_view->setVisible(true); updateBorderVisibility(); - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(true); - switch (this->editMode) { - case EditMode::Metatiles: - case EditMode::Collision: - map_item->setEditsEnabled(true); - this->editGroup.setActiveStack(&this->layout->editHistory); - break; - case EditMode::Connections: - this->cursorMapTileRect->setActive(false); - map_item->setEditsEnabled(false); - case EditMode::Events: - if (this->map) { - this->editGroup.setActiveStack(this->map->editHistory()); - } - break; - case EditMode::Header: - case EditMode::Encounters: - default: - this->editGroup.setActiveStack(nullptr); - break; + QUndoStack *editStack = this->map ? this->map->editHistory() : nullptr; + bool usesCursor = false; + if (this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision) { + if (this->layout) editStack = &this->layout->editHistory; + usesCursor = true; } + this->cursorMapTileRect->setSingleTileMode(); + this->cursorMapTileRect->setActive(usesCursor); + this->editGroup.setActiveStack(editStack); + + if (this->events_group) { this->events_group->setVisible(this->editMode == EditMode::Events); } From ac6750de4471b06d826c03a6738c87c7172bdbc1 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 13:43:10 -0500 Subject: [PATCH 162/364] Keep search focus when changing map list tabs --- include/mainwindow.h | 2 +- include/ui/maplisttoolbar.h | 2 ++ src/mainwindow.cpp | 11 +++++++++-- src/ui/maplisttoolbar.cpp | 4 ++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 42d346f3..7425486e 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -380,7 +380,7 @@ private: void updateMapList(); void openMapListItem(const QModelIndex &index); - void saveMapListTab(int index); + void onMapListTabChanged(int index); void displayMapProperties(); void checkToolButtons(); diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h index bf749a3e..9890b584 100644 --- a/include/ui/maplisttoolbar.h +++ b/include/ui/maplisttoolbar.h @@ -37,6 +37,8 @@ public: void setFilterLocked(bool locked) { m_filterLocked = locked; } bool isFilterLocked() const { return m_filterLocked; } + void setSearchFocus(); + signals: void filterCleared(MapTree*); void addFolderClicked(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bb158751..617fdccc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -454,7 +454,7 @@ void MainWindow::initMapList() { connect(ui->mapListToolBar_Locations, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLocationDialog); connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); - connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); + connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::onMapListTabChanged); } void MainWindow::updateWindowTitle() { @@ -1465,8 +1465,15 @@ void MainWindow::currentMetatilesSelectionChanged() { scrollMetatileSelectorToSelection(); } -void MainWindow::saveMapListTab(int index) { +void MainWindow::onMapListTabChanged(int index) { + // Save current tab for future sessions. porymapConfig.mapListTab = index; + + // After changing a map list tab the old tab's search widget can keep focus, which isn't helpful + // (and might be a little confusing to the user, because they don't know that each search bar is secretly a separate object). + // When we change tabs we'll automatically focus in on the search bar. This should also make finding maps a little quicker. + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->setSearchFocus(); } void MainWindow::openMapListItem(const QModelIndex &index) { diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index d03aa838..10a33c47 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -139,3 +139,7 @@ void MapListToolBar::applyFilter(const QString &filterText) { void MapListToolBar::clearFilter() { applyFilter(""); } + +void MapListToolBar::setSearchFocus() { + ui->lineEdit_filterBox->setFocus(); +} From df861b59ad0e9847c7386567d89e08f807aa2d5f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 14:50:00 -0500 Subject: [PATCH 163/364] Shift events if map moves when resized --- include/editor.h | 2 ++ src/editor.cpp | 5 ++--- src/mainwindow.cpp | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/editor.h b/include/editor.h index 81bf1611..d744ac99 100644 --- a/include/editor.h +++ b/include/editor.h @@ -180,6 +180,8 @@ public: qreal collisionOpacity = 0.5; static QList> collisionIcons; + int eventShiftActionId = 0; + void objectsView_onMousePress(QMouseEvent *event); int getBorderDrawDistance(int dimension); diff --git a/src/editor.cpp b/src/editor.cpp index ce8752c3..8a0f391b 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1379,10 +1379,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i // do nothing here, at least for now } else if (objectEditAction == EditAction::Shift) { static QPoint selection_origin; - static unsigned actionId = 0; if (event->type() == QEvent::GraphicsSceneMouseRelease) { - actionId++; + this->eventShiftActionId++; } else { if (event->type() == QEvent::GraphicsSceneMousePress) { selection_origin = QPoint(pos.x(), pos.y()); @@ -1398,7 +1397,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } selection_origin = QPoint(pos.x(), pos.y()); - map->commit(new EventShift(selectedEvents, xDelta, yDelta, actionId)); + map->commit(new EventShift(selectedEvents, xDelta, yDelta, this->eventShiftActionId)); } } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 617fdccc..74f579c9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2609,6 +2609,8 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { popup.setupLayoutView(); if (popup.exec() == QDialog::Accepted) { Layout *layout = this->editor->layout; + Map *map = this->editor->map; + QMargins result = popup.getResult(); QSize borderResult = popup.getBorderResult(); QSize oldLayoutDimensions(layout->getWidth(), layout->getHeight()); @@ -2626,6 +2628,15 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { oldBorder, layout->border )); } + // If we're in map-editing mode, adjust the events' position by the same amount. + if (map) { + auto events = map->getEvents(); + int deltaX = result.left(); + int deltaY = result.top(); + if ((deltaX || deltaY) && !events.isEmpty()) { + map->commit(new EventShift(events, deltaX, deltaY, this->editor->eventShiftActionId++)); + } + } } } From 01126a888a71df42f6052adcffb72c87f591c7ff Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 16:25:58 -0500 Subject: [PATCH 164/364] Apply the "map opened" API callback to layouts, add utility functions --- docsrc/manual/scripting-capabilities.rst | 27 +++++++++++++++++++++--- include/scriptutility.h | 3 +++ src/mainwindow.cpp | 12 +++++++++-- src/scriptapi/apimap.cpp | 2 +- src/scriptapi/apiutility.cpp | 21 ++++++++++++++++++ 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index fe47f67f..b645f49a 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -153,9 +153,9 @@ Callbacks .. js:function:: onMapOpened(mapName) - Called when a map is opened. + Called when a map or layout is opened. - :param mapName: the name of the opened map + :param mapName: the name of the opened map or layout :type mapName: string .. js:function:: onBlockChanged(x, y, prevBlock, newBlock) @@ -1995,7 +1995,28 @@ All utility functions are callable via the global ``utility`` object. Gets the list of map names. - :returns: the list of map names + :returns: the list of map names (e.g. `PetalburgCity`) + :rtype: array + +.. js:function:: utility.getMapConstants() + + Gets the list of map IDs (e.g. `MAP_PETALBURG_CITY`) + + :returns: the list of map IDs + :rtype: array + +.. js:function:: utility.getLayoutNames() + + Gets the list of layout names. + + :returns: the list of layout names (e.g. `PetalburgCity_Layout`) + :rtype: array + +.. js:function:: utility.getLayoutConstants() + + Gets the list of layout IDs (e.g. `LAYOUT_PETALBURG_CITY`) + + :returns: the list of layout IDs :rtype: array .. js:function:: utility.getTilesetNames() diff --git a/include/scriptutility.h b/include/scriptutility.h index 0d6fca75..78272eec 100644 --- a/include/scriptutility.h +++ b/include/scriptutility.h @@ -42,6 +42,9 @@ public: Q_INVOKABLE QList getMetatileLayerOpacity(); Q_INVOKABLE void setMetatileLayerOpacity(QList order); Q_INVOKABLE QList getMapNames(); + Q_INVOKABLE QList getMapConstants(); + Q_INVOKABLE QList getLayoutNames(); + Q_INVOKABLE QList getLayoutConstants(); Q_INVOKABLE QList getTilesetNames(); Q_INVOKABLE QList getPrimaryTilesetNames(); Q_INVOKABLE QList getSecondaryTilesetNames(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 74f579c9..23dbbae7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -947,6 +947,7 @@ bool MainWindow::setLayout(QString layoutId) { connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); + Scripting::cb_MapOpened(layout->name); updateTilesetEditor(); userConfig.recentMapOrLayout = layoutId; @@ -2815,8 +2816,15 @@ void MainWindow::reloadScriptEngine() { Scripting::populateGlobalObject(this); // Lying to the scripts here, simulating a project reload Scripting::cb_ProjectOpened(projectConfig.projectDir); - if (editor && editor->map) - Scripting::cb_MapOpened(editor->map->name()); // TODO: API should have equivalent for layout + if (this->editor) { + QString curName; + if (this->editor->map) + curName = this->editor->map->name(); + else if (editor->layout) + curName = this->editor->layout->name; + + Scripting::cb_MapOpened(curName); + } } void MainWindow::on_horizontalSlider_MetatileZoom_valueChanged(int value) { diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 2532f132..0ee3316e 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -794,7 +794,7 @@ void MainWindow::setMetatileTile(int metatileId, int tileIndex, QJSValue tileObj } QJSValue MainWindow::getTilePixels(int tileId) { - if (tileId < 0 || !this->editor || !this->editor->project || !this->editor->map || !this->editor->layout) + if (tileId < 0 || !this->editor || !this->editor->layout) return QJSValue(); QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (tileImage.isNull() || tileImage.sizeInBytes() < 64) diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index e5cebc54..5d2072a9 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -249,6 +249,27 @@ QList ScriptUtility::getMapNames() { return window->editor->project->mapNames; } +QList ScriptUtility::getMapConstants() { + if (!window || !window->editor || !window->editor->project) + return QList(); + return window->editor->project->mapConstantsToMapNames.keys(); +} + +QList ScriptUtility::getLayoutNames() { + QList names; + if (!window || !window->editor || !window->editor->project) + return names; + for (const auto &layout : window->editor->project->mapLayouts) + names.append(layout->name); + return names; +} + +QList ScriptUtility::getLayoutConstants() { + if (!window || !window->editor || !window->editor->project) + return QList(); + return window->editor->project->layoutIds; +} + QList ScriptUtility::getTilesetNames() { if (!window || !window->editor || !window->editor->project) return QList(); From c49470c47eba221913bffe9364e52660e26a24eb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 7 Feb 2025 19:27:54 -0500 Subject: [PATCH 165/364] Fix some memory leaks --- include/editor.h | 7 ++-- include/mainwindow.h | 2 ++ src/core/editcommands.cpp | 29 ++++------------- src/editor.cpp | 56 +++++++++++++++++--------------- src/mainwindow.cpp | 47 +++++++++++++-------------- src/project.cpp | 1 + src/ui/draggablepixmapitem.cpp | 2 +- src/ui/neweventtoolbutton.cpp | 2 +- src/ui/projectsettingseditor.cpp | 2 +- 9 files changed, 67 insertions(+), 81 deletions(-) diff --git a/include/editor.h b/include/editor.h index d744ac99..e1f939bd 100644 --- a/include/editor.h +++ b/include/editor.h @@ -109,14 +109,15 @@ public: void toggleBorderVisibility(bool visible, bool enableScriptCallback = true); void updateCustomMapAttributes(); - DraggablePixmapItem *addMapEvent(Event *event); + DraggablePixmapItem *addEventPixmapItem(Event *event); + void removeEventPixmapItem(Event *event); bool eventLimitReached(Map *, Event::Type); void selectMapEvent(DraggablePixmapItem *object, bool toggle = false); DraggablePixmapItem *addNewEvent(Event::Type type); void updateSelectedEvents(); void duplicateSelectedEvents(); - void redrawObject(DraggablePixmapItem *item); - QList getObjects(); + void redrawEventPixmapItem(DraggablePixmapItem *item); + QList getEventPixmapItems(); void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); diff --git a/include/mainwindow.h b/include/mainwindow.h index 7425486e..43401d9e 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -325,6 +325,7 @@ private: QAction *undoAction = nullptr; QAction *redoAction = nullptr; + QPointer undoView = nullptr; QAction *copyAction = nullptr; QAction *pasteAction = nullptr; @@ -353,6 +354,7 @@ private: bool setProjectUI(); void clearProjectUI(); + void openEditHistory(); void openNewMapDialog(); void openDuplicateMapDialog(const QString &mapName); NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr); diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 61410dc4..94354896 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -327,9 +327,7 @@ void EventCreate::redo() { QUndoCommand::redo(); map->addEvent(event); - - editor->project->setEventPixmap(event); - editor->addMapEvent(event); + editor->addEventPixmapItem(event); // select this event editor->selected_events->clear(); @@ -338,12 +336,7 @@ void EventCreate::redo() { void EventCreate::undo() { map->removeEvent(event); - - if (editor->scene->items().contains(event->getPixmapItem())) { - editor->scene->removeItem(event->getPixmapItem()); - } - editor->selected_events->removeOne(event->getPixmapItem()); - + editor->removeEventPixmapItem(event); editor->shouldReselectEvents(); QUndoCommand::undo(); @@ -378,11 +371,7 @@ void EventDelete::redo() { for (Event *event : selectedEvents) { map->removeEvent(event); - - if (editor->scene->items().contains(event->getPixmapItem())) { - editor->scene->removeItem(event->getPixmapItem()); - } - editor->selected_events->removeOne(event->getPixmapItem()); + editor->removeEventPixmapItem(event); } editor->selected_events->clear(); @@ -394,8 +383,7 @@ void EventDelete::redo() { void EventDelete::undo() { for (Event *event : selectedEvents) { map->addEvent(event); - editor->project->setEventPixmap(event); - editor->addMapEvent(event); + editor->addEventPixmapItem(event); } // select these events @@ -436,8 +424,7 @@ void EventDuplicate::redo() { for (Event *event : selectedEvents) { map->addEvent(event); - editor->project->setEventPixmap(event); - editor->addMapEvent(event); + editor->addEventPixmapItem(event); } // select these events @@ -451,11 +438,7 @@ void EventDuplicate::redo() { void EventDuplicate::undo() { for (Event *event : selectedEvents) { map->removeEvent(event); - - if (editor->scene->items().contains(event->getPixmapItem())) { - editor->scene->removeItem(event->getPixmapItem()); - } - editor->selected_events->removeOne(event->getPixmapItem()); + editor->removeEventPixmapItem(event); } editor->shouldReselectEvents(); diff --git a/src/editor.cpp b/src/editor.cpp index 8a0f391b..54f48acb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1392,7 +1392,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i QList selectedEvents; - for (DraggablePixmapItem *pixmapItem : getObjects()) { + for (DraggablePixmapItem *pixmapItem : getEventPixmapItems()) { selectedEvents.append(pixmapItem->event); } selection_origin = QPoint(pos.x(), pos.y()); @@ -1692,15 +1692,13 @@ void Editor::displayMovementPermissionSelector() { void Editor::clearMapEvents() { if (events_group) { + if (events_group->scene()) { + events_group->scene()->removeItem(events_group); + } for (QGraphicsItem *child : events_group->childItems()) { events_group->removeFromGroup(child); delete child; } - - if (events_group->scene()) { - events_group->scene()->removeItem(events_group); - } - delete events_group; events_group = nullptr; } @@ -1714,18 +1712,30 @@ void Editor::displayMapEvents() { scene->addItem(events_group); for (const auto &event : map->getEvents()) { - project->setEventPixmap(event); - addMapEvent(event); + addEventPixmapItem(event); } + //objects_group->setFiltersChildEvents(false); events_group->setHandlesChildEvents(false); } -DraggablePixmapItem *Editor::addMapEvent(Event *event) { - DraggablePixmapItem *object = new DraggablePixmapItem(event, this); - this->redrawObject(object); - events_group->addToGroup(object); - return object; +DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { + this->project->setEventPixmap(event); + auto item = new DraggablePixmapItem(event, this); + redrawEventPixmapItem(item); + this->events_group->addToGroup(item); + return item; +} + +void Editor::removeEventPixmapItem(Event *event) { + auto item = event->getPixmapItem(); + if (!item) return; + + this->events_group->removeFromGroup(item); + this->selected_events->removeOne(item); + + event->setPixmapItem(nullptr); + delete item; } void Editor::clearMapConnections() { @@ -1977,7 +1987,7 @@ Tileset* Editor::getCurrentMapPrimaryTileset() return project->getTileset(tilesetLabel); } -QList Editor::getObjects() { +QList Editor::getEventPixmapItems() { QList list; for (QGraphicsItem *child : events_group->childItems()) { list.append(static_cast(child)); @@ -1985,7 +1995,7 @@ QList Editor::getObjects() { return list; } -void Editor::redrawObject(DraggablePixmapItem *item) { +void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { if (item && item->event && !item->event->getPixmap().isNull()) { qreal opacity = item->event->getUsingSprite() ? 1.0 : 0.7; item->setOpacity(opacity); @@ -2040,8 +2050,8 @@ void Editor::shouldReselectEvents() { } void Editor::updateSelectedEvents() { - for (DraggablePixmapItem *item : getObjects()) { - redrawObject(item); + for (DraggablePixmapItem *item : getEventPixmapItems()) { + redrawEventPixmapItem(item); } emit objectsChanged(); @@ -2073,17 +2083,9 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { int event_offs = Event::getIndexOffset(eventGroup); index = index - event_offs; Event *event = this->map->getEvent(eventGroup, index); - DraggablePixmapItem *selectedEvent = nullptr; - for (QGraphicsItem *child : this->events_group->childItems()) { - DraggablePixmapItem *item = static_cast(child); - if (item->event == event) { - selectedEvent = item; - break; - } - } - if (selectedEvent) { - this->selectMapEvent(selectedEvent); + if (event && event->getPixmapItem()) { + this->selectMapEvent(event->getPixmapItem()); } else { updateSelectedEvents(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 23dbbae7..a379a2cd 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -96,6 +96,7 @@ MainWindow::~MainWindow() saveGlobalConfigs(); delete label_MapRulerStatus; + delete undoView; delete editor; delete ui; } @@ -358,15 +359,15 @@ void MainWindow::initEditor() { ui->menuEdit->addAction(undoAction); ui->menuEdit->addAction(redoAction); - QUndoView *undoView = new QUndoView(&editor->editGroup); - undoView->setWindowTitle(tr("Edit History")); - undoView->setAttribute(Qt::WA_QuitOnClose, false); + this->undoView = new QUndoView(&editor->editGroup); + this->undoView->setWindowTitle(tr("Edit History")); + this->undoView->setAttribute(Qt::WA_QuitOnClose, false); // Show the EditHistory dialog with Ctrl+E QAction *showHistory = new QAction("Show Edit History...", this); showHistory->setObjectName("action_ShowEditHistory"); showHistory->setShortcut(QKeySequence("Ctrl+E")); - connect(showHistory, &QAction::triggered, [this, undoView](){ openSubWindow(undoView); }); + connect(showHistory, &QAction::triggered, this, &MainWindow::openEditHistory); ui->menuEdit->addAction(showHistory); @@ -391,6 +392,10 @@ void MainWindow::initEditor() { }); } +void MainWindow::openEditHistory() { + openSubWindow(this->undoView); +} + void MainWindow::initMiscHeapObjects() { ui->tabWidget_EventType->clear(); } @@ -1003,13 +1008,12 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ int index = event_id - Event::getIndexOffset(event_group); Event* event = editor->map->getEvent(event_group, index); if (event) { - for (DraggablePixmapItem *item : editor->getObjects()) { - if (item->event == event) { - editor->selected_events->clear(); - editor->selected_events->append(item); - editor->updateSelectedEvents(); - return; - } + auto item = event->getPixmapItem(); + if (item) { + editor->selected_events->clear(); + editor->selected_events->append(item); + editor->updateSelectedEvents(); + return; } } // Can still warp to this map, but can't select the specified event @@ -1993,9 +1997,9 @@ void MainWindow::displayEventTabs() { } void MainWindow::updateObjects() { - QList all_objects = editor->getObjects(); + QList items = editor->getEventPixmapItems(); for (auto i = this->lastSelectedEvent.cbegin(), end = this->lastSelectedEvent.cend(); i != end; i++) { - if (i.value() && !all_objects.contains(i.value())) + if (i.value() && !items.contains(i.value())) this->lastSelectedEvent.insert(i.key(), nullptr); } displayEventTabs(); @@ -2017,7 +2021,7 @@ void MainWindow::updateSelectedObjects() { DraggablePixmapItem *selectedEvent = all_events.first()->getPixmapItem(); if (selectedEvent) { editor->selected_events->append(selectedEvent); - editor->redrawObject(selectedEvent); + editor->redrawEventPixmapItem(selectedEvent); events.append(selectedEvent); } } @@ -2162,7 +2166,7 @@ Event::Group MainWindow::getEventGroupFromTabWidget(QWidget *tab) { void MainWindow::eventTabChanged(int index) { if (editor->map) { Event::Group group = getEventGroupFromTabWidget(ui->tabWidget_EventType->widget(index)); - DraggablePixmapItem *selectedEvent = this->lastSelectedEvent.value(group, nullptr); + DraggablePixmapItem *selectedItem = this->lastSelectedEvent.value(group, nullptr); switch (group) { case Event::Group::Object: @@ -2182,18 +2186,11 @@ void MainWindow::eventTabChanged(int index) { } if (!isProgrammaticEventTabChange) { - if (!selectedEvent && editor->map->getNumEvents(group)) { + if (!selectedItem) { Event *event = editor->map->getEvent(group, 0); - for (QGraphicsItem *child : editor->events_group->childItems()) { - DraggablePixmapItem *item = static_cast(child); - if (item->event == event) { - selectedEvent = item; - break; - } - } + if (event) selectedItem = event->getPixmapItem(); } - - if (selectedEvent) editor->selectMapEvent(selectedEvent); + if (selectedItem) editor->selectMapEvent(selectedItem); } } diff --git a/src/project.cpp b/src/project.cpp index f3957ff4..58b0100e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -420,6 +420,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); map->setIsPersistedToFile(false); + this->mapCache.insert(map->name(), map); emit mapCreated(map, settings.group); diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 78a00bb6..3e54a046 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -29,7 +29,7 @@ void DraggablePixmapItem::emitPositionChanged() { void DraggablePixmapItem::updatePixmap() { editor->project->setEventPixmap(event, true); this->updatePosition(); - editor->redrawObject(this); + editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); } diff --git a/src/ui/neweventtoolbutton.cpp b/src/ui/neweventtoolbutton.cpp index b569cec9..a7e65f90 100644 --- a/src/ui/neweventtoolbutton.cpp +++ b/src/ui/neweventtoolbutton.cpp @@ -52,7 +52,7 @@ void NewEventToolButton::init() this->newSecretBaseAction->setIcon(QIcon(":/icons/add.ico")); connect(this->newSecretBaseAction, &QAction::triggered, this, &NewEventToolButton::newSecretBase); - QMenu *alignMenu = new QMenu(); + QMenu *alignMenu = new QMenu(this); alignMenu->addAction(this->newObjectAction); alignMenu->addAction(this->newCloneObjectAction); alignMenu->addAction(this->newWarpAction); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 016480a7..9a9cbfc1 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -112,7 +112,7 @@ void ProjectSettingsEditor::initUi() { // Validate that the border metatiles text is a comma-separated list of metatile values static const QString regex_Hex = "(0[xX])?[A-Fa-f0-9]+"; static const QRegularExpression expression_HexList(QString("^(%1,)*%1$").arg(regex_Hex)); // Comma-separated list of hex values - QRegularExpressionValidator *validator_HexList = new QRegularExpressionValidator(expression_HexList); + QRegularExpressionValidator *validator_HexList = new QRegularExpressionValidator(expression_HexList, this); ui->lineEdit_BorderMetatiles->setValidator(validator_HexList); this->setBorderMetatilesUi(projectConfig.useCustomBorderSize); From 2100aaac93dfff6ed0ec470f1a2adea34eecc859 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 9 Feb 2025 17:06:30 -0500 Subject: [PATCH 166/364] Fix some internal use of 'objects' when referring to events --- forms/mainwindow.ui | 6 +++--- include/editor.h | 10 +++++----- include/mainwindow.h | 4 ++-- src/editor.cpp | 37 ++++++++++++++++++------------------- src/mainwindow.cpp | 40 ++++++++++++++++++++-------------------- src/ui/graphicsview.cpp | 2 +- 6 files changed, 49 insertions(+), 50 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 4a9e439a..a759e070 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1484,7 +1484,7 @@ - + 1 @@ -1499,7 +1499,7 @@ 1 - + true @@ -1582,7 +1582,7 @@ - + 0 diff --git a/include/editor.h b/include/editor.h index e1f939bd..3e2249f2 100644 --- a/include/editor.h +++ b/include/editor.h @@ -112,7 +112,7 @@ public: DraggablePixmapItem *addEventPixmapItem(Event *event); void removeEventPixmapItem(Event *event); bool eventLimitReached(Map *, Event::Type); - void selectMapEvent(DraggablePixmapItem *object, bool toggle = false); + void selectMapEvent(DraggablePixmapItem *item, bool toggle = false); DraggablePixmapItem *addNewEvent(Event::Type type); void updateSelectedEvents(); void duplicateSelectedEvents(); @@ -157,7 +157,7 @@ public: enum class EditAction { None, Paint, Select, Fill, Shift, Pick, Move }; EditAction mapEditAction = EditAction::Paint; - EditAction objectEditAction = EditAction::Select; + EditAction eventEditAction = EditAction::Select; enum class EditMode { None, Disabled, Metatiles, Collision, Header, Events, Connections, Encounters }; EditMode editMode = EditMode::None; @@ -171,7 +171,7 @@ public: void setEditingMetatiles(); void setEditingCollision(); void setEditingHeader(); - void setEditingObjects(); + void setEditingEvents(); void setEditingConnections(); void setEditingEncounters(); @@ -183,7 +183,7 @@ public: int eventShiftActionId = 0; - void objectsView_onMousePress(QMouseEvent *event); + void eventsView_onMousePress(QMouseEvent *event); int getBorderDrawDistance(int dimension); @@ -257,7 +257,7 @@ private slots: void onWheelZoom(int); signals: - void objectsChanged(); + void eventsChanged(); void openConnectedMap(MapConnection*); void wildMonTableOpened(EncounterTableModel*); void wildMonTableClosed(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 43401d9e..3fe187f3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -221,8 +221,8 @@ private slots: void addNewEvent(Event::Type type); void tryAddEventTab(QWidget * tab); void displayEventTabs(); - void updateSelectedObjects(); - void updateObjects(); + void updateSelectedEvents(); + void updateEvents(); void on_toolButton_Paint_clicked(); void on_toolButton_Select_clicked(); diff --git a/src/editor.cpp b/src/editor.cpp index 54f48acb..6b560ced 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -178,7 +178,7 @@ void Editor::setEditingHeader() { setEditorView(); } -void Editor::setEditingObjects() { +void Editor::setEditingEvents() { this->editMode = EditMode::Events; setEditorView(); @@ -1295,7 +1295,7 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { } void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { - // TODO: add event tab object painting tool buttons stuff here + // TODO: add event tab event painting tool buttons stuff here if (!item->getEditsEnabled()) { return; } @@ -1350,10 +1350,10 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i item->shift(event); } } else if (this->editMode == EditMode::Events) { - if (objectEditAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { + if (eventEditAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { // Right-clicking while in paint mode will change mode to select. if (event->buttons() & Qt::RightButton) { - this->objectEditAction = EditAction::Select; + this->eventEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Paint->setChecked(false); @@ -1370,14 +1370,14 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i DraggablePixmapItem *newEvent = addNewEvent(eventType); if (newEvent) { newEvent->move(pos.x(), pos.y()); - emit objectsChanged(); + emit eventsChanged(); selectMapEvent(newEvent); } } } - } else if (objectEditAction == EditAction::Select) { + } else if (eventEditAction == EditAction::Select) { // do nothing here, at least for now - } else if (objectEditAction == EditAction::Shift) { + } else if (eventEditAction == EditAction::Shift) { static QPoint selection_origin; if (event->type() == QEvent::GraphicsSceneMouseRelease) { @@ -1715,7 +1715,6 @@ void Editor::displayMapEvents() { addEventPixmapItem(event); } - //objects_group->setFiltersChildEvents(false); events_group->setHandlesChildEvents(false); } @@ -2054,23 +2053,23 @@ void Editor::updateSelectedEvents() { redrawEventPixmapItem(item); } - emit objectsChanged(); + emit eventsChanged(); } -void Editor::selectMapEvent(DraggablePixmapItem *object, bool toggle) { - if (!selected_events || !object) +void Editor::selectMapEvent(DraggablePixmapItem *item, bool toggle) { + if (!selected_events || !item) return; if (!toggle) { // Selecting just this event selected_events->clear(); - selected_events->append(object); - } else if (!selected_events->contains(object)) { + selected_events->append(item); + } else if (!selected_events->contains(item)) { // Adding event to group selection - selected_events->append(object); + selected_events->append(item); } else if (selected_events->length() > 1) { // Removing event from group selection - selected_events->removeOne(object); + selected_events->removeOne(item); } else { // Attempting to toggle the only currently-selected event. // Unselecting an event this way would be unexpected, so we ignore it. @@ -2271,13 +2270,13 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working // Since the DraggablePixmapItem's event fires first, we can set a temp // variable "selectingEvent" so that we can detect whether or not the user // is clicking on the background instead of an event. -void Editor::objectsView_onMousePress(QMouseEvent *event) { - // make sure we are in object editing mode +void Editor::eventsView_onMousePress(QMouseEvent *event) { + // make sure we are in event editing mode if (map_item && this->editMode != EditMode::Events) { return; } - if (this->objectEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { - this->objectEditAction = EditAction::Select; + if (this->eventEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { + this->eventEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Paint->setChecked(false); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a379a2cd..f4370f52 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -337,14 +337,14 @@ void MainWindow::checkForUpdates(bool) {} void MainWindow::initEditor() { this->editor = new Editor(ui); - connect(this->editor, &Editor::objectsChanged, this, &MainWindow::updateObjects); + connect(this->editor, &Editor::eventsChanged, this, &MainWindow::updateEvents); connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); - connect(ui->toolButton_deleteObject, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); + connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); this->loadUserSettings(); @@ -1760,7 +1760,7 @@ void MainWindow::paste() { if (!newEvents.empty()) { editor->map->commit(new EventPaste(this->editor, editor->map, newEvents)); - updateObjects(); + updateEvents(); } break; @@ -1816,8 +1816,8 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) clickToolButtonFromEditAction(editor->mapEditAction); } else if (index == MainTab::Events) { ui->stackedWidget_MapEvents->setCurrentIndex(1); - editor->setEditingObjects(); - clickToolButtonFromEditAction(editor->objectEditAction); + editor->setEditingEvents(); + clickToolButtonFromEditAction(editor->eventEditAction); } else if (index == MainTab::Connections) { editor->setEditingConnections(); ui->graphicsView_Connections->setFocus(); // Avoid opening tab with focus on something editable @@ -1958,13 +1958,13 @@ void MainWindow::resetMapViewScale() { void MainWindow::addNewEvent(Event::Type type) { if (editor && editor->project) { - DraggablePixmapItem *object = editor->addNewEvent(type); - if (object) { + DraggablePixmapItem *item = editor->addNewEvent(type); + if (item) { auto halfSize = ui->graphicsView_Map->size() / 2; auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height()); - object->moveTo(Metatile::coordFromPixmapCoord(centerPos)); - updateObjects(); - editor->selectMapEvent(object); + item->moveTo(Metatile::coordFromPixmapCoord(centerPos)); + updateEvents(); + editor->selectMapEvent(item); } else { WarningMessage msgBox(QStringLiteral("Failed to add new event."), this); if (Event::typeToGroup(type) == Event::Group::Object) { @@ -1996,17 +1996,17 @@ void MainWindow::displayEventTabs() { tryAddEventTab(ui->tab_Healspots); } -void MainWindow::updateObjects() { +void MainWindow::updateEvents() { QList items = editor->getEventPixmapItems(); for (auto i = this->lastSelectedEvent.cbegin(), end = this->lastSelectedEvent.cend(); i != end; i++) { if (i.value() && !items.contains(i.value())) this->lastSelectedEvent.insert(i.key(), nullptr); } displayEventTabs(); - updateSelectedObjects(); + updateSelectedEvents(); } -void MainWindow::updateSelectedObjects() { +void MainWindow::updateSelectedEvents() { QList events; if (editor->selected_events && editor->selected_events->length()) { @@ -2259,7 +2259,7 @@ void MainWindow::on_toolButton_Paint_clicked() if (ui->mainTabBar->currentIndex() == MainTab::Map) editor->mapEditAction = Editor::EditAction::Paint; else - editor->objectEditAction = Editor::EditAction::Paint; + editor->eventEditAction = Editor::EditAction::Paint; editor->settings->mapCursor = QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10); @@ -2280,7 +2280,7 @@ void MainWindow::on_toolButton_Select_clicked() if (ui->mainTabBar->currentIndex() == MainTab::Map) editor->mapEditAction = Editor::EditAction::Select; else - editor->objectEditAction = Editor::EditAction::Select; + editor->eventEditAction = Editor::EditAction::Select; editor->settings->mapCursor = QCursor(); editor->cursorMapTileRect->setSingleTileMode(); @@ -2299,7 +2299,7 @@ void MainWindow::on_toolButton_Fill_clicked() if (ui->mainTabBar->currentIndex() == MainTab::Map) editor->mapEditAction = Editor::EditAction::Fill; else - editor->objectEditAction = Editor::EditAction::Fill; + editor->eventEditAction = Editor::EditAction::Fill; editor->settings->mapCursor = QCursor(QPixmap(":/icons/fill_color_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2318,7 +2318,7 @@ void MainWindow::on_toolButton_Dropper_clicked() if (ui->mainTabBar->currentIndex() == MainTab::Map) editor->mapEditAction = Editor::EditAction::Pick; else - editor->objectEditAction = Editor::EditAction::Pick; + editor->eventEditAction = Editor::EditAction::Pick; editor->settings->mapCursor = QCursor(QPixmap(":/icons/pipette_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2337,7 +2337,7 @@ void MainWindow::on_toolButton_Move_clicked() if (ui->mainTabBar->currentIndex() == MainTab::Map) editor->mapEditAction = Editor::EditAction::Move; else - editor->objectEditAction = Editor::EditAction::Move; + editor->eventEditAction = Editor::EditAction::Move; editor->settings->mapCursor = QCursor(QPixmap(":/icons/move.ico"), 7, 7); editor->cursorMapTileRect->setSingleTileMode(); @@ -2356,7 +2356,7 @@ void MainWindow::on_toolButton_Shift_clicked() if (ui->mainTabBar->currentIndex() == MainTab::Map) editor->mapEditAction = Editor::EditAction::Shift; else - editor->objectEditAction = Editor::EditAction::Shift; + editor->eventEditAction = Editor::EditAction::Shift; editor->settings->mapCursor = QCursor(QPixmap(":/icons/shift_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2375,7 +2375,7 @@ void MainWindow::checkToolButtons() { if (ui->mainTabBar->currentIndex() == MainTab::Map) { editAction = editor->mapEditAction; } else { - editAction = editor->objectEditAction; + editAction = editor->eventEditAction; if (editAction == Editor::EditAction::Select && editor->map_ruler) editor->map_ruler->setEnabled(true); else if (editor->map_ruler) diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index 73827211..68479e98 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -5,7 +5,7 @@ void GraphicsView::mousePressEvent(QMouseEvent *event) { QGraphicsView::mousePressEvent(event); if (editor) { - editor->objectsView_onMousePress(event); + editor->eventsView_onMousePress(event); } } From a5141dea5d656664eadf6c5a1709d4fd417abc37 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 10 Feb 2025 11:50:39 -0500 Subject: [PATCH 167/364] Fix events not rendering after certain layout changes --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f4370f52..c57cdd00 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -961,8 +961,8 @@ bool MainWindow::setLayout(QString layoutId) { } void MainWindow::redrawMapScene() { - editor->displayMap(); editor->displayLayout(); + editor->displayMap(); refreshMapScene(); } From c20de521b862637644fb17c93c507be5eaca75e2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 10 Feb 2025 16:06:33 -0500 Subject: [PATCH 168/364] Add event selection settings --- forms/preferenceeditor.ui | 29 +++++++++++++++++++++++++++++ include/config.h | 3 +++ include/editor.h | 2 ++ src/config.cpp | 9 +++++++++ src/editor.cpp | 12 +++++++++++- src/mainwindow.cpp | 3 +++ src/ui/preferenceeditor.cpp | 7 ++++++- 7 files changed, 63 insertions(+), 2 deletions(-) diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index a7009e6e..1133b497 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -57,6 +57,35 @@ + + + + Event Selection Mode + + + + + + If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. + + + Select by clicking on sprite + + + + + + + If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. + + + Select by clicking within bounding rectangle + + + + + + diff --git a/include/config.h b/include/config.h index 502ee1ec..d07b566a 100644 --- a/include/config.h +++ b/include/config.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "events.h" @@ -83,6 +84,7 @@ public: this->lastUpdateCheckTime = QDateTime(); this->lastUpdateCheckVersion = porymapVersion; this->rateLimitTimes.clear(); + this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; } void addRecentProject(QString project); void setRecentProjects(QStringList projects); @@ -136,6 +138,7 @@ public: QDateTime lastUpdateCheckTime; QVersionNumber lastUpdateCheckVersion; QMap rateLimitTimes; + QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode; QByteArray wildMonChartGeometry; QByteArray newMapDialogGeometry; QByteArray newLayoutDialogGeometry; diff --git a/include/editor.h b/include/editor.h index 3e2249f2..5de799db 100644 --- a/include/editor.h +++ b/include/editor.h @@ -116,6 +116,8 @@ public: DraggablePixmapItem *addNewEvent(Event::Type type); void updateSelectedEvents(); void duplicateSelectedEvents(); + void redrawAllEvents(); + void redrawEvents(const QList &events); void redrawEventPixmapItem(DraggablePixmapItem *item); QList getEventPixmapItems(); diff --git a/src/config.cpp b/src/config.cpp index 2356f7a9..744a6394 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -417,6 +417,14 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { if (match.hasMatch()) { this->rateLimitTimes.insert(match.captured("url"), QDateTime::fromString(value).toLocalTime()); } + } else if (key == "event_selection_shape_mode") { + if (value == "mask") { + this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; + } else if (value == "bounding_rect") { + this->eventSelectionShapeMode = QGraphicsPixmapItem::BoundingRectShape; + } else { + logWarn(QString("Invalid config value for %1: '%2'. Must be 'mask' or 'bounding_rect'.").arg(key).arg(value)); + } } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -484,6 +492,7 @@ QMap PorymapConfig::getKeyValueMap() { if (!time.isNull() && time > QDateTime::currentDateTime()) map.insert("rate_limit_time/" + i.key().toString(), time.toUTC().toString()); } + map.insert("event_selection_shape_mode", (this->eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) ? "mask" : "bounding_rect"); return map; } diff --git a/src/editor.cpp b/src/editor.cpp index 6b560ced..2824dd6c 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1986,6 +1986,16 @@ Tileset* Editor::getCurrentMapPrimaryTileset() return project->getTileset(tilesetLabel); } +void Editor::redrawAllEvents() { + if (this->map) redrawEvents(this->map->getEvents()); +} + +void Editor::redrawEvents(const QList &events) { + for (const auto &event : events) { + redrawEventPixmapItem(event->getPixmapItem()); + } +} + QList Editor::getEventPixmapItems() { QList list; for (QGraphicsItem *child : events_group->childItems()) { @@ -2000,7 +2010,7 @@ void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { item->setOpacity(opacity); project->setEventPixmap(item->event, true); item->setPixmap(item->event->getPixmap()); - item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); + item->setShapeMode(porymapConfig.eventSelectionShapeMode); if (selected_events && selected_events->contains(item)) { QImage image = item->pixmap().toImage(); QPainter painter(&image); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c57cdd00..1c204771 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2754,6 +2754,9 @@ void MainWindow::togglePreferenceSpecificUi() { if (this->updatePromoter) this->updatePromoter->updatePreferences(); + + // Redraw all events to use updated porymapConfig.eventSelectionShapeMode + this->editor->redrawAllEvents(); } void MainWindow::openProjectSettingsEditor(int tab) { diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index a51b614d..77a1cbb5 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -45,6 +45,11 @@ void PreferenceEditor::initFields() { void PreferenceEditor::updateFields() { themeSelector->setCurrentText(porymapConfig.theme); + if (porymapConfig.eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) { + ui->radioButton_OnSprite->setChecked(true); + } else if (porymapConfig.eventSelectionShapeMode == QGraphicsPixmapItem::BoundingRectShape) { + ui->radioButton_WithinRect->setChecked(true); + } ui->lineEdit_TextEditorOpenFolder->setText(porymapConfig.textEditorOpenFolder); ui->lineEdit_TextEditorGotoLine->setText(porymapConfig.textEditorGotoLine); ui->checkBox_MonitorProjectFiles->setChecked(porymapConfig.monitorFiles); @@ -58,7 +63,7 @@ void PreferenceEditor::saveFields() { porymapConfig.theme = theme; emit themeChanged(theme); } - + porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape; porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text(); porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text(); porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked(); From ee986b8e56d3f6cc1d69aa352fc8fab3b89e74af Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 12 Feb 2025 15:39:40 -0500 Subject: [PATCH 169/364] Remove unnecessary const qualifier, unused variable --- include/core/map.h | 2 +- src/core/map.cpp | 2 +- src/core/maplayout.cpp | 4 +--- src/editor.cpp | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index a25db5e9..b223536d 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -84,7 +84,7 @@ public: void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); - int getIndexOfEvent(const Event *) const; + int getIndexOfEvent(Event *) const; void deleteConnections(); QList getConnections() const; diff --git a/src/core/map.cpp b/src/core/map.cpp index e784ba5f..aac20138 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -230,7 +230,7 @@ void Map::addEvent(Event *event) { if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event); } -int Map::getIndexOfEvent(const Event *event) const { +int Map::getIndexOfEvent(Event *event) const { return m_events.value(event->getEventGroup()).indexOf(event); } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 35fcc2f7..2b52a80f 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -177,8 +177,6 @@ void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bo } void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { - int oldWidth = this->width; - int oldHeight = this->height; int newWidth = this->width + margins.left() + margins.right(); int newHeight = this->height + margins.top() + margins.bottom(); @@ -190,7 +188,7 @@ void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { if ((x < margins.left()) || (x >= newWidth - margins.right()) || (y < margins.top()) || (y >= newHeight - margins.bottom())) { newBlockdata.append(0); } else { - int index = (y - margins.top()) * oldWidth + (x - margins.left()); + int index = (y - margins.top()) * this->width + (x - margins.left()); newBlockdata.append(this->blockdata.value(index)); } } diff --git a/src/editor.cpp b/src/editor.cpp index 823d1196..5aa79310 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2192,7 +2192,7 @@ void Editor::deleteSelectedEvents() { // If deleting multiple events, just let editor work out next selected. Event *nextSelectedEvent = nullptr; if (eventsToDelete.length() == 1) { - const Event *eventToDelete = eventsToDelete.first(); + Event *eventToDelete = eventsToDelete.first(); Event::Group event_group = eventToDelete->getEventGroup(); int index = this->map->getIndexOfEvent(eventToDelete); if (index != this->map->getNumEvents(event_group) - 1) From 8f5880f5bddb667761a66505d658097cfe893b2c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 12 Feb 2025 15:54:32 -0500 Subject: [PATCH 170/364] Fix crash when duplicating maps --- src/core/map.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/core/map.cpp b/src/core/map.cpp index aac20138..d2ab8ef6 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -35,13 +35,8 @@ Map::Map(const Map &other, QObject *parent) : Map(parent) { // Copy events for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) { - QList newEvents; - for (const auto &event : i.value()) { - auto newEvent = event->duplicate(); - m_ownedEvents.insert(newEvent); - newEvents.append(newEvent); - } - m_events[i.key()] = newEvents; + for (const auto &event : i.value()) + addEvent(event->duplicate()); } // Duplicating the map connections is probably not desirable, so we skip them. From 6574e1b68ad67bd01eb94f9db09edd2470fad4b6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 12 Feb 2025 16:04:22 -0500 Subject: [PATCH 171/364] Re-enable workflow on PR for dev branch --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1346f310..ff6e22eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,8 +9,6 @@ on: tags: - '*' pull_request: - branches: - - master # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From 5feb391a9dc0608d96bf0817f5eb193f36ea2838 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 13 Feb 2025 11:17:05 -0500 Subject: [PATCH 172/364] Read colorpicker screen using cursor position --- include/ui/colorpicker.h | 2 +- src/ui/colorpicker.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/ui/colorpicker.h b/include/ui/colorpicker.h index fa431341..69a6c9b2 100644 --- a/include/ui/colorpicker.h +++ b/include/ui/colorpicker.h @@ -27,7 +27,7 @@ private: QColor color = Qt::white; - void hover(int mouseX, int mouseY); + void hover(const QPoint &pos); }; #endif // COLORPICKER_H diff --git a/src/ui/colorpicker.cpp b/src/ui/colorpicker.cpp index dccffc0e..5d2d3992 100644 --- a/src/ui/colorpicker.cpp +++ b/src/ui/colorpicker.cpp @@ -27,7 +27,7 @@ ColorPicker::ColorPicker(QWidget *parent) : QPoint cursorPos = QCursor::pos(); if (lastCursorPos != cursorPos) { lastCursorPos = cursorPos; - this->hover(cursorPos.x(), cursorPos.y()); + this->hover(cursorPos); } }); timer->start(10); @@ -40,15 +40,17 @@ ColorPicker::~ColorPicker() delete ui; } -void ColorPicker::hover(int mouseX, int mouseY) { - QScreen *screen = QGuiApplication::primaryScreen(); - if (const QWindow *window = windowHandle()) - screen = window->screen(); +void ColorPicker::hover(const QPoint &pos) { + QScreen *screen = QGuiApplication::screenAt(pos); + if (!screen) { + const QWindow *window = windowHandle(); + if (window) screen = window->screen(); + } if (!screen) return; // 15 X 15 box with 8x magnification = 120px square) - QPixmap grab = screen->grabWindow(0, mouseX - zoom_box_dimensions / 2, mouseY - zoom_box_dimensions / 2, zoom_box_dimensions, zoom_box_dimensions); + QPixmap grab = screen->grabWindow(0, pos.x() - zoom_box_dimensions / 2, pos.y() - zoom_box_dimensions / 2, zoom_box_dimensions, zoom_box_dimensions); int pixelRatio = grab.devicePixelRatio(); // TODO: investigate for high dpi displays why text is too high res From f442f44f727cdbc21e2bcc650573ea69f8086f43 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 13 Feb 2025 11:40:09 -0500 Subject: [PATCH 173/364] Properly update search bar's clear text button --- src/ui/maplisttoolbar.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 10a33c47..d35e4656 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -123,6 +123,12 @@ void MapListToolBar::applyFilter(const QString &filterText) { const QSignalBlocker b(ui->lineEdit_filterBox); ui->lineEdit_filterBox->setText(filterText); + // The clear button does not properly disappear when filterText is empty. + // It seems like this is because blocking the QLineEdit's signals prevents + // it from communicating the text change to QLineEditPrivate. + // We toggle the button ourselves as a workaround. + ui->lineEdit_filterBox->setClearButtonEnabled(!filterText.isEmpty()); + if (m_list) { auto model = static_cast(m_list->model()); if (model) model->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); From 3be7f54d05acef2ec8c4bf7c26b52a391dfe56a5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 13 Feb 2025 21:14:58 -0500 Subject: [PATCH 174/364] Only show file watcher warning when Porymap is active --- include/mainwindow.h | 2 +- include/project.h | 6 ++++-- src/mainwindow.cpp | 44 +++++++++++++++++++++++++++++++++----------- src/project.cpp | 22 ++++++++++++++++++++-- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 3fe187f3..2d065b37 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -367,7 +367,7 @@ private: void scrollMapListToCurrentMap(MapTree *list); void scrollMapListToCurrentLayout(MapTree *list); void resetMapListFilters(); - void showFileWatcherWarning(QString filepath); + void showFileWatcherWarning(); QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); diff --git a/include/project.h b/include/project.h index fcdde462..f3a1093b 100644 --- a/include/project.h +++ b/include/project.h @@ -71,7 +71,7 @@ public: QMap facingDirections; ParseUtil parser; QFileSystemWatcher fileWatcher; - QMap modifiedFileTimestamps; + QSet modifiedFiles; bool usingAsmTilesets; QSet disabledSettingsNames; QSet topLevelMapFields; @@ -252,6 +252,7 @@ public: private: QMap mapSectionDisplayNames; + QMap modifiedFileTimestamps; void updateLayout(Layout *); @@ -259,6 +260,7 @@ private: void setNewLayoutBorder(Layout *layout); void ignoreWatchedFileTemporarily(QString filepath); + void recordFileChange(const QString &filepath); static int num_tiles_primary; static int num_tiles_total; @@ -270,7 +272,7 @@ private: static int max_object_events; signals: - void fileChanged(QString filepath); + void fileChanged(const QString &filepath); void mapLoaded(Map *map); void mapCreated(Map *newMap, const QString &groupName); void layoutCreated(Layout *newLayout); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 277aaf97..9e59758e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -69,6 +69,8 @@ MainWindow::MainWindow(QWidget *parent) : QCoreApplication::setApplicationVersion(PORYMAP_VERSION); QApplication::setApplicationDisplayName(QApplication::applicationName()); QApplication::setWindowIcon(QIcon(":/icons/porymap-icon-2.ico")); + connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::showFileWatcherWarning); + ui->setupUi(this); cleanupLargeLog(); @@ -791,26 +793,46 @@ void MainWindow::openSubWindow(QWidget * window) { } } -void MainWindow::showFileWatcherWarning(QString filepath) { - if (!porymapConfig.monitorFiles || !isProjectOpen()) +void MainWindow::showFileWatcherWarning() { + if (!porymapConfig.monitorFiles || !isProjectOpen()) + return; + + // Only show the file watcher warning if Porymap is the currently active application. + // This stops Porymap from bugging users when they switch to their projects to make edits; + // we'll warn them about the need to reload when they return to Porymap. + if (QGuiApplication::applicationState() != Qt::ApplicationActive) return; Project *project = this->editor->project; - if (project->modifiedFileTimestamps.contains(filepath)) { - if (QDateTime::currentMSecsSinceEpoch() < project->modifiedFileTimestamps[filepath]) { - return; - } - project->modifiedFileTimestamps.remove(filepath); + QStringList modifiedFiles(project->modifiedFiles.constBegin(), project->modifiedFiles.constEnd()); + if (modifiedFiles.isEmpty()) + return; + project->modifiedFiles.clear(); + + // Only allow one of these warnings at a single time. + // Additional file changes are ignored while the warning is already active. + static bool showing = false; + if (showing) + return; + showing = true; + + // Strip project root from filepaths + const QString root = project->root + "/"; + for (auto &path : modifiedFiles) { + path.remove(root); } - static bool showing = false; - if (showing) return; + QuestionMessage msgBox("", this); + if (modifiedFiles.count() == 1) { + msgBox.setText(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(modifiedFiles.first())); + } else { + msgBox.setText(QStringLiteral("Some project files have changed on disk. Would you like to reload the project?")); + msgBox.setDetailedText(QStringLiteral("The following files have changed:\n") + modifiedFiles.join("\n")); + } - QuestionMessage msgBox(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(filepath.remove(project->root + "/")), this); QCheckBox showAgainCheck("Do not ask again."); msgBox.setCheckBox(&showAgainCheck); - showing = true; auto reply = msgBox.exec(); if (reply == QMessageBox::Yes) { on_action_Reload_Project_triggered(); diff --git a/src/project.cpp b/src/project.cpp index fb2d3d2b..3f510e6b 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -35,7 +35,7 @@ int Project::max_object_events = 64; Project::Project(QObject *parent) : QObject(parent) { - QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::fileChanged); + QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::recordFileChange); } Project::~Project() @@ -660,7 +660,25 @@ void Project::saveMapLayouts() { void Project::ignoreWatchedFileTemporarily(QString filepath) { // Ignore any file-change events for this filepath for the next 5 seconds. - modifiedFileTimestamps.insert(filepath, QDateTime::currentMSecsSinceEpoch() + 5000); + this->modifiedFileTimestamps.insert(filepath, QDateTime::currentMSecsSinceEpoch() + 5000); +} + +void Project::recordFileChange(const QString &filepath) { + if (this->modifiedFiles.contains(filepath)) { + // We already recorded a change to this file + return; + } + + if (this->modifiedFileTimestamps.contains(filepath)) { + if (QDateTime::currentMSecsSinceEpoch() < this->modifiedFileTimestamps[filepath]) { + // We're still ignoring changes to this file + return; + } + this->modifiedFileTimestamps.remove(filepath); + } + + this->modifiedFiles.insert(filepath); + emit fileChanged(filepath); } void Project::saveMapGroups() { From 4180134a289225227b6dcd56b09fc21806e3858b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Feb 2025 13:17:03 -0500 Subject: [PATCH 175/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a39341a1..736e6217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - `Export Map Stitch Image` now shows a preview of the full image, not just the current map. - `Custom Attributes` tables now display numbers using spin boxes. The `type` column was removed, because `value`'s type is now obvious. - Unrecognized map names in Event or Connections data will no longer be overwritten. +- It's now possible to click on an event's sprite even if a different event's rectangle is overlapping it. The old selection behavior is available via a new setting. - Reduced diff noise when saving maps. - Map names and ``MAP_NAME`` constants are no longer required to match. - Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. From 59871d5739b6a4be0895d8223aaee908591f95bb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Feb 2025 14:48:38 -0500 Subject: [PATCH 176/364] Add version matrix to Linux build, bump macOS to LTS version --- .github/workflows/main.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff6e22eb..ea31470d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,10 @@ on: workflow_dispatch: jobs: - build-qt5-linux: + build-linux: + strategy: + matrix: + qtversion: [5.14.2, 6.8.2] runs-on: ubuntu-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it @@ -23,7 +26,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v4 with: - version: '5.14.2' + version: ${{ matrix.qtversion }} modules: 'qtcharts' cache: 'true' @@ -47,7 +50,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v4 with: - version: '6.7.*' + version: '6.8.2' modules: 'qtcharts' cache: 'true' From 00e71afd7df375705aa576f913083550e0dbcaa9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 15 Feb 2025 00:59:19 -0500 Subject: [PATCH 177/364] Clean up the wild encounter JSON parsing with some comments --- src/project.cpp | 108 ++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 41 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index fb2d3d2b..ecad57dd 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1640,66 +1640,92 @@ bool Project::readWildMonData() { // The most common value will be used as the default for new groups. QMap> encounterRateFrequencyMaps; - for (OrderedJson subObjectRef : wildMonObj["wild_encounter_groups"].array_items()) { - OrderedJson::object subObject = subObjectRef.object_items(); - if (!subObject["for_maps"].bool_value()) { - this->extraEncounterGroups.push_back(subObject); + // Parse "wild_encounter_groups". This is the main object array containing all the data in this file. + for (OrderedJson mainArrayJson : wildMonObj["wild_encounter_groups"].array_items()) { + OrderedJson::object mainArrayObject = mainArrayJson.object_items(); + + // We're only interested in wild encounter data that's associated with maps ("for_maps" == true). + // Any other wild encounter data (e.g. for Battle Pike / Battle Pyramid) will be ignored. + // We'll record any data that's not for maps in extraEncounterGroups to be outputted when we save. + if (!mainArrayObject["for_maps"].bool_value()) { + this->extraEncounterGroups.push_back(mainArrayObject); continue; } - for (const OrderedJson &field : subObject["fields"].array_items()) { + // Parse the "fields" data. This is like the header for the wild encounters data. + // Each element describes a type of wild encounter Porymap can expect to find, and we represent this data with an EncounterField. + // They should contain a name ("type"), the number of encounter slots and the ratio at which they occur ("encounter_rates"), + // and whether the encounters are divided into groups (like fishing rods). + for (const OrderedJson &fieldJson : mainArrayObject["fields"].array_items()) { + OrderedJson::object fieldObject = fieldJson.object_items(); + EncounterField encounterField; - OrderedJson::object fieldObj = field.object_items(); - encounterField.name = fieldObj["type"].string_value(); - for (auto val : fieldObj["encounter_rates"].array_items()) { + encounterField.name = fieldObject["type"].string_value(); + + for (auto val : fieldObject["encounter_rates"].array_items()) { encounterField.encounterRates.append(val.int_value()); } - QList subGroups; - for (auto groupPair : fieldObj["groups"].object_items()) { - subGroups.append(groupPair.first); - } - for (QString group : subGroups) { - OrderedJson::object groupsObj = fieldObj["groups"].object_items(); - for (auto slotNum : groupsObj[group].array_items()) { - encounterField.groups[group].append(slotNum.int_value()); + // Each element of the "groups" array is an object with the group name as the key (e.g. "old_rod") + // and an array of slot numbers indicating which encounter slots in this encounter type belong to that group. + for (auto groupPair : fieldObject["groups"].object_items()) { + const QString groupName = groupPair.first; + for (auto slotNum : groupPair.second.array_items()) { + encounterField.groups[groupName].append(slotNum.int_value()); } } + encounterRateFrequencyMaps.insert(encounterField.name, QMap()); this->wildMonFields.append(encounterField); } - auto encounters = subObject["encounters"].array_items(); - for (const auto &encounter : encounters) { - OrderedJson::object encounterObj = encounter.object_items(); - QString mapConstant = encounterObj["map"].string_value(); + // Parse the "encounters" data. This is the meat of the wild encounters data. + // Each element is an object that will tell us which map it's associated with, + // its symbol name (which we will display in the Groups dropdown) and a list of + // pokémon associated with any of the encounter types described by the data we parsed above. + for (const auto &encounterJson : mainArrayObject["encounters"].array_items()) { + OrderedJson::object encounterObj = encounterJson.object_items(); WildPokemonHeader header; + // Check for each possible encounter type. for (const EncounterField &monField : this->wildMonFields) { - QString field = monField.name; - if (!encounterObj[field].is_null()) { - OrderedJson::object encounterFieldObj = encounterObj[field].object_items(); - header.wildMons[field].active = true; - header.wildMons[field].encounterRate = encounterFieldObj["encounter_rate"].int_value(); - encounterRateFrequencyMaps[field][header.wildMons[field].encounterRate]++; - for (auto mon : encounterFieldObj["mons"].array_items()) { - WildPokemon newMon; - OrderedJson::object monObj = mon.object_items(); - newMon.minLevel = monObj["min_level"].int_value(); - newMon.maxLevel = monObj["max_level"].int_value(); - newMon.species = monObj["species"].string_value(); - header.wildMons[field].wildPokemon.append(newMon); - } - // If the user supplied too few pokémon for this group then we fill in the rest. - for (int i = header.wildMons[field].wildPokemon.length(); i < monField.encounterRates.length(); i++) { - WildPokemon newMon; // Keep default values - header.wildMons[field].wildPokemon.append(newMon); - } + const QString field = monField.name; + if (encounterObj[field].is_null()) { + // Encounter type isn't present + continue; } + OrderedJson::object encounterFieldObj = encounterObj[field].object_items(); + + WildMonInfo monInfo; + monInfo.active = true; + + // Read encounter rate + monInfo.encounterRate = encounterFieldObj["encounter_rate"].int_value(); + encounterRateFrequencyMaps[field][monInfo.encounterRate]++; + + // Read wild pokémon list + for (auto monJson : encounterFieldObj["mons"].array_items()) { + OrderedJson::object monObj = monJson.object_items(); + + WildPokemon newMon; + newMon.minLevel = monObj["min_level"].int_value(); + newMon.maxLevel = monObj["max_level"].int_value(); + newMon.species = monObj["species"].string_value(); + monInfo.wildPokemon.append(newMon); + } + + // If the user supplied too few pokémon for this group then we fill in the rest with default values. + for (int i = monInfo.wildPokemon.length(); i < monField.encounterRates.length(); i++) { + monInfo.wildPokemon.append(WildPokemon()); + } + header.wildMons[field] = monInfo; } - this->wildMonData[mapConstant].insert({encounterObj["base_label"].string_value(), header}); - this->encounterGroupLabels.append(encounterObj["base_label"].string_value()); + + const QString mapConstant = encounterObj["map"].string_value(); + const QString baseLabel = encounterObj["base_label"].string_value(); + this->wildMonData[mapConstant].insert({baseLabel, header}); + this->encounterGroupLabels.append(baseLabel); } } From 17c35a8d985d47eae6fca11c25cb95b424e2eea2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 18 Feb 2025 14:53:25 -0500 Subject: [PATCH 178/364] Condense parser error messages --- include/core/parseutil.h | 19 +++-- include/lib/orderedjson.h | 6 +- src/core/parseutil.cpp | 59 ++++++--------- src/lib/orderedjson.cpp | 8 +- src/project.cpp | 146 +++++++++++++++++++++---------------- src/ui/prefab.cpp | 8 +- src/ui/regionmapeditor.cpp | 18 ++--- 7 files changed, 135 insertions(+), 129 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index b220597a..b8ba9f55 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -44,25 +44,24 @@ class ParseUtil public: ParseUtil(); void set_root(const QString &dir); - static QString readTextFile(const QString &path); + static QString readTextFile(const QString &path, QString *error = nullptr); void invalidateTextFile(const QString &path); static int textFileLineCount(const QString &path); QList parseAsm(const QString &filename); QStringList readCArray(const QString &filename, const QString &label); QMap readCArrayMulti(const QString &filename); - QMap readNamedIndexCArray(const QString &text, const QString &label); + QMap readNamedIndexCArray(const QString &text, const QString &label, QString *error = nullptr); QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList); - QMap readCDefinesByName(const QString &filename, const QStringList &names); - QStringList readCDefineNames(const QString &filename, const QStringList ®exList); + QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error = nullptr); + QMap readCDefinesByName(const QString &filename, const QStringList &names, QString *error = nullptr); + QStringList readCDefineNames(const QString &filename, const QStringList ®exList, QString *error = nullptr); tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); - bool tryParseJsonFile(QJsonDocument *out, const QString &filepath); - bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath); - bool ensureFieldsExist(const QJsonObject &obj, const QList &fields); + bool tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error = nullptr); + bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error = nullptr); // Returns the 1-indexed line number for the definition of scriptLabel in the scripts file at filePath. // Returns 0 if a definition for scriptLabel cannot be found. @@ -102,8 +101,8 @@ private: QMap expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; - ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex); - QMap evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex); + ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error); + QMap evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error); bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const; bool defineNameMatchesFilter(const QString &name, const QList &filterList) const; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 386937f9..544112f1 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -182,15 +182,15 @@ public: // Parse. If parse fails, return Json() and assign an error message to err. static Json parse(const QString & in, - QString & err, + QString * err = nullptr, JsonParse strategy = JsonParse::STANDARD); static Json parse(const char * in, - QString & err, + QString * err = nullptr, JsonParse strategy = JsonParse::STANDARD) { if (in) { return parse(QString(in), err, strategy); } else { - err = "null input"; + if (err) *err = "null input"; return nullptr; } } diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index d67989ae..9769d86e 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -67,10 +67,10 @@ QString ParseUtil::createErrorMessage(const QString &message, const QString &exp return QString("%1:%2:%3: %4").arg(this->file).arg(lineNum).arg(colNum).arg(message); } -QString ParseUtil::readTextFile(const QString &path) { +QString ParseUtil::readTextFile(const QString &path, QString *error) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1': ").arg(path) + file.errorString()); + if (error) *error = file.errorString(); return QString(); } QTextStream in(&file); @@ -380,7 +380,7 @@ bool ParseUtil::defineNameMatchesFilter(const QString &name, const QListfile = filename; @@ -389,12 +389,9 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const } QString filepath = this->root + "/" + this->file; - this->text = readTextFile(filepath); - - if (this->text.isNull()) { - logError(QString("Failed to read C defines file: '%1'").arg(filepath)); + this->text = readTextFile(filepath, error); + if (this->text.isNull()) return result; - } static const QRegularExpression re_extraChars("(//.*)|(\\/+\\*+[^*]*\\*+\\/+)"); this->text.replace(re_extraChars, ""); @@ -466,8 +463,8 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const } // Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on). -QMap ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex) { - ParsedDefines defines = readCDefines(filename, filterList, useRegex); +QMap ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) { + ParsedDefines defines = readCDefines(filename, filterList, useRegex, error); // Evaluate defines QMap filteredValues; @@ -486,20 +483,20 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS } // Find and evaluate a specific set of defines with known names. -QMap ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names) { - return evaluateCDefines(filename, names, false); +QMap ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names, QString *error) { + return evaluateCDefines(filename, names, false, error); } // Find and evaluate an unknown list of defines with a known name pattern. -QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList) { - return evaluateCDefines(filename, regexList, true); +QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error) { + return evaluateCDefines(filename, regexList, true, error); } // Find an unknown list of defines with a known name pattern. // Similar to readCDefinesByRegex, but for cases where we only need to show a list of define names. // We can skip evaluating any expressions (and by extension skip reporting any errors from this process). -QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList) { - return readCDefines(filename, regexList, true).filteredNames; +QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList, QString *error) { + return readCDefines(filename, regexList, true, error).filteredNames; } QStringList ParseUtil::readCArray(const QString &filename, const QString &label) { @@ -558,8 +555,8 @@ QMap ParseUtil::readCArrayMulti(const QString &filename) { return map; } -QMap ParseUtil::readNamedIndexCArray(const QString &filename, const QString &label) { - this->text = readTextFile(this->root + "/" + filename); +QMap ParseUtil::readNamedIndexCArray(const QString &filename, const QString &label, QString *error) { + this->text = readTextFile(this->root + "/" + filename, error); QMap map; QRegularExpression re_text(QString(R"(\b%1\b\s*(\[?[^\]]*\])?\s*=\s*\{([^\}]*)\})").arg(label)); @@ -659,10 +656,10 @@ QStringList ParseUtil::getLabelValues(const QList &list, const QStr return values; } -bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) { +bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { - logError(QString("Error: Could not open %1 for reading").arg(filepath)); + if (error) *error = file.errorString(); return false; } @@ -671,7 +668,7 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) { const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &parseError); file.close(); if (parseError.error != QJsonParseError::NoError) { - logError(QString("Error: Failed to parse json file %1: %2").arg(filepath).arg(parseError.errorString())); + if (error) *error = parseError.errorString(); return false; } @@ -679,23 +676,15 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) { return true; } -bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath) { +bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error) { QString err; - QString jsonTxt = readTextFile(filepath); - *out = OrderedJson::parse(jsonTxt, err).object_items(); - if (!err.isEmpty()) { - logError(QString("Error: Failed to parse json file %1: %2").arg(filepath).arg(err)); + QString jsonTxt = readTextFile(filepath, error); + if (error && !error->isEmpty()) { return false; } - return true; -} - -bool ParseUtil::ensureFieldsExist(const QJsonObject &obj, const QList &fields) { - for (QString field : fields) { - if (!obj.contains(field)) { - logError(QString("JSON object is missing field '%1'.").arg(field)); - return false; - } + *out = OrderedJson::parse(jsonTxt, error).object_items(); + if (error && !error->isEmpty()) { + return false; } return true; } diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index e1a600aa..24bb264a 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -393,7 +393,7 @@ struct JsonParser final { */ const QString &str; int i; - QString &err; + QString *err; bool failed; const JsonParse strategy; @@ -407,8 +407,8 @@ struct JsonParser final { template T fail(QString &&msg, const T err_ret) { - if (!failed) - err = std::move(msg); + if (!failed && err) + *err = std::move(msg); failed = true; return err_ret; } @@ -775,7 +775,7 @@ struct JsonParser final { }; }//namespace { -Json Json::parse(const QString &in, QString &err, JsonParse strategy) { +Json Json::parse(const QString &in, QString *err, JsonParse strategy) { JsonParser parser { in, 0, err, false, strategy }; Json result = parser.parse_json(0); diff --git a/src/project.cpp b/src/project.cpp index ecad57dd..330c58c2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -196,8 +196,9 @@ void Project::initTopLevelMapFields() { bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName); - if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath))) { - logError(QString("Failed to read map data from %1").arg(mapFilepath)); + QString error; + if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath), &error)) { + logError(QString("Failed to read map data from '%1': %2").arg(mapFilepath).arg(error)); return false; } return true; @@ -514,8 +515,9 @@ bool Project::readMapLayouts() { const QString fullFilepath = QString("%1/%2").arg(this->root).arg(layoutsFilepath); fileWatcher.addPath(fullFilepath); QJsonDocument layoutsDoc; - if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath)) { - logError(QString("Failed to read map layouts from %1").arg(fullFilepath)); + QString error; + if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath, &error)) { + logError(QString("Failed to read map layouts from '%1': %2").arg(fullFilepath).arg(error)); return false; } @@ -1626,13 +1628,15 @@ bool Project::readWildMonData() { this->pokemonMaxLevel = qMax(this->pokemonMinLevel, this->pokemonMaxLevel); // Read encounter data - QString wildMonJsonFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_wild_encounters)); + const QString wildMonJsonBaseFilepath = projectConfig.getFilePath(ProjectFilePath::json_wild_encounters); + QString wildMonJsonFilepath = QString("%1/%2").arg(root).arg(wildMonJsonBaseFilepath); fileWatcher.addPath(wildMonJsonFilepath); OrderedJson::object wildMonObj; - if (!parser.tryParseOrderedJsonFile(&wildMonObj, wildMonJsonFilepath)) { + QString error; + if (!parser.tryParseOrderedJsonFile(&wildMonObj, wildMonJsonFilepath, &error)) { // Failing to read wild encounters data is not a critical error, the encounter editor will just be disabled - logWarn(QString("Failed to read wild encounters from %1").arg(wildMonJsonFilepath)); + logWarn(QString("Failed to read wild encounters from '%1': %2").arg(wildMonJsonBaseFilepath).arg(error)); return true; } @@ -1761,8 +1765,9 @@ bool Project::readMapGroups() { const QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_map_groups); fileWatcher.addPath(filepath); QJsonDocument mapGroupsDoc; - if (!parser.tryParseJsonFile(&mapGroupsDoc, filepath)) { - logError(QString("Failed to read map groups from %1").arg(filepath)); + QString error; + if (!parser.tryParseJsonFile(&mapGroupsDoc, filepath, &error)) { + logError(QString("Failed to read map groups from '%1': %2").arg(filepath).arg(error)); return false; } @@ -2268,8 +2273,9 @@ bool Project::readRegionMapSections() { QJsonDocument doc; const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath); - if (!parser.tryParseJsonFile(&doc, filepath)) { - logError(QString("Failed to read region map sections from '%1'").arg(baseFilepath)); + QString error; + if (!parser.tryParseJsonFile(&doc, filepath, &error)) { + logError(QString("Failed to read region map sections from '%1': %2").arg(baseFilepath).arg(error)); return false; } fileWatcher.addPath(filepath); @@ -2394,8 +2400,9 @@ bool Project::readHealLocations() { QJsonDocument doc; const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_heal_locations); const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath); - if (!parser.tryParseJsonFile(&doc, filepath)) { - logError(QString("Failed to read heal locations from '%1'").arg(baseFilepath)); + QString error; + if (!parser.tryParseJsonFile(&doc, filepath, &error)) { + logError(QString("Failed to read heal locations from '%1': %2").arg(baseFilepath).arg(error)); return false; } fileWatcher.addPath(filepath); @@ -2421,9 +2428,10 @@ bool Project::readItemNames() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items); fileWatcher.addPath(root + "/" + filename); - itemNames = parser.readCDefineNames(filename, regexList); - if (itemNames.isEmpty()) - logWarn(QString("Failed to read item constants from %1").arg(filename)); + QString error; + this->itemNames = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read item constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2431,9 +2439,10 @@ bool Project::readFlagNames() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags); fileWatcher.addPath(root + "/" + filename); - flagNames = parser.readCDefineNames(filename, regexList); - if (flagNames.isEmpty()) - logWarn(QString("Failed to read flag constants from %1").arg(filename)); + QString error; + this->flagNames = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read flag constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2441,9 +2450,10 @@ bool Project::readVarNames() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars); fileWatcher.addPath(root + "/" + filename); - varNames = parser.readCDefineNames(filename, regexList); - if (varNames.isEmpty()) - logWarn(QString("Failed to read var constants from %1").arg(filename)); + QString error; + this->varNames = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read var constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2451,18 +2461,20 @@ bool Project::readMovementTypes() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement); fileWatcher.addPath(root + "/" + filename); - movementTypes = parser.readCDefineNames(filename, regexList); - if (movementTypes.isEmpty()) - logWarn(QString("Failed to read movement type constants from %1").arg(filename)); + QString error; + this->movementTypes = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read movement type constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readInitialFacingDirections() { QString filename = projectConfig.getFilePath(ProjectFilePath::initial_facing_table); fileWatcher.addPath(root + "/" + filename); - facingDirections = parser.readNamedIndexCArray(filename, projectConfig.getIdentifier(ProjectIdentifier::symbol_facing_directions)); - if (facingDirections.isEmpty()) - logWarn(QString("Failed to read initial movement type facing directions from %1").arg(filename)); + QString error; + this->facingDirections = parser.readNamedIndexCArray(filename, projectConfig.getIdentifier(ProjectIdentifier::symbol_facing_directions), &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read initial movement type facing directions from '%1': %2").arg(filename).arg(error)); return true; } @@ -2470,9 +2482,10 @@ bool Project::readMapTypes() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); fileWatcher.addPath(root + "/" + filename); - mapTypes = parser.readCDefineNames(filename, regexList); - if (mapTypes.isEmpty()) - logWarn(QString("Failed to read map type constants from %1").arg(filename)); + QString error; + this->mapTypes = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read map type constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2480,9 +2493,10 @@ bool Project::readMapBattleScenes() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); fileWatcher.addPath(root + "/" + filename); - mapBattleScenes = parser.readCDefineNames(filename, regexList); - if (mapBattleScenes.isEmpty()) - logWarn(QString("Failed to read map battle scene constants from %1").arg(filename)); + QString error; + this->mapBattleScenes = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read map battle scene constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2490,9 +2504,10 @@ bool Project::readWeatherNames() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); fileWatcher.addPath(root + "/" + filename); - weatherNames = parser.readCDefineNames(filename, regexList); - if (weatherNames.isEmpty()) - logWarn(QString("Failed to read weather constants from %1").arg(filename)); + QString error; + this->weatherNames = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read weather constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2503,9 +2518,10 @@ bool Project::readCoordEventWeatherNames() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); fileWatcher.addPath(root + "/" + filename); - coordEventWeatherNames = parser.readCDefineNames(filename, regexList); - if (coordEventWeatherNames.isEmpty()) - logWarn(QString("Failed to read coord event weather constants from %1").arg(filename)); + QString error; + this->coordEventWeatherNames = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read coord event weather constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2516,9 +2532,10 @@ bool Project::readSecretBaseIds() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases); fileWatcher.addPath(root + "/" + filename); - secretBaseIds = parser.readCDefineNames(filename, regexList); - if (secretBaseIds.isEmpty()) - logWarn(QString("Failed to read secret base id constants from '%1'").arg(filename)); + QString error; + this->secretBaseIds = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read secret base id constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2526,9 +2543,10 @@ bool Project::readBgEventFacingDirections() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg); fileWatcher.addPath(root + "/" + filename); - bgEventFacingDirections = parser.readCDefineNames(filename, regexList); - if (bgEventFacingDirections.isEmpty()) - logWarn(QString("Failed to read bg event facing direction constants from %1").arg(filename)); + QString error; + this->bgEventFacingDirections = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read bg event facing direction constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2536,9 +2554,10 @@ bool Project::readTrainerTypes() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types); fileWatcher.addPath(root + "/" + filename); - trainerTypes = parser.readCDefineNames(filename, regexList); - if (trainerTypes.isEmpty()) - logWarn(QString("Failed to read trainer type constants from %1").arg(filename)); + QString error; + this->trainerTypes = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read trainer type constants from '%1': %2").arg(filename).arg(error)); return true; } @@ -2549,13 +2568,14 @@ bool Project::readMetatileBehaviors() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors); fileWatcher.addPath(root + "/" + filename); - QMap defines = parser.readCDefinesByRegex(filename, regexList); - if (defines.isEmpty()) { - // Not having any metatile behavior names is ok (their values will be displayed instead). - // If the user's metatiles can have nonzero values then warn them, as they likely want names. - if (projectConfig.metatileBehaviorMask) - logWarn(QString("Failed to read metatile behaviors from %1.").arg(filename)); - return true; + QString error; + QMap defines = parser.readCDefinesByRegex(filename, regexList, &error); + if (defines.isEmpty() && projectConfig.metatileBehaviorMask) { + // Not having any metatile behavior names is ok (their values will be displayed instead) + // but if the user's metatiles can have nonzero values then warn them, as they likely want names. + QString warning = QString("Failed to read metatile behaviors from '%1'").arg(filename); + if (!error.isEmpty()) warning += QString(": %1").arg(error); + logWarn(warning); } for (auto i = defines.cbegin(), end = defines.cend(); i != end; i++) { @@ -2571,9 +2591,10 @@ bool Project::readSongNames() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs); fileWatcher.addPath(root + "/" + filename); - this->songNames = parser.readCDefineNames(filename, regexList); - if (this->songNames.isEmpty()) - logWarn(QString("Failed to read song names from %1.").arg(filename)); + QString error; + this->songNames = parser.readCDefineNames(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read song names from '%1': %2").arg(filename).arg(error)); // Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically. // The default song should be the first in the list, not the first alphabetically, so save that before sorting. @@ -2586,9 +2607,10 @@ bool Project::readObjEventGfxConstants() { const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events); fileWatcher.addPath(root + "/" + filename); - this->gfxDefines = parser.readCDefinesByRegex(filename, regexList); - if (this->gfxDefines.isEmpty()) - logWarn(QString("Failed to read object event graphics constants from %1.").arg(filename)); + QString error; + this->gfxDefines = parser.readCDefinesByRegex(filename, regexList, &error); + if (!error.isEmpty()) + logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error)); return true; } diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index 6a406072..bef6d9fe 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -27,8 +27,12 @@ void Prefab::loadPrefabs() { QJsonDocument prefabDoc; QString validPath = Project::getExistingFilepath(filepath); - if (validPath.isEmpty() || !parser.tryParseJsonFile(&prefabDoc, validPath)) { - logError(QString("Failed to read prefab data from %1").arg(filepath)); + if (validPath.isEmpty()) + return; + + QString error; + if (!parser.tryParseJsonFile(&prefabDoc, validPath, &error)) { + logError(QString("Failed to read prefab data from %1: %2").arg(filepath).arg(error)); return; } filepath = validPath; diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 21b5c76d..94b7c8c6 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -120,26 +120,20 @@ bool RegionMapEditor::saveRegionMapEntries() { void buildEmeraldDefaults(poryjson::Json &json) { ParseUtil parser; QString emeraldDefault = parser.readTextFile(":/text/region_map_default_emerald.json"); - - QString err; - json = poryjson::Json::parse(emeraldDefault, err); + json = poryjson::Json::parse(emeraldDefault); } void buildRubyDefaults(poryjson::Json &json) { ParseUtil parser; QString emeraldDefault = parser.readTextFile(":/text/region_map_default_ruby.json"); - - QString err; - json = poryjson::Json::parse(emeraldDefault, err); + json = poryjson::Json::parse(emeraldDefault); } void buildFireredDefaults(poryjson::Json &json) { ParseUtil parser; QString fireredDefault = parser.readTextFile(":/text/region_map_default_firered.json"); - - QString err; - json = poryjson::Json::parse(fireredDefault, err); + json = poryjson::Json::parse(fireredDefault); } poryjson::Json RegionMapEditor::buildDefaultJson() { @@ -199,8 +193,7 @@ bool RegionMapEditor::buildConfigDialog() { poryjson::Json::object newJson; poryjson::Json::array mapArr; for (auto item : regionMapList->findItems("*", Qt::MatchWildcard)) { - QString err; - poryjson::Json itemJson = poryjson::Json::parse(item->data(Qt::UserRole).toString(), err); + poryjson::Json itemJson = poryjson::Json::parse(item->data(Qt::UserRole).toString()); mapArr.append(itemJson); } newJson["region_maps"] = mapArr; @@ -213,8 +206,7 @@ bool RegionMapEditor::buildConfigDialog() { connect(regionMapList, &QListWidget::itemDoubleClicked, [this, &rmConfigJsonUpdate, updateMapList, regionMapList](QListWidgetItem *item) { int itemIndex = regionMapList->row(item); - QString err; - poryjson::Json clickedJson = poryjson::Json::parse(item->data(Qt::UserRole).toString(), err); + poryjson::Json clickedJson = poryjson::Json::parse(item->data(Qt::UserRole).toString()); RegionMapPropertiesDialog dialog(this); dialog.setProject(this->project); From 27ec547ac19fd83ca09e5f503f5f3d9634db3e03 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 18 Feb 2025 15:22:42 -0500 Subject: [PATCH 179/364] Fix regression re-enabling the Wild Pokemon tab --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 660ebe4c..02fec046 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -915,7 +915,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { this->ui->mainTabBar->setTabEnabled(MainTab::Events, mapEditingEnabled); this->ui->mainTabBar->setTabEnabled(MainTab::Header, mapEditingEnabled); this->ui->mainTabBar->setTabEnabled(MainTab::Connections, mapEditingEnabled); - this->ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled && editor->project->wildEncountersLoaded); this->ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled); } From 007d11a3372c7e86141235ad85c1d1109efebabb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 10:16:53 -0500 Subject: [PATCH 180/364] Add events overlay to map view (#678) --- forms/mainwindow.ui | 12 +++++ include/config.h | 2 + include/editor.h | 17 ++----- include/mainwindow.h | 1 + src/config.cpp | 3 ++ src/editor.cpp | 112 +++++++++++++++++++------------------------ src/mainwindow.cpp | 42 +++++++++++----- 7 files changed, 100 insertions(+), 89 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 057df05d..7c2d45ba 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2882,6 +2882,7 @@ + @@ -3236,6 +3237,17 @@ Show Dive/Emerge Map + + + true + + + false + + + Show Events in Map View + + true diff --git a/include/config.h b/include/config.h index 6eb84470..7f263da0 100644 --- a/include/config.h +++ b/include/config.h @@ -81,6 +81,7 @@ public: this->projectSettingsTab = 0; this->warpBehaviorWarningDisabled = false; this->eventDeleteWarningDisabled = false; + this->eventOverlayEnabled = false; this->checkForUpdates = true; this->lastUpdateCheckTime = QDateTime(); this->lastUpdateCheckVersion = porymapVersion; @@ -136,6 +137,7 @@ public: int projectSettingsTab; bool warpBehaviorWarningDisabled; bool eventDeleteWarningDisabled; + bool eventOverlayEnabled; bool checkForUpdates; QDateTime lastUpdateCheckTime; QVersionNumber lastUpdateCheckVersion; diff --git a/include/editor.h b/include/editor.h index 0c11a1e8..689d2f4f 100644 --- a/include/editor.h +++ b/include/editor.h @@ -119,6 +119,7 @@ public: void redrawEvents(const QList &events); void redrawEventPixmapItem(DraggablePixmapItem *item); QList getEventPixmapItems(); + qreal getEventOpacity(const Event *event) const; void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); @@ -161,21 +162,11 @@ public: EditAction eventEditAction = EditAction::Select; enum class EditMode { None, Disabled, Metatiles, Collision, Header, Events, Connections, Encounters }; - EditMode editMode = EditMode::None; - void setEditMode(EditMode mode) { this->editMode = mode; } - EditMode getEditMode() { return this->editMode; } + void setEditMode(EditMode editMode); + EditMode getEditMode() const { return this->editMode; } bool getEditingLayout(); - void setEditorView(); - - void setEditingMetatiles(); - void setEditingCollision(); - void setEditingHeader(); - void setEditingEvents(); - void setEditingConnections(); - void setEditingEncounters(); - void setMapEditingButtonsEnabled(bool enabled); int scaleIndex = 2; @@ -211,6 +202,8 @@ private: const QImage collisionPlaceholder = QImage(":/images/collisions_unknown.png"); QPixmap collisionSheetPixmap; + EditMode editMode = EditMode::None; + void clearMap(); void clearMetatileSelector(); void clearMovementPermissionSelector(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 2d065b37..805899a1 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -261,6 +261,7 @@ private slots: void on_checkBox_MirrorConnections_stateChanged(int selected); void on_actionDive_Emerge_Map_triggered(); + void on_actionShow_Events_In_Map_View_triggered(); void on_groupBox_DiveMapOpacity_toggled(bool on); void on_slider_DiveEmergeMapOpacity_valueChanged(int value); void on_slider_DiveMapOpacity_valueChanged(int value); diff --git a/src/config.cpp b/src/config.cpp index a61393ab..b22972a4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -394,6 +394,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->warpBehaviorWarningDisabled = getConfigBool(key, value); } else if (key == "event_delete_warning_disabled") { this->eventDeleteWarningDisabled = getConfigBool(key, value); + } else if (key == "event_overlay_enabled") { + this->eventOverlayEnabled = getConfigBool(key, value); } else if (key == "check_for_updates") { this->checkForUpdates = getConfigBool(key, value); } else if (key == "last_update_check_time") { @@ -479,6 +481,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); + map.insert("event_overlay_enabled", QString::number(this->eventOverlayEnabled)); map.insert("check_for_updates", QString::number(this->checkForUpdates)); map.insert("last_update_check_time", this->lastUpdateCheckTime.toUTC().toString()); map.insert("last_update_check_version", this->lastUpdateCheckVersion.toString()); diff --git a/src/editor.cpp b/src/editor.cpp index a160f462..6a2e0c20 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -113,8 +113,12 @@ bool Editor::getEditingLayout() { return this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision; } -void Editor::setEditorView() { - // based on editMode +void Editor::setEditMode(EditMode editMode) { + // At the moment we can't early return if editMode == this->editMode, because this function also takes care of refreshing the map view. + // The main window relies on this when switching projects (the edit mode will remain the same, but it needs a refresh). + auto oldEditMode = this->editMode; + this->editMode = editMode; + if (!map_item || !collision_item) return; if (!this->layout) return; @@ -132,70 +136,34 @@ void Editor::setEditorView() { break; default: current_view = nullptr; - return; + break; } map_item->setEditsEnabled(this->editMode != EditMode::Connections); map_item->draw(); collision_item->draw(); - current_view->setVisible(true); + if (current_view) current_view->setVisible(true); updateBorderVisibility(); QUndoStack *editStack = this->map ? this->map->editHistory() : nullptr; - bool usesCursor = false; - if (this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision) { - if (this->layout) editStack = &this->layout->editHistory; - usesCursor = true; + bool editingLayout = getEditingLayout(); + if (editingLayout && this->layout) { + editStack = &this->layout->editHistory; } - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(usesCursor); + this->cursorMapTileRect->setActive(editingLayout); this->editGroup.setActiveStack(editStack); + setMapEditingButtonsEnabled(editingLayout); - - if (this->events_group) { - this->events_group->setVisible(this->editMode == EditMode::Events); + if (this->editMode == EditMode::Events || oldEditMode == EditMode::Events) { + // When switching to or from the Events tab the opacity of the events changes. Redraw the events to reflect that change. + redrawAllEvents(); + } + if (this->editMode == EditMode::Events){ + updateWarpEventWarnings(); } - setMapEditingButtonsEnabled(this->editMode != EditMode::Events); -} - -void Editor::setEditingMetatiles() { - this->editMode = EditMode::Metatiles; - - setEditorView(); -} - -void Editor::setEditingCollision() { - this->editMode = EditMode::Collision; - - setEditorView(); -} - -void Editor::setEditingHeader() { - this->editMode = EditMode::Header; - - setEditorView(); -} - -void Editor::setEditingEvents() { - this->editMode = EditMode::Events; - - setEditorView(); - updateWarpEventWarnings(); -} - -void Editor::setEditingConnections() { - this->editMode = EditMode::Connections; - - setEditorView(); -} - -void Editor::setEditingEncounters() { - this->editMode = EditMode::Encounters; - - setEditorView(); } void Editor::setMapEditingButtonsEnabled(bool enabled) { @@ -1486,10 +1454,6 @@ bool Editor::displayMap() { displayMapEvents(); displayMapConnections(); maskNonVisibleConnectionTiles(); - - if (events_group) { - events_group->setVisible(false); - } return true; } @@ -2003,20 +1967,40 @@ QList Editor::getEventPixmapItems() { return list; } +qreal Editor::getEventOpacity(const Event *event) const { + // There are 4 possible opacities for an event's sprite: + // - Off the Events tab, and the event overlay is off (0.0) + // - Off the Events tab, and the event overlay is on (0.5) + // - On the Events tab, and the event has a default sprite (0.7) + // - On the Events tab, and the event has a custom sprite (1.0) + if (this->editMode != EditMode::Events) + return porymapConfig.eventOverlayEnabled ? 0.5 : 0.0; + return event->getUsingSprite() ? 1.0 : 0.7; +} + void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { if (item && item->event && !item->event->getPixmap().isNull()) { - qreal opacity = item->event->getUsingSprite() ? 1.0 : 0.7; - item->setOpacity(opacity); + item->setOpacity(getEventOpacity(item->event)); project->setEventPixmap(item->event, true); item->setPixmap(item->event->getPixmap()); item->setShapeMode(porymapConfig.eventSelectionShapeMode); - if (selected_events && selected_events->contains(item)) { - QImage image = item->pixmap().toImage(); - QPainter painter(&image); - painter.setPen(QColor(255, 0, 255)); - painter.drawRect(0, 0, image.width() - 1, image.height() - 1); - painter.end(); - item->setPixmap(QPixmap::fromImage(image)); + + if (this->editMode == EditMode::Events) { + if (selected_events && selected_events->contains(item)) { + // Draw the selection rectangle + QImage image = item->pixmap().toImage(); + QPainter painter(&image); + painter.setPen(QColor(255, 0, 255)); + painter.drawRect(0, 0, image.width() - 1, image.height() - 1); + painter.end(); + item->setPixmap(QPixmap::fromImage(image)); + } + item->setAcceptedMouseButtons(Qt::AllButtons); + } else { + // Can't interact with event pixmaps outside of event editing mode. + // We could do setEnabled(false), but rather than ignoring the mouse events this + // would reject them, which would prevent painting on the map behind the events. + item->setAcceptedMouseButtons(Qt::NoButton); } item->updatePosition(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3f359d0e..a38b3bd0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -525,17 +525,11 @@ void MainWindow::loadUserSettings() { ui->actionCursor_Tile_Outline->setChecked(porymapConfig.showCursorTile); this->editor->settings->cursorTileRectEnabled = porymapConfig.showCursorTile; - // Border - ui->checkBox_ToggleBorder->setChecked(porymapConfig.showBorder); - // Grid const QSignalBlocker b_Grid(ui->checkBox_ToggleGrid); ui->actionShow_Grid->setChecked(porymapConfig.showGrid); ui->checkBox_ToggleGrid->setChecked(porymapConfig.showGrid); - // Mirror connections - ui->checkBox_MirrorConnections->setChecked(porymapConfig.mirrorConnectingMaps); - // Collision opacity/transparency const QSignalBlocker b_CollisionTransparency(ui->horizontalSlider_CollisionTransparency); this->editor->collisionOpacity = static_cast(porymapConfig.collisionOpacity) / 100; @@ -555,6 +549,10 @@ void MainWindow::loadUserSettings() { ui->horizontalSlider_MetatileZoom->setValue(porymapConfig.metatilesZoom); ui->horizontalSlider_CollisionZoom->setValue(porymapConfig.collisionZoom); + ui->checkBox_MirrorConnections->setChecked(porymapConfig.mirrorConnectingMaps); + ui->checkBox_ToggleBorder->setChecked(porymapConfig.showBorder); + ui->actionShow_Events_In_Map_View->setChecked(porymapConfig.eventOverlayEnabled); + setTheme(porymapConfig.theme); setDivingMapsVisible(porymapConfig.showDiveEmergeMaps); } @@ -1787,14 +1785,20 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) if (index != oldIndex) Scripting::cb_MapViewTabChanged(oldIndex, index); + static const QMap tabIndexToEditMode = { + {MapViewTab::Metatiles, Editor::EditMode::Metatiles}, + {MapViewTab::Collision, Editor::EditMode::Collision}, + {MapViewTab::Prefabs, Editor::EditMode::Metatiles}, + }; + if (tabIndexToEditMode.contains(index)) { + editor->setEditMode(tabIndexToEditMode.value(index)); + } + if (index == MapViewTab::Metatiles) { - editor->setEditingMetatiles(); refreshMetatileViews(); } else if (index == MapViewTab::Collision) { - editor->setEditingCollision(); refreshCollisionSelector(); } else if (index == MapViewTab::Prefabs) { - editor->setEditingMetatiles(); if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) { // User hasn't set up prefabs and hasn't been prompted before. // Ask if they'd like to import the default prefabs file. @@ -1821,19 +1825,26 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) }; ui->mainStackedWidget->setCurrentIndex(tabIndexToStackIndex.value(index)); + static const QMap tabIndexToEditMode = { + // MainTab::Map itself has no edit mode, depends on mapViewTab. + {MainTab::Events, Editor::EditMode::Events}, + {MainTab::Header, Editor::EditMode::Header}, + {MainTab::Connections, Editor::EditMode::Connections}, + {MainTab::WildPokemon, Editor::EditMode::Encounters}, + }; + if (tabIndexToEditMode.contains(index)) { + editor->setEditMode(tabIndexToEditMode.value(index)); + } + if (index == MainTab::Map) { ui->stackedWidget_MapEvents->setCurrentIndex(0); on_mapViewTab_tabBarClicked(ui->mapViewTab->currentIndex()); clickToolButtonFromEditAction(editor->mapEditAction); } else if (index == MainTab::Events) { ui->stackedWidget_MapEvents->setCurrentIndex(1); - editor->setEditingEvents(); clickToolButtonFromEditAction(editor->eventEditAction); } else if (index == MainTab::Connections) { - editor->setEditingConnections(); ui->graphicsView_Connections->setFocus(); // Avoid opening tab with focus on something editable - } else if (index == MainTab::WildPokemon) { - editor->setEditingEncounters(); } if (!editor->map) return; @@ -1883,6 +1894,11 @@ void MainWindow::on_actionCursor_Tile_Outline_triggered() } } +void MainWindow::on_actionShow_Events_In_Map_View_triggered() { + porymapConfig.eventOverlayEnabled = ui->actionShow_Events_In_Map_View->isChecked(); + this->editor->redrawAllEvents(); +} + void MainWindow::on_actionShow_Grid_triggered() { this->editor->toggleGrid(ui->actionShow_Grid->isChecked()); } From 559f2ae6da238c8ffefc1a7b20f153ea2c28f767 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 10:27:26 -0500 Subject: [PATCH 181/364] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 736e6217..c806738b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add a search button to the `Wild Pokémon` tab that shows the encounter data for a species across all maps. - Add charts to the `Wild Pokémon` tab that show species and level distributions for the current map. - Add options for customizing the map grid under `View -> Grid Settings`. +- Add an option to display Event sprites while editing the map. - Add an option to display a dividing line between tilesets in the Tileset Editor. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. @@ -40,6 +41,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Map names and ``MAP_NAME`` constants are no longer required to match. - Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. - Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8. +- The prompt to reload the project when a file has changed will now only appear when Porymap is the active application. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. From 43d5e32b96d7c5da57cff8bf6884ab01e65d58e9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 11:07:11 -0500 Subject: [PATCH 182/364] Fix Save All after layout split changes --- include/editor.h | 6 +++--- include/mainwindow.h | 1 + include/project.h | 6 +++--- src/editor.cpp | 39 ++++++++++++++++++++++----------------- src/mainwindow.cpp | 30 +++++++++++++++++------------- src/project.cpp | 20 ++++++++++++++------ 6 files changed, 60 insertions(+), 42 deletions(-) diff --git a/include/editor.h b/include/editor.h index 689d2f4f..09eb53ad 100644 --- a/include/editor.h +++ b/include/editor.h @@ -57,9 +57,8 @@ public: GridSettings gridSettings; void setProject(Project * project); - void save(); - void saveProject(); - void saveUiFields(); + void saveAll(); + void saveCurrent(); void saveEncounterTabData(); void closeProject(); @@ -204,6 +203,7 @@ private: EditMode editMode = EditMode::None; + void save(bool currentOnly); void clearMap(); void clearMetatileSelector(); void clearMovementPermissionSelector(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 805899a1..47f278a9 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -175,6 +175,7 @@ private slots: void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); void on_action_Save_Project_triggered(); + void save(bool all = true); void openWarpMap(QString map_name, int event_id, Event::Group event_group); diff --git a/include/project.h b/include/project.h index f3a1093b..e5fad3ba 100644 --- a/include/project.h +++ b/include/project.h @@ -167,13 +167,13 @@ public: void loadTilesetMetatileLabels(Tileset*); void readTilesetPaths(Tileset* tileset); + void saveAll(); + void saveGlobalData(); void saveLayout(Layout *); void saveLayoutBlockdata(Layout *); void saveLayoutBorder(Layout *); void writeBlockdata(QString, const Blockdata &); - void saveAllMaps(); - void saveMap(Map *); - void saveAllDataStructures(); + void saveMap(Map *map, bool skipLayout = false); void saveConfig(); void saveMapLayouts(); void saveMapGroups(); diff --git a/src/editor.cpp b/src/editor.cpp index 6a2e0c20..9912391b 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -69,28 +69,30 @@ Editor::~Editor() closeProject(); } -void Editor::saveProject() { - if (project) { - saveUiFields(); - project->saveAllMaps(); - project->saveAllDataStructures(); - } +void Editor::saveCurrent() { + save(true); } -void Editor::save() { - if (this->project && this->map) { - saveUiFields(); - this->project->saveMap(this->map); - this->project->saveAllDataStructures(); - } - else if (this->project && this->layout) { - this->project->saveLayout(this->layout); - this->project->saveAllDataStructures(); - } +void Editor::saveAll() { + save(false); } -void Editor::saveUiFields() { +void Editor::save(bool currentOnly) { + if (!this->project) + return; + saveEncounterTabData(); + + if (currentOnly) { + if (this->map) { + this->project->saveMap(this->map); + } else if (this->layout) { + this->project->saveLayout(this->layout); + } + this->project->saveGlobalData(); + } else { + this->project->saveAll(); + } } void Editor::setProject(Project * project) { @@ -651,6 +653,9 @@ void Editor::configureEncounterJSON(QWidget *window) { } void Editor::saveEncounterTabData() { + if (!this->map || !this->project) + return; + // This function does not save to disk so it is safe to use before user clicks Save. QStackedWidget *stack = ui->stackedWidget_WildMons; QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a38b3bd0..1cddb3cc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1307,12 +1307,6 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { addNewEvent(Event::Type::HealLocation); } - // TODO: Creating a new map shouldn't be automatically saved. - // For one, it takes away the option to discard the new map. - // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. - editor->project->saveMap(newMap); - editor->project->saveAllDataStructures(); - // Add new map to the map lists this->mapGroupModel->insertMapItem(newMap->name(), groupName); this->mapLocationModel->insertMapItem(newMap->name(), newMap->header()->location()); @@ -1328,7 +1322,12 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } - userSetMap(newMap->name()); + if (userSetMap(newMap->name())) { + // TODO: Creating a new map shouldn't be automatically saved. + // For one, it takes away the option to discard the new map. + // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. + save(true); + } } // Called any time a new layout is created (including as a byproduct of creating a new map) @@ -1551,14 +1550,19 @@ void MainWindow::updateMapList() { } void MainWindow::on_action_Save_Project_triggered() { - editor->saveProject(); - updateWindowTitle(); - updateMapList(); - saveGlobalConfigs(); + save(false); } void MainWindow::on_action_Save_triggered() { - editor->save(); + save(true); +} + +void MainWindow::save(bool currentOnly) { + if (currentOnly) { + this->editor->saveCurrent(); + } else { + this->editor->saveAll(); + } updateWindowTitle(); updateMapList(); saveGlobalConfigs(); @@ -3008,7 +3012,7 @@ bool MainWindow::closeProject() { auto reply = msgBox.exec(); if (reply == QMessageBox::Yes) { - editor->saveProject(); + save(); } else if (reply == QMessageBox::No) { logWarn("Closing project with unsaved changes."); } else if (reply == QMessageBox::Cancel) { diff --git a/src/project.cpp b/src/project.cpp index 38b1810b..3a397926 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1155,12 +1155,17 @@ void Project::writeBlockdata(QString path, const Blockdata &blockdata) { } } -void Project::saveAllMaps() { - for (auto *map : mapCache.values()) - saveMap(map); +void Project::saveAll() { + for (auto map : this->mapCache) { + saveMap(map, true); // Avoid double-saving the layouts + } + for (auto layout : this->mapLayouts) { + saveLayout(layout); + } + saveGlobalData(); } -void Project::saveMap(Map *map) { +void Project::saveMap(Map *map, bool skipLayout) { // Create/Modify a few collateral files for brand new maps. const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); @@ -1289,12 +1294,15 @@ void Project::saveMap(Map *map) { jsonDoc.dump(&mapFile); mapFile.close(); - saveLayout(map->layout()); + if (!skipLayout) saveLayout(map->layout()); map->setClean(); } void Project::saveLayout(Layout *layout) { + if (!layout || !layout->loaded) + return; + saveLayoutBorder(layout); saveLayoutBlockdata(layout); @@ -1317,7 +1325,7 @@ void Project::updateLayout(Layout *layout) { } } -void Project::saveAllDataStructures() { +void Project::saveGlobalData() { saveMapLayouts(); saveMapGroups(); saveRegionMapSections(); From 1ab8b830d842e06c3550795b14ca2f4b16edc2c7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 11:39:45 -0500 Subject: [PATCH 183/364] Add in-game reload message --- include/config.h | 2 ++ src/config.cpp | 3 +++ src/mainwindow.cpp | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/include/config.h b/include/config.h index 7f263da0..f92566e2 100644 --- a/include/config.h +++ b/include/config.h @@ -87,6 +87,7 @@ public: this->lastUpdateCheckVersion = porymapVersion; this->rateLimitTimes.clear(); this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; + this->shownInGameReloadMessage = false; } void addRecentProject(QString project); void setRecentProjects(QStringList projects); @@ -146,6 +147,7 @@ public: QByteArray wildMonChartGeometry; QByteArray newMapDialogGeometry; QByteArray newLayoutDialogGeometry; + bool shownInGameReloadMessage; protected: virtual QString getConfigFilepath() override; diff --git a/src/config.cpp b/src/config.cpp index b22972a4..d0ac2115 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -422,6 +422,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } else { logWarn(QString("Invalid config value for %1: '%2'. Must be 'mask' or 'bounding_rect'.").arg(key).arg(value)); } + } else if (key == "shown_in_game_reload_message") { + this->shownInGameReloadMessage = getConfigBool(key, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -492,6 +494,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("rate_limit_time/" + i.key().toString(), time.toUTC().toString()); } map.insert("event_selection_shape_mode", (this->eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) ? "mask" : "bounding_rect"); + map.insert("shown_in_game_reload_message", this->shownInGameReloadMessage ? "1" : "0"); return map; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1cddb3cc..f541dace 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1565,6 +1565,15 @@ void MainWindow::save(bool currentOnly) { } updateWindowTitle(); updateMapList(); + + if (!porymapConfig.shownInGameReloadMessage) { + // Show a one-time warning that the user may need to reload their map to see their new changes. + static const QString message = QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, " + "the changes may not appear until you leave the map and return."); + InfoMessage::show(message, this); + porymapConfig.shownInGameReloadMessage = true; + } + saveGlobalConfigs(); } From c987cb322df76ac3a705546cff10788b16784168 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 11:47:13 -0500 Subject: [PATCH 184/364] Fix default save argument --- include/mainwindow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 47f278a9..6128cc47 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -175,7 +175,7 @@ private slots: void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); void on_action_Save_Project_triggered(); - void save(bool all = true); + void save(bool currentOnly = false); void openWarpMap(QString map_name, int event_id, Event::Group event_group); From a2aa20ec460229f080609771ede25038ef3df5e8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 13:18:12 -0500 Subject: [PATCH 185/364] Generalize event loading --- include/core/events.h | 6 +-- include/project.h | 1 + src/core/events.cpp | 86 ++++++++++++------------------------- src/editor.cpp | 2 +- src/mainwindow.cpp | 8 ++-- src/project.cpp | 98 +++++++++++++------------------------------ 6 files changed, 65 insertions(+), 136 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index bdf9d5a2..265ec541 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -186,9 +186,9 @@ public: void setIdName(QString newIdName) { this->idName = newIdName; } QString getIdName() const { return this->idName; } - static QString eventGroupToString(Event::Group group); - static QString eventTypeToString(Event::Type type); - static Event::Type eventTypeFromString(QString type); + static QString groupToString(Event::Group group); + static QString typeToString(Event::Type type); + static Event::Type typeFromString(QString type); static void clearIcons(); static void setIcons(); diff --git a/include/project.h b/include/project.h index f3a1093b..97b9a698 100644 --- a/include/project.h +++ b/include/project.h @@ -157,6 +157,7 @@ public: void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); + bool loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); diff --git a/src/core/events.cpp b/src/core/events.cpp index 87421035..648fb760 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -75,70 +75,36 @@ void Event::modify() { this->map->modify(); } -QString Event::eventGroupToString(Event::Group group) { - switch (group) { - case Event::Group::Object: - return "Object"; - case Event::Group::Warp: - return "Warp"; - case Event::Group::Coord: - return "Trigger"; - case Event::Group::Bg: - return "BG"; - case Event::Group::Heal: - return "Heal Location"; - default: - return ""; - } +const QMap groupToStringMap = { + {Event::Group::Object, "Object"}, + {Event::Group::Warp, "Warp"}, + {Event::Group::Coord, "Trigger"}, + {Event::Group::Bg, "BG"}, + {Event::Group::Heal, "Heal Location"}, +}; + +QString Event::groupToString(Event::Group group) { + return groupToStringMap.value(group); } -QString Event::eventTypeToString(Event::Type type) { - switch (type) { - case Event::Type::Object: - return "event_object"; - case Event::Type::CloneObject: - return "event_clone_object"; - case Event::Type::Warp: - return "event_warp"; - case Event::Type::Trigger: - return "event_trigger"; - case Event::Type::WeatherTrigger: - return "event_weather_trigger"; - case Event::Type::Sign: - return "event_sign"; - case Event::Type::HiddenItem: - return "event_hidden_item"; - case Event::Type::SecretBase: - return "event_secret_base"; - case Event::Type::HealLocation: - return "event_heal_location"; - default: - return ""; - } +const QMap typeToStringMap = { + {Event::Type::Object, "object"}, + {Event::Type::CloneObject, "clone_object"}, + {Event::Type::Warp, "warp"}, + {Event::Type::Trigger, "trigger"}, + {Event::Type::WeatherTrigger, "weather"}, + {Event::Type::Sign, "sign"}, + {Event::Type::HiddenItem, "hidden_item"}, + {Event::Type::SecretBase, "secret_base"}, + {Event::Type::HealLocation, "heal_location"}, +}; + +QString Event::typeToString(Event::Type type) { + return typeToStringMap.value(type); } -Event::Type Event::eventTypeFromString(QString type) { - if (type == "event_object") { - return Event::Type::Object; - } else if (type == "event_clone_object") { - return Event::Type::CloneObject; - } else if (type == "event_warp") { - return Event::Type::Warp; - } else if (type == "event_trigger") { - return Event::Type::Trigger; - } else if (type == "event_weather_trigger") { - return Event::Type::WeatherTrigger; - } else if (type == "event_sign") { - return Event::Type::Sign; - } else if (type == "event_hidden_item") { - return Event::Type::HiddenItem; - } else if (type == "event_secret_base") { - return Event::Type::SecretBase; - } else if (type == "event_heal_location") { - return Event::Type::HealLocation; - } else { - return Event::Type::None; - } +Event::Type Event::typeFromString(QString type) { + return typeToStringMap.key(type, Event::Type::None); } void Event::loadPixmap(Project *) { diff --git a/src/editor.cpp b/src/editor.cpp index 6a2e0c20..f654b805 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2092,7 +2092,7 @@ void Editor::duplicateSelectedEvents() { Event *original = selected_events->at(i)->event; Event::Type eventType = original->getEventType(); if (eventLimitReached(eventType)) { - logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::eventTypeToString(eventType))); + logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::typeToString(eventType))); continue; } Event *duplicate = original->duplicate(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a38b3bd0..785c77af 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1039,7 +1039,7 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } } // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::eventGroupToString(event_group)).arg(event_id).arg(map_name)); + logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name)); } void MainWindow::displayMapProperties() { @@ -1639,7 +1639,7 @@ void MainWindow::copy() { for (auto item : events) { Event *event = item->event; OrderedJson::object eventContainer; - eventContainer["event_type"] = Event::eventTypeToString(event->getEventType()); + eventContainer["event_type"] = Event::typeToString(event->getEventType()); OrderedJson::object eventJson = event->buildEventJson(editor->project); eventContainer["event"] = eventJson; eventsArray.append(eventContainer); @@ -1751,7 +1751,7 @@ void MainWindow::paste() { for (QJsonValue event : events) { // paste the event to the map const QString typeString = event["event_type"].toString(); - Event::Type type = Event::eventTypeFromString(typeString); + Event::Type type = Event::typeFromString(typeString); if (this->editor->eventLimitReached(type)) { logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString)); @@ -2009,7 +2009,7 @@ void MainWindow::addNewEvent(Event::Type type) { void MainWindow::tryAddEventTab(QWidget * tab) { auto group = getEventGroupFromTabWidget(tab); if (editor->map->getNumEvents(group)) - ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::eventGroupToString(group))); + ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::groupToString(group))); } void MainWindow::displayEventTabs() { diff --git a/src/project.cpp b/src/project.cpp index 38b1810b..21dc5199 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -203,6 +203,21 @@ bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { return true; } +bool Project::loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType) { + QString typeString = ParseUtil::jsonToQString(json["type"]); + Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromString(typeString); + Event* event = Event::create(type); + if (!event) { + return false; + } + if (!event->loadFromJson(json, this)) { + delete event; + return false; + } + map->addEvent(event); + return true; +} + bool Project::loadMapData(Map* map) { if (!map->isPersistedToFile()) { return true; @@ -241,75 +256,22 @@ bool Project::loadMapData(Map* map) { // Events map->resetEvents(); - QJsonArray objectEventsArr = mapObj["object_events"].toArray(); - for (int i = 0; i < objectEventsArr.size(); i++) { - QJsonObject event = objectEventsArr[i].toObject(); - // If clone objects are not enabled then no type field is present - QString type = projectConfig.eventCloneObjectEnabled ? ParseUtil::jsonToQString(event["type"]) : "object"; - if (type.isEmpty() || type == "object") { - ObjectEvent *object = new ObjectEvent(); - object->loadFromJson(event, this); - map->addEvent(object); - } else if (type == "clone") { - CloneObjectEvent *clone = new CloneObjectEvent(); - if (clone->loadFromJson(event, this)) { - map->addEvent(clone); + static const QMap defaultEventTypes = { + // Map of the expected keys for each event group, and the default type of that group. + // If the default type is Type::None then each event must specify its type, or its an error. + {"object_events", Event::Type::Object}, + {"warp_events", Event::Type::Warp}, + {"coord_events", Event::Type::None}, + {"bg_events", Event::Type::None}, + }; + for (auto i = defaultEventTypes.constBegin(); i != defaultEventTypes.constEnd(); i++) { + QString eventGroupKey = i.key(); + Event::Type defaultType = i.value(); + const QJsonArray eventsJsonArr = mapObj[eventGroupKey].toArray(); + for (int i = 0; i < eventsJsonArr.size(); i++) { + if (!loadMapEvent(map, eventsJsonArr.at(i).toObject(), defaultType)) { + logError(QString("Failed to load event for %1, in %2 at index %3.").arg(map->name()).arg(eventGroupKey).arg(i)); } - else { - delete clone; - } - } else { - logError(QString("Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'.").arg(map->name()).arg(i).arg(type)); - } - } - - QJsonArray warpEventsArr = mapObj["warp_events"].toArray(); - for (int i = 0; i < warpEventsArr.size(); i++) { - QJsonObject event = warpEventsArr[i].toObject(); - WarpEvent *warp = new WarpEvent(); - if (warp->loadFromJson(event, this)) { - map->addEvent(warp); - } - else { - delete warp; - } - } - - QJsonArray coordEventsArr = mapObj["coord_events"].toArray(); - for (int i = 0; i < coordEventsArr.size(); i++) { - QJsonObject event = coordEventsArr[i].toObject(); - QString type = ParseUtil::jsonToQString(event["type"]); - if (type == "trigger") { - TriggerEvent *coord = new TriggerEvent(); - coord->loadFromJson(event, this); - map->addEvent(coord); - } else if (type == "weather") { - WeatherTriggerEvent *coord = new WeatherTriggerEvent(); - coord->loadFromJson(event, this); - map->addEvent(coord); - } else { - logError(QString("Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'.").arg(map->name()).arg(i).arg(type)); - } - } - - QJsonArray bgEventsArr = mapObj["bg_events"].toArray(); - for (int i = 0; i < bgEventsArr.size(); i++) { - QJsonObject event = bgEventsArr[i].toObject(); - QString type = ParseUtil::jsonToQString(event["type"]); - if (type == "sign") { - SignEvent *bg = new SignEvent(); - bg->loadFromJson(event, this); - map->addEvent(bg); - } else if (type == "hidden_item") { - HiddenItemEvent *bg = new HiddenItemEvent(); - bg->loadFromJson(event, this); - map->addEvent(bg); - } else if (type == "secret_base") { - SecretBaseEvent *bg = new SecretBaseEvent(); - bg->loadFromJson(event, this); - map->addEvent(bg); - } else { - logError(QString("Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'.").arg(map->name()).arg(i).arg(type)); } } From 7fc985fc1dc7071473ccb3ae31a5aa5ca96e15f7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 13:25:21 -0500 Subject: [PATCH 186/364] Recognize local_id field --- src/core/events.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/events.cpp b/src/core/events.cpp index 648fb760..3d2ef9d2 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -155,6 +155,7 @@ Event *ObjectEvent::duplicate() const { copy->setX(this->getX()); copy->setY(this->getY()); copy->setElevation(this->getElevation()); + copy->setIdName(this->getIdName()); copy->setGfx(this->getGfx()); copy->setMovement(this->getMovement()); copy->setRadiusX(this->getRadiusX()); @@ -182,6 +183,9 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { if (projectConfig.eventCloneObjectEnabled) { objectJson["type"] = "object"; } + QString idName = this->getIdName(); + if (!idName.isEmpty()) + objectJson["local_id"] = idName; objectJson["graphics_id"] = this->getGfx(); objectJson["x"] = this->getX(); objectJson["y"] = this->getY(); @@ -202,6 +206,7 @@ bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"])); + this->setIdName(ParseUtil::jsonToQString(json["local_id"])); this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); this->setMovement(ParseUtil::jsonToQString(json["movement_type"])); this->setRadiusX(ParseUtil::jsonToInt(json["movement_range_x"])); @@ -229,6 +234,7 @@ void ObjectEvent::setDefaultValues(Project *project) { } const QSet expectedObjectFields = { + "local_id", "graphics_id", "elevation", "movement_type", @@ -334,6 +340,7 @@ Event *CloneObjectEvent::duplicate() const { copy->setX(this->getX()); copy->setY(this->getY()); copy->setElevation(this->getElevation()); + copy->setIdName(this->getIdName()); copy->setGfx(this->getGfx()); copy->setTargetID(this->getTargetID()); copy->setTargetMap(this->getTargetMap()); @@ -354,6 +361,9 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { OrderedJson::object cloneJson; cloneJson["type"] = "clone"; + QString idName = this->getIdName(); + if (!idName.isEmpty()) + cloneJson["local_id"] = idName; cloneJson["graphics_id"] = this->getGfx(); cloneJson["x"] = this->getX(); cloneJson["y"] = this->getY(); @@ -368,6 +378,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); + this->setIdName(ParseUtil::jsonToQString(json["local_id"])); this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); @@ -390,6 +401,7 @@ void CloneObjectEvent::setDefaultValues(Project *project) { const QSet expectedCloneObjectFields = { "type", + "local_id", "graphics_id", "target_local_id", "target_map", From ac8db41299d04a88f41c3c25b7d882d87be0c5b8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 16 Feb 2025 15:31:20 -0500 Subject: [PATCH 187/364] Add event group limit --- forms/projectsettingseditor.ui | 23 +++++++++ include/config.h | 2 + include/editor.h | 5 +- include/mainwindow.h | 1 - include/project.h | 5 +- src/config.cpp | 3 ++ src/editor.cpp | 81 +++++++++++++++++++------------- src/mainwindow.cpp | 49 +++++-------------- src/project.cpp | 19 +++++--- src/ui/projectsettingseditor.cpp | 3 ++ 10 files changed, 107 insertions(+), 84 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index da006eff..a088d87e 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1341,6 +1341,29 @@ + + + + + + + Maximum Events per Event group + + + + + + + <html><head/><body><p>Maps cannot have more than this number of events in each event group. Object events are additionally limited by 'define_obj_event_count' on the Identifiers tab.</p></body></html> + + + 1 + + + + + + diff --git a/include/config.h b/include/config.h index 7f263da0..d0c2ff9f 100644 --- a/include/config.h +++ b/include/config.h @@ -319,6 +319,7 @@ public: this->unusedTileNormal = 0x3014; this->unusedTileCovered = 0x0000; this->unusedTileSplit = 0x0000; + this->maxEventsPerGroup = 255; this->identifiers.clear(); this->readKeys.clear(); } @@ -388,6 +389,7 @@ public: int collisionSheetWidth; int collisionSheetHeight; QList warpBehaviors; + int maxEventsPerGroup; protected: virtual QString getConfigFilepath() override; diff --git a/include/editor.h b/include/editor.h index 689d2f4f..e2e09c42 100644 --- a/include/editor.h +++ b/include/editor.h @@ -110,9 +110,9 @@ public: DraggablePixmapItem *addEventPixmapItem(Event *event); void removeEventPixmapItem(Event *event); - bool eventLimitReached(Map *, Event::Type); + bool canAddEvents(const QList &events); void selectMapEvent(DraggablePixmapItem *item, bool toggle = false); - DraggablePixmapItem *addNewEvent(Event::Type type); + Event *addNewEvent(Event::Type type); void updateSelectedEvents(); void duplicateSelectedEvents(); void redrawAllEvents(); @@ -185,7 +185,6 @@ public: void shouldReselectEvents(); void scaleMapView(int); static void openInTextEditor(const QString &path, int lineNum = 0); - bool eventLimitReached(Event::Type type); void setCollisionGraphics(); public slots: diff --git a/include/mainwindow.h b/include/mainwindow.h index 805899a1..412362c5 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -218,7 +218,6 @@ private slots: void on_actionMove_triggered(); void on_actionMap_Shift_triggered(); - void addNewEvent(Event::Type type); void tryAddEventTab(QWidget * tab); void displayEventTabs(); void updateSelectedEvents(); diff --git a/include/project.h b/include/project.h index 97b9a698..c2707063 100644 --- a/include/project.h +++ b/include/project.h @@ -245,7 +245,7 @@ public: static int getMapDataSize(int width, int height); static bool mapDimensionsValid(int width, int height); bool calculateDefaultMapSize(); - static int getMaxObjectEvents(); + int getMaxEvents(Event::Group group); static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); @@ -263,6 +263,8 @@ private: void ignoreWatchedFileTemporarily(QString filepath); void recordFileChange(const QString &filepath); + int maxEventsPerGroup; + int maxObjectEvents; static int num_tiles_primary; static int num_tiles_total; static int num_metatiles_primary; @@ -270,7 +272,6 @@ private: static int num_pals_total; static int max_map_data_size; static int default_map_dimension; - static int max_object_events; signals: void fileChanged(const QString &filepath); diff --git a/src/config.cpp b/src/config.cpp index b22972a4..89ab50f9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -803,6 +803,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); for (auto s : behaviorList) this->warpBehaviors.append(getConfigUint32(key, s)); + } else if (key == "max_events_per_group") { + this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -898,6 +900,7 @@ QMap ProjectConfig::getKeyValueMap() { for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); map.insert("warp_behaviors", warpBehaviorStrs.join(",")); + map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup)); return map; } diff --git a/src/editor.cpp b/src/editor.cpp index f654b805..a7ce2390 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1330,17 +1330,13 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } else { // Left-clicking while in paint mode will add a new event of the // type of the first currently selected events. - // Disallow adding heal locations, deleting them is not possible yet Event::Type eventType = Event::Type::Object; if (this->selected_events->size() > 0) eventType = this->selected_events->first()->event->getEventType(); - DraggablePixmapItem *newEvent = addNewEvent(eventType); - if (newEvent) { - newEvent->move(pos.x(), pos.y()); - emit eventsChanged(); - selectMapEvent(newEvent); - } + Event* event = addNewEvent(eventType); + if (event && event->getPixmapItem()) + event->getPixmapItem()->moveTo(pos); } } else if (eventEditAction == EditAction::Select) { // do nothing here, at least for now @@ -2083,47 +2079,66 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { } } -void Editor::duplicateSelectedEvents() { - if (!selected_events || !selected_events->length() || !map || !current_view || this->getEditingLayout()) - return; +bool Editor::canAddEvents(const QList &events) { + if (!this->project || !this->map) + return false; - QList selectedEvents; - for (int i = 0; i < selected_events->length(); i++) { - Event *original = selected_events->at(i)->event; - Event::Type eventType = original->getEventType(); - if (eventLimitReached(eventType)) { - logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::typeToString(eventType))); - continue; + QMap newEventCounts; + for (const auto &event : events) { + Event::Group group = event->getEventGroup(); + int maxEvents = this->project->getMaxEvents(group); + if (this->map->getNumEvents(group) + newEventCounts[group]++ >= maxEvents) { + return false; } - Event *duplicate = original->duplicate(); - duplicate->setX(duplicate->getX() + 1); - duplicate->setY(duplicate->getY() + 1); - selectedEvents.append(duplicate); } - map->commit(new EventDuplicate(this, map, selectedEvents)); + return true; } -DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { - if (!project || !map || eventLimitReached(type)) +void Editor::duplicateSelectedEvents() { + if (!selected_events || !selected_events->length() || !project || !map || !current_view || this->getEditingLayout()) + return; + + QList duplicatedEvents; + for (int i = 0; i < selected_events->length(); i++) { + duplicatedEvents.append(selected_events->at(i)->event->duplicate()); + } + if (!canAddEvents(duplicatedEvents)) { + WarningMessage::show(QStringLiteral("Unable to duplicate, the maximum number of events would be exceeded."), ui->graphicsView_Map); + qDeleteAll(duplicatedEvents); + return; + } + this->map->commit(new EventDuplicate(this, this->map, duplicatedEvents)); +} + +Event *Editor::addNewEvent(Event::Type type) { + if (!this->project || !this->map) return nullptr; + Event::Group group = Event::typeToGroup(type); + int maxEvents = this->project->getMaxEvents(group); + if (this->map->getNumEvents(group) >= maxEvents) { + WarningMessage::show(QString("The maximum number of %1 events (%2) has been reached.").arg(Event::groupToString(group)).arg(maxEvents), ui->graphicsView_Map); + return nullptr; + } + Event *event = Event::create(type); if (!event) return nullptr; event->setMap(this->map); event->setDefaultValues(this->project); - map->commit(new EventCreate(this, map, event)); - return event->getPixmapItem(); -} -// Currently only object events have an explicit limit -bool Editor::eventLimitReached(Event::Type event_type) { - if (project && map) { - if (Event::typeToGroup(event_type) == Event::Group::Object) - return map->getNumEvents(Event::Group::Object) >= project->getMaxObjectEvents(); + // This will add the event to the map, create the event pixmap item, and select the event. + this->map->commit(new EventCreate(this, this->map, event)); + + auto pixmapItem = event->getPixmapItem(); + if (pixmapItem) { + auto halfSize = ui->graphicsView_Map->size() / 2; + auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height()); + pixmapItem->moveTo(Metatile::coordFromPixmapCoord(centerPos)); } - return false; + + return event; } void Editor::deleteSelectedEvents() { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 785c77af..5c7e5cdb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -264,8 +264,6 @@ void MainWindow::initCustomUI() { } void MainWindow::initExtraSignals() { - // other signals - connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this, &MainWindow::addNewEvent); connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged); // Change pages on wild encounter groups @@ -343,6 +341,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); + connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); this->loadUserSettings(); @@ -1304,7 +1303,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); if (newMap->needsHealLocation()) { - addNewEvent(Event::Type::HealLocation); + this->editor->addNewEvent(Event::Type::HealLocation); } // TODO: Creating a new map shouldn't be automatically saved. @@ -1750,14 +1749,7 @@ void MainWindow::paste() { QJsonArray events = pasteObject["events"].toArray(); for (QJsonValue event : events) { // paste the event to the map - const QString typeString = event["event_type"].toString(); - Event::Type type = Event::typeFromString(typeString); - - if (this->editor->eventLimitReached(type)) { - logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString)); - continue; - } - + Event::Type type = Event::typeFromString(event["event_type"].toString()); Event *pasteEvent = Event::create(type); if (!pasteEvent) continue; @@ -1766,12 +1758,16 @@ void MainWindow::paste() { pasteEvent->setMap(this->editor->map); newEvents.append(pasteEvent); } + if (newEvents.empty()) + return; - if (!newEvents.empty()) { - editor->map->commit(new EventPaste(this->editor, editor->map, newEvents)); - updateEvents(); + if (!this->editor->canAddEvents(newEvents)) { + WarningMessage::show(QStringLiteral("Unable to paste, the maximum number of events would be exceeded."), this); + qDeleteAll(newEvents); + return; } - + this->editor->map->commit(new EventPaste(this->editor, this->editor->map, newEvents)); + updateEvents(); break; } } @@ -1983,29 +1979,6 @@ void MainWindow::resetMapViewScale() { editor->scaleMapView(0); } -void MainWindow::addNewEvent(Event::Type type) { - if (editor && editor->project) { - DraggablePixmapItem *item = editor->addNewEvent(type); - if (item) { - auto halfSize = ui->graphicsView_Map->size() / 2; - auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height()); - item->moveTo(Metatile::coordFromPixmapCoord(centerPos)); - updateEvents(); - editor->selectMapEvent(item); - } else { - WarningMessage msgBox(QStringLiteral("Failed to add new event."), this); - if (Event::typeToGroup(type) == Event::Group::Object) { - msgBox.setInformativeText(QString("The limit for object events (%1) has been reached.\n\n" - "This limit can be adjusted with %2 in '%3'.") - .arg(editor->project->getMaxObjectEvents()) - .arg(projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count)) - .arg(projectConfig.getFilePath(ProjectFilePath::constants_global))); - } - msgBox.exec(); - } - } -} - void MainWindow::tryAddEventTab(QWidget * tab) { auto group = getEventGroupFromTabWidget(tab); if (editor->map->getNumEvents(group)) diff --git a/src/project.cpp b/src/project.cpp index 21dc5199..b25b3776 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -30,7 +30,6 @@ int Project::num_pals_primary = 6; int Project::num_pals_total = 13; int Project::max_map_data_size = 10240; // 0x2800 int Project::default_map_dimension = 20; -int Project::max_object_events = 64; Project::Project(QObject *parent) : QObject(parent) @@ -2578,21 +2577,22 @@ bool Project::readMiscellaneousConstants() { fileWatcher.addPath(root + "/" + filename); QMap defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); + this->maxObjectEvents = 64; // Default value auto it = defines.find(maxObjectEventsName); if (it != defines.end()) { if (it.value() > 0) { - Project::max_object_events = it.value(); + this->maxObjectEvents = it.value(); } else { logWarn(QString("Value for '%1' is %2, must be greater than 0. Using default (%3) instead.") .arg(maxObjectEventsName) .arg(it.value()) - .arg(Project::max_object_events)); + .arg(this->maxObjectEvents)); } } else { logWarn(QString("Value for '%1' not found. Using default (%2) instead.") .arg(maxObjectEventsName) - .arg(Project::max_object_events)); + .arg(this->maxObjectEvents)); } return true; @@ -2943,9 +2943,14 @@ bool Project::calculateDefaultMapSize(){ return true; } -int Project::getMaxObjectEvents() -{ - return Project::max_object_events; +// Object events have their own limit specified by ProjectIdentifier::define_obj_event_count. +// The default value for this is 64. All events (object events included) are also limited by +// the data types of the event counters in the project. This would normally be u8, so the limit is 255. +// We let the users tell us this limit in case they change these data types. +int Project::getMaxEvents(Event::Group group) { + if (group == Event::Group::Object) + return qMin(this->maxObjectEvents, projectConfig.maxEventsPerGroup); + return projectConfig.maxEventsPerGroup; } QString Project::getEmptyMapDefineName() { diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 1250e8dd..86614482 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -135,6 +135,7 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue); ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue); ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue); + ui->spinBox_MaxEvents->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -464,6 +465,7 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_UnusedTileNormal->setValue(projectConfig.unusedTileNormal); ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); + ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -538,6 +540,7 @@ void ProjectSettingsEditor::save() { projectConfig.unusedTileNormal = ui->spinBox_UnusedTileNormal->value(); projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); + projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value(); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); From 88f5a90b2feb097b5a4a464364ad948e28cba80b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 16 Feb 2025 18:03:24 -0500 Subject: [PATCH 188/364] Convert selected_events from DraggablePixmapItem to Event --- include/core/map.h | 1 + include/editor.h | 7 ++- include/mainwindow.h | 2 +- src/core/editcommands.cpp | 21 ++------ src/core/map.cpp | 4 ++ src/editor.cpp | 91 +++++++++++++--------------------- src/mainwindow.cpp | 72 +++++++++++---------------- src/ui/draggablepixmapitem.cpp | 18 +++---- 8 files changed, 86 insertions(+), 130 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index b223536d..f68b4a2b 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -85,6 +85,7 @@ public: void removeEvent(Event *); void addEvent(Event *); int getIndexOfEvent(Event *) const; + bool hasEvent(Event *) const; void deleteConnections(); QList getConnections() const; diff --git a/include/editor.h b/include/editor.h index e2e09c42..0450cefe 100644 --- a/include/editor.h +++ b/include/editor.h @@ -111,14 +111,13 @@ public: DraggablePixmapItem *addEventPixmapItem(Event *event); void removeEventPixmapItem(Event *event); bool canAddEvents(const QList &events); - void selectMapEvent(DraggablePixmapItem *item, bool toggle = false); + void selectMapEvent(Event *event, bool toggle = false); Event *addNewEvent(Event::Type type); - void updateSelectedEvents(); + void updateEvents(); void duplicateSelectedEvents(); void redrawAllEvents(); void redrawEvents(const QList &events); void redrawEventPixmapItem(DraggablePixmapItem *item); - QList getEventPixmapItems(); qreal getEventOpacity(const Event *event) const; void updateCursorRectPos(int x, int y); @@ -153,7 +152,7 @@ public: CurrentSelectedMetatilesPixmapItem *current_metatile_selection_item = nullptr; QPointer movement_permissions_selector_item = nullptr; - QList *selected_events = nullptr; + QList selectedEvents; QPointer selected_connection_item = nullptr; QPointer connection_to_select = nullptr; diff --git a/include/mainwindow.h b/include/mainwindow.h index 412362c5..1aa0a413 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -332,7 +332,7 @@ private: MapHeaderForm *mapHeaderForm = nullptr; - QMap lastSelectedEvent; + QMap lastSelectedEvent; bool isProgrammaticEventTabChange; diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 94354896..0cc378fc 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -328,10 +328,7 @@ void EventCreate::redo() { map->addEvent(event); editor->addEventPixmapItem(event); - - // select this event - editor->selected_events->clear(); - editor->selectMapEvent(event->getPixmapItem()); + editor->selectMapEvent(event); } void EventCreate::undo() { @@ -374,9 +371,9 @@ void EventDelete::redo() { editor->removeEventPixmapItem(event); } - editor->selected_events->clear(); + editor->selectedEvents.clear(); if (nextSelectedEvent) - editor->selected_events->append(nextSelectedEvent->getPixmapItem()); + editor->selectedEvents.append(nextSelectedEvent); editor->shouldReselectEvents(); } @@ -386,11 +383,7 @@ void EventDelete::undo() { editor->addEventPixmapItem(event); } - // select these events - editor->selected_events->clear(); - for (Event *event : selectedEvents) { - editor->selected_events->append(event->getPixmapItem()); - } + editor->selectedEvents = selectedEvents; editor->shouldReselectEvents(); QUndoCommand::undo(); @@ -427,11 +420,7 @@ void EventDuplicate::redo() { editor->addEventPixmapItem(event); } - // select these events - editor->selected_events->clear(); - for (Event *event : selectedEvents) { - editor->selected_events->append(event->getPixmapItem()); - } + editor->selectedEvents = selectedEvents; editor->shouldReselectEvents(); } diff --git a/src/core/map.cpp b/src/core/map.cpp index d2ab8ef6..d45a4c1f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -229,6 +229,10 @@ int Map::getIndexOfEvent(Event *event) const { return m_events.value(event->getEventGroup()).indexOf(event); } +bool Map::hasEvent(Event *event) const { + return getIndexOfEvent(event) >= 0; +} + void Map::deleteConnections() { qDeleteAll(m_ownedConnections); m_ownedConnections.clear(); diff --git a/src/editor.cpp b/src/editor.cpp index a7ce2390..ecc056f0 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -28,7 +28,6 @@ QList> Editor::collisionIcons; Editor::Editor(Ui::MainWindow* ui) { this->ui = ui; - this->selected_events = new QList; this->settings = new Settings(); this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, 30 * 8, 20 * 8, qRgb(255, 255, 255)); this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255)); @@ -40,7 +39,7 @@ Editor::Editor(Ui::MainWindow* ui) /// the index is changed. connect(&editGroup, &QUndoGroup::indexChanged, [this](int) { if (selectNewEvents) { - updateSelectedEvents(); + updateEvents(); selectNewEvents = false; } }); @@ -58,7 +57,6 @@ Editor::Editor(Ui::MainWindow* ui) Editor::~Editor() { - delete this->selected_events; delete this->settings; delete this->playerViewRect; delete this->cursorMapTileRect; @@ -1167,7 +1165,7 @@ bool Editor::setMap(QString map_name) { editGroup.addStack(map->editHistory()); editGroup.setActiveStack(map->editHistory()); - selected_events->clear(); + this->selectedEvents.clear(); if (!displayMap()) { return false; } @@ -1176,7 +1174,7 @@ bool Editor::setMap(QString map_name) { connect(map, &Map::openScriptRequested, this, &Editor::openScript); connect(map, &Map::connectionAdded, this, &Editor::displayConnection); connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap); - updateSelectedEvents(); + updateEvents(); return true; } @@ -1331,8 +1329,8 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i // Left-clicking while in paint mode will add a new event of the // type of the first currently selected events. Event::Type eventType = Event::Type::Object; - if (this->selected_events->size() > 0) - eventType = this->selected_events->first()->event->getEventType(); + if (!this->selectedEvents.isEmpty()) + eventType = this->selectedEvents.first()->getEventType(); Event* event = addNewEvent(eventType); if (event && event->getPixmapItem()) @@ -1352,15 +1350,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i if (pos.x() != selection_origin.x() || pos.y() != selection_origin.y()) { int xDelta = pos.x() - selection_origin.x(); int yDelta = pos.y() - selection_origin.y(); - - QList selectedEvents; - - for (DraggablePixmapItem *pixmapItem : getEventPixmapItems()) { - selectedEvents.append(pixmapItem->event); - } selection_origin = QPoint(pos.x(), pos.y()); - map->commit(new EventShift(selectedEvents, xDelta, yDelta, this->eventShiftActionId)); + this->map->commit(new EventShift(this->map->getEvents(), xDelta, yDelta, this->eventShiftActionId)); } } } @@ -1661,7 +1653,7 @@ void Editor::clearMapEvents() { delete events_group; events_group = nullptr; } - selected_events->clear(); + this->selectedEvents.clear(); } void Editor::displayMapEvents() { @@ -1690,7 +1682,7 @@ void Editor::removeEventPixmapItem(Event *event) { if (!item) return; this->events_group->removeFromGroup(item); - this->selected_events->removeOne(item); + this->selectedEvents.removeOne(event); event->setPixmapItem(nullptr); delete item; @@ -1955,14 +1947,6 @@ void Editor::redrawEvents(const QList &events) { } } -QList Editor::getEventPixmapItems() { - QList list; - for (QGraphicsItem *child : events_group->childItems()) { - list.append(static_cast(child)); - } - return list; -} - qreal Editor::getEventOpacity(const Event *event) const { // There are 4 possible opacities for an event's sprite: // - Off the Events tab, and the event overlay is off (0.0) @@ -1982,7 +1966,7 @@ void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { item->setShapeMode(porymapConfig.eventSelectionShapeMode); if (this->editMode == EditMode::Events) { - if (selected_events && selected_events->contains(item)) { + if (this->selectedEvents.contains(item->event)) { // Draw the selection rectangle QImage image = item->pixmap().toImage(); QPainter painter(&image); @@ -2027,44 +2011,40 @@ void Editor::updateWarpEventWarning(Event *event) { void Editor::updateWarpEventWarnings() { if (porymapConfig.warpBehaviorWarningDisabled) return; - if (selected_events) { - for (auto selection : *selected_events) - updateWarpEventWarning(selection->event); - } + for (const auto &event : this->selectedEvents) + updateWarpEventWarning(event); } void Editor::shouldReselectEvents() { selectNewEvents = true; } -void Editor::updateSelectedEvents() { - for (DraggablePixmapItem *item : getEventPixmapItems()) { - redrawEventPixmapItem(item); - } - +// TODO: This is frequently used to do more work than necessary. +void Editor::updateEvents() { + redrawAllEvents(); emit eventsChanged(); } -void Editor::selectMapEvent(DraggablePixmapItem *item, bool toggle) { - if (!selected_events || !item) +void Editor::selectMapEvent(Event *event, bool toggle) { + if (!event) return; if (!toggle) { // Selecting just this event - selected_events->clear(); - selected_events->append(item); - } else if (!selected_events->contains(item)) { + this->selectedEvents.clear(); + this->selectedEvents.append(event); + } else if (!this->selectedEvents.contains(event)) { // Adding event to group selection - selected_events->append(item); - } else if (selected_events->length() > 1) { + this->selectedEvents.append(event); + } else if (this->selectedEvents.length() > 1) { // Removing event from group selection - selected_events->removeOne(item); + this->selectedEvents.removeOne(event); } else { // Attempting to toggle the only currently-selected event. // Unselecting an event this way would be unexpected, so we ignore it. return; } - updateSelectedEvents(); + updateEvents(); } void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { @@ -2072,10 +2052,10 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { index = index - event_offs; Event *event = this->map->getEvent(eventGroup, index); - if (event && event->getPixmapItem()) { - this->selectMapEvent(event->getPixmapItem()); + if (event) { + selectMapEvent(event); } else { - updateSelectedEvents(); + updateEvents(); } } @@ -2095,12 +2075,12 @@ bool Editor::canAddEvents(const QList &events) { } void Editor::duplicateSelectedEvents() { - if (!selected_events || !selected_events->length() || !project || !map || !current_view || this->getEditingLayout()) + if (this->selectedEvents.isEmpty() || !project || !map || !current_view || this->getEditingLayout()) return; QList duplicatedEvents; - for (int i = 0; i < selected_events->length(); i++) { - duplicatedEvents.append(selected_events->at(i)->event->duplicate()); + for (const auto &event : this->selectedEvents) { + duplicatedEvents.append(event->duplicate()); } if (!canAddEvents(duplicatedEvents)) { WarningMessage::show(QStringLiteral("Unable to duplicate, the maximum number of events would be exceeded."), ui->graphicsView_Map); @@ -2142,13 +2122,12 @@ Event *Editor::addNewEvent(Event::Type type) { } void Editor::deleteSelectedEvents() { - if (!this->selected_events || this->selected_events->length() == 0 || !this->map || this->editMode != EditMode::Events) + if (this->selectedEvents.isEmpty() || !this->map || this->editMode != EditMode::Events) return; QList eventsToDelete; bool skipWarning = porymapConfig.eventDeleteWarningDisabled; - for (DraggablePixmapItem *item : *this->selected_events) { - Event* event = item->event; + for (auto event : this->selectedEvents) { const QString idName = event->getIdName(); if (skipWarning || idName.isEmpty()) { eventsToDelete.append(event); @@ -2166,7 +2145,7 @@ void Editor::deleteSelectedEvents() { msgBox.setCheckBox(new QCheckBox(QStringLiteral("Don't warn me again"))); QAbstractButton* deleteAllButton = nullptr; - if (this->selected_events->length() > 1) { + if (this->selectedEvents.length() > 1) { deleteAllButton = msgBox.addButton(QStringLiteral("Delete All"), QMessageBox::DestructiveRole); msgBox.addButton(QStringLiteral("Skip"), QMessageBox::NoRole); } @@ -2191,7 +2170,7 @@ void Editor::deleteSelectedEvents() { } } // TODO: Are we just calling this to invalidate connections? - event->setPixmapItem(item); + event->setPixmapItem(event->getPixmapItem()); } if (eventsToDelete.isEmpty()) return; @@ -2308,9 +2287,9 @@ void Editor::eventsView_onMousePress(QMouseEvent *event) { } bool multiSelect = event->modifiers() & Qt::ControlModifier; - if (!selectingEvent && !multiSelect && selected_events->length() > 1) { + if (!selectingEvent && !multiSelect && this->selectedEvents.length() > 1) { // User is clearing group selection by clicking on the background - this->selectMapEvent(selected_events->first()); + this->selectMapEvent(this->selectedEvents.first()); } selectingEvent = false; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5c7e5cdb..ae39f6f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1027,18 +1027,13 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ // Select the target event. int index = event_id - Event::getIndexOffset(event_group); - Event* event = editor->map->getEvent(event_group, index); + Event* event = this->editor->map->getEvent(event_group, index); if (event) { - auto item = event->getPixmapItem(); - if (item) { - editor->selected_events->clear(); - editor->selected_events->append(item); - editor->updateSelectedEvents(); - return; - } + this->editor->selectMapEvent(event); + } else { + // Can still warp to this map, but can't select the specified event + logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name)); } - // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name)); } void MainWindow::displayMapProperties() { @@ -1628,15 +1623,8 @@ void MainWindow::copy() { OrderedJson::object copyObject; copyObject["object"] = "events"; - QList events; - if (editor->selected_events && editor->selected_events->length()) { - events = *editor->selected_events; - } - OrderedJson::array eventsArray; - - for (auto item : events) { - Event *event = item->event; + for (const auto &event : this->editor->selectedEvents) { OrderedJson::object eventContainer; eventContainer["event_type"] = Event::typeToString(event->getEventType()); OrderedJson::object eventJson = event->buildEventJson(editor->project); @@ -1997,31 +1985,32 @@ void MainWindow::displayEventTabs() { } void MainWindow::updateEvents() { - QList items = editor->getEventPixmapItems(); - for (auto i = this->lastSelectedEvent.cbegin(), end = this->lastSelectedEvent.cend(); i != end; i++) { - if (i.value() && !items.contains(i.value())) - this->lastSelectedEvent.insert(i.key(), nullptr); + if (this->editor->map) { + for (auto i = this->lastSelectedEvent.begin(); i != this->lastSelectedEvent.end(); i++) { + if (i.value() && !this->editor->map->hasEvent(i.value())) + this->lastSelectedEvent.insert(i.key(), nullptr); + } } displayEventTabs(); updateSelectedEvents(); } void MainWindow::updateSelectedEvents() { - QList events; + QList events; - if (editor->selected_events && editor->selected_events->length()) { - events = *editor->selected_events; + if (!this->editor->selectedEvents.isEmpty()) { + events = this->editor->selectedEvents; } else { - QList all_events; - if (editor->map) { - all_events = editor->map->getEvents(); + QList allEvents; + if (this->editor->map) { + allEvents = this->editor->map->getEvents(); } - if (all_events.length()) { - DraggablePixmapItem *selectedEvent = all_events.first()->getPixmapItem(); + if (!allEvents.isEmpty()) { + Event *selectedEvent = allEvents.first(); if (selectedEvent) { - editor->selected_events->append(selectedEvent); - editor->redrawEventPixmapItem(selectedEvent); + this->editor->selectedEvents.append(selectedEvent); + this->editor->redrawEventPixmapItem(selectedEvent->getPixmapItem()); events.append(selectedEvent); } } @@ -2034,12 +2023,13 @@ void MainWindow::updateSelectedEvents() { if (events.length() == 1) { // single selected event case - Event *current = events[0]->event; + Event *current = events.constFirst(); Event::Group eventGroup = current->getEventGroup(); int event_offs = Event::getIndexOffset(eventGroup); - if (eventGroup != Event::Group::None) - this->lastSelectedEvent.insert(eventGroup, current->getPixmapItem()); + if (eventGroup != Event::Group::None) { + this->lastSelectedEvent.insert(eventGroup, current); + } switch (eventGroup) { case Event::Group::Object: { @@ -2110,8 +2100,7 @@ void MainWindow::updateSelectedEvents() { this->isProgrammaticEventTabChange = false; QList frames; - for (DraggablePixmapItem *item : events) { - Event *event = item->event; + for (auto event : events) { EventFrame *eventFrame = event->createEventFrame(); eventFrame->populate(this->editor->project); eventFrame->initialize(); @@ -2166,7 +2155,7 @@ Event::Group MainWindow::getEventGroupFromTabWidget(QWidget *tab) { void MainWindow::eventTabChanged(int index) { if (editor->map) { Event::Group group = getEventGroupFromTabWidget(ui->tabWidget_EventType->widget(index)); - DraggablePixmapItem *selectedItem = this->lastSelectedEvent.value(group, nullptr); + Event *selectedEvent = this->lastSelectedEvent.value(group, nullptr); switch (group) { case Event::Group::Object: @@ -2189,11 +2178,8 @@ void MainWindow::eventTabChanged(int index) { } if (!isProgrammaticEventTabChange) { - if (!selectedItem) { - Event *event = editor->map->getEvent(group, 0); - if (event) selectedItem = event->getPixmapItem(); - } - if (selectedItem) editor->selectMapEvent(selectedItem); + if (!selectedEvent) selectedEvent = this->editor->map->getEvent(group, 0); + this->editor->selectMapEvent(selectedEvent); } } diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 4880f530..846cfb7e 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -8,11 +8,11 @@ static unsigned currentActionId = 0; void DraggablePixmapItem::updatePosition() { - int x = event->getPixelX(); - int y = event->getPixelY(); + int x = this->event->getPixelX(); + int y = this->event->getPixelY(); setX(x); setY(y); - if (editor->selected_events && editor->selected_events->contains(this)) { + if (this->editor->selectedEvents.contains(this->event)) { setZValue(event->getY() + 1); } else { setZValue(event->getY()); @@ -40,10 +40,10 @@ void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { this->lastPos = Metatile::coordFromPixmapCoord(mouse->scenePos()); bool selectionToggle = mouse->modifiers() & Qt::ControlModifier; - if (selectionToggle || !editor->selected_events->contains(this)) { + if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. - this->editor->selectMapEvent(this, selectionToggle); + this->editor->selectMapEvent(this->event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: // 1. This is the only selected event, and the selection is pointless. @@ -84,10 +84,8 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { emit this->editor->map_item->hoveredMapMetatileChanged(pos); QList selectedEvents; - if (editor->selected_events->contains(this)) { - for (DraggablePixmapItem *item : *editor->selected_events) { - selectedEvents.append(item->event); - } + if (this->editor->selectedEvents.contains(this->event)) { + selectedEvents = this->editor->selectedEvents; } else { selectedEvents.append(this->event); } @@ -103,7 +101,7 @@ void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { if (this->releaseSelectionQueued) { this->releaseSelectionQueued = false; if (Metatile::coordFromPixmapCoord(mouse->scenePos()) == this->lastPos) - this->editor->selectMapEvent(this); + this->editor->selectMapEvent(this->event); } } From 5890324c96520c8f9f903e224a1bdf83bb027a25 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Feb 2025 15:02:51 -0500 Subject: [PATCH 189/364] Fix crash when changing theme of an empty chart --- src/ui/wildmonchart.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index c27e7968..69b7b17e 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -392,7 +392,7 @@ void WildMonChart::updateTheme() { // distribution chart determine what those mapping are (it always includes every // species in the table) and then we apply those mappings to subsequent charts. QChart *chart = ui->chartView_SpeciesDistribution->chart(); - if (!chart) + if (!chart || chart->series().isEmpty()) return; chart->setTheme(theme); saveSpeciesColors(static_cast(chart->series().at(0))->barSets()); From 089d90c9cb2e592478d43406d97b34f1ed85fd73 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 19 Feb 2025 16:39:29 -0500 Subject: [PATCH 190/364] update tile & metatile usage counts automatically when painting --- include/ui/eventfilters.h | 13 +++++++++++++ include/ui/tileseteditor.h | 1 + src/ui/eventfilters.cpp | 9 +++++++++ src/ui/tileseteditor.cpp | 22 +++++++++++++++++++++- src/ui/tileseteditortileselector.cpp | 2 +- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/include/ui/eventfilters.h b/include/ui/eventfilters.h index 851c344b..4fc5ee83 100644 --- a/include/ui/eventfilters.h +++ b/include/ui/eventfilters.h @@ -26,3 +26,16 @@ signals: void wheelZoom(int delta); public slots: }; + + + +/// Emits a signal when a window gets activated / regains focus +class ActiveWindowFilter : public QObject { + Q_OBJECT +public: + ActiveWindowFilter(QObject *parent) : QObject(parent) {} + virtual ~ActiveWindowFilter() {} + bool eventFilter(QObject *obj, QEvent *event) override; +signals: + void activated(); +}; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 53d5717f..0198122f 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -57,6 +57,7 @@ public slots: void onSelectedMetatileChanged(uint16_t); private slots: + void onWindowActivated(); void onHoveredMetatileChanged(uint16_t); void onHoveredMetatileCleared(); void onHoveredTileChanged(uint16_t); diff --git a/src/ui/eventfilters.cpp b/src/ui/eventfilters.cpp index 1e7b2b80..553b21fc 100644 --- a/src/ui/eventfilters.cpp +++ b/src/ui/eventfilters.cpp @@ -24,3 +24,12 @@ bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) { } return false; } + + + +bool ActiveWindowFilter::eventFilter(QObject*, QEvent *event) { + if (event->type() == QEvent::WindowActivate) { + emit activated(); + } + return false; +} diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e05750dc..8065497c 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -9,6 +9,7 @@ #include "shortcut.h" #include "filedialog.h" #include "validator.h" +#include "eventfilters.h" #include #include #include @@ -40,6 +41,10 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this)); + ActiveWindowFilter *filter = new ActiveWindowFilter(this); + connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated); + this->installEventFilter(filter); + setAttributesUi(); initMetatileSelector(); initMetatileLayersItem(); @@ -300,6 +305,16 @@ void TilesetEditor::restoreWindowState() { this->ui->splitter->restoreState(geometry.value("tileset_editor_splitter_state")); } +void TilesetEditor::onWindowActivated() { + // User may have made layout edits since window was last focused, so update counts + if (this->metatileSelector) { + if (this->metatileSelector->selectorShowUnused || this->metatileSelector->selectorShowCounts) { + countMetatileUsage(); + this->metatileSelector->draw(); + } + } +} + void TilesetEditor::initMetatileHistory() { metatileHistory.clear(); MetatileHistoryItem *commit = new MetatileHistoryItem(0, nullptr, new Metatile(), QString(), QString()); @@ -455,6 +470,10 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { tile.xflip = tiles.at(selectedTileIndex).xflip; tile.yflip = tiles.at(selectedTileIndex).yflip; tile.palette = tiles.at(selectedTileIndex).palette; + if (this->tileSelector->showUnused) { + this->tileSelector->usedTiles[tile.tileId] += 1; + this->tileSelector->usedTiles[prevMetatile->tiles[tileIndex].tileId] -= 1; + } } selectedTileIndex++; } @@ -462,6 +481,7 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { this->metatileSelector->draw(); this->metatileLayersItem->draw(); + this->tileSelector->draw(); this->commitMetatileChange(prevMetatile); } @@ -1049,7 +1069,7 @@ void TilesetEditor::on_actionShow_Tileset_Divider_triggered(bool checked) { void TilesetEditor::countMetatileUsage() { // do not double count - metatileSelector->usedMetatiles.fill(0); + this->metatileSelector->usedMetatiles.fill(0); for (auto layout : this->project->mapLayouts.values()) { bool usesPrimary = false; diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 4cec089e..01e4008c 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -304,7 +304,7 @@ void TilesetEditorTileSelector::drawUnused() { unusedPainter.setOpacity(0.5); for (int tile = 0; tile < this->usedTiles.size(); tile++) { - if (!usedTiles[tile]) { + if (!this->usedTiles[tile]) { unusedPainter.drawPixmap((tile % 16) * 16, (tile / 16) * 16, redX); } } From 393d313d4209e8c43fe0aecae391179613aff840 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 20 Feb 2025 12:11:03 -0500 Subject: [PATCH 191/364] Minor startup speed improvement --- src/mainwindow.cpp | 2 -- src/ui/filterchildrenproxymodel.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a38b3bd0..618c3496 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1127,14 +1127,12 @@ bool MainWindow::setProjectUI() { this->locationListProxyModel = new FilterChildrenProxyModel(); locationListProxyModel->setSourceModel(this->mapLocationModel); ui->locationList->setModel(locationListProxyModel); - ui->locationList->setSortingEnabled(true); ui->locationList->sortByColumn(0, Qt::SortOrder::AscendingOrder); this->layoutTreeModel = new LayoutTreeModel(editor->project); this->layoutListProxyModel = new FilterChildrenProxyModel(); this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); ui->layoutList->setModel(layoutListProxyModel); - ui->layoutList->setSortingEnabled(true); ui->layoutList->sortByColumn(0, Qt::SortOrder::AscendingOrder); ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->topLevelMapFields); diff --git a/src/ui/filterchildrenproxymodel.cpp b/src/ui/filterchildrenproxymodel.cpp index 06d7a184..22113a6d 100644 --- a/src/ui/filterchildrenproxymodel.cpp +++ b/src/ui/filterchildrenproxymodel.cpp @@ -45,7 +45,7 @@ bool NumericSortProxyModel::lessThan(const QModelIndex &source_left, const QMode if (l.canConvert() && r.canConvert()) { // We need to override lexical comparison of strings to do a numeric sort. - QCollator collator; + static QCollator collator; collator.setNumericMode(true); return collator.compare(l.toString(), r.toString()) < 0; } From 239515366c35708cc9b5e96042285bee67e86fae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 20 Feb 2025 21:47:49 -0500 Subject: [PATCH 192/364] Fix PrefixValidator being too strict --- src/core/validator.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/validator.cpp b/src/core/validator.cpp index b3b36589..f682efc3 100644 --- a/src/core/validator.cpp +++ b/src/core/validator.cpp @@ -10,10 +10,17 @@ bool PrefixValidator::missingPrefix(const QString &input) const { QValidator::State PrefixValidator::validate(QString &input, int &pos) const { auto state = QRegularExpressionValidator::validate(input, pos); - if (state == QValidator::Acceptable) { - // This input could be valid. If there's a prefix we should require it now. - if (missingPrefix(input)) + if (missingPrefix(input)) { + if (state == QValidator::Acceptable) { + // If the input was valid, it should be intermediate because it's missing the prefix. state = QValidator::Intermediate; + } else if (state == QValidator::Invalid) { + // If the input was invalid, we should check if it could become valid once it has the prefix. + QString withPrefix = m_prefix + input; + if (QRegularExpressionValidator::validate(withPrefix, pos) != QValidator::Invalid) { + state = QValidator::Intermediate; + } + } } return state; } From 70be815bdbec58aa85baaf658e2329b47159fcc9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 15:25:19 -0500 Subject: [PATCH 193/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c806738b..1c635563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix certain input fields allowing invalid identifiers, like names starting with numbers. - Fix crash in the Shortcuts Editor when applying changes after closing certain windows. - Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. +- The Metatile / Tile usage counts in the Tileset Editor now update to reflect changes. ## [5.4.1] - 2024-03-21 ### Fixed From 8e86e2bf7fa91f039411a838e5ee16ff8434ba6b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 15:40:00 -0500 Subject: [PATCH 194/364] Fix tile usage not updating with Undo/Redo --- src/ui/tileseteditor.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 68f2dbeb..f88e6205 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -843,6 +843,18 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, Q if (metatileId == this->getSelectedMetatileId()) this->ui->lineEdit_metatileLabel->setText(newLabel); + // Update tile usage if any tiles changed + if (this->tileSelector && this->tileSelector->showUnused) { + int numTiles = projectConfig.getNumTilesInMetatile(); + for (int i = 0; i < numTiles; i++) { + if (src->tiles[i].tileId != dest->tiles[i].tileId) { + this->tileSelector->usedTiles[src->tiles[i].tileId] += 1; + this->tileSelector->usedTiles[dest->tiles[i].tileId] -= 1; + } + } + this->tileSelector->draw(); + } + this->metatile = dest; *this->metatile = *src; this->metatileSelector->select(metatileId); From 6c14b3ee15ae3b836b187bcc431b7236c15b74aa Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 21:39:36 -0500 Subject: [PATCH 195/364] Restore zoom behavior --- forms/mainwindow.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 7c2d45ba..01566116 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -351,6 +351,12 @@ false + + QGraphicsView::ViewportAnchor::AnchorUnderMouse + + + QGraphicsView::ViewportAnchor::AnchorUnderMouse + From 6ae5bf8b8c3bcfac72453d48e393bccec848107e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 21:57:17 -0500 Subject: [PATCH 196/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c635563..d4356d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix crash in the Shortcuts Editor when applying changes after closing certain windows. - Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. - The Metatile / Tile usage counts in the Tileset Editor now update to reflect changes. +- Fix regression that stopped the map zoom from centering on the cursor. ## [5.4.1] - 2024-03-21 ### Fixed From 73036f38e14c0ca511918abf640bf0db6a60d4fb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 23:07:22 -0500 Subject: [PATCH 197/364] Move theme icons to separate folders --- .../branch_closed.png} | Bin .../branch_end.png} | Bin .../branch_more.png} | Bin .../branch_open.png} | Bin .../checkbox_checked.png} | Bin .../checkbox_checked@2x.png} | Bin .../checkbox_checked_disabled.png} | Bin .../checkbox_checked_disabled@2x.png} | Bin .../checkbox_unchecked.png} | Bin .../checkbox_unchecked@2x.png} | Bin .../checkbox_unchecked_disabled.png} | Bin .../checkbox_unchecked_disabled@2x.png} | Bin .../branch_closed.png} | Bin .../branch_end.png} | Bin .../branch_more.png} | Bin .../branch_open.png} | Bin .../checkbox_checked.png} | Bin .../checkbox_checked@2x.png} | Bin .../checkbox_checked_disabled.png} | Bin .../checkbox_checked_disabled@2x.png} | Bin .../checkbox_unchecked.png} | Bin .../checkbox_unchecked@2x.png} | Bin .../checkbox_unchecked_disabled.png} | Bin .../checkbox_unchecked_disabled@2x.png} | Bin resources/icons/ui/midnight_vline.png | Bin 124 -> 0 bytes resources/images.qrc | 48 +++++++++--------- resources/themes/dark.qss | 36 ++++++------- resources/themes/midnight.qss | 36 ++++++------- 28 files changed, 60 insertions(+), 60 deletions(-) rename resources/icons/ui/{dark_branch_closed.png => dark/branch_closed.png} (100%) rename resources/icons/ui/{dark_branch_end.png => dark/branch_end.png} (100%) rename resources/icons/ui/{dark_branch_more.png => dark/branch_more.png} (100%) rename resources/icons/ui/{dark_branch_open.png => dark/branch_open.png} (100%) rename resources/icons/ui/{dark_checkbox_checked.png => dark/checkbox_checked.png} (100%) rename resources/icons/ui/{dark_checkbox_checked@2x.png => dark/checkbox_checked@2x.png} (100%) rename resources/icons/ui/{dark_checkbox_checked_disabled.png => dark/checkbox_checked_disabled.png} (100%) rename resources/icons/ui/{dark_checkbox_checked_disabled@2x.png => dark/checkbox_checked_disabled@2x.png} (100%) rename resources/icons/ui/{dark_checkbox_unchecked.png => dark/checkbox_unchecked.png} (100%) rename resources/icons/ui/{dark_checkbox_unchecked@2x.png => dark/checkbox_unchecked@2x.png} (100%) rename resources/icons/ui/{dark_checkbox_unchecked_disabled.png => dark/checkbox_unchecked_disabled.png} (100%) rename resources/icons/ui/{dark_checkbox_unchecked_disabled@2x.png => dark/checkbox_unchecked_disabled@2x.png} (100%) rename resources/icons/ui/{midnight_branch_closed.png => midnight/branch_closed.png} (100%) rename resources/icons/ui/{midnight_branch_end.png => midnight/branch_end.png} (100%) rename resources/icons/ui/{midnight_branch_more.png => midnight/branch_more.png} (100%) rename resources/icons/ui/{midnight_branch_open.png => midnight/branch_open.png} (100%) rename resources/icons/ui/{midnight_checkbox_checked.png => midnight/checkbox_checked.png} (100%) rename resources/icons/ui/{midnight_checkbox_checked@2x.png => midnight/checkbox_checked@2x.png} (100%) rename resources/icons/ui/{midnight_checkbox_checked_disabled.png => midnight/checkbox_checked_disabled.png} (100%) rename resources/icons/ui/{midnight_checkbox_checked_disabled@2x.png => midnight/checkbox_checked_disabled@2x.png} (100%) rename resources/icons/ui/{midnight_checkbox_unchecked.png => midnight/checkbox_unchecked.png} (100%) rename resources/icons/ui/{midnight_checkbox_unchecked@2x.png => midnight/checkbox_unchecked@2x.png} (100%) rename resources/icons/ui/{midnight_checkbox_unchecked_disabled.png => midnight/checkbox_unchecked_disabled.png} (100%) rename resources/icons/ui/{midnight_checkbox_unchecked_disabled@2x.png => midnight/checkbox_unchecked_disabled@2x.png} (100%) delete mode 100644 resources/icons/ui/midnight_vline.png diff --git a/resources/icons/ui/dark_branch_closed.png b/resources/icons/ui/dark/branch_closed.png similarity index 100% rename from resources/icons/ui/dark_branch_closed.png rename to resources/icons/ui/dark/branch_closed.png diff --git a/resources/icons/ui/dark_branch_end.png b/resources/icons/ui/dark/branch_end.png similarity index 100% rename from resources/icons/ui/dark_branch_end.png rename to resources/icons/ui/dark/branch_end.png diff --git a/resources/icons/ui/dark_branch_more.png b/resources/icons/ui/dark/branch_more.png similarity index 100% rename from resources/icons/ui/dark_branch_more.png rename to resources/icons/ui/dark/branch_more.png diff --git a/resources/icons/ui/dark_branch_open.png b/resources/icons/ui/dark/branch_open.png similarity index 100% rename from resources/icons/ui/dark_branch_open.png rename to resources/icons/ui/dark/branch_open.png diff --git a/resources/icons/ui/dark_checkbox_checked.png b/resources/icons/ui/dark/checkbox_checked.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked.png rename to resources/icons/ui/dark/checkbox_checked.png diff --git a/resources/icons/ui/dark_checkbox_checked@2x.png b/resources/icons/ui/dark/checkbox_checked@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked@2x.png rename to resources/icons/ui/dark/checkbox_checked@2x.png diff --git a/resources/icons/ui/dark_checkbox_checked_disabled.png b/resources/icons/ui/dark/checkbox_checked_disabled.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked_disabled.png rename to resources/icons/ui/dark/checkbox_checked_disabled.png diff --git a/resources/icons/ui/dark_checkbox_checked_disabled@2x.png b/resources/icons/ui/dark/checkbox_checked_disabled@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked_disabled@2x.png rename to resources/icons/ui/dark/checkbox_checked_disabled@2x.png diff --git a/resources/icons/ui/dark_checkbox_unchecked.png b/resources/icons/ui/dark/checkbox_unchecked.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked.png rename to resources/icons/ui/dark/checkbox_unchecked.png diff --git a/resources/icons/ui/dark_checkbox_unchecked@2x.png b/resources/icons/ui/dark/checkbox_unchecked@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked@2x.png rename to resources/icons/ui/dark/checkbox_unchecked@2x.png diff --git a/resources/icons/ui/dark_checkbox_unchecked_disabled.png b/resources/icons/ui/dark/checkbox_unchecked_disabled.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked_disabled.png rename to resources/icons/ui/dark/checkbox_unchecked_disabled.png diff --git a/resources/icons/ui/dark_checkbox_unchecked_disabled@2x.png b/resources/icons/ui/dark/checkbox_unchecked_disabled@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked_disabled@2x.png rename to resources/icons/ui/dark/checkbox_unchecked_disabled@2x.png diff --git a/resources/icons/ui/midnight_branch_closed.png b/resources/icons/ui/midnight/branch_closed.png similarity index 100% rename from resources/icons/ui/midnight_branch_closed.png rename to resources/icons/ui/midnight/branch_closed.png diff --git a/resources/icons/ui/midnight_branch_end.png b/resources/icons/ui/midnight/branch_end.png similarity index 100% rename from resources/icons/ui/midnight_branch_end.png rename to resources/icons/ui/midnight/branch_end.png diff --git a/resources/icons/ui/midnight_branch_more.png b/resources/icons/ui/midnight/branch_more.png similarity index 100% rename from resources/icons/ui/midnight_branch_more.png rename to resources/icons/ui/midnight/branch_more.png diff --git a/resources/icons/ui/midnight_branch_open.png b/resources/icons/ui/midnight/branch_open.png similarity index 100% rename from resources/icons/ui/midnight_branch_open.png rename to resources/icons/ui/midnight/branch_open.png diff --git a/resources/icons/ui/midnight_checkbox_checked.png b/resources/icons/ui/midnight/checkbox_checked.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked.png rename to resources/icons/ui/midnight/checkbox_checked.png diff --git a/resources/icons/ui/midnight_checkbox_checked@2x.png b/resources/icons/ui/midnight/checkbox_checked@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked@2x.png rename to resources/icons/ui/midnight/checkbox_checked@2x.png diff --git a/resources/icons/ui/midnight_checkbox_checked_disabled.png b/resources/icons/ui/midnight/checkbox_checked_disabled.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked_disabled.png rename to resources/icons/ui/midnight/checkbox_checked_disabled.png diff --git a/resources/icons/ui/midnight_checkbox_checked_disabled@2x.png b/resources/icons/ui/midnight/checkbox_checked_disabled@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked_disabled@2x.png rename to resources/icons/ui/midnight/checkbox_checked_disabled@2x.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked.png b/resources/icons/ui/midnight/checkbox_unchecked.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked.png rename to resources/icons/ui/midnight/checkbox_unchecked.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked@2x.png b/resources/icons/ui/midnight/checkbox_unchecked@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked@2x.png rename to resources/icons/ui/midnight/checkbox_unchecked@2x.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked_disabled.png b/resources/icons/ui/midnight/checkbox_unchecked_disabled.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked_disabled.png rename to resources/icons/ui/midnight/checkbox_unchecked_disabled.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked_disabled@2x.png b/resources/icons/ui/midnight/checkbox_unchecked_disabled@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked_disabled@2x.png rename to resources/icons/ui/midnight/checkbox_unchecked_disabled@2x.png diff --git a/resources/icons/ui/midnight_vline.png b/resources/icons/ui/midnight_vline.png deleted file mode 100644 index 8f0c336fd8c07b851766be334a0b3ab2744afaff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^fk14<#0(_2UW&B=Qak}ZA+G=b|4$C{g`lg;Rdj*k zEG0pH!3;H4#wCm%2Z4NPPZ!4!3;*PV2F7y|Y&r=cB@k%Q#l#>uQ|xhh=&~H3G6qjq KKbLh*2~7Z6Micons/viewsprites.ico icons/application_form_edit.ico icons/connections.ico - icons/ui/dark_checkbox_checked_disabled.png - icons/ui/dark_checkbox_checked_disabled@2x.png - icons/ui/dark_checkbox_checked.png - icons/ui/dark_checkbox_checked@2x.png - icons/ui/dark_checkbox_unchecked_disabled.png - icons/ui/dark_checkbox_unchecked_disabled@2x.png - icons/ui/dark_checkbox_unchecked.png - icons/ui/dark_checkbox_unchecked@2x.png - icons/ui/dark_branch_closed.png - icons/ui/dark_branch_open.png - icons/ui/dark_branch_end.png - icons/ui/dark_branch_more.png - icons/ui/midnight_checkbox_checked_disabled.png - icons/ui/midnight_checkbox_checked_disabled@2x.png - icons/ui/midnight_checkbox_checked.png - icons/ui/midnight_checkbox_checked@2x.png - icons/ui/midnight_checkbox_unchecked_disabled.png - icons/ui/midnight_checkbox_unchecked_disabled@2x.png - icons/ui/midnight_checkbox_unchecked.png - icons/ui/midnight_checkbox_unchecked@2x.png - icons/ui/midnight_branch_closed.png - icons/ui/midnight_branch_open.png - icons/ui/midnight_branch_end.png - icons/ui/midnight_branch_more.png + icons/ui/dark/checkbox_checked_disabled.png + icons/ui/dark/checkbox_checked_disabled@2x.png + icons/ui/dark/checkbox_checked.png + icons/ui/dark/checkbox_checked@2x.png + icons/ui/dark/checkbox_unchecked_disabled.png + icons/ui/dark/checkbox_unchecked_disabled@2x.png + icons/ui/dark/checkbox_unchecked.png + icons/ui/dark/checkbox_unchecked@2x.png + icons/ui/dark/branch_closed.png + icons/ui/dark/branch_open.png + icons/ui/dark/branch_end.png + icons/ui/dark/branch_more.png + icons/ui/midnight/checkbox_checked_disabled.png + icons/ui/midnight/checkbox_checked_disabled@2x.png + icons/ui/midnight/checkbox_checked.png + icons/ui/midnight/checkbox_checked@2x.png + icons/ui/midnight/checkbox_unchecked_disabled.png + icons/ui/midnight/checkbox_unchecked_disabled@2x.png + icons/ui/midnight/checkbox_unchecked.png + icons/ui/midnight/checkbox_unchecked@2x.png + icons/ui/midnight/branch_closed.png + icons/ui/midnight/branch_open.png + icons/ui/midnight/branch_end.png + icons/ui/midnight/branch_more.png images/blank_tileset.png images/collisions.png images/collisions_unknown.png diff --git a/resources/themes/dark.qss b/resources/themes/dark.qss index e02d2a45..cf240867 100644 --- a/resources/themes/dark.qss +++ b/resources/themes/dark.qss @@ -100,39 +100,39 @@ QMenu::indicator { } QMenu::indicator { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked:selected { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:checked { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QMenu::indicator:non-exclusive:checked:selected { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QMenu::indicator:exclusive:unchecked { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:exclusive:unchecked:selected { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:exclusive:checked { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QMenu::indicator:exclusive:checked:selected { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } @@ -204,7 +204,7 @@ QCheckBox::indicator { } QRadioButton::indicator::unchecked, QCheckBox::indicator::unchecked { - image: url(:/icons/ui/dark_checkbox_unchecked.png) + image: url(:/icons/ui/dark/checkbox_unchecked.png) } QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { @@ -212,11 +212,11 @@ QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { } QCheckBox::indicator:unchecked:disabled { - image: url(":/icons/ui/dark_checkbox_unchecked_disabled.png"); + image: url(":/icons/ui/dark/checkbox_unchecked_disabled.png"); } QRadioButton::indicator::checked, QCheckBox::indicator::checked { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { @@ -224,7 +224,7 @@ QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { } QCheckBox::indicator:checked:disabled { - image: url(":/icons/ui/dark_checkbox_checked_disabled.png"); + image: url(":/icons/ui/dark/checkbox_checked_disabled.png"); } /* Map List View */ @@ -247,27 +247,27 @@ QTreeView::branch { } QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(:/icons/ui/dark_vline.png) 0; + border-image: url(:/icons/ui/dark/vline.png) 0; } QTreeView::branch:has-siblings:adjoins-item { - border-image: url(:/icons/ui/dark_branch_more.png) 0; + border-image: url(:/icons/ui/dark/branch_more.png) 0; } QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(:/icons/ui/dark_branch_end.png) 0; + border-image: url(:/icons/ui/dark/branch_end.png) 0; } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/dark_branch_closed.png); + image: url(:/icons/ui/dark/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/dark_branch_open.png); + image: url(:/icons/ui/dark/branch_open.png); } /* Scroll Bar */ diff --git a/resources/themes/midnight.qss b/resources/themes/midnight.qss index a8587e59..6b9e6bfa 100644 --- a/resources/themes/midnight.qss +++ b/resources/themes/midnight.qss @@ -102,39 +102,39 @@ QMenu::indicator { } QMenu::indicator { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked:selected { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:checked { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QMenu::indicator:non-exclusive:checked:selected { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QMenu::indicator:exclusive:unchecked { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:exclusive:unchecked:selected { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:exclusive:checked { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QMenu::indicator:exclusive:checked:selected { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } /* Combo Boxes */ @@ -203,7 +203,7 @@ QCheckBox::indicator { } QRadioButton::indicator::unchecked, QCheckBox::indicator::unchecked { - image: url(:/icons/ui/midnight_checkbox_unchecked.png) + image: url(:/icons/ui/midnight/checkbox_unchecked.png) } QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { @@ -211,11 +211,11 @@ QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { } QCheckBox::indicator:unchecked:disabled { - image: url(":/icons/ui/midnight_checkbox_unchecked_disabled.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked_disabled.png"); } QRadioButton::indicator::checked, QCheckBox::indicator::checked { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { @@ -223,7 +223,7 @@ QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { } QCheckBox::indicator:checked:disabled { - image: url(":/icons/ui/midnight_checkbox_checked_disabled.png"); + image: url(":/icons/ui/midnight/checkbox_checked_disabled.png"); } /* Map List View */ @@ -246,27 +246,27 @@ QTreeView::branch { } QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(:/icons/ui/midnight_vline.png) 0; + border-image: url(:/icons/ui/midnight/vline.png) 0; } QTreeView::branch:has-siblings:adjoins-item { - border-image: url(:/icons/ui/midnight_branch_more.png) 0; + border-image: url(:/icons/ui/midnight/branch_more.png) 0; } QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(:/icons/ui/midnight_branch_end.png) 0; + border-image: url(:/icons/ui/midnight/branch_end.png) 0; } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/midnight_branch_closed.png); + image: url(:/icons/ui/midnight/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/midnight_branch_open.png); + image: url(:/icons/ui/midnight/branch_open.png); } /* Scroll Bar */ From f093a16851e1ef11a346f038cbf78181f9d68fb2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 23:32:42 -0500 Subject: [PATCH 198/364] Add basic radio buttons to dark themes --- resources/icons/ui/dark/radio_checked.png | Bin 0 -> 1260 bytes resources/icons/ui/dark/radio_checked@2x.png | Bin 0 -> 2718 bytes .../icons/ui/dark/radio_checked_disabled.png | Bin 0 -> 1336 bytes .../ui/dark/radio_checked_disabled@2x.png | Bin 0 -> 2871 bytes resources/icons/ui/dark/radio_unchecked.png | Bin 0 -> 1007 bytes .../icons/ui/dark/radio_unchecked@2x.png | Bin 0 -> 2167 bytes .../ui/dark/radio_unchecked_disabled.png | Bin 0 -> 1045 bytes .../ui/dark/radio_unchecked_disabled@2x.png | Bin 0 -> 2277 bytes resources/icons/ui/midnight/radio_checked.png | Bin 0 -> 1260 bytes .../icons/ui/midnight/radio_checked@2x.png | Bin 0 -> 2718 bytes .../ui/midnight/radio_checked_disabled.png | Bin 0 -> 1336 bytes .../ui/midnight/radio_checked_disabled@2x.png | Bin 0 -> 2871 bytes .../icons/ui/midnight/radio_unchecked.png | Bin 0 -> 1007 bytes .../icons/ui/midnight/radio_unchecked@2x.png | Bin 0 -> 2167 bytes .../ui/midnight/radio_unchecked_disabled.png | Bin 0 -> 1045 bytes .../midnight/radio_unchecked_disabled@2x.png | Bin 0 -> 2277 bytes resources/images.qrc | 16 ++++ resources/themes/dark.qss | 72 ++++++++++++++--- resources/themes/midnight.qss | 74 +++++++++++++++--- 19 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 resources/icons/ui/dark/radio_checked.png create mode 100644 resources/icons/ui/dark/radio_checked@2x.png create mode 100644 resources/icons/ui/dark/radio_checked_disabled.png create mode 100644 resources/icons/ui/dark/radio_checked_disabled@2x.png create mode 100644 resources/icons/ui/dark/radio_unchecked.png create mode 100644 resources/icons/ui/dark/radio_unchecked@2x.png create mode 100644 resources/icons/ui/dark/radio_unchecked_disabled.png create mode 100644 resources/icons/ui/dark/radio_unchecked_disabled@2x.png create mode 100644 resources/icons/ui/midnight/radio_checked.png create mode 100644 resources/icons/ui/midnight/radio_checked@2x.png create mode 100644 resources/icons/ui/midnight/radio_checked_disabled.png create mode 100644 resources/icons/ui/midnight/radio_checked_disabled@2x.png create mode 100644 resources/icons/ui/midnight/radio_unchecked.png create mode 100644 resources/icons/ui/midnight/radio_unchecked@2x.png create mode 100644 resources/icons/ui/midnight/radio_unchecked_disabled.png create mode 100644 resources/icons/ui/midnight/radio_unchecked_disabled@2x.png diff --git a/resources/icons/ui/dark/radio_checked.png b/resources/icons/ui/dark/radio_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..acb8901556460474f328be0e2900ef1bfd5702ca GIT binary patch literal 1260 zcmVuM9cu+l_9 z^P`|biUd=VF1iRTEy)XO+in6&v_go$yP!okwS-KsN?Z_O3T1Y8X5N`~tHr=dt99o6 zcQG^TJ3ISbcmA)Q=RD_}|Nr?s58Uan**NzP4Mm)Mu-VXRun1BMhJzTwlmq8n;Ly_+ zi6sA50fX7>G7Exku$42+d%;0_VzJn_|4G2Wz`%URtv!sfrDPfg>&0|RW`l*An&G<& zg`iL=^P^{>#lPVOp#pCf`W-XvUn_cV%Iu1Mt3=7+riMD*U(kwrfXh}{w zn|%)P4iLxim3KDA<1LqFEFhD~#D$#~EC7z>NB?=Ty}kYSsffoTnM~H#)Yl(EYy)nX zf)$?UU7S`xGMSuLQ&;~zuoUco7j1dX%!1jFmm$g0zP}r+%OHn>WuE5^hka_s2i4X! z928m#^iD+HKQt6^^ZD(DtrLoZ1&EB`ghOy7Okie`)SdDDxUlV*dsNbDGt-1CFyQ;0 zj<|pr2@1E`+uGW$hi2oXeg6%xw}FO9CVd0+rWcKV6edX|5_OGD^Dlvxm~6f0MNXE- zoN%-&-UE=?{_*4)-~Sw}YYK9JMz8~EKYK{h*yK7oIz~;rRm`pK@YtAubUJ;%;0fTj zn}1yYB%C$v=k^MFK@u~>&b05p8aBjYEj?hDfrnD5p@*vk1ZEomGwei1N5^Q{p2=k5 zpuGTQvw1k}UElW~80(n{g5v<@7M`jSV6w*n5MPJW6wF=$YG=V*Mq@#?qe7DOO{ul2 zT7VHLwNjNqwtf!e#aQ%YxG!n2)NH8|ppsF#H5>{sP<*ZeBx2!yKA$h=UQ|AaPE}MT zOw;>J5sf?LD3vQPRBY8Q9P=;>3^H>Tfc$XJb=`9AtK|nSbO^~7y@J+CnL5cFAz4MN zeyps^^SrSeRXWfc0SMflDgn`G^eWgH!Gimmo41EkUK{!Kb!NO32JnR!ZP_2T zrn1=$z*68x&s#M1H>fz_foXSX68nE~oW-><&dmcFEMI%pFm1E!UaA2p2bwE_0;!UxyRB#T*|Sr zXAc7}W1RIOkyTR)7#J9s@6^=&09qpPL2E3sYqAfCBp1NU3X_am+RyC*y$@VK+{T zQ{b7Bb55dHrc*8qI*v1(&*uYRA+8%EU@gWbpjc2TC?@o4n)w4O6dSVSt7@^GBZ!dY!n1fvCTDL$<^&^dyjr?s{9@@(?n+4vXz WXZM{iMREE70000} literal 0 HcmV?d00001 diff --git a/resources/icons/ui/dark/radio_checked@2x.png b/resources/icons/ui/dark/radio_checked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e190476212f1d5a496f444d581232f975e8d6189 GIT binary patch literal 2718 zcmV;P3Ssq$P)~QB$P@}pyFy! z3&apWsFFwV5D*E9C<+QyKtxa;5>ixQkTNMCq{Kpj5{pNw2%(}vK@5n4gxbwa_w-D% zgz(4%*qxcapFeg3-90niGrO}XOY{BfbH97;*JtkB?tAWOU_bU_KmMN~`(lv(ZQG`| z4_EpqY91hS0CWR55WrLcbOO5o?gAta$Qy+Ew?=MAr_;HK);3`Q$mjDt3b>F8iy=5_ zjBauyo}z~PyA5Nbh_Iz1(c%D*6uVG35lUSH$ii0o79c~xvUj#`e`x;v`Q;Y+Z4GR@ zun?kdCosQNKI;tyaA`V~daxBUV%!19X0tP)>vsy|+z6Ti@HY{xhmy_Iu|)yzYL&|F zN=Jv6D3!WMP6Kp{q7Db-H~?P*&>7)#g9a`!P4o5f=pR=Avab7iQG6W0Vb!hN4ak#1 zd7`aNo=zr{?=<7vb=^+S^G={>rvO-7dkhRgaFLZt{eCR^fpG-Dw%v0mTm@iKl_>CT z4Y<`b&G*LY-;YctlbAAP>USu-0gxHtb`rN(rg4LasAhdP9RQM=op)~raBZkb3An1P z>p$;JCJW8_Z3LS)zuh&dbLc7oe?nkNP#7NnU~O|XTZZBKNpA*na&tKk`t%jdrZ;^C0_0VwY>%d&Lutt-9ctR4TQ#5pkJJCUHPl*AhUx4B(tb`T+6@ zgmT_fylHak<(+cE?W_4&BcGR?c|^{M+5ASpWJSALs- zYZ|%vjT<+%XYKqwARZh0%>#gZhN3;2bDX6lW1m>v-Q8~yyd|hvt{)s6JfL2mx=!sv zp^v}#vwO4`NAKH#p2{xs#5ntkl%Yr;LxE`>AUsl z^=E*>)d2~D-M>mC4oQM~a$AZOV5eC#Yt8^&q?{XWwy+jYNMi(4}QqU+}X z@JDQ2cX#(&QG@34g>wnHBu?D;AvbJsoSHZDF+;>7@kqd^a6v6@%>WQMCFl@;jvBC7 zEKZh@x3PT!4YDlmbysu=Y8ISat4~z`1_uWZ0O5a@!Yk{4i5hIU>|KT6i_%16p5qiQ zj3T6y$(I56XYI6Hp>TAVR~3Na;o)NeBtEh5)t;VN`7rO}R#F$>2l1L)3kt7{Y8Mej zLEwiIrK<9jRRK`I+(0K_Lzv@-Z5O@>!Srak6M}hLT(>uhAY?$hKsz$bsj&vg5&kO$ zt-qS{38?eTsP>73rqmfx?LvmM^N28~)*_e=5a>ld`$bK77P$V{(5n8^evk}kCt!s+ zwPrw9;7a8END;D67ry`~OvX^c+PlJp8UbK{xppy%^nRN4_CWBnU)i zpKaToBtV2@NW8Y$aYuJ2zMR2!TN8u#pSG z96!YK)(X(nX-sP%j%k{&L@~5rLZ{|ztqQ=0ot-ZL@SnebuD?*I`?#?zYY2k-WA(Ne zBD^muB|q|rmPG-h($|N1RRI`1TYJH8Yg1}bnCC~iz5ONtJEJAH6s|78bAT|iu6s0s z?QY%yhv5KH10UgaSCUL4q*m!W^!#UZr#^dP5A53;NQz; zvu4!5mSw%K0VmfF!q^K^1lY(*<+QltxJ)LKpzs%=-gnhI9jY6ER4TO&pnow;h~Bm| zPN->`StxZZfSM5>6Nra(P5R;zvj8x8%Ct)n-1fdZ{87h>di=Tp5D`LY*M-dIJB|~% z2M0!Gf}JwH0q|!C?Jb&t06QR9k~WQV;ubvsc(YhMR771L@Im}?&YbCWHN+e;80!VH z+Rs;z$FkY%%s4R%MJkqQ-sY9dvq7u?@L^mZA9ezAt5+${wo<8k8##i{WHO0F+52Ol zhKP7=$BylF&jo=7{*(g)12gpY#A^V2H!Xj!l!i{|?d=_o>%SMpVsUc0tWKiFGeFG4 zNEZ3vQK4@`!&WG4638Qa_UwE%ylaa?&dIL;aGB3krX~yGDpwj>m2$blvC!0d_$$H$ ztI~#X$#@DRT-WU!8X8i~1hN+FeExD0_xNo>xg?!7R>t*hcv;Rm&Upd}Rb0q@AMDi5XUT&GGA2s5mspnG8ac%?TnxG0iUuxIS{Pe6@JDTw|USu+v z#N??{e$&?7?{!4p7>Mf z_#OaWhw>I!mKB-E^`F6HURTl<+Bd90=y}mO8ssuQ$CDVe=?&v zPT^FJEKyt5X(q!Wh+Z_C+wps$8nCN$li~p4j-eNK?t5C@PK6)e;v#B0CCtS zpUn*vi-uOIEEM3#(a+{A2&RFUDnJ`Y@`<|uxPWYiaI;1}pGu|P_;091-jDs*kN;i# Y2Wxc(Y@iPbXFR9J=Oms@O9RTPH5wP!jYB;_JT1S-&gDFjReB>{q>(AJ7T zDLEQaBK86yCi-B4f)FPS3iZVVMU>hWjq<|uqHQS90n)yJ1ZzXULSifzORF&uFp9LD z*~^ESDQ9|b@s0I#_S)q zw=!71|Cj$&K)RE+AZ$&cpwABy_bj5@f@LKq?n}UOXVVz5_H$re)YYa?ZO|iOq}gY} zZ5^So^_meA!)m9ANE)yjm>P|*v7O|es4D(r$O6)xycyO?6)-D8EdJ1qNeAlc3PSyf z_WS+j)P?J>0v`a$z%^s-s$-R<=LRicSyoQ6!E**-g24Blk^jDS0XUf|J1qF5}z8Ff}Vb z86n>q`z^2>&|Wi;x;FP|FlT{3)tH&b14XxEbAG?yoIc<3u8O=5jO=5@{|LNSTVDLB zo5XP(-z_`l9FPL62$mPu#O6#~zA(E1RApy(@>3VCe;rL_U*rIz751dA&EMxVX5|Ey?=`n?6z|C=-nG5@lxoMez4z~GLpt{D9=bye=9IB6nP0O~PodI}Jh0;t-f zZaiZ=ZvY8{pzdy?)oV7!X$$Ky0IR&vLjbBt5p{Yw4n|oq4Dtw}@UnYs)Y}Xoksi`b zfJB@Loj!LN36axmxHi>2koJTa_T(sbd=+#zzuzwd#SCDc0kGZRavdOKLK9=2f`S4o zaP>X}TyqcFAG#C8*4Eeo=iURq0f;a?4nnA5xTf*%CZNete@o1FPk^B=`jqZ|DJHlqj+) z%39e&K(MN`72zbpgmE`)z8mz$c=HZWGnj)&A}Rul=T6?~_NQiTTp4Y0KL-O}cHIr) zcDS{&6+i@kMs{{$%->K{)Oqt^lL@+q{y#?uW!G5G?RD-h>Nt)MVRuBJZo`$!xH7qX z@u$}0S@UKJEYwiK?Dem#JXBv_uK;Zg4Yv7We3x7O^r9Fr_BEjAx&V3N5dOt%$j#Q>Cu(vuUx6`w}2u{@1B2W!_L@>=5+t%J0wmy$4L7NtA9vs zr|H~K7x?P;zBmT;W3tT2U1+Q+1fCsCJkW^Pt--R=+JORx^lSEF=H_W36Ur1L%SAOw uBW=|Kv;ZxlT90@tYs-r+4F&hXw*LV4fZDKt8Qnqv0000N7w{MeOrqlA9*+1U7zkAMk=iPVTz2}|<&f`4J>^}$^PJkeBFy#X;85hD)3tRm-SWW}>YxYvNDFz_xhybL}d$npt; zdSo$hEEyE=1&~4{(m*s0j0r@X3#I}9$>9_-u+0Km+E}-C-$?u)Q2=I3Wj8AdZ3b|K zwvB;f0NxZtTLXcCw^}!CI1-KSTxnVIs6hWrf|v{9B0U=bP61flT<3m$ST}$X1R&p0 zwt$G%0Z0sSis(6;&HBt=UR%>UT>WZDl4R>s(#iom4&YL4I{{DUPR@9|va(W%R(I3^ zaJgJ!`@Zg{!2Fn|NdaS>6{u{ecOQ;cuNf2-ElxRM6^{_%=KwBLw=rn5UnDH_dR{ml zjn1e7;BvXd_74x&g6M9ImTo~@+~}!(KN@|*MWMqv)=$`k&8Hqy5Uo9YTmN0+l*&}pRn|$051VB(M%rzyTSBa zx~1yV6rcK?x#?|QwJ{&4+-k_UzH!K!qIv1yXq)h^DDTvcdsZB#-Z0u=Jvfk0=d9 zWElBqzzo;+eP8@(&YX%1B60nx{ucp!c1A!L!zYp+2*(fq3NU^VETETK>(_T08J8r< z<{OYV1DF-b(Wt?g?@J79aJgKDudTPH<`kGT4~id=BuRI)>nfWrm0k|!+f^c2k@=|z zE>d40?Rfxs5qQG}gG#pV?T$DK(=C0k0Q`|kl+mlC=j(BG1CXF3EI{!5@OJAK_rHvc z7L+*e1;H7~xsih(pXDgmy_q3+Je~lUJZc$%@7Lpo8BRPms58+PBZH^k&1Z}_v4kLA z5pGBifkV;DLk$p6fR%%4ZoqWXyAgD)e zT0c^Aw7if2@JZV(0BkDJ-&;IYpJ{nRkR-{%#6OAbb8NwcpBlASR#qxt1U)!~MM$19 z^c7$+S$#=_ZAONAIqmxZE-~W73E2fD4_s$NAaJnnj4UljCjgkFzEl+YD1@Un!kBN= z9#1d=C58kN8sB-6mJ%8O;%kBhis82(;5cW2!-EJR@3a~|lC{2%({l8_0x3a8fCzhi zI+v4QFc@PE+ed}?o}#5h41iz`n`1k@D$~o>o+}szz*|!@06@JHv`Lbrp5S!@5VS@1 z3lVk$($)qMM#s4UAz`E{LYybYu^<9-$gmlL*IVf)z;J~aS12&dK2I+#O$1<7iF_W9 z$EW6mt^)>7YR_H+=|)DF@kL}$;{zh&09bmgX&TWnEhjVpz^B1hLT64*`(DHL_`=wB zqH=A}HKR*Q(OU=XS6>pyaIpcVdc*cOf>;>ZCKf@yGjKr52?>B;1RXXqFip!*Lr#^O z07P{f6C-@uSYNfhhqyu6%11H zE|*I%Xikuj;nng&0&x0lZHHRsCygQvtit~!fTM;E#}=O-r<9dOgm*sd&Oy)`h5G$H z89TK+-ETMnn(ePrWMr(tv;Hflh#8|;V)+iw%9BO}5?Y`6Exq)(ZU9(TY5}N@nrUXC zw8(JB=l^=_Dgcc}oY;U~X|8kE8ZlfhmmntXChcWCu5JLDx2)?XLX%3AC@WU&ad;5C z-b%k5@ZZfqJP%3VlT+jhL^zJyv9J3csLmk>_)yNqS}n&! z-pw_A%z-=N6oj3MjJqNo$4Qc8W9DD0Wz4)X-04u*05or`ew#RGGPGF;Pek&vwSIl4 zKj6OwAl--$3(S8!E>iL$C1wGjS4piv@V2)n+0Sdj@r^bztOzU*7SVln$!WL;2L?xi zeB@hqg6Nm5v-~s-#@7HXZLY6g5NXl_fa&*Cj3LmkG&+8>Wy`v-J{V?JJ3HFDGQXXZ z3E(@YMFd9Sxa+38cIaUHH%5KCy4sY^j`nxQPrSLBNVyO|7GQe!c9eisY!N?b*|_#y zGaV#JvdMDdR?ykB7Xf>E#)z_i?%EZ$b|~WMu)TQ6rEKx<0pl{2c)RsP^2~;Y)dOan z0|ry)r;?Q01*3$4Yz$_Rht3VZ0;n6z?Ig&PMt!Z?U0Wm+lsMM_SfUciLh!UmWjm${ zB=Q~3TZrI=sv?X*>ssp8Ix&I*iCk$}a{6(<5=|iWV4q+9ATvL&mJ!3*RPU~i)Yo*8 zRDq*xA&7!1E*Pk~NF04Q+NTvmkiEqDFas;0n$L)^s%fKpnHdG9YlJ%Y@7|M{m2&}r z@0~FOKk2qcb$mF^N~W5BwNO$TVDdO9KjE=Q9hEfsH>~ZRLx*i>8y0> zTaLl4Fk(IViPRWTv9#S$md8Z( z07i%UgkA!+0@!LvKG_s)FeFK`^#s!M1o2z|^8n~A!)0PDH8b0X!-&h}?ei90$yUoM z01gA1Oh6kG{>n@TElS{HR=6i&RMK&U`U3;LloY~18U@5SL72cyxgeSiaH@gM4@h8X zlgGVlB>u0k|oax&d?o zSRtqQtD`N@G(z0|G&Q@VJd1%+1{VRCFdQ8Oe5rum6s)|Qn?3fO;rt)KgpK&aK5x;L zN`hqufwBN)BACVjNCS|H!IPV#0Qv#?9N+^C9AFV>YxS((kN<*N^Z4Jze*l02 V>VUGh1^NI0002ovPDHLkV1jGCL5lzY literal 0 HcmV?d00001 diff --git a/resources/icons/ui/dark/radio_unchecked.png b/resources/icons/ui/dark/radio_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..9ffddc4a0715b69284258264cad4ac20eaf9e27c GIT binary patch literal 1007 zcmVU7}Tu4Jq*geaA)P12-|;)S@besm}2 z@5M=C+O%s+Qg8NLy}aki^ZUIg@7ICbJ!~_{*XHKpjvpK~bO0=l6vJ>3bC}BDTnZe< z)A4w&qXILf(rycaF|d6btb4^lr-p`xzHLijdV2aU$L)F_nl~OCsL`@ubURQRx0gu1DypP04l;x7rod=`^@}XNlS7H zrP4EqH-HqztbFH4Di!--!ve)(F(vG>U>oqczx2=Z!^6YZZv|c%xm>P0(%tDds2`~1FnFX7H*F%yOW@ksi-ZUtqV0R*ss5Iv|D}U4# z?Ku;|nP>+NU}j2q-k6~e1u>cXiKJzNUM4U-JNuB(F~o0yfAd&7@Meh{3kxrUO@a0o zi{;1G6mZ2`05W?$olgJJF1#L@OlHZ%Yhi7THNS;MqfjVpmpOC5?~4o9w`Ve$rA`Sz zeLj?b0^P5w^I&~dG~Pg9b{H_jzU(v{!oLE7vjFB+pQ!gY1Wfj5sLI!!hX2RVH?`LO zRskbkYvs2}K+;UD8EX+xty8*LVT;1|{d(;k^@-TB5B!g@TtF}vwz>wlis-s-z4m;4 zk7=5Ol)|haxm5y=6HNgalpB4Gr>2Vlh(lW?Ak)DRp$jbnuGvJ*?(xaV$*zvWB?BD+ zAaKW91U%212fHBHcJIKzi=7t8mr92T54j%`i5-o*!Ln5lm_|cgPUmvD{!YQO*=*G6 z=L|X(?ux4jq&@G7(5JwCk!bhFlGJ%PebM~91t8zA9i5Eii>0^Q zk*FO&uL3Ut^DaEsJbGB;N>;lMUIy;L_-wJK=Xhpd;KpX)*=#m?XJ6kLVJCq<1LKjj z=S{B6TXl3h+AvjjCiv5ufL}Xh0+V(Ns;xylAM`h@#)^a{;aT*24z#QcQrVy zb1l#3^LOL89{^9)lFJekGM#f_#&MjA@B0BTi0cj!a0p`rNYzT5C#W7zCX>Ic#n{k? zNuf~QBMZiWhc}WBc2%a)l;@paH*;eJ!^r3J2^{CBa1NLh$A|$g;nrFPT_Om^lgZ@N dX6oMF@h@G4e&dGkK3M<&002ovPDHLkV1npA;S~S? literal 0 HcmV?d00001 diff --git a/resources/icons/ui/dark/radio_unchecked@2x.png b/resources/icons/ui/dark/radio_unchecked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2160a32ed532593c6ed5b88651e64a56f5a54704 GIT binary patch literal 2167 zcmV--2#EKIP)^@RCt{2n}3WPWf{l6&&=N5UE8~2pheT&*`2++TGC=-IgOB} zS`verwooKdFajy5QZ+^i)L;VEA62MQewa`Uf);5oL7}mRAN_;2sY(MFjV|`cU3X`8 z_u8YDB5l>~k9oiT(bmq)&hG8r&MsHI&wum0&-=`Go_+V7nfHALm|=z){-2RfoMiOy z;dx!f%5sXj6p%##QUERh&@X^a;5fkJfaCxtW-6?P z-~&?(lO^#yHT*mkkH01&3{@mTP5?=<3Wckn)CNFSgc)0aGzDAUI&$R6;o;$Oh;hTg zvI;98YBPc1uwvFa6~K*2!+0W$5z%r2GMUW9(DnNS^0_*03c&A0um?&;sNAR;8@CZgM*iN{-Fxab0dFTT$14mw{yD4X^GoK>KiN#orV>C>v+VqK2tMm; zIz&y@q~h^cnu#}6Mhk^lm!qDoo^a()mC~t?4Gj$)Z#0(H%!%!Meq+_e3-$}uaHlRF z0ES|*g09JOg4?|<{9sqKXFEx~(O30Fs9BAc&v2Z321JvaF?(^_?^UWu-O)Sl}`p(gkbY ztzjR8h_F&Ft_S1~E_0NkJ-VBW)azR}0S9e+8Hlwm6QQ(E8-{VH6~XObXlSTNr+lpd zM_uNn$z1MB_4?IJ07=myKXBUw+-{n&{Vi&38>v+4O@f=;Hi6tVF)=Z_R-c*)unL9c z1eUo>WdXhwg3k7$)FTk&&K?rD=v1ln#aiyQM}Wc|?qd=BB#}sD!co~i&dg1oABx0n zwVn+EZ-d!v_Px;b0)Vrf%5*C6R4VmmsA}5;N!`lj4*(eYuTk&`(=cB2`v#5x(e)c} z?&1O9rSs+D0YF3?5>I=K3O5D$1}1>ODsMpiCNy;&LUi$J7JMq`Z%~zhiHV8XAg*xR zoU;Dg&=t0sWGwb70Qab!n=KSB^ZQjLpja$^1VHqhg?}CxSdjDkO?N~@QQ-OEM5(HE z%Bln?V2P&_urGXhok2*Cc7e9o?-MWw$R+M01#L8JVVy!qpLSm2_X(N=lO6{;)BfIH z3z8n~1WdnA&j0;B zn>?KG`vfJxb{{F|RWs-Lf~G}N?qdR0l}~jyb-;Zj;(DVx&rss{v~$Go6EFwBx7o;L zexLIN&AXrj0n!0JUjEhF`!WFc{`=C=LLnBuy!Jv;BV?t=sPsL4zp4bB*;{+rZHp+i z((gCj$>j5w!Mme;+)6HZ&F>ey9zgNG{;m#DTSw5VHNUVzq~iArOh82AcAPG1I1O>- z=;6cjLRHvCNNS{9=e7xDTaa&H0t~}A1Y(!V)Ez07{oCQw7TYdd4{(voG?7ijb_My? zTou-YKk^s^U(RGQiEt#gk90a6rSQ96tK1(n{DC?NFbv}*fbPjKDtcsN2pZeUtT}VP z0Pkz>vEsjbw$}2mnE(+Xl=e-Z`9|BetJc+~3vY~%UnHXLa@&OX{-Q1ed<{K`{9MEvFG(IXGl>o@7{aHU+l9p2T|xaeKa zjE;`_i=*CEX7}|!2*F2PrZRO|H(62TqzM=t96SQ0T?3$a&dMty-Q5q9>`1NgWLT$n zfNyo%gtESovYm!%giJHGpWr%=kyvZx3Xk|#SUL$w$=dc;N&MK?^guEhf2@%hP5pvd z+rAf&4PF)4Tk6uUd;fw3M{9Iz3+Z$^I;+3`VIgkuHN9vV@s+i1dw!ZNlhdty{%H_v zd`*_pWc5Hi{?}&WO_i~+vH6j%=&ya-_WcN|*oB)#xDnoZB>(`4EoLIVK}4KpW4Bf;oy#p3;u!!J1~k77U?;(y zPN}r3*<_^C>1c0X-*OGwDgajl40;SN3uS$y6J8K4-^Rwq$K$%=JOJX_I&NhF_Cl~5 zO5RXP##FZ)>#9^vmP#e3r>Cb+DK(d*NljY_;&K320qCz2^VKuu@_%eae~2GUVP)b>{- z_i(Rvz1KM}UVGX9>74KUzyI&|9nLx5|G=Fdl1;L&_IRoF6{wT~C4do_Qiz~B zisbc&0%w2BsX&#}d@ycJyP!|6v+fw8CqunmBO4N^a9XyBwI2d6B!^}c1`RqCM<%@{ zKC>8$+dQ+SIIecLi1>h7U{^B#yp7T{GSK;Z)&f;d^ImIZ0N9@ZiyOOs??>TqTWl?9 z*LBUMt5eSd?*L`MFUH!V7y7%etXrVG{&<8*OcEHXC?qeP4ZZtBSk}M2wn) z7y3G8QWH%2jM#j^=>(j?4Z;C5*w@vL(g~DUV?QNr(JM|WpKE#y%oy;88gu`!zhiC# z@a1qE$2)7cT>3Zki=kq)H{wPg4Ezcp!k*0%$VbZ(EFw!|{|Okv$)l>x5>PQd z0K{C(6j;>3q}`DNEiL(*z!yc1CaL`y0-=HKX@rXiMLVwBmvc?8tUpnc+~mFw`Ohu) z1}okIYo!%H1h=}Op)gl)$8o#}rvL;!3FxWchc6*B7Z5GXWm+8ZeFuA?gFX=ttHV70}_myXk*y_@o%?ctn0_5+-Z8;Skn$Xxt@Gr9I>sT z-tOS-60`a>d$#V?-7yoZ6C)L(TB?a{)dNI;h^UStp8jB>>oR22RKi0C P00000NkvXXu0mjf>yPoi literal 0 HcmV?d00001 diff --git a/resources/icons/ui/dark/radio_unchecked_disabled@2x.png b/resources/icons/ui/dark/radio_unchecked_disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4de5d0d2d8411305a232bf65c59408ce6d9b7e75 GIT binary patch literal 2277 zcmVd{F`UqefQbtW@lz+=72GbVGRGzppm#qp;CHx zP}&y?;@OfhgMeuOXMiXT5;hP500JPs0ErG3)Gi1|{XXxy`;L@!5WpmT z9RrsEye^0ig~R=Cv^MO$6i@D4Wkbria9{~REC+Fs(LDh40a({u=Y4fp4}b&=$aj}L zNJKRNk|IPAz2J1(fA^O^ZSNUwel-+DarVme3II<5xJO@4z?R(X%%`fVs${&m<2JzK z@rYw*yPgK~MqQNzqs9(YHPw4B#+x@56cw#Yy=oVa5#ezFUsBgG=wL`V*0lLvx)M*$ zxD4=kJmT2<7x#nc0bQ0ZL0o*;SN(Q8`G$)^clqQHp#hN{?z{HnYk5uk_XlFlWsB#= z&hhtEf#^{keL}EUdbrN}K|Hwh-FY4$$!PJn_`f7D=ta;4B#r6}S&W_U!Su^Kc1yMe3v}OXP_aP;@v;A~S zEO}yj1(vN`HxCd;0VJ#Fea&^=wXk4z7BBMM<-Z`pb83zW549Zd?j7n{F;u|P;a(~JEw2sar2 zzR3|V*E#-Qk3_pJngK-N@Nae8{+4?0iA022iPlE%XAEpnbIf?swPww@!LAw9fMrU> zLJ%!bQ9;RJj|n<0MJO$_1Hgq_1YrVSP5#MX@q@Pj#xJ!5z1&*AyE9>h7Nf1Uwhv6Y zABrDS6vc4JH#8tmDfudxzptW#GRRMxrqW_$*m_?9_(K(y*&{RZjbaTAa7f34&`y%x zY;E*@W}-$*;q&>zVDhOs0zYIFJIE`*%QY7hHJT-Eq(CI}ntx#Q8blayqsN@D<|NT; z=E}1kx!IFWf}n1-=>eqW=;I;`;A_tB0&uFRzqj~yUeL#lc2reWNiZ}&oMaKPP8s0=BGz)W>fO8PJ&=1u_#opzq74~aB@_+Bj`NB!787zQY) z*FIGrV>Ak+stW{+__w}k7#E>ERUa~>0fITMHMY@&w%Xc$03j9SR1`(s!5bQ&StJPw zF)F_KtwMwW#JH>pV2)@ucM9@KN|FGm*8>K8KHq>kCUPEdeW&(z8OX3kp7pSG+tYQ_ zWqnMf0l+63f+Bm)I|fN|nx@P+uMaVr2Xv^51iH^;StABwq0!F3Iekoo0fM2~Y-C`8 zKITq=VAg6UoQa6h69abVaR9gW-wBvo=q{gZg+MFe@puG-eyAZCZTh$f18(fC9anQ9 z(j~eB@YefXIneAVQy|ox`Hnu$a6Nzk-RG|?GF9COfP`K%ztPrK71GBU8o)uhAAoAB znU)kPrFWaE&_Z}T9zjg2bo3shSVIGv8*93V;8#&eL21`-hmTquKijn$z>J$^0zM61 z%Jdt>4!SDL;6JL%1b%dhyX@xSMPmbS-rtE*Fl z1NDW4KhqzM`mdr_rf&mbk%|hE;EJIRRSs!@zhTGcEO9@8eic1Gcvs&`7@@PqL&3GG z;t>E3t2ttt$0Xcm#dEwW-HSe|aXHH#>M|q-9 z6vdf2bNK94tVEXY+h zq@>ErKV`;75WcLfTw=0#EpSzPS6MgnDt8yKEs7)dLtGsR=y<90G92mU7J>uNjJ> zIJ?6c3kC6V04o3(O~Zr4Snsd*Ru4x%V8SrwT2VTM?Y8FuxTAyx2{^)pzcSMYHW~hi zC3ic$?og96^+H2KJMCgS>G>aGv{MFii!J4j>KJcW%A_ z5CG_(0H0&v9EuM9cu+l_9 z^P`|biUd=VF1iRTEy)XO+in6&v_go$yP!okwS-KsN?Z_O3T1Y8X5N`~tHr=dt99o6 zcQG^TJ3ISbcmA)Q=RD_}|Nr?s58Uan**NzP4Mm)Mu-VXRun1BMhJzTwlmq8n;Ly_+ zi6sA50fX7>G7Exku$42+d%;0_VzJn_|4G2Wz`%URtv!sfrDPfg>&0|RW`l*An&G<& zg`iL=^P^{>#lPVOp#pCf`W-XvUn_cV%Iu1Mt3=7+riMD*U(kwrfXh}{w zn|%)P4iLxim3KDA<1LqFEFhD~#D$#~EC7z>NB?=Ty}kYSsffoTnM~H#)Yl(EYy)nX zf)$?UU7S`xGMSuLQ&;~zuoUco7j1dX%!1jFmm$g0zP}r+%OHn>WuE5^hka_s2i4X! z928m#^iD+HKQt6^^ZD(DtrLoZ1&EB`ghOy7Okie`)SdDDxUlV*dsNbDGt-1CFyQ;0 zj<|pr2@1E`+uGW$hi2oXeg6%xw}FO9CVd0+rWcKV6edX|5_OGD^Dlvxm~6f0MNXE- zoN%-&-UE=?{_*4)-~Sw}YYK9JMz8~EKYK{h*yK7oIz~;rRm`pK@YtAubUJ;%;0fTj zn}1yYB%C$v=k^MFK@u~>&b05p8aBjYEj?hDfrnD5p@*vk1ZEomGwei1N5^Q{p2=k5 zpuGTQvw1k}UElW~80(n{g5v<@7M`jSV6w*n5MPJW6wF=$YG=V*Mq@#?qe7DOO{ul2 zT7VHLwNjNqwtf!e#aQ%YxG!n2)NH8|ppsF#H5>{sP<*ZeBx2!yKA$h=UQ|AaPE}MT zOw;>J5sf?LD3vQPRBY8Q9P=;>3^H>Tfc$XJb=`9AtK|nSbO^~7y@J+CnL5cFAz4MN zeyps^^SrSeRXWfc0SMflDgn`G^eWgH!Gimmo41EkUK{!Kb!NO32JnR!ZP_2T zrn1=$z*68x&s#M1H>fz_foXSX68nE~oW-><&dmcFEMI%pFm1E!UaA2p2bwE_0;!UxyRB#T*|Sr zXAc7}W1RIOkyTR)7#J9s@6^=&09qpPL2E3sYqAfCBp1NU3X_am+RyC*y$@VK+{T zQ{b7Bb55dHrc*8qI*v1(&*uYRA+8%EU@gWbpjc2TC?@o4n)w4O6dSVSt7@^GBZ!dY!n1fvCTDL$<^&^dyjr?s{9@@(?n+4vXz WXZM{iMREE70000} literal 0 HcmV?d00001 diff --git a/resources/icons/ui/midnight/radio_checked@2x.png b/resources/icons/ui/midnight/radio_checked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e190476212f1d5a496f444d581232f975e8d6189 GIT binary patch literal 2718 zcmV;P3Ssq$P)~QB$P@}pyFy! z3&apWsFFwV5D*E9C<+QyKtxa;5>ixQkTNMCq{Kpj5{pNw2%(}vK@5n4gxbwa_w-D% zgz(4%*qxcapFeg3-90niGrO}XOY{BfbH97;*JtkB?tAWOU_bU_KmMN~`(lv(ZQG`| z4_EpqY91hS0CWR55WrLcbOO5o?gAta$Qy+Ew?=MAr_;HK);3`Q$mjDt3b>F8iy=5_ zjBauyo}z~PyA5Nbh_Iz1(c%D*6uVG35lUSH$ii0o79c~xvUj#`e`x;v`Q;Y+Z4GR@ zun?kdCosQNKI;tyaA`V~daxBUV%!19X0tP)>vsy|+z6Ti@HY{xhmy_Iu|)yzYL&|F zN=Jv6D3!WMP6Kp{q7Db-H~?P*&>7)#g9a`!P4o5f=pR=Avab7iQG6W0Vb!hN4ak#1 zd7`aNo=zr{?=<7vb=^+S^G={>rvO-7dkhRgaFLZt{eCR^fpG-Dw%v0mTm@iKl_>CT z4Y<`b&G*LY-;YctlbAAP>USu-0gxHtb`rN(rg4LasAhdP9RQM=op)~raBZkb3An1P z>p$;JCJW8_Z3LS)zuh&dbLc7oe?nkNP#7NnU~O|XTZZBKNpA*na&tKk`t%jdrZ;^C0_0VwY>%d&Lutt-9ctR4TQ#5pkJJCUHPl*AhUx4B(tb`T+6@ zgmT_fylHak<(+cE?W_4&BcGR?c|^{M+5ASpWJSALs- zYZ|%vjT<+%XYKqwARZh0%>#gZhN3;2bDX6lW1m>v-Q8~yyd|hvt{)s6JfL2mx=!sv zp^v}#vwO4`NAKH#p2{xs#5ntkl%Yr;LxE`>AUsl z^=E*>)d2~D-M>mC4oQM~a$AZOV5eC#Yt8^&q?{XWwy+jYNMi(4}QqU+}X z@JDQ2cX#(&QG@34g>wnHBu?D;AvbJsoSHZDF+;>7@kqd^a6v6@%>WQMCFl@;jvBC7 zEKZh@x3PT!4YDlmbysu=Y8ISat4~z`1_uWZ0O5a@!Yk{4i5hIU>|KT6i_%16p5qiQ zj3T6y$(I56XYI6Hp>TAVR~3Na;o)NeBtEh5)t;VN`7rO}R#F$>2l1L)3kt7{Y8Mej zLEwiIrK<9jRRK`I+(0K_Lzv@-Z5O@>!Srak6M}hLT(>uhAY?$hKsz$bsj&vg5&kO$ zt-qS{38?eTsP>73rqmfx?LvmM^N28~)*_e=5a>ld`$bK77P$V{(5n8^evk}kCt!s+ zwPrw9;7a8END;D67ry`~OvX^c+PlJp8UbK{xppy%^nRN4_CWBnU)i zpKaToBtV2@NW8Y$aYuJ2zMR2!TN8u#pSG z96!YK)(X(nX-sP%j%k{&L@~5rLZ{|ztqQ=0ot-ZL@SnebuD?*I`?#?zYY2k-WA(Ne zBD^muB|q|rmPG-h($|N1RRI`1TYJH8Yg1}bnCC~iz5ONtJEJAH6s|78bAT|iu6s0s z?QY%yhv5KH10UgaSCUL4q*m!W^!#UZr#^dP5A53;NQz; zvu4!5mSw%K0VmfF!q^K^1lY(*<+QltxJ)LKpzs%=-gnhI9jY6ER4TO&pnow;h~Bm| zPN->`StxZZfSM5>6Nra(P5R;zvj8x8%Ct)n-1fdZ{87h>di=Tp5D`LY*M-dIJB|~% z2M0!Gf}JwH0q|!C?Jb&t06QR9k~WQV;ubvsc(YhMR771L@Im}?&YbCWHN+e;80!VH z+Rs;z$FkY%%s4R%MJkqQ-sY9dvq7u?@L^mZA9ezAt5+${wo<8k8##i{WHO0F+52Ol zhKP7=$BylF&jo=7{*(g)12gpY#A^V2H!Xj!l!i{|?d=_o>%SMpVsUc0tWKiFGeFG4 zNEZ3vQK4@`!&WG4638Qa_UwE%ylaa?&dIL;aGB3krX~yGDpwj>m2$blvC!0d_$$H$ ztI~#X$#@DRT-WU!8X8i~1hN+FeExD0_xNo>xg?!7R>t*hcv;Rm&Upd}Rb0q@AMDi5XUT&GGA2s5mspnG8ac%?TnxG0iUuxIS{Pe6@JDTw|USu+v z#N??{e$&?7?{!4p7>Mf z_#OaWhw>I!mKB-E^`F6HURTl<+Bd90=y}mO8ssuQ$CDVe=?&v zPT^FJEKyt5X(q!Wh+Z_C+wps$8nCN$li~p4j-eNK?t5C@PK6)e;v#B0CCtS zpUn*vi-uOIEEM3#(a+{A2&RFUDnJ`Y@`<|uxPWYiaI;1}pGu|P_;091-jDs*kN;i# Y2Wxc(Y@iPbXFR9J=Oms@O9RTPH5wP!jYB;_JT1S-&gDFjReB>{q>(AJ7T zDLEQaBK86yCi-B4f)FPS3iZVVMU>hWjq<|uqHQS90n)yJ1ZzXULSifzORF&uFp9LD z*~^ESDQ9|b@s0I#_S)q zw=!71|Cj$&K)RE+AZ$&cpwABy_bj5@f@LKq?n}UOXVVz5_H$re)YYa?ZO|iOq}gY} zZ5^So^_meA!)m9ANE)yjm>P|*v7O|es4D(r$O6)xycyO?6)-D8EdJ1qNeAlc3PSyf z_WS+j)P?J>0v`a$z%^s-s$-R<=LRicSyoQ6!E**-g24Blk^jDS0XUf|J1qF5}z8Ff}Vb z86n>q`z^2>&|Wi;x;FP|FlT{3)tH&b14XxEbAG?yoIc<3u8O=5jO=5@{|LNSTVDLB zo5XP(-z_`l9FPL62$mPu#O6#~zA(E1RApy(@>3VCe;rL_U*rIz751dA&EMxVX5|Ey?=`n?6z|C=-nG5@lxoMez4z~GLpt{D9=bye=9IB6nP0O~PodI}Jh0;t-f zZaiZ=ZvY8{pzdy?)oV7!X$$Ky0IR&vLjbBt5p{Yw4n|oq4Dtw}@UnYs)Y}Xoksi`b zfJB@Loj!LN36axmxHi>2koJTa_T(sbd=+#zzuzwd#SCDc0kGZRavdOKLK9=2f`S4o zaP>X}TyqcFAG#C8*4Eeo=iURq0f;a?4nnA5xTf*%CZNete@o1FPk^B=`jqZ|DJHlqj+) z%39e&K(MN`72zbpgmE`)z8mz$c=HZWGnj)&A}Rul=T6?~_NQiTTp4Y0KL-O}cHIr) zcDS{&6+i@kMs{{$%->K{)Oqt^lL@+q{y#?uW!G5G?RD-h>Nt)MVRuBJZo`$!xH7qX z@u$}0S@UKJEYwiK?Dem#JXBv_uK;Zg4Yv7We3x7O^r9Fr_BEjAx&V3N5dOt%$j#Q>Cu(vuUx6`w}2u{@1B2W!_L@>=5+t%J0wmy$4L7NtA9vs zr|H~K7x?P;zBmT;W3tT2U1+Q+1fCsCJkW^Pt--R=+JORx^lSEF=H_W36Ur1L%SAOw uBW=|Kv;ZxlT90@tYs-r+4F&hXw*LV4fZDKt8Qnqv0000N7w{MeOrqlA9*+1U7zkAMk=iPVTz2}|<&f`4J>^}$^PJkeBFy#X;85hD)3tRm-SWW}>YxYvNDFz_xhybL}d$npt; zdSo$hEEyE=1&~4{(m*s0j0r@X3#I}9$>9_-u+0Km+E}-C-$?u)Q2=I3Wj8AdZ3b|K zwvB;f0NxZtTLXcCw^}!CI1-KSTxnVIs6hWrf|v{9B0U=bP61flT<3m$ST}$X1R&p0 zwt$G%0Z0sSis(6;&HBt=UR%>UT>WZDl4R>s(#iom4&YL4I{{DUPR@9|va(W%R(I3^ zaJgJ!`@Zg{!2Fn|NdaS>6{u{ecOQ;cuNf2-ElxRM6^{_%=KwBLw=rn5UnDH_dR{ml zjn1e7;BvXd_74x&g6M9ImTo~@+~}!(KN@|*MWMqv)=$`k&8Hqy5Uo9YTmN0+l*&}pRn|$051VB(M%rzyTSBa zx~1yV6rcK?x#?|QwJ{&4+-k_UzH!K!qIv1yXq)h^DDTvcdsZB#-Z0u=Jvfk0=d9 zWElBqzzo;+eP8@(&YX%1B60nx{ucp!c1A!L!zYp+2*(fq3NU^VETETK>(_T08J8r< z<{OYV1DF-b(Wt?g?@J79aJgKDudTPH<`kGT4~id=BuRI)>nfWrm0k|!+f^c2k@=|z zE>d40?Rfxs5qQG}gG#pV?T$DK(=C0k0Q`|kl+mlC=j(BG1CXF3EI{!5@OJAK_rHvc z7L+*e1;H7~xsih(pXDgmy_q3+Je~lUJZc$%@7Lpo8BRPms58+PBZH^k&1Z}_v4kLA z5pGBifkV;DLk$p6fR%%4ZoqWXyAgD)e zT0c^Aw7if2@JZV(0BkDJ-&;IYpJ{nRkR-{%#6OAbb8NwcpBlASR#qxt1U)!~MM$19 z^c7$+S$#=_ZAONAIqmxZE-~W73E2fD4_s$NAaJnnj4UljCjgkFzEl+YD1@Un!kBN= z9#1d=C58kN8sB-6mJ%8O;%kBhis82(;5cW2!-EJR@3a~|lC{2%({l8_0x3a8fCzhi zI+v4QFc@PE+ed}?o}#5h41iz`n`1k@D$~o>o+}szz*|!@06@JHv`Lbrp5S!@5VS@1 z3lVk$($)qMM#s4UAz`E{LYybYu^<9-$gmlL*IVf)z;J~aS12&dK2I+#O$1<7iF_W9 z$EW6mt^)>7YR_H+=|)DF@kL}$;{zh&09bmgX&TWnEhjVpz^B1hLT64*`(DHL_`=wB zqH=A}HKR*Q(OU=XS6>pyaIpcVdc*cOf>;>ZCKf@yGjKr52?>B;1RXXqFip!*Lr#^O z07P{f6C-@uSYNfhhqyu6%11H zE|*I%Xikuj;nng&0&x0lZHHRsCygQvtit~!fTM;E#}=O-r<9dOgm*sd&Oy)`h5G$H z89TK+-ETMnn(ePrWMr(tv;Hflh#8|;V)+iw%9BO}5?Y`6Exq)(ZU9(TY5}N@nrUXC zw8(JB=l^=_Dgcc}oY;U~X|8kE8ZlfhmmntXChcWCu5JLDx2)?XLX%3AC@WU&ad;5C z-b%k5@ZZfqJP%3VlT+jhL^zJyv9J3csLmk>_)yNqS}n&! z-pw_A%z-=N6oj3MjJqNo$4Qc8W9DD0Wz4)X-04u*05or`ew#RGGPGF;Pek&vwSIl4 zKj6OwAl--$3(S8!E>iL$C1wGjS4piv@V2)n+0Sdj@r^bztOzU*7SVln$!WL;2L?xi zeB@hqg6Nm5v-~s-#@7HXZLY6g5NXl_fa&*Cj3LmkG&+8>Wy`v-J{V?JJ3HFDGQXXZ z3E(@YMFd9Sxa+38cIaUHH%5KCy4sY^j`nxQPrSLBNVyO|7GQe!c9eisY!N?b*|_#y zGaV#JvdMDdR?ykB7Xf>E#)z_i?%EZ$b|~WMu)TQ6rEKx<0pl{2c)RsP^2~;Y)dOan z0|ry)r;?Q01*3$4Yz$_Rht3VZ0;n6z?Ig&PMt!Z?U0Wm+lsMM_SfUciLh!UmWjm${ zB=Q~3TZrI=sv?X*>ssp8Ix&I*iCk$}a{6(<5=|iWV4q+9ATvL&mJ!3*RPU~i)Yo*8 zRDq*xA&7!1E*Pk~NF04Q+NTvmkiEqDFas;0n$L)^s%fKpnHdG9YlJ%Y@7|M{m2&}r z@0~FOKk2qcb$mF^N~W5BwNO$TVDdO9KjE=Q9hEfsH>~ZRLx*i>8y0> zTaLl4Fk(IViPRWTv9#S$md8Z( z07i%UgkA!+0@!LvKG_s)FeFK`^#s!M1o2z|^8n~A!)0PDH8b0X!-&h}?ei90$yUoM z01gA1Oh6kG{>n@TElS{HR=6i&RMK&U`U3;LloY~18U@5SL72cyxgeSiaH@gM4@h8X zlgGVlB>u0k|oax&d?o zSRtqQtD`N@G(z0|G&Q@VJd1%+1{VRCFdQ8Oe5rum6s)|Qn?3fO;rt)KgpK&aK5x;L zN`hqufwBN)BACVjNCS|H!IPV#0Qv#?9N+^C9AFV>YxS((kN<*N^Z4Jze*l02 V>VUGh1^NI0002ovPDHLkV1jGCL5lzY literal 0 HcmV?d00001 diff --git a/resources/icons/ui/midnight/radio_unchecked.png b/resources/icons/ui/midnight/radio_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..9ffddc4a0715b69284258264cad4ac20eaf9e27c GIT binary patch literal 1007 zcmVU7}Tu4Jq*geaA)P12-|;)S@besm}2 z@5M=C+O%s+Qg8NLy}aki^ZUIg@7ICbJ!~_{*XHKpjvpK~bO0=l6vJ>3bC}BDTnZe< z)A4w&qXILf(rycaF|d6btb4^lr-p`xzHLijdV2aU$L)F_nl~OCsL`@ubURQRx0gu1DypP04l;x7rod=`^@}XNlS7H zrP4EqH-HqztbFH4Di!--!ve)(F(vG>U>oqczx2=Z!^6YZZv|c%xm>P0(%tDds2`~1FnFX7H*F%yOW@ksi-ZUtqV0R*ss5Iv|D}U4# z?Ku;|nP>+NU}j2q-k6~e1u>cXiKJzNUM4U-JNuB(F~o0yfAd&7@Meh{3kxrUO@a0o zi{;1G6mZ2`05W?$olgJJF1#L@OlHZ%Yhi7THNS;MqfjVpmpOC5?~4o9w`Ve$rA`Sz zeLj?b0^P5w^I&~dG~Pg9b{H_jzU(v{!oLE7vjFB+pQ!gY1Wfj5sLI!!hX2RVH?`LO zRskbkYvs2}K+;UD8EX+xty8*LVT;1|{d(;k^@-TB5B!g@TtF}vwz>wlis-s-z4m;4 zk7=5Ol)|haxm5y=6HNgalpB4Gr>2Vlh(lW?Ak)DRp$jbnuGvJ*?(xaV$*zvWB?BD+ zAaKW91U%212fHBHcJIKzi=7t8mr92T54j%`i5-o*!Ln5lm_|cgPUmvD{!YQO*=*G6 z=L|X(?ux4jq&@G7(5JwCk!bhFlGJ%PebM~91t8zA9i5Eii>0^Q zk*FO&uL3Ut^DaEsJbGB;N>;lMUIy;L_-wJK=Xhpd;KpX)*=#m?XJ6kLVJCq<1LKjj z=S{B6TXl3h+AvjjCiv5ufL}Xh0+V(Ns;xylAM`h@#)^a{;aT*24z#QcQrVy zb1l#3^LOL89{^9)lFJekGM#f_#&MjA@B0BTi0cj!a0p`rNYzT5C#W7zCX>Ic#n{k? zNuf~QBMZiWhc}WBc2%a)l;@paH*;eJ!^r3J2^{CBa1NLh$A|$g;nrFPT_Om^lgZ@N dX6oMF@h@G4e&dGkK3M<&002ovPDHLkV1npA;S~S? literal 0 HcmV?d00001 diff --git a/resources/icons/ui/midnight/radio_unchecked@2x.png b/resources/icons/ui/midnight/radio_unchecked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2160a32ed532593c6ed5b88651e64a56f5a54704 GIT binary patch literal 2167 zcmV--2#EKIP)^@RCt{2n}3WPWf{l6&&=N5UE8~2pheT&*`2++TGC=-IgOB} zS`verwooKdFajy5QZ+^i)L;VEA62MQewa`Uf);5oL7}mRAN_;2sY(MFjV|`cU3X`8 z_u8YDB5l>~k9oiT(bmq)&hG8r&MsHI&wum0&-=`Go_+V7nfHALm|=z){-2RfoMiOy z;dx!f%5sXj6p%##QUERh&@X^a;5fkJfaCxtW-6?P z-~&?(lO^#yHT*mkkH01&3{@mTP5?=<3Wckn)CNFSgc)0aGzDAUI&$R6;o;$Oh;hTg zvI;98YBPc1uwvFa6~K*2!+0W$5z%r2GMUW9(DnNS^0_*03c&A0um?&;sNAR;8@CZgM*iN{-Fxab0dFTT$14mw{yD4X^GoK>KiN#orV>C>v+VqK2tMm; zIz&y@q~h^cnu#}6Mhk^lm!qDoo^a()mC~t?4Gj$)Z#0(H%!%!Meq+_e3-$}uaHlRF z0ES|*g09JOg4?|<{9sqKXFEx~(O30Fs9BAc&v2Z321JvaF?(^_?^UWu-O)Sl}`p(gkbY ztzjR8h_F&Ft_S1~E_0NkJ-VBW)azR}0S9e+8Hlwm6QQ(E8-{VH6~XObXlSTNr+lpd zM_uNn$z1MB_4?IJ07=myKXBUw+-{n&{Vi&38>v+4O@f=;Hi6tVF)=Z_R-c*)unL9c z1eUo>WdXhwg3k7$)FTk&&K?rD=v1ln#aiyQM}Wc|?qd=BB#}sD!co~i&dg1oABx0n zwVn+EZ-d!v_Px;b0)Vrf%5*C6R4VmmsA}5;N!`lj4*(eYuTk&`(=cB2`v#5x(e)c} z?&1O9rSs+D0YF3?5>I=K3O5D$1}1>ODsMpiCNy;&LUi$J7JMq`Z%~zhiHV8XAg*xR zoU;Dg&=t0sWGwb70Qab!n=KSB^ZQjLpja$^1VHqhg?}CxSdjDkO?N~@QQ-OEM5(HE z%Bln?V2P&_urGXhok2*Cc7e9o?-MWw$R+M01#L8JVVy!qpLSm2_X(N=lO6{;)BfIH z3z8n~1WdnA&j0;B zn>?KG`vfJxb{{F|RWs-Lf~G}N?qdR0l}~jyb-;Zj;(DVx&rss{v~$Go6EFwBx7o;L zexLIN&AXrj0n!0JUjEhF`!WFc{`=C=LLnBuy!Jv;BV?t=sPsL4zp4bB*;{+rZHp+i z((gCj$>j5w!Mme;+)6HZ&F>ey9zgNG{;m#DTSw5VHNUVzq~iArOh82AcAPG1I1O>- z=;6cjLRHvCNNS{9=e7xDTaa&H0t~}A1Y(!V)Ez07{oCQw7TYdd4{(voG?7ijb_My? zTou-YKk^s^U(RGQiEt#gk90a6rSQ96tK1(n{DC?NFbv}*fbPjKDtcsN2pZeUtT}VP z0Pkz>vEsjbw$}2mnE(+Xl=e-Z`9|BetJc+~3vY~%UnHXLa@&OX{-Q1ed<{K`{9MEvFG(IXGl>o@7{aHU+l9p2T|xaeKa zjE;`_i=*CEX7}|!2*F2PrZRO|H(62TqzM=t96SQ0T?3$a&dMty-Q5q9>`1NgWLT$n zfNyo%gtESovYm!%giJHGpWr%=kyvZx3Xk|#SUL$w$=dc;N&MK?^guEhf2@%hP5pvd z+rAf&4PF)4Tk6uUd;fw3M{9Iz3+Z$^I;+3`VIgkuHN9vV@s+i1dw!ZNlhdty{%H_v zd`*_pWc5Hi{?}&WO_i~+vH6j%=&ya-_WcN|*oB)#xDnoZB>(`4EoLIVK}4KpW4Bf;oy#p3;u!!J1~k77U?;(y zPN}r3*<_^C>1c0X-*OGwDgajl40;SN3uS$y6J8K4-^Rwq$K$%=JOJX_I&NhF_Cl~5 zO5RXP##FZ)>#9^vmP#e3r>Cb+DK(d*NljY_;&K320qCz2^VKuu@_%eae~2GUVP)b>{- z_i(Rvz1KM}UVGX9>74KUzyI&|9nLx5|G=Fdl1;L&_IRoF6{wT~C4do_Qiz~B zisbc&0%w2BsX&#}d@ycJyP!|6v+fw8CqunmBO4N^a9XyBwI2d6B!^}c1`RqCM<%@{ zKC>8$+dQ+SIIecLi1>h7U{^B#yp7T{GSK;Z)&f;d^ImIZ0N9@ZiyOOs??>TqTWl?9 z*LBUMt5eSd?*L`MFUH!V7y7%etXrVG{&<8*OcEHXC?qeP4ZZtBSk}M2wn) z7y3G8QWH%2jM#j^=>(j?4Z;C5*w@vL(g~DUV?QNr(JM|WpKE#y%oy;88gu`!zhiC# z@a1qE$2)7cT>3Zki=kq)H{wPg4Ezcp!k*0%$VbZ(EFw!|{|Okv$)l>x5>PQd z0K{C(6j;>3q}`DNEiL(*z!yc1CaL`y0-=HKX@rXiMLVwBmvc?8tUpnc+~mFw`Ohu) z1}okIYo!%H1h=}Op)gl)$8o#}rvL;!3FxWchc6*B7Z5GXWm+8ZeFuA?gFX=ttHV70}_myXk*y_@o%?ctn0_5+-Z8;Skn$Xxt@Gr9I>sT z-tOS-60`a>d$#V?-7yoZ6C)L(TB?a{)dNI;h^UStp8jB>>oR22RKi0C P00000NkvXXu0mjf>yPoi literal 0 HcmV?d00001 diff --git a/resources/icons/ui/midnight/radio_unchecked_disabled@2x.png b/resources/icons/ui/midnight/radio_unchecked_disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4de5d0d2d8411305a232bf65c59408ce6d9b7e75 GIT binary patch literal 2277 zcmVd{F`UqefQbtW@lz+=72GbVGRGzppm#qp;CHx zP}&y?;@OfhgMeuOXMiXT5;hP500JPs0ErG3)Gi1|{XXxy`;L@!5WpmT z9RrsEye^0ig~R=Cv^MO$6i@D4Wkbria9{~REC+Fs(LDh40a({u=Y4fp4}b&=$aj}L zNJKRNk|IPAz2J1(fA^O^ZSNUwel-+DarVme3II<5xJO@4z?R(X%%`fVs${&m<2JzK z@rYw*yPgK~MqQNzqs9(YHPw4B#+x@56cw#Yy=oVa5#ezFUsBgG=wL`V*0lLvx)M*$ zxD4=kJmT2<7x#nc0bQ0ZL0o*;SN(Q8`G$)^clqQHp#hN{?z{HnYk5uk_XlFlWsB#= z&hhtEf#^{keL}EUdbrN}K|Hwh-FY4$$!PJn_`f7D=ta;4B#r6}S&W_U!Su^Kc1yMe3v}OXP_aP;@v;A~S zEO}yj1(vN`HxCd;0VJ#Fea&^=wXk4z7BBMM<-Z`pb83zW549Zd?j7n{F;u|P;a(~JEw2sar2 zzR3|V*E#-Qk3_pJngK-N@Nae8{+4?0iA022iPlE%XAEpnbIf?swPww@!LAw9fMrU> zLJ%!bQ9;RJj|n<0MJO$_1Hgq_1YrVSP5#MX@q@Pj#xJ!5z1&*AyE9>h7Nf1Uwhv6Y zABrDS6vc4JH#8tmDfudxzptW#GRRMxrqW_$*m_?9_(K(y*&{RZjbaTAa7f34&`y%x zY;E*@W}-$*;q&>zVDhOs0zYIFJIE`*%QY7hHJT-Eq(CI}ntx#Q8blayqsN@D<|NT; z=E}1kx!IFWf}n1-=>eqW=;I;`;A_tB0&uFRzqj~yUeL#lc2reWNiZ}&oMaKPP8s0=BGz)W>fO8PJ&=1u_#opzq74~aB@_+Bj`NB!787zQY) z*FIGrV>Ak+stW{+__w}k7#E>ERUa~>0fITMHMY@&w%Xc$03j9SR1`(s!5bQ&StJPw zF)F_KtwMwW#JH>pV2)@ucM9@KN|FGm*8>K8KHq>kCUPEdeW&(z8OX3kp7pSG+tYQ_ zWqnMf0l+63f+Bm)I|fN|nx@P+uMaVr2Xv^51iH^;StABwq0!F3Iekoo0fM2~Y-C`8 zKITq=VAg6UoQa6h69abVaR9gW-wBvo=q{gZg+MFe@puG-eyAZCZTh$f18(fC9anQ9 z(j~eB@YefXIneAVQy|ox`Hnu$a6Nzk-RG|?GF9COfP`K%ztPrK71GBU8o)uhAAoAB znU)kPrFWaE&_Z}T9zjg2bo3shSVIGv8*93V;8#&eL21`-hmTquKijn$z>J$^0zM61 z%Jdt>4!SDL;6JL%1b%dhyX@xSMPmbS-rtE*Fl z1NDW4KhqzM`mdr_rf&mbk%|hE;EJIRRSs!@zhTGcEO9@8eic1Gcvs&`7@@PqL&3GG z;t>E3t2ttt$0Xcm#dEwW-HSe|aXHH#>M|q-9 z6vdf2bNK94tVEXY+h zq@>ErKV`;75WcLfTw=0#EpSzPS6MgnDt8yKEs7)dLtGsR=y<90G92mU7J>uNjJ> zIJ?6c3kC6V04o3(O~Zr4Snsd*Ru4x%V8SrwT2VTM?Y8FuxTAyx2{^)pzcSMYHW~hi zC3ic$?og96^+H2KJMCgS>G>aGv{MFii!J4j>KJcW%A_ z5CG_(0H0&v9Eicons/ui/dark/branch_open.png icons/ui/dark/branch_end.png icons/ui/dark/branch_more.png + icons/ui/dark/radio_checked_disabled.png + icons/ui/dark/radio_checked_disabled@2x.png + icons/ui/dark/radio_checked.png + icons/ui/dark/radio_checked@2x.png + icons/ui/dark/radio_unchecked_disabled.png + icons/ui/dark/radio_unchecked_disabled@2x.png + icons/ui/dark/radio_unchecked.png + icons/ui/dark/radio_unchecked@2x.png icons/ui/midnight/checkbox_checked_disabled.png icons/ui/midnight/checkbox_checked_disabled@2x.png icons/ui/midnight/checkbox_checked.png @@ -73,6 +81,14 @@ icons/ui/midnight/branch_open.png icons/ui/midnight/branch_end.png icons/ui/midnight/branch_more.png + icons/ui/midnight/radio_checked_disabled.png + icons/ui/midnight/radio_checked_disabled@2x.png + icons/ui/midnight/radio_checked.png + icons/ui/midnight/radio_checked@2x.png + icons/ui/midnight/radio_unchecked_disabled.png + icons/ui/midnight/radio_unchecked_disabled@2x.png + icons/ui/midnight/radio_unchecked.png + icons/ui/midnight/radio_unchecked@2x.png images/blank_tileset.png images/collisions.png images/collisions_unknown.png diff --git a/resources/themes/dark.qss b/resources/themes/dark.qss index cf240867..69dc7cd5 100644 --- a/resources/themes/dark.qss +++ b/resources/themes/dark.qss @@ -198,16 +198,23 @@ QCheckBox QWidget:disabled { } QCheckBox::indicator { - margin-left: 4px; height: 16px; width: 16px; } -QRadioButton::indicator::unchecked, QCheckBox::indicator::unchecked { +QCheckBox::indicator::unchecked { image: url(:/icons/ui/dark/checkbox_unchecked.png) } -QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { +QCheckBox::indicator::checked { + image: url(":/icons/ui/dark/checkbox_checked.png"); +} + +QCheckBox::indicator:unchecked:hover { + +} + +QCheckBox::indicator:checked:hover { } @@ -215,18 +222,61 @@ QCheckBox::indicator:unchecked:disabled { image: url(":/icons/ui/dark/checkbox_unchecked_disabled.png"); } -QRadioButton::indicator::checked, QCheckBox::indicator::checked { - image: url(":/icons/ui/dark/checkbox_checked.png"); -} - -QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { - -} - QCheckBox::indicator:checked:disabled { image: url(":/icons/ui/dark/checkbox_checked_disabled.png"); } + +/* Radio Buttons */ + +QRadioButton { + background-color: #19232D; + color: #F0F0F0; + spacing: 4px; + outline: none; + padding-top: 4px; + padding-bottom: 4px; +} + +QRadioButton:focus { + border: none; +} + +QRadioButton QWidget:disabled { + background-color: #19232D; + color: #787878; +} + +QRadioButton::indicator { + height: 16px; + width: 16px; +} + +QRadioButton::indicator::unchecked { + image: url(:/icons/ui/dark/radio_unchecked.png) +} + +QRadioButton::indicator::checked { + image: url(":/icons/ui/dark/radio_checked.png"); +} + +QRadioButton::indicator:unchecked:hover { + +} + +QRadioButton::indicator:checked:hover { + +} + +QRadioButton::indicator:unchecked:disabled { + image: url(":/icons/ui/dark/radio_unchecked_disabled.png"); +} + +QRadioButton::indicator:checked:disabled { + image: url(":/icons/ui/dark/radio_checked_disabled.png"); +} + + /* Map List View */ QTreeView { diff --git a/resources/themes/midnight.qss b/resources/themes/midnight.qss index 6b9e6bfa..734dffa1 100644 --- a/resources/themes/midnight.qss +++ b/resources/themes/midnight.qss @@ -178,6 +178,8 @@ QLineEdit { color: #F8F8F2; } +/* Checkboxes */ + QCheckBox { background-color: #31332b; color: #F8F8F2; @@ -197,16 +199,23 @@ QCheckBox QWidget:disabled { } QCheckBox::indicator { - margin-left: 4px; height: 16px; width: 16px; } -QRadioButton::indicator::unchecked, QCheckBox::indicator::unchecked { +QCheckBox::indicator::unchecked { image: url(:/icons/ui/midnight/checkbox_unchecked.png) } -QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { +QCheckBox::indicator::checked { + image: url(":/icons/ui/midnight/checkbox_checked.png"); +} + +QCheckBox::indicator:unchecked:hover { + +} + +QCheckBox::indicator:checked:hover { } @@ -214,18 +223,61 @@ QCheckBox::indicator:unchecked:disabled { image: url(":/icons/ui/midnight/checkbox_unchecked_disabled.png"); } -QRadioButton::indicator::checked, QCheckBox::indicator::checked { - image: url(":/icons/ui/midnight/checkbox_checked.png"); -} - -QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { - -} - QCheckBox::indicator:checked:disabled { image: url(":/icons/ui/midnight/checkbox_checked_disabled.png"); } + +/* Radio Buttons */ + +QRadioButton { + background-color: #31332b; + color: #F8F8F2; + spacing: 4px; + outline: none; + padding-top: 4px; + padding-bottom: 4px; +} + +QRadioButton:focus { + border: none; +} + +QRadioButton QWidget:disabled { + background-color: #49483E; + color: #F8F8F2; +} + +QRadioButton::indicator { + height: 16px; + width: 16px; +} + +QRadioButton::indicator::unchecked { + image: url(:/icons/ui/midnight/radio_unchecked.png) +} + +QRadioButton::indicator::checked { + image: url(":/icons/ui/midnight/radio_checked.png"); +} + +QRadioButton::indicator:unchecked:hover { + +} + +QRadioButton::indicator:checked:hover { + +} + +QRadioButton::indicator:unchecked:disabled { + image: url(":/icons/ui/midnight/radio_unchecked_disabled.png"); +} + +QRadioButton::indicator:checked:disabled { + image: url(":/icons/ui/midnight/radio_checked_disabled.png"); +} + + /* Map List View */ QTreeView { From 67c3a4befdcd8de9c4463688433075cdd9d86d90 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 12:45:17 -0500 Subject: [PATCH 199/364] Fix config appending 0s to saved window geometry/state --- include/config.h | 4 ++-- src/config.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/config.h b/include/config.h index 7f263da0..b05e4387 100644 --- a/include/config.h +++ b/include/config.h @@ -155,8 +155,8 @@ protected: virtual void setUnreadKeys() override {}; private: - QString stringFromByteArray(QByteArray); - QByteArray bytesFromString(QString); + QString stringFromByteArray(const QByteArray&); + QByteArray bytesFromString(const QString&); QStringList recentProjects; QByteArray mainWindowGeometry; diff --git a/src/config.cpp b/src/config.cpp index b22972a4..c4221d30 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -496,18 +496,18 @@ QMap PorymapConfig::getKeyValueMap() { return map; } -QString PorymapConfig::stringFromByteArray(QByteArray bytearray) { +QString PorymapConfig::stringFromByteArray(const QByteArray &bytearray) { QString ret; - for (auto ch : bytearray) { + for (const auto &ch : bytearray) { ret += QString::number(static_cast(ch)) + ":"; } return ret; } -QByteArray PorymapConfig::bytesFromString(QString in) { +QByteArray PorymapConfig::bytesFromString(const QString &in) { QByteArray ba; - QStringList split = in.split(":"); - for (auto ch : split) { + QStringList split = in.split(":", Qt::SkipEmptyParts); + for (const auto &ch : split) { ba.append(static_cast(ch.toInt())); } return ba; From 1b510b6a6eef7cd534707922c653c8e698f10857 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 14:54:44 -0500 Subject: [PATCH 200/364] Add utility.cpp, fix bug when map/layout name is just underscores --- include/core/map.h | 3 ++- include/core/maplayout.h | 4 +--- include/core/utility.h | 13 +++++++++++ include/project.h | 2 -- porymap.pro | 2 ++ src/core/map.cpp | 13 ++++------- src/core/maplayout.cpp | 11 ++++----- src/core/utility.cpp | 40 ++++++++++++++++++++++++++++++++ src/project.cpp | 24 ++++++------------- src/ui/movablerect.cpp | 9 +++---- src/ui/projectsettingseditor.cpp | 3 ++- src/ui/resizelayoutpopup.cpp | 6 ++--- src/ui/wildmonchart.cpp | 9 ++----- 13 files changed, 82 insertions(+), 57 deletions(-) create mode 100644 include/core/utility.h create mode 100644 src/core/utility.cpp diff --git a/include/core/map.h b/include/core/map.h index b223536d..3df0ed88 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -45,7 +45,8 @@ public: void setConstantName(const QString &constantName) { m_constantName = constantName; } QString constantName() const { return m_constantName; } - static QString mapConstantFromName(QString mapName, bool includePrefix = true); + static QString mapConstantFromName(const QString &name); + QString expectedConstantName() const { return Map::mapConstantFromName(m_name); } void setLayout(Layout *layout); Layout* layout() const { return m_layout; } diff --git a/include/core/maplayout.h b/include/core/maplayout.h index da58c4b7..f8fb1b1c 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -20,9 +20,7 @@ public: Layout() {} Layout(const Layout &other); - static QString layoutConstantFromName(QString mapName); - static QString defaultSuffix(); - + static QString layoutConstantFromName(const QString &name); bool loaded = false; diff --git a/include/core/utility.h b/include/core/utility.h new file mode 100644 index 00000000..b1dbe8bc --- /dev/null +++ b/include/core/utility.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef UTILITY_H +#define UTILITY_H + +#include + +namespace Util { + void numericalModeSort(QStringList &list); + int roundUp(int numToRound, int multiple); + QString toDefineCase(QString input); +} + +#endif // UTILITY_H diff --git a/include/project.h b/include/project.h index f3a1093b..5019a147 100644 --- a/include/project.h +++ b/include/project.h @@ -248,8 +248,6 @@ public: static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); - static void numericalModeSort(QStringList &list); - private: QMap mapSectionDisplayNames; QMap modifiedFileTimestamps; diff --git a/porymap.pro b/porymap.pro index 97265940..2675e700 100644 --- a/porymap.pro +++ b/porymap.pro @@ -51,6 +51,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/core/parseutil.cpp \ src/core/tile.cpp \ src/core/tileset.cpp \ + src/core/utility.cpp \ src/core/validator.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ @@ -162,6 +163,7 @@ HEADERS += include/core/advancemapparser.h \ include/core/parseutil.h \ include/core/tile.h \ include/core/tileset.h \ + include/core/utility.h \ include/core/validator.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ diff --git a/src/core/map.cpp b/src/core/map.cpp index d2ab8ef6..a77744b3 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -2,7 +2,7 @@ #include "map.h" #include "imageproviders.h" #include "scripting.h" - +#include "utility.h" #include "editcommands.h" #include @@ -56,14 +56,9 @@ void Map::setLayout(Layout *layout) { } } -QString Map::mapConstantFromName(QString mapName, bool includePrefix) { - // Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'. - static const QRegularExpression caseChange("([a-z])([A-Z])"); - QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); - const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : ""; - QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper(); - static const QRegularExpression underscores("_+"); - return withMapAndUppercase.replace(underscores, "_"); +// We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID. +QString Map::mapConstantFromName(const QString &name) { + return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); } int Map::getWidth() const { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 2b52a80f..54f630f7 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -4,6 +4,7 @@ #include "scripting.h" #include "imageproviders.h" +#include "utility.h" Layout::Layout(const Layout &other) : Layout() { copyFrom(&other); @@ -32,13 +33,9 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -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("_+"); - return withMapAndUppercase.replace(underscores, "_"); +QString Layout::layoutConstantFromName(const QString &name) { + // TODO: Expose "LAYOUT_" to config + return "LAYOUT_" + Util::toDefineCase(name); } Layout::Settings Layout::settings() const { diff --git a/src/core/utility.cpp b/src/core/utility.cpp new file mode 100644 index 00000000..60f0089d --- /dev/null +++ b/src/core/utility.cpp @@ -0,0 +1,40 @@ +#include "utility.h" + +#include +#include + +// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. +// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable +// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). +// We can use QCollator to sort these lists with better handling for numbers. +void Util::numericalModeSort(QStringList &list) { + static QCollator collator; + collator.setNumericMode(true); + std::sort(list.begin(), list.end(), collator); +} + +int Util::roundUp(int numToRound, int multiple) { + if (multiple <= 0) + return numToRound; + + int remainder = abs(numToRound) % multiple; + if (remainder == 0) + return numToRound; + + if (numToRound < 0) + return -(abs(numToRound) - remainder); + else + return numToRound + multiple - remainder; +} + +// Ex: input 'GraniteCave_B1F' returns 'GRANITE_CAVE_B1F'. +QString Util::toDefineCase(QString input) { + static const QRegularExpression re_CaseChange("([a-z])([A-Z])"); + input.replace(re_CaseChange, "\\1_\\2"); + + // Remove sequential underscores + static const QRegularExpression re_Underscores("_+"); + input.replace(re_Underscores, "_"); + + return input.toUpper(); +} diff --git a/src/project.cpp b/src/project.cpp index 38b1810b..5e839c8e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -10,6 +10,7 @@ #include "filedialog.h" #include "validator.h" #include "orderedjson.h" +#include "utility.h" #include #include @@ -349,7 +350,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setNeedsHealLocation(settings.canFlyTo); // Generate a unique MAP constant. - map->setConstantName(toUniqueIdentifier(Map::mapConstantFromName(map->name()))); + map->setConstantName(toUniqueIdentifier(map->expectedConstantName())); Layout *layout = this->mapLayouts.value(settings.layout.id); if (!layout) { @@ -2074,8 +2075,8 @@ bool Project::readTilesetLabels() { } } - numericalModeSort(this->primaryTilesetLabels); - numericalModeSort(this->secondaryTilesetLabels); + Util::numericalModeSort(this->primaryTilesetLabels); + Util::numericalModeSort(this->secondaryTilesetLabels); bool success = true; if (this->secondaryTilesetLabels.isEmpty()) { @@ -2348,7 +2349,7 @@ bool Project::readRegionMapSections() { if (!this->mapSectionIdNames.contains(defaultName)) { this->mapSectionIdNames.append(defaultName); } - numericalModeSort(this->mapSectionIdNames); + Util::numericalModeSort(this->mapSectionIdNames); return true; } @@ -2372,7 +2373,7 @@ void Project::addNewMapsec(const QString &idName) { } this->mapSectionIdNames.append(idName); - numericalModeSort(this->mapSectionIdNames); + Util::numericalModeSort(this->mapSectionIdNames); this->hasUnsavedDataChanges = true; @@ -2596,7 +2597,7 @@ bool Project::readSongNames() { // Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically. // The default song should be the first in the list, not the first alphabetically, so save that before sorting. this->defaultSong = this->songNames.value(0, "0"); - numericalModeSort(this->songNames); + Util::numericalModeSort(this->songNames); return true; } @@ -3058,14 +3059,3 @@ bool Project::hasUnsavedChanges() { } return false; } - -// TODO: This belongs in a more general utility file, once we have one. -// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. -// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable -// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). -// We can use QCollator to sort these lists with better handling for numbers. -void Project::numericalModeSort(QStringList &list) { - QCollator collator; - collator.setNumericMode(true); - std::sort(list.begin(), list.end(), collator); -} diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index ba323185..fde7f820 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -3,6 +3,7 @@ #include #include "movablerect.h" +#include "utility.h" MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color) : QGraphicsRectItem(0, 0, width, height) @@ -22,10 +23,6 @@ void MovableRect::updateLocation(int x, int y) { ************************************************************************ ******************************************************************************/ -int roundUp(int numToRound, int multiple) { - return (numToRound + multiple - 1) & -multiple; -} - ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) : QObject(parent), MovableRect(enabled, width * 16, height * 16, color) @@ -117,8 +114,8 @@ void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) { } void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - int dx = roundUp(event->scenePos().x() - this->clickedPos.x(), 16); - int dy = roundUp(event->scenePos().y() - this->clickedPos.y(), 16); + int dx = Util::roundUp(event->scenePos().x() - this->clickedPos.x(), 16); + int dy = Util::roundUp(event->scenePos().y() - this->clickedPos.y(), 16); QRect resizedRect = this->clickedRect; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 1250e8dd..c536fd07 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -3,6 +3,7 @@ #include "noscrollcombobox.h" #include "prefab.h" #include "filedialog.h" +#include "utility.h" #include #include @@ -293,7 +294,7 @@ QStringList ProjectSettingsEditor::getWarpBehaviorsList() { void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) { list.removeDuplicates(); - Project::numericalModeSort(list); + Util::numericalModeSort(list); ui->textEdit_WarpBehaviors->setText(list.join("\n")); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 45559b70..20a378b9 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -2,12 +2,10 @@ #include "editor.h" #include "movablerect.h" #include "config.h" +#include "utility.h" #include "ui_resizelayoutpopup.h" -// TODO: put this in a util file or something -extern int roundUp(int, int); - CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { } void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { @@ -62,7 +60,7 @@ void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionChange && scene()) { QPointF newPos = value.toPointF(); - return QPointF(roundUp(newPos.x(), 16), roundUp(newPos.y(), 16)); + return QPointF(Util::roundUp(newPos.x(), 16), Util::roundUp(newPos.y(), 16)); } else return QGraphicsItem::itemChange(change, value); diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 69b7b17e..521706d6 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -2,6 +2,7 @@ #include "wildmonchart.h" #include "ui_wildmonchart.h" #include "config.h" +#include "utility.h" static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts"); @@ -367,13 +368,7 @@ QChart* WildMonChart::createLevelDistributionChart() { series->attachAxis(axisY); // We round the y-axis max up to a multiple of 5. - auto roundUp = [](int num, int multiple) { - auto remainder = num % multiple; - if (remainder == 0) - return num; - return num + multiple - remainder; - }; - axisY->setMax(roundUp(qCeil(axisY->max()), 5)); + axisY->setMax(Util::roundUp(qCeil(axisY->max()), 5)); return chart; } From 11d9d7b7950001b7d8e38e8ae50b1224323445d6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 15:09:06 -0500 Subject: [PATCH 201/364] Add layout prefix to config, fix species prefix hardcoded length --- docsrc/manual/project-files.rst | 1 + include/config.h | 1 + src/config.cpp | 1 + src/core/maplayout.cpp | 3 +-- src/project.cpp | 5 +++-- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 5510c61d..d4c47950 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -109,6 +109,7 @@ In addition to these files, there are some specific symbol and macro names that ``define_attribute_encounter``, ``METATILE_ATTRIBUTE_ENCOUNTER_TYPE``, name used to extract setting from ``symbol_attribute_table`` ``define_metatile_label_prefix``, ``METATILE_``, expected prefix for metatile label macro names ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, default prefix for heal location macro names + ``define_layout_prefix``, ``LAYOUT_``, default prefix for layout macro names ``define_map_prefix``, ``MAP_``, expected prefix for map macro names ``define_map_dynamic``, ``DYNAMIC``, macro name after prefix for Dynamic maps ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps diff --git a/include/config.h b/include/config.h index b05e4387..0cdc102d 100644 --- a/include/config.h +++ b/include/config.h @@ -216,6 +216,7 @@ enum ProjectIdentifier { define_attribute_encounter, define_metatile_label_prefix, define_heal_locations_prefix, + define_layout_prefix, define_map_prefix, define_map_dynamic, define_map_empty, diff --git a/src/config.cpp b/src/config.cpp index c4221d30..d8d2b821 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -101,6 +101,7 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}}, {ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}}, {ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}}, + {ProjectIdentifier::define_layout_prefix, {"define_layout_prefix", "LAYOUT_"}}, {ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}}, {ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "DYNAMIC"}}, {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 54f630f7..110db91c 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -34,8 +34,7 @@ void Layout::copyFrom(const Layout *other) { } QString Layout::layoutConstantFromName(const QString &name) { - // TODO: Expose "LAYOUT_" to config - return "LAYOUT_" + Util::toDefineCase(name); + return projectConfig.getIdentifier(ProjectIdentifier::define_layout_prefix) + Util::toDefineCase(name); } Layout::Settings Layout::settings() const { diff --git a/src/project.cpp b/src/project.cpp index 5e839c8e..4ae42686 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2804,7 +2804,8 @@ bool Project::readSpeciesIconPaths() { const QMap iconIncbins = parser.readCIncbinMulti(incfilename); // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). - const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix))}; + const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); + const QStringList regexList = {QString("\\b%1").arg(speciesPrefix)}; const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); fileWatcher.addPath(root + "/" + constantsFilename); QStringList speciesNames = parser.readCDefineNames(constantsFilename, regexList); @@ -2832,7 +2833,7 @@ bool Project::readSpeciesIconPaths() { } // Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz' - possibleDirNames.append(species.mid(8).toLower()); + possibleDirNames.append(species.mid(speciesPrefix.length()).toLower()); // Permute paths with underscores. // Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo' From 29f88e6b76c6c41bc9f88c9e61120b3e6077b7b5 Mon Sep 17 00:00:00 2001 From: garak Date: Sat, 22 Feb 2025 15:32:14 -0500 Subject: [PATCH 202/364] fix drag+drop on groups moved upward in list --- src/ui/maplistmodels.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 0d42b896..ac07c3a6 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -285,18 +285,18 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i stream >> groupName; } - this->insertRow(row, parentIndex); - // copy children to new node int sourceRow = data->data("application/porymap.mapgroupmodel.source.row").toInt(); QModelIndex originIndex = this->index(sourceRow, 0); QModelIndexList children; QStringList mapsToMove; for (int i = 0; i < this->rowCount(originIndex); ++i ) { - children << this->index( i, 0, originIndex); - mapsToMove << this->index( i, 0 , originIndex).data(MapListUserRoles::NameRole).toString(); + children << this->index(i, 0, originIndex); + mapsToMove << this->index(i, 0 , originIndex).data(MapListUserRoles::NameRole).toString(); } + this->insertRow(row, parentIndex); + QModelIndex groupIndex = index(row, 0, parentIndex); QStandardItem *groupItem = this->itemFromIndex(groupIndex); createMapFolderItem(groupName, groupItem); From 632eeef53ff99b117517c2712f158b7ed262ce51 Mon Sep 17 00:00:00 2001 From: garak Date: Sat, 22 Feb 2025 15:52:04 -0500 Subject: [PATCH 203/364] fix dropping groups beyond end of group list The issue was that the end-of-list drop zone is quite small and groups were being dropped outside of the bounds of the mapList --- src/ui/maplistmodels.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index ac07c3a6..8efbc3c0 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -277,6 +277,11 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i if (parentIndex.row() != -1 || parentIndex.column() != -1) { return false; } + + if (row < 0) { + row = this->rowCount(parentIndex); + } + QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group"); QDataStream stream(&encodedData, QIODevice::ReadOnly); QString groupName; @@ -296,7 +301,6 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i } this->insertRow(row, parentIndex); - QModelIndex groupIndex = index(row, 0, parentIndex); QStandardItem *groupItem = this->itemFromIndex(groupIndex); createMapFolderItem(groupName, groupItem); From a7ae458468761776a1a769db403ccb3ca913bec2 Mon Sep 17 00:00:00 2001 From: garak Date: Sat, 22 Feb 2025 16:15:19 -0500 Subject: [PATCH 204/364] add support for drag+dropping multiple map groups --- src/ui/maplistmodels.cpp | 59 ++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 8efbc3c0..a8c330d2 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -235,17 +235,23 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { QDataStream stream(&encodedData, QIODevice::WriteOnly); - // if dropping a selection containing a group(s) and map(s), clear all selection but first group. + bool droppingGroups = false; + + // if dropping a selection containing both group(s) and map(s), only copy groups for (const QModelIndex &index : indexes) { if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { QString groupName = data(index, MapListUserRoles::NameRole).toString(); stream << groupName; - mimeData->setData("application/porymap.mapgroupmodel.group", encodedData); - mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row())); - return mimeData; + stream << index.row(); + droppingGroups = true; } } + if (droppingGroups) { + mimeData->setData("application/porymap.mapgroupmodel.group", encodedData); + return mimeData; + } + for (const QModelIndex &index : indexes) { if (index.isValid()) { QString mapName = data(index, MapListUserRoles::NameRole).toString(); @@ -284,30 +290,41 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group"); QDataStream stream(&encodedData, QIODevice::ReadOnly); - QString groupName; + QStringList droppedGroups; + QList droppedGroupIndexes; + int rowCount = 0; while (!stream.atEnd()) { + QString groupName; stream >> groupName; + int groupIndex; + stream >> groupIndex; + droppedGroups << groupName; + droppedGroupIndexes << groupIndex; + rowCount++; } - // copy children to new node - int sourceRow = data->data("application/porymap.mapgroupmodel.source.row").toInt(); - QModelIndex originIndex = this->index(sourceRow, 0); - QModelIndexList children; - QStringList mapsToMove; - for (int i = 0; i < this->rowCount(originIndex); ++i ) { - children << this->index(i, 0, originIndex); - mapsToMove << this->index(i, 0 , originIndex).data(MapListUserRoles::NameRole).toString(); - } + for (int r = 0; r < rowCount; r++) { + QString groupName = droppedGroups[r]; + // copy children to new node + int sourceRow = droppedGroupIndexes[r]; + QModelIndex originIndex = this->index(sourceRow, 0); + QModelIndexList children; + QStringList mapsToMove; + for (int i = 0; i < this->rowCount(originIndex); ++i ) { + children << this->index(i, 0, originIndex); + mapsToMove << this->index(i, 0 , originIndex).data(MapListUserRoles::NameRole).toString(); + } - this->insertRow(row, parentIndex); - QModelIndex groupIndex = index(row, 0, parentIndex); - QStandardItem *groupItem = this->itemFromIndex(groupIndex); - createMapFolderItem(groupName, groupItem); + this->insertRow(row + r, parentIndex); + QModelIndex groupIndex = index(row + r, 0, parentIndex); + QStandardItem *groupItem = this->itemFromIndex(groupIndex); + createMapFolderItem(groupName, groupItem); - for (QString mapName : mapsToMove) { - QStandardItem *mapItem = createMapItem(mapName); - groupItem->appendRow(mapItem); + for (QString mapName : mapsToMove) { + QStandardItem *mapItem = createMapItem(mapName); + groupItem->appendRow(mapItem); + } } } else if (data->hasFormat("application/porymap.mapgroupmodel.map")) { From bf5ead848d93c737f4471c3bd770452a7ab3ca8b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 20 Feb 2025 16:31:08 -0500 Subject: [PATCH 205/364] Only load event sprites when requested --- include/core/events.h | 36 ++----- include/project.h | 19 +++- src/core/events.cpp | 93 ++--------------- src/editor.cpp | 6 +- src/project.cpp | 186 ++++++++++++++++++++++++--------- src/ui/draggablepixmapitem.cpp | 2 +- src/ui/mapimageexporter.cpp | 2 +- 7 files changed, 169 insertions(+), 175 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 265ec541..73e61141 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -41,14 +41,6 @@ public: virtual void visitSign(SignEvent *) = 0; }; -struct EventGraphics -{ - QImage spritesheet; - int spriteWidth; - int spriteHeight; - bool inanimate; -}; - /// /// Event base class -- purely virtual @@ -64,11 +56,7 @@ public: Event& operator=(const Event &other) = delete; protected: - Event() { - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } + Event() {} // public enums & static methods public: @@ -143,8 +131,8 @@ public: int getZ() const { return this->elevation; } int getElevation() const { return this->elevation; } - int getPixelX() const { return (this->x * 16) - qMax(0, (this->spriteWidth - 16) / 2); } - int getPixelY() const { return (this->y * 16) - qMax(0, this->spriteHeight - 16); } + int getPixelX() const { return (this->x * 16) - qMax(0, (pixmap.width() - 16) / 2); } + int getPixelY() const { return (this->y * 16) - qMax(0, pixmap.height() - 16); } virtual EventFrame *getEventFrame(); virtual EventFrame *createEventFrame() = 0; @@ -172,14 +160,8 @@ public: void setPixmapItem(DraggablePixmapItem *item); DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; } - void setUsingSprite(bool newUsingSprite) { this->usingSprite = newUsingSprite; } - bool getUsingSprite() const { return this->usingSprite; } - - void setSpriteWidth(int newSpriteWidth) { this->spriteWidth = newSpriteWidth; } - int getspriteWidth() const { return this->spriteWidth; } - - void setSpriteHeight(int newSpriteHeight) { this->spriteHeight = newSpriteHeight; } - int getspriteHeight() const { return this->spriteHeight; } + void setUsesDefaultPixmap(bool newUsesDefaultPixmap) { this->usesDefaultPixmap = newUsesDefaultPixmap; } + bool getUsesDefaultPixmap() const { return this->usesDefaultPixmap; } int getEventIndex(); @@ -204,9 +186,7 @@ protected: int y = 0; int elevation = 0; - int spriteWidth = 16; - int spriteHeight = 16; - bool usingSprite = false; + bool usesDefaultPixmap = true; // Some events can have an associated #define name that should be unique to this event. // e.g. object events can have a 'LOCALID', or Heal Locations have a 'HEAL_LOCATION' id. @@ -273,10 +253,6 @@ public: void setFlag(QString newFlag) { this->flag = newFlag; } QString getFlag() const { return this->flag; } -public: - void setFrameFromMovement(QString movement); - void setPixmapFromSpritesheet(EventGraphics * gfx); - protected: QString gfx; diff --git a/include/project.h b/include/project.h index c2707063..38a31f4a 100644 --- a/include/project.h +++ b/include/project.h @@ -45,7 +45,6 @@ public: QStringList layoutIdsMaster; QMap mapLayouts; QMap mapLayoutsMaster; - QMap eventGraphicsMap; QMap gfxDefines; QString defaultSong; QStringList songNames; @@ -68,7 +67,6 @@ public: QMap unusedMetatileLabels; QMap metatileBehaviorMap; QMap metatileBehaviorMapInverse; - QMap facingDirections; ParseUtil parser; QFileSystemWatcher fileWatcher; QSet modifiedFiles; @@ -210,7 +208,10 @@ public: bool readFieldmapMasks(); QMap> readObjEventGfxInfo(); - void setEventPixmap(Event *event, bool forceLoad = false); + QPixmap getEventPixmap(const QString &gfxName, const QString &movementName); + QPixmap getEventPixmap(const QString &gfxName, int frame, bool hFlip); + QPixmap getEventPixmap(Event::Group group); + void loadEventPixmap(Event *event, bool forceLoad = false); QString fixPalettePath(QString path); QString fixGraphicPath(QString path); @@ -254,6 +255,18 @@ public: private: QMap mapSectionDisplayNames; QMap modifiedFileTimestamps; + QMap facingDirections; + + struct EventGraphics + { + QString filepath; + bool loaded = false; + QImage spritesheet; + int spriteWidth = -1; + int spriteHeight = -1; + bool inanimate = false; + }; + QMap eventGraphicsMap; void updateLayout(Layout *); diff --git a/src/core/events.cpp b/src/core/events.cpp index 3d2ef9d2..37e5ec0a 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -107,9 +107,10 @@ Event::Type Event::typeFromString(QString type) { return typeToStringMap.key(type, Event::Type::None); } -void Event::loadPixmap(Project *) { +void Event::loadPixmap(Project *project) { const QPixmap * pixmap = Event::icons.value(this->getEventGroup()); this->pixmap = pixmap ? *pixmap : QPixmap(); + this->usesDefaultPixmap = true; } void Event::clearIcons() { @@ -230,7 +231,6 @@ void ObjectEvent::setDefaultValues(Project *project) { this->setRadiusX(0); this->setRadiusY(0); this->setSightRadiusBerryTreeID("0"); - this->setFrameFromMovement(project->facingDirections.value(this->getMovement())); } const QSet expectedObjectFields = { @@ -257,78 +257,11 @@ QSet ObjectEvent::getExpectedFields() { } void ObjectEvent::loadPixmap(Project *project) { - EventGraphics *eventGfx = project->eventGraphicsMap.value(this->gfx, nullptr); - if (!eventGfx) { - // Invalid gfx constant. - // If this is a number, try to use that instead. - bool ok; - int altGfx = ParseUtil::gameStringToInt(this->gfx, &ok); - if (ok && (altGfx < project->gfxDefines.count())) { - eventGfx = project->eventGraphicsMap.value(project->gfxDefines.key(altGfx, "NULL"), nullptr); - } - } - if (!eventGfx || eventGfx->spritesheet.isNull()) { - // No sprite associated with this gfx constant. - // Use default sprite instead. + this->pixmap = project->getEventPixmap(this->gfx, this->movement); + if (!this->pixmap.isNull()) { + this->usesDefaultPixmap = false; + } else { Event::loadPixmap(project); - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } else { - this->setFrameFromMovement(project->facingDirections.value(this->movement)); - this->setPixmapFromSpritesheet(eventGfx); - } -} - -void ObjectEvent::setPixmapFromSpritesheet(EventGraphics * gfx) -{ - QImage img; - if (gfx->inanimate) { - img = gfx->spritesheet.copy(0, 0, gfx->spriteWidth, gfx->spriteHeight); - } else { - int x = 0; - int y = 0; - - // Get frame's position in spritesheet. - // Assume horizontal layout. If position would exceed sheet width, try vertical layout. - if ((this->frame + 1) * gfx->spriteWidth <= gfx->spritesheet.width()) { - x = this->frame * gfx->spriteWidth; - } else if ((this->frame + 1) * gfx->spriteHeight <= gfx->spritesheet.height()) { - y = this->frame * gfx->spriteHeight; - } - - img = gfx->spritesheet.copy(x, y, gfx->spriteWidth, gfx->spriteHeight); - - // Right-facing sprite is just the left-facing sprite mirrored - if (this->hFlip) { - img = img.transformed(QTransform().scale(-1, 1)); - } - } - // Set first palette color fully transparent. - img.setColor(0, qRgba(0, 0, 0, 0)); - pixmap = QPixmap::fromImage(img); - this->spriteWidth = gfx->spriteWidth; - this->spriteHeight = gfx->spriteHeight; - this->usingSprite = true; -} - -void ObjectEvent::setFrameFromMovement(QString facingDir) { - // defaults - // TODO: read this from a file somewhere? - this->frame = 0; - this->hFlip = false; - if (facingDir == "DIR_NORTH") { - this->frame = 1; - this->hFlip = false; - } else if (facingDir == "DIR_SOUTH") { - this->frame = 0; - this->hFlip = false; - } else if (facingDir == "DIR_WEST") { - this->frame = 2; - this->hFlip = false; - } else if (facingDir == "DIR_EAST") { - this->frame = 2; - this->hFlip = true; } } @@ -430,19 +363,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); } - - EventGraphics *eventGfx = project->eventGraphicsMap.value(gfx, nullptr); - if (!eventGfx || eventGfx->spritesheet.isNull()) { - // No sprite associated with this gfx constant. - // Use default sprite instead. - Event::loadPixmap(project); - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } else { - this->setFrameFromMovement(project->facingDirections.value(this->movement)); - this->setPixmapFromSpritesheet(eventGfx); - } + ObjectEvent::loadPixmap(project); } diff --git a/src/editor.cpp b/src/editor.cpp index ecc056f0..ad99dded 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1670,7 +1670,7 @@ void Editor::displayMapEvents() { } DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { - this->project->setEventPixmap(event); + this->project->loadEventPixmap(event); auto item = new DraggablePixmapItem(event, this); redrawEventPixmapItem(item); this->events_group->addToGroup(item); @@ -1955,13 +1955,13 @@ qreal Editor::getEventOpacity(const Event *event) const { // - On the Events tab, and the event has a custom sprite (1.0) if (this->editMode != EditMode::Events) return porymapConfig.eventOverlayEnabled ? 0.5 : 0.0; - return event->getUsingSprite() ? 1.0 : 0.7; + return event->getUsesDefaultPixmap() ? 0.7 : 1.0; } void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { if (item && item->event && !item->event->getPixmap().isNull()) { item->setOpacity(getEventOpacity(item->event)); - project->setEventPixmap(item->event, true); + project->loadEventPixmap(item->event, true); item->setPixmap(item->event->getPixmap()); item->setShapeMode(porymapConfig.eventSelectionShapeMode); diff --git a/src/project.cpp b/src/project.cpp index b25b3776..7f9a1ed5 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2664,92 +2664,176 @@ QStringList Project::getEventScriptsFilePaths() const { return filePaths; } -void Project::setEventPixmap(Event *event, bool forceLoad) { +void Project::loadEventPixmap(Event *event, bool forceLoad) { if (event && (event->getPixmap().isNull() || forceLoad)) event->loadPixmap(this); } void Project::clearEventGraphics() { - qDeleteAll(eventGraphicsMap); - eventGraphicsMap.clear(); + qDeleteAll(this->eventGraphicsMap); + this->eventGraphicsMap.clear(); } bool Project::readEventGraphics() { clearEventGraphics(); - fileWatcher.addPaths(QStringList() << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers) - << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info) - << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables) - << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); - const QString pointersFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers); - const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers); - QMap pointerHash = parser.readNamedIndexCArray(pointersFilepath, pointersName); + const QString gfxInfoFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); + const QString picTablesFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables); + const QString gfxFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx); + fileWatcher.addPaths({pointersFilepath, gfxInfoFilepath, picTablesFilepath, gfxFilepath}); - QStringList gfxNames = gfxDefines.keys(); + // Read the table mapping OBJ_EVENT_GFX constants to the names of pointers to data about their graphics. + const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers); + const QMap pointerMap = parser.readNamedIndexCArray(pointersFilepath, pointersName); // The positions of each of the required members for the gfx info struct. // For backwards compatibility if the struct doesn't use initializers. - static const auto gfxInfoMemberMap = QHash{ + static const QHash gfxInfoMemberMap = { {8, "inanimate"}, {11, "oam"}, {12, "subspriteTables"}, {14, "images"}, }; + // Read the structs containing data about each of the event sprites. + auto gfxInfos = parser.readCStructs(gfxInfoFilepath, "", gfxInfoMemberMap); - QString filepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); - auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap); + // We need data in both of these files to translate data from the structs above into the path for a .png file. + const QMap picTables = parser.readCArrayMulti(picTablesFilepath); + const QMap graphicIncbins = parser.readCIncbinMulti(gfxFilepath); - QMap picTables = parser.readCArrayMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables)); - QMap graphicIncbins = parser.readCIncbinMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); + for (auto i = this->gfxDefines.constBegin(); i != this->gfxDefines.constEnd(); i++) { + const QString gfxName = i.key(); - for (QString gfxName : gfxNames) { - QString info_label = pointerHash[gfxName].replace("&", ""); + // Strip the address-of operator to get the pointer's name. We'll use this name to get data about the event's sprite. + // If we don't recognize the name, ignore it. The event will use a default sprite. + QString info_label = pointerMap[gfxName].replace("&", ""); if (!gfxInfos.contains(info_label)) continue; + const QHash gfxInfoAttributes = gfxInfos[info_label]; - auto gfxInfoAttributes = gfxInfos[info_label]; + auto gfx = new EventGraphics; - auto eventGraphics = new EventGraphics; - eventGraphics->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); - QString pic_label = gfxInfoAttributes.value("images"); - QString dimensions_label = gfxInfoAttributes.value("oam"); - QString subsprites_label = gfxInfoAttributes.value("subspriteTables"); - - QString gfx_label = picTables[pic_label].value(0); + // We need the .png filepath for the event's sprite. This is buried behind a few levels of indirection. + // The 'images' field gives us the name of the table containing the sprite's image data. + // The entries in this table are expected to be in the format (PngSymbolName, ...). + // We extract the symbol name of the .png's INCBIN'd data by looking at the first entry in this table. + // Once we have the .png's symbol name we can get the actual filepath from its INCBIN. + QString gfx_label = picTables[gfxInfoAttributes.value("images")].value(0); static const QRegularExpression re_parens("[\\(\\)]"); gfx_label = gfx_label.section(re_parens, 1, 1); - QString path = graphicIncbins[gfx_label]; + gfx->filepath = fixGraphicPath(graphicIncbins[gfx_label]); - if (!path.isNull()) { - path = fixGraphicPath(path); - eventGraphics->spritesheet = QImage(root + "/" + path); - if (!eventGraphics->spritesheet.isNull()) { - // Infer the sprite dimensions from the OAM labels. - static const QRegularExpression re("\\S+_(\\d+)x(\\d+)"); - QRegularExpressionMatch dimensionMatch = re.match(dimensions_label); - QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label); - if (oamTablesMatch.hasMatch()) { - eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); - eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); - } else if (dimensionMatch.hasMatch()) { - eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); - eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); - } else { - eventGraphics->spriteWidth = eventGraphics->spritesheet.width(); - eventGraphics->spriteHeight = eventGraphics->spritesheet.height(); - } - } - } else { - eventGraphics->spritesheet = QImage(); - eventGraphics->spriteWidth = 16; - eventGraphics->spriteHeight = 16; + // Note: gfx has a 'spritesheet' field that will contain a QImage for the event's sprite. + // We don't create this QImage yet. Reading the image now is unnecessary overhead for startup. + // We'll read the image file when the event's sprite is first requested to be drawn. + + // The .png file is expected to be a spritesheet that can have multiple frames. + // We only want to show one frame at a time, so we need to know the dimensions of each frame. + // TODO: Describe different ways we read these. Use width/height? + static const QRegularExpression re("\\S+_(\\d+)x(\\d+)"); + QRegularExpressionMatch dimensionMatch = re.match(gfxInfoAttributes.value("oam")); + QRegularExpressionMatch oamTablesMatch = re.match(gfxInfoAttributes.value("subspriteTables")); + if (oamTablesMatch.hasMatch()) { + gfx->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); + gfx->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); + } else if (dimensionMatch.hasMatch()) { + gfx->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); + gfx->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); } - eventGraphicsMap.insert(gfxName, eventGraphics); + + // Inanimate events will only ever use the first frame of their spritesheet. + gfx->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); + + this->eventGraphicsMap.insert(gfxName, gfx); } return true; } +QPixmap Project::getEventPixmap(const QString &gfxName, const QString &movementName) { + struct FrameData { + int index = 0; + bool hFlip = false; + }; + // TODO: Expose as a setting to users + static const QMap directionToFrameData = { + {"DIR_SOUTH", { .index = 0, .hFlip = false }}, + {"DIR_NORTH", { .index = 1, .hFlip = false }}, + {"DIR_WEST", { .index = 2, .hFlip = false }}, + {"DIR_EAST", { .index = 2, .hFlip = true }}, // East-facing sprite is just the West-facing sprite mirrored + }; + const QString direction = this->facingDirections.value(movementName, "DIR_SOUTH"); + auto frameData = directionToFrameData.value(direction); + return getEventPixmap(gfxName, frameData.index, frameData.hFlip); +} + +QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { + EventGraphics* gfx = this->eventGraphicsMap.value(gfxName, nullptr); + if (!gfx) { + // Invalid gfx constant. If this is a number, try to use that instead. + bool ok; + int gfxNum = ParseUtil::gameStringToInt(gfxName, &ok); + if (ok && gfxNum < this->gfxDefines.count()) { + gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr); + } + } + if (gfx && !gfx->loaded) { + // This is the first request for this event's sprite. We'll attempt to load it now. + if (!gfx->filepath.isEmpty()) { + gfx->spritesheet = QImage(QString("%1/%2").arg(this->root).arg(gfx->filepath)); + if (gfx->spritesheet.isNull()) { + logWarn(QString("Failed to open '%1' for event's sprite. Event will use a default sprite instead.").arg(gfx->filepath)); + } else { + // If we were unable to find the dimensions of a frame within the spritesheet we'll use the full image dimensions. + if (gfx->spriteWidth <= 0) { + gfx->spriteWidth = gfx->spritesheet.width(); + } + if (gfx->spriteHeight <= 0) { + gfx->spriteHeight = gfx->spritesheet.height(); + } + } + } + // Set this whether we were successful or not, we only need to try to load it once. + gfx->loaded = true; + } + if (!gfx || gfx->spritesheet.isNull()) { + // Either we didn't recognize the gfxName, or we were unable to load the sprite's image. + return QPixmap(); + } + + QImage img; + if (gfx->inanimate) { + img = gfx->spritesheet.copy(0, 0, gfx->spriteWidth, gfx->spriteHeight); + } else { + int x = 0; + int y = 0; + + // Get frame's position in spritesheet. + // Assume horizontal layout. If position would exceed sheet width, try vertical layout. + if ((frame + 1) * gfx->spriteWidth <= gfx->spritesheet.width()) { + x = frame * gfx->spriteWidth; + } else if ((frame + 1) * gfx->spriteHeight <= gfx->spritesheet.height()) { + y = frame * gfx->spriteHeight; + } + + img = gfx->spritesheet.copy(x, y, gfx->spriteWidth, gfx->spriteHeight); + if (hFlip) { + img = img.transformed(QTransform().scale(-1, 1)); + } + } + // Set first palette color fully transparent. + img.setColor(0, qRgba(0, 0, 0, 0)); + QPixmap pixmap = QPixmap::fromImage(img); + + // TODO: Cache? + return pixmap; +} + +QPixmap Project::getEventPixmap(Event::Group) { + // TODO + return QPixmap(); +} + bool Project::readSpeciesIconPaths() { this->speciesToIconPath.clear(); diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 846cfb7e..f2fdeb91 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -27,7 +27,7 @@ void DraggablePixmapItem::emitPositionChanged() { } void DraggablePixmapItem::updatePixmap() { - editor->project->setEventPixmap(event, true); + editor->project->loadEventPixmap(event, true); this->updatePosition(); editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 0237795a..4b11ea95 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -517,7 +517,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { || (m_settings.showBGs && group == Event::Group::Bg) || (m_settings.showTriggers && group == Event::Group::Coord) || (m_settings.showHealLocations && group == Event::Group::Heal)) { - m_editor->project->setEventPixmap(event); + m_editor->project->loadEventPixmap(event); eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage()); } } From eef9a37d16185f2cd3bc3a7ebbc82992bf550215 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 12:40:06 -0500 Subject: [PATCH 206/364] Cache event pixmaps --- include/core/events.h | 4 ---- src/core/events.cpp | 40 +------------------------------------ src/mainwindow.cpp | 3 --- src/project.cpp | 46 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 42 insertions(+), 51 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 73e61141..6ecad0c1 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -107,8 +107,6 @@ public: static Event* create(Event::Type type); - static QMap icons; - // standard public methods public: @@ -171,8 +169,6 @@ public: static QString groupToString(Event::Group group); static QString typeToString(Event::Type type); static Event::Type typeFromString(QString type); - static void clearIcons(); - static void setIcons(); // protected attributes protected: diff --git a/src/core/events.cpp b/src/core/events.cpp index 37e5ec0a..c124e58d 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -4,8 +4,6 @@ #include "project.h" #include "config.h" -QMap Event::icons; - Event* Event::create(Event::Type type) { switch (type) { case Event::Type::Object: return new ObjectEvent(); @@ -108,46 +106,10 @@ Event::Type Event::typeFromString(QString type) { } void Event::loadPixmap(Project *project) { - const QPixmap * pixmap = Event::icons.value(this->getEventGroup()); - this->pixmap = pixmap ? *pixmap : QPixmap(); + this->pixmap = project->getEventPixmap(this->getEventGroup()); this->usesDefaultPixmap = true; } -void Event::clearIcons() { - qDeleteAll(icons); - icons.clear(); -} - -void Event::setIcons() { - clearIcons(); - const int w = 16; - const int h = 16; - static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png"); - - // Custom event icons may be provided by the user. - const int numIcons = qMin(defaultIcons.width() / w, static_cast(Event::Group::None)); - for (int i = 0; i < numIcons; i++) { - Event::Group group = static_cast(i); - QString customIconPath = projectConfig.getEventIconPath(group); - if (customIconPath.isEmpty()) { - // No custom icon specified, use the default icon. - icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h)); - continue; - } - - // Try to load custom icon - QString validPath = Project::getExistingFilepath(customIconPath); - if (!validPath.isEmpty()) customIconPath = validPath; // Otherwise allow it to fail with the original path - const QPixmap customIcon = QPixmap(customIconPath); - if (customIcon.isNull()) { - // Custom icon failed to load, use the default icon. - icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h)); - logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath)); - } else { - icons[group] = new QPixmap(customIcon.scaled(w, h)); - } - } -} Event *ObjectEvent::duplicate() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2124bf65..a38505e2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1103,7 +1103,6 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled); - Event::setIcons(); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); @@ -1161,8 +1160,6 @@ void MainWindow::clearProjectUI() { delete this->layoutTreeModel; delete this->layoutListProxyModel; resetMapListFilters(); - - Event::clearIcons(); } void MainWindow::scrollMapList(MapTree *list, const QString &itemName) { diff --git a/src/project.cpp b/src/project.cpp index 7f9a1ed5..bd4e9bf9 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -44,6 +44,7 @@ Project::~Project() clearMapLayouts(); clearEventGraphics(); clearHealLocations(); + QPixmapCache::clear(); } void Project::set_root(QString dir) { @@ -2768,6 +2769,12 @@ QPixmap Project::getEventPixmap(const QString &gfxName, const QString &movementN } QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { + QPixmap pixmap; + const QString cacheKey = QString("EVENT#%1#%2#%3").arg(gfxName).arg(frame).arg(hFlip ? "1" : "0"); + if (QPixmapCache::find(cacheKey, &pixmap)) { + return pixmap; + } + EventGraphics* gfx = this->eventGraphicsMap.value(gfxName, nullptr); if (!gfx) { // Invalid gfx constant. If this is a number, try to use that instead. @@ -2823,15 +2830,44 @@ QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { } // Set first palette color fully transparent. img.setColor(0, qRgba(0, 0, 0, 0)); - QPixmap pixmap = QPixmap::fromImage(img); - // TODO: Cache? + pixmap = QPixmap::fromImage(img); + QPixmapCache::insert(cacheKey, pixmap); return pixmap; } -QPixmap Project::getEventPixmap(Event::Group) { - // TODO - return QPixmap(); +QPixmap Project::getEventPixmap(Event::Group group) { + if (group == Event::Group::None) + return QPixmap(); + + QPixmap pixmap; + const QString cacheKey = QString("EVENT#%1").arg(Event::groupToString(group)); + if (QPixmapCache::find(cacheKey, &pixmap)) { + return pixmap; + } + + const int defaultWidth = 16; + const int defaultHeight = 16; + static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png"); + QPixmap defaultIcon = QPixmap(defaultIcons.copy(static_cast(group) * defaultWidth, 0, defaultWidth, defaultHeight)); + + // Custom event icons may be provided by the user. + QString customIconPath = projectConfig.getEventIconPath(group); + if (customIconPath.isEmpty()) { + // No custom icon specified, use the default icon. + pixmap = defaultIcon; + } else { + // Try to load custom icon + QString validPath = Project::getExistingFilepath(customIconPath); + if (!validPath.isEmpty()) customIconPath = validPath; // Otherwise allow it to fail with the original path + pixmap = QPixmap(customIconPath); + if (pixmap.isNull()) { + pixmap = defaultIcon; + logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath)); + } + } + QPixmapCache::insert(cacheKey, pixmap); + return pixmap; } bool Project::readSpeciesIconPaths() { From 880e5847c71c80fe5190010a41c18f6387fbe1f9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 14:38:39 -0500 Subject: [PATCH 207/364] Remove global scripts from autocomplete --- include/config.h | 2 ++ include/project.h | 1 + src/config.cpp | 3 +++ src/core/map.cpp | 7 +++---- src/project.cpp | 22 +++++++++++++++++----- src/ui/eventframes.cpp | 17 ++++++++++++----- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/include/config.h b/include/config.h index 720bd8d0..0b8f3ee3 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,7 @@ public: this->textEditorGotoLine = ""; this->paletteEditorBitDepth = 24; this->projectSettingsTab = 0; + this->loadAllEventScripts = false; this->warpBehaviorWarningDisabled = false; this->eventDeleteWarningDisabled = false; this->eventOverlayEnabled = false; @@ -135,6 +136,7 @@ public: QString textEditorGotoLine; int paletteEditorBitDepth; int projectSettingsTab; + bool loadAllEventScripts; bool warpBehaviorWarningDisabled; bool eventDeleteWarningDisabled; bool eventOverlayEnabled; diff --git a/include/project.h b/include/project.h index 38a31f4a..4a8ec1f7 100644 --- a/include/project.h +++ b/include/project.h @@ -219,6 +219,7 @@ public: static QString getScriptFileExtension(bool usePoryScript); QString getScriptDefaultString(bool usePoryScript, QString mapName) const; QStringList getEventScriptsFilePaths() const; + void insertGlobalScriptLabels(QStringList &scriptLabels) const; QString getDefaultPrimaryTilesetLabel() const; QString getDefaultSecondaryTilesetLabel() const; diff --git a/src/config.cpp b/src/config.cpp index b56baa4c..22c44b19 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -390,6 +390,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "project_settings_tab") { this->projectSettingsTab = getConfigInteger(key, value, 0); + } else if (key == "load_all_event_scripts") { + this->loadAllEventScripts = getConfigBool(key, value); } else if (key == "warp_behavior_warning_disabled") { this->warpBehaviorWarningDisabled = getConfigBool(key, value); } else if (key == "event_delete_warning_disabled") { @@ -479,6 +481,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("text_editor_goto_line", this->textEditorGotoLine); map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth)); map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); + map.insert("load_all_event_scripts", QString::number(this->loadAllEventScripts)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); map.insert("event_overlay_enabled", QString::number(this->eventOverlayEnabled)); diff --git a/src/core/map.cpp b/src/core/map.cpp index d45a4c1f..7945ab42 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -152,13 +152,12 @@ QStringList Map::getScriptLabels(Event::Group group) { scriptLabels = scriptTracker.getScripts(); } - // Add scripts from map's scripts file, and empty names. + // Add labels from the map's scripts file scriptLabels.append(m_scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); - scriptLabels.prepend("0x0"); - scriptLabels.prepend("NULL"); - scriptLabels.removeAll(""); + scriptLabels.removeAll("0"); + scriptLabels.removeAll("0x0"); scriptLabels.removeDuplicates(); return scriptLabels; diff --git a/src/project.cpp b/src/project.cpp index bd4e9bf9..c8f12a34 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2600,16 +2600,28 @@ bool Project::readMiscellaneousConstants() { } bool Project::readEventScriptLabels() { - globalScriptLabels.clear(); - for (const auto &filePath : getEventScriptsFilePaths()) - globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); + this->globalScriptLabels.clear(); - globalScriptLabels.sort(Qt::CaseInsensitive); - globalScriptLabels.removeDuplicates(); + if (porymapConfig.loadAllEventScripts) + return true; + + for (const auto &filePath : getEventScriptsFilePaths()) + this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); + + this->globalScriptLabels.sort(Qt::CaseInsensitive); + this->globalScriptLabels.removeDuplicates(); return true; } +void Project::insertGlobalScriptLabels(QStringList &scriptLabels) const { + if (this->globalScriptLabels.isEmpty()) + return; + scriptLabels.append(this->globalScriptLabels); + scriptLabels.sort(); + scriptLabels.removeDuplicates(); +} + QString Project::fixPalettePath(QString path) { static const QRegularExpression re_gbapal("\\.gbapal$"); path = path.replace(re_gbapal, ".pal"); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 40a545ab..45085e7c 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -173,12 +173,19 @@ void EventFrame::setActive(bool active) { } void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { - // The script dropdown is populated with scripts used by the map's events and from its scripts file. - if (this->event->getMap()) - combo->addItems(this->event->getMap()->getScriptLabels(this->event->getEventGroup())); + // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. + if (!this->event->getMap()) + return; - // The dropdown's autocomplete has all script labels across the full project. - auto completer = new QCompleter(project->globalScriptLabels, combo); + QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup()); + combo->addItems(scripts); + + // Depending on the settings, the autocomplete may also contain all global scripts. + if (porymapConfig.loadAllEventScripts) { + project->insertGlobalScriptLabels(scripts); + } + + auto completer = new QCompleter(scripts, combo); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); completer->setFilterMode(Qt::MatchContains); From 9afaa4ae3ebb768768632aea4d7cae4dfcb325e0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 15:18:39 -0500 Subject: [PATCH 208/364] Add setting to re-enable global script autocomplete --- forms/preferenceeditor.ui | 498 +++++++++++++++++++--------------- include/project.h | 1 + include/ui/eventframes.h | 4 +- include/ui/preferenceeditor.h | 1 + src/editor.cpp | 1 + src/mainwindow.cpp | 15 +- src/project.cpp | 15 +- src/ui/eventframes.cpp | 3 + src/ui/preferenceeditor.cpp | 6 + 9 files changed, 306 insertions(+), 238 deletions(-) diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index 228c21be..2ce37cbe 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -6,8 +6,8 @@ 0 0 - 522 - 493 + 530 + 432 @@ -19,244 +19,302 @@ 9 - - - Miscellaneous + + + 0 - - - - - If checked, a prompt to reload your project will appear if relevant project files are edited - - - Monitor project files - - - - - - - If checked, Porymap will automatically open your most recently opened project on startup - - - Open recent project on launch - - - - - - - If checked, Porymap will automatically alert you on startup if a new release is available - - - Automatically check for updates - - - - - - - If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. - - - Disable warning when deleting events with IDs - - - - - - - - - - Event Selection Mode - - - - - - If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. - - - Select by clicking on sprite - - - - - - - If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. - - - Select by clicking within bounding rectangle - - - - - - - - - - - 0 - 0 - - - - Application Theme - - - - - - - Preferred Text Editor - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 492 - 327 - + + + General + + + + 12 + + + 12 + + + 12 + + + + + If checked, a prompt to reload your project will appear if relevant project files are edited - - - QLayout::SetMinimumSize - - - - - <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - Goto Line Command - - - - - + + Monitor project files + + + + + + + If checked, Porymap will automatically open your most recently opened project on startup + + + Open recent project on launch + + + + + + + If checked, Porymap will automatically alert you on startup if a new release is available + + + Automatically check for updates + + + + + + + + 0 + 0 + + + + Application Theme + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Events + + + + 12 + + + 12 + + + 12 + + + + + If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. + + + Disable warning when deleting events with IDs + + + + + + + <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. Enabling this setting will make Porymap's startup slower.</p></body></html> + + + Autocomplete Script labels using all possible scripts + + + + + + + Selection Mode + + + + - The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. - - e.g. code %D - - - true - - - - - - <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true + Select by clicking on sprite - - - - Open Directory Command - - - - - + + - The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. - - e.g. code --goto %F:%L - - - true + + Select by clicking within bounding rectangle - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 15 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Text Editor + + + + 12 + + + 12 + + + 12 + + + + + QFrame::Shape::NoFrame + + + true + + + + + 0 + 0 + 476 + 343 + + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Goto Line Command + + + + + + + The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + + + e.g. code %D + + + true + + + + + + + <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Open Directory Command + + + + + + + The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + + + e.g. code --goto %F:%L + + + true + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 15 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/include/project.h b/include/project.h index 4a8ec1f7..bdca7444 100644 --- a/include/project.h +++ b/include/project.h @@ -298,6 +298,7 @@ signals: void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName); void mapSectionIdNamesChanged(const QStringList &idNames); void mapsExcluded(const QStringList &excludedMapNames); + void eventScriptLabelsRead(); }; #endif // PROJECT_H diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index cfde161c..763d6baf 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -31,8 +31,6 @@ public: void invalidateUi(); void invalidateValues(); - void populateScriptDropdown(NoScrollComboBox * combo, Project * project); - virtual void setActive(bool active); public: @@ -59,6 +57,8 @@ protected: bool initialized = false; bool connected = false; + void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + private: Event *event; }; diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h index c59641c2..e4541495 100644 --- a/include/ui/preferenceeditor.h +++ b/include/ui/preferenceeditor.h @@ -23,6 +23,7 @@ public: signals: void preferencesSaved(); void themeChanged(const QString &theme); + void scriptSettingsChanged(bool on); private: Ui::PreferenceEditor *ui; diff --git a/src/editor.cpp b/src/editor.cpp index ad99dded..6c269195 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -13,6 +13,7 @@ #include "customattributesframe.h" #include "validator.h" #include "message.h" +#include "eventframes.h" #include #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a38505e2..f91cc12e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2719,12 +2719,10 @@ void MainWindow::on_actionOpen_Config_Folder_triggered() { void MainWindow::on_actionPreferences_triggered() { if (!preferenceEditor) { preferenceEditor = new PreferenceEditor(this); - connect(preferenceEditor, &PreferenceEditor::themeChanged, - this, &MainWindow::setTheme); - connect(preferenceEditor, &PreferenceEditor::themeChanged, - editor, &Editor::maskNonVisibleConnectionTiles); - connect(preferenceEditor, &PreferenceEditor::preferencesSaved, - this, &MainWindow::togglePreferenceSpecificUi); + connect(preferenceEditor, &PreferenceEditor::themeChanged, this, &MainWindow::setTheme); + connect(preferenceEditor, &PreferenceEditor::themeChanged, editor, &Editor::maskNonVisibleConnectionTiles); + connect(preferenceEditor, &PreferenceEditor::preferencesSaved, this, &MainWindow::togglePreferenceSpecificUi); + connect(preferenceEditor, &PreferenceEditor::scriptSettingsChanged, editor->project, &Project::readEventScriptLabels); } openSubWindow(preferenceEditor); @@ -2739,8 +2737,9 @@ void MainWindow::togglePreferenceSpecificUi() { if (this->updatePromoter) this->updatePromoter->updatePreferences(); - // Redraw all events to use updated porymapConfig.eventSelectionShapeMode - this->editor->redrawAllEvents(); + // Changes to porymapConfig.loadAllEventScripts or porymapConfig.eventSelectionShapeMode + // require us to repopulate the EventFrames and redraw event pixmaps, respectively. + this->editor->updateEvents(); } void MainWindow::openProjectSettingsEditor(int tab) { diff --git a/src/project.cpp b/src/project.cpp index c8f12a34..7977c41b 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2602,15 +2602,14 @@ bool Project::readMiscellaneousConstants() { bool Project::readEventScriptLabels() { this->globalScriptLabels.clear(); - if (porymapConfig.loadAllEventScripts) - return true; - - for (const auto &filePath : getEventScriptsFilePaths()) - this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); - - this->globalScriptLabels.sort(Qt::CaseInsensitive); - this->globalScriptLabels.removeDuplicates(); + if (porymapConfig.loadAllEventScripts) { + for (const auto &filePath : getEventScriptsFilePaths()) + this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); + this->globalScriptLabels.sort(Qt::CaseInsensitive); + this->globalScriptLabels.removeDuplicates(); + } + emit eventScriptLabelsRead(); return true; } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 45085e7c..82334722 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -195,6 +195,9 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj if (popup) popup->setUniformItemSizes(true); combo->setCompleter(completer); + + // If the project changes the script labels, update the EventFrame. + connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index 130e1a62..f9071ec2 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -56,6 +56,7 @@ void PreferenceEditor::updateFields() { ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch); ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates); ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled); + ui->checkBox_AutocompleteAllScripts->setChecked(porymapConfig.loadAllEventScripts); } void PreferenceEditor::saveFields() { @@ -64,6 +65,11 @@ void PreferenceEditor::saveFields() { porymapConfig.theme = theme; emit themeChanged(theme); } + bool loadAllEventScripts = ui->checkBox_AutocompleteAllScripts->isChecked(); + if (loadAllEventScripts != porymapConfig.loadAllEventScripts) { + porymapConfig.loadAllEventScripts = loadAllEventScripts; + emit scriptSettingsChanged(loadAllEventScripts); + } porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape; porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text(); porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text(); From 491b003f2f93e248ab70886f1b5d6acc707d750e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 18:05:06 -0500 Subject: [PATCH 209/364] Make use of the dimension fields in ObjectEventGraphicsInfo --- src/project.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 7977c41b..41375270 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2702,8 +2702,9 @@ bool Project::readEventGraphics() { // The positions of each of the required members for the gfx info struct. // For backwards compatibility if the struct doesn't use initializers. static const QHash gfxInfoMemberMap = { + {4, "width"}, + {5, "height"}, {8, "inanimate"}, - {11, "oam"}, {12, "subspriteTables"}, {14, "images"}, }; @@ -2742,16 +2743,23 @@ bool Project::readEventGraphics() { // The .png file is expected to be a spritesheet that can have multiple frames. // We only want to show one frame at a time, so we need to know the dimensions of each frame. - // TODO: Describe different ways we read these. Use width/height? - static const QRegularExpression re("\\S+_(\\d+)x(\\d+)"); - QRegularExpressionMatch dimensionMatch = re.match(gfxInfoAttributes.value("oam")); - QRegularExpressionMatch oamTablesMatch = re.match(gfxInfoAttributes.value("subspriteTables")); - if (oamTablesMatch.hasMatch()) { - gfx->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); - gfx->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); - } else if (dimensionMatch.hasMatch()) { - gfx->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); - gfx->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); + // The true dimensions are buried in the subsprite data, so we try to infer the dimensions from the name of the 'subspriteTables' symbol. + // If we are unable to do this, we can read the dimensions from the width and height fields. + // This is much more straightforward, but the numbers are not necessarily accurate (one vanilla event sprite, + // the Town Map in FRLG, has width/height values that differ from its true dimensions). + static const QRegularExpression re_dimensions("\\S+_(\\d+)x(\\d+)"); + const QRegularExpressionMatch dimensionsMatch = re_dimensions.match(gfxInfoAttributes.value("subspriteTables")); + if (dimensionsMatch.hasMatch()) { + gfx->spriteWidth = dimensionsMatch.captured(1).toInt(nullptr, 0); + gfx->spriteHeight = dimensionsMatch.captured(2).toInt(nullptr, 0); + } else if (gfxInfoAttributes.contains("width") && gfxInfoAttributes.contains("height")) { + gfx->spriteWidth = gfxInfoAttributes.value("width").toInt(nullptr, 0); + gfx->spriteHeight = gfxInfoAttributes.value("height").toInt(nullptr, 0); + } + // If we fail to get sprite dimensions then they should remain -1, and the sprite will use the full spritesheet as its image. + if (gfx->spriteWidth <= 0 || gfx->spriteHeight <= 0) { + gfx->spriteWidth = -1; + gfx->spriteHeight = -1; } // Inanimate events will only ever use the first frame of their spritesheet. From 7637dc5ad6a9786735c43eedb7e9b5cd7c1ccbae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 18:47:52 -0500 Subject: [PATCH 210/364] Fix Qt5 build --- src/project.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index 41375270..0b9b2af4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2720,7 +2720,8 @@ bool Project::readEventGraphics() { // Strip the address-of operator to get the pointer's name. We'll use this name to get data about the event's sprite. // If we don't recognize the name, ignore it. The event will use a default sprite. - QString info_label = pointerMap[gfxName].replace("&", ""); + QString info_label = pointerMap.value(gfxName); + info_label.replace("&", ""); if (!gfxInfos.contains(info_label)) continue; const QHash gfxInfoAttributes = gfxInfos[info_label]; From ae55d64e5bef0b94882df1405a86847a4c2f8e88 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 19:38:32 -0500 Subject: [PATCH 211/364] Stop rendering map connections twice on load --- src/ui/connectionpixmapitem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index e22b0fab..93bab7c5 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -11,7 +11,8 @@ ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection) this->setEditable(true); setFlag(ItemIsFocusable, true); this->basePixmap = pixmap(); - refresh(); + updateOrigin(); + render(false); // If the connection changes externally we want to update the pixmap to reflect the change. connect(connection, &MapConnection::offsetChanged, this, &ConnectionPixmapItem::updatePos); From b78f23f259a3bea158a6e19600c1714fe6f13bb8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 25 Feb 2025 16:46:46 -0500 Subject: [PATCH 212/364] Make Duplicate Map/Layout more accessible --- forms/mainwindow.ui | 6 ++++++ include/mainwindow.h | 1 + src/mainwindow.cpp | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 01566116..86f50047 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2906,6 +2906,7 @@ + @@ -3278,6 +3279,11 @@ New Layout... + + + Duplicate Current Map/Layout... + + diff --git a/include/mainwindow.h b/include/mainwindow.h index 862ea387..64d5e38f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -361,6 +361,7 @@ private: NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr); void openNewLayoutDialog(); void openDuplicateLayoutDialog(const QString &layoutId); + void openDuplicateMapOrLayoutDialog(); void openNewMapGroupDialog(); void openNewLocationDialog(); void openSubWindow(QWidget * window); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fbe50e75..483004f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -297,6 +297,7 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog); connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); + connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -1412,6 +1413,14 @@ void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { } } +void MainWindow::openDuplicateMapOrLayoutDialog() { + if (this->editor->map) { + openDuplicateMapDialog(this->editor->map->name()); + } else if (this->editor->layout) { + openDuplicateLayoutDialog(this->editor->layout->id); + } +} + void MainWindow::on_actionNew_Tileset_triggered() { auto dialog = new NewTilesetDialog(editor->project, this); connect(dialog, &NewTilesetDialog::applied, [this](Tileset *tileset) { From c6c64aae154744e992b6e735fd9aa9e7d6ec6756 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 25 Feb 2025 18:34:58 -0500 Subject: [PATCH 213/364] Stop automatically saving maps/layouts on creation --- include/core/maplayout.h | 1 + src/core/maplayout.cpp | 2 +- src/mainwindow.cpp | 7 +------ src/project.cpp | 40 ++++++++++++++++++++++++---------------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index da58c4b7..fb4c0118 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -28,6 +28,7 @@ public: QString id; QString name; + QString newFolderPath; int width; int height; diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 2b52a80f..79e3b832 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -438,5 +438,5 @@ QPixmap Layout::getLayoutItemPixmap() { } bool Layout::hasUnsavedChanges() const { - return !this->editHistory.isClean(); + return !this->editHistory.isClean() || !this->newFolderPath.isEmpty(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 483004f8..90b4b676 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1315,12 +1315,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } - if (userSetMap(newMap->name())) { - // TODO: Creating a new map shouldn't be automatically saved. - // For one, it takes away the option to discard the new map. - // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. - save(true); - } + userSetMap(newMap->name()); } // Called any time a new layout is created (including as a byproduct of creating a new map) diff --git a/src/project.cpp b/src/project.cpp index 6810e7d7..ac5d37e6 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -385,20 +385,10 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout // Otherwise the new layout's folder name will just be the layout's name. const QString folderName = !settings.folderName.isEmpty() ? settings.folderName : layout->name; const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders) + folderName; + layout->newFolderPath = folderPath; layout->border_path = folderPath + "/border.bin"; layout->blockdata_path = folderPath + "/map.bin"; - // Create a new directory for the layout, if it doesn't already exist. - const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); - if (!QDir::root().mkpath(fullPath)) { - logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); - delete layout; - return nullptr; - } - - this->mapLayouts.insert(layout->id, layout); - this->layoutIds.append(layout->id); - if (layout->blockdata.isEmpty()) { // Fill layout using default fill settings setNewLayoutBlockdata(layout); @@ -408,7 +398,15 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout setNewLayoutBorder(layout); } - saveLayout(layout); // TODO: Ideally we shouldn't automatically save new layouts + // No need for a full load, we already have all the blockdata. + layout->loaded = loadLayoutTilesets(layout); + if (!layout->loaded) { + delete layout; + return nullptr; + } + + this->mapLayouts.insert(layout->id, layout); + this->layoutIds.append(layout->id); emit layoutCreated(layout); @@ -948,25 +946,25 @@ bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); - logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset)); layout->tileset_primary_label = defaultTileset; layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { - logError(QString("Failed to set default primary tileset.")); + logError(QString("%1 has invalid primary tileset '%2'.").arg(layout->name).arg(layout->tileset_primary_label)); return false; } + logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset)); } layout->tileset_secondary = getTileset(layout->tileset_secondary_label); if (!layout->tileset_secondary) { QString defaultTileset = this->getDefaultSecondaryTilesetLabel(); - logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset)); layout->tileset_secondary_label = defaultTileset; layout->tileset_secondary = getTileset(layout->tileset_secondary_label); if (!layout->tileset_secondary) { - logError(QString("Failed to set default secondary tileset.")); + logError(QString("%1 has invalid secondary tileset '%2'.").arg(layout->name).arg(layout->tileset_secondary_label)); return false; } + logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset)); } return true; } @@ -1266,6 +1264,16 @@ void Project::saveLayout(Layout *layout) { if (!layout || !layout->loaded) return; + if (!layout->newFolderPath.isEmpty()) { + // Layout directory doesn't exist yet, create it now. + const QString fullPath = QString("%1/%2").arg(this->root).arg(layout->newFolderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); + return; + } + layout->newFolderPath = QString(); + } + saveLayoutBorder(layout); saveLayoutBlockdata(layout); From 3cf7059ffb3be8bb566fcfd88c6416572805b3da Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 25 Feb 2025 18:51:37 -0500 Subject: [PATCH 214/364] Update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4356d46..72b530af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ## [Unreleased] ### Added -- Redesigned the map list, adding new features including opening/editing layouts with no associated map, duplicating maps or layouts (accessible via right-click), editing the names of map groups, rearranging maps and map groups, and hiding empty folders. +- Redesigned the map list, adding new features including opening/editing layouts with no associated map, editing the names of map groups, rearranging maps and map groups, and hiding empty folders. - Add a drop-down for changing the layout of the currently opened map. +- Add an option to duplicate maps/layouts. - Redesigned the Connections tab, adding new features including the option to open or display diving maps and a list UI for easier edit access. - Add a `Close Project` option - Add a search button to the `Wild Pokémon` tab that shows the encounter data for a species across all maps. @@ -25,6 +26,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ### Changed - `Change Dimensions` now has an interactive resizing rectangle. - Redesigned the new map dialog, including better error checking and a collapsible section for header data. +- New maps/layouts are no longer saved automatically, and can be fully discarded by closing without saving. - Map groups and ``MAPSEC`` names specified when creating a new map will be added automatically if they don't already exist. - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. - Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions. From cdd233f7d512b330b851a136644b927485bfcd8b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Feb 2025 12:38:33 -0500 Subject: [PATCH 215/364] Consider 'shared_scripts_map' when reading map scripts --- src/core/map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/map.cpp b/src/core/map.cpp index 7945ab42..04f5180d 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -168,7 +168,7 @@ QString Map::getScriptsFilePath() const { auto path = QDir::cleanPath(QString("%1/%2/%3/scripts") .arg(projectConfig.projectDir) .arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)) - .arg(m_name)); + .arg(!m_sharedScriptsMap.isEmpty() ? m_sharedScriptsMap : m_name)); auto extension = Project::getScriptFileExtension(usePoryscript); if (usePoryscript && !QFile::exists(path + extension)) extension = Project::getScriptFileExtension(false); From 4e590378f819216c5d80314b45b7b26a6efb18e3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Feb 2025 12:52:24 -0500 Subject: [PATCH 216/364] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b530af..0544760d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add an option to display a dividing line between tilesets in the Tileset Editor. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. -- Add buttons to hide and show empty folders in each map tree view. - Add a setting to specify the tile values to use for the unused metatile layer. +- Add a setting to specify the maximum number of events in a group. A warning will be shown if too many events are added. ### Changed - `Change Dimensions` now has an interactive resizing rectangle. @@ -44,6 +44,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. - Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8. - The prompt to reload the project when a file has changed will now only appear when Porymap is the active application. +- `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. @@ -56,6 +57,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix `About porymap` opening a new window each time it's activated. - Fix the `Edit History` window not raising to the front when reactivated. - New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded. +- Fix species on the wild pokémon tab retaining icons from previously-opened projects. - Fix invalid species names clearing from wild pokémon data when revisited. - Fix editing wild pokémon data not marking the map as unsaved. - Fix editing an event's `Custom Attributes` not marking the map as unsaved. @@ -88,6 +90,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. - The Metatile / Tile usage counts in the Tileset Editor now update to reflect changes. - Fix regression that stopped the map zoom from centering on the cursor. +- Fix `Open Map Scripts` not working on maps with a `shared_scripts_map` field. ## [5.4.1] - 2024-03-21 ### Fixed From 3e1548788838bd903885fb39db1db8d8f8824c8d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 23 Feb 2025 02:25:49 -0500 Subject: [PATCH 217/364] Add missing map group error check --- src/project.cpp | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 7a148d48..8d19fd02 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -314,6 +314,26 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t // Generate a unique MAP constant. map->setConstantName(toUniqueIdentifier(Map::mapConstantFromName(map->name()))); + // Make sure we keep the order of the map names the same as in the map group order. + int mapNamePos; + if (this->groupNames.contains(settings.group)) { + mapNamePos = 0; + for (const auto &name : this->groupNames) { + mapNamePos += this->groupNameToMapNames[name].length(); + if (name == settings.group) + break; + } + } else if (isValidNewIdentifier(settings.group)) { + // Adding map to a map group that doesn't exist yet. + // Create the group, and we already know the map will be last in the list. + addNewMapGroup(settings.group); + mapNamePos = this->mapNames.length(); + } else { + logError(QString("Cannot create new map with invalid map group name '%1'.").arg(settings.group)); + delete map; + return nullptr; + } + Layout *layout = this->mapLayouts.value(settings.layout.id); if (!layout) { // Layout doesn't already exist, create it. @@ -329,24 +349,6 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t } map->setLayout(layout); - // Make sure we keep the order of the map names the same as in the map group order. - int mapNamePos; - if (this->groupNames.contains(settings.group)) { - mapNamePos = 0; - for (const auto &name : this->groupNames) { - mapNamePos += this->groupNameToMapNames[name].length(); - if (name == settings.group) - break; - } - } else { - // Adding map to a map group that doesn't exist yet. - // Create the group, and we already know the map will be last in the list. - if (isValidNewIdentifier(settings.group)) { - addNewMapGroup(settings.group); - } - mapNamePos = this->mapNames.length(); - } - const QString location = map->header()->location(); if (!this->mapSectionIdNames.contains(location) && isValidNewIdentifier(location)) { // Unrecognized MAPSEC name, we can automatically add a new MAPSEC for it. From 36b4ffa02dabca42072c7787729a3e600c883442 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Feb 2025 13:23:26 -0500 Subject: [PATCH 218/364] Fix changes for unsaved new maps applying to map groups --- src/project.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/project.cpp b/src/project.cpp index 8d19fd02..006e5b63 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -664,6 +664,10 @@ void Project::saveMapGroups() { for (const auto &groupName : this->groupNames) { OrderedJson::array groupArr; for (const auto &mapName : this->groupNameToMapNames.value(groupName)) { + if (this->mapCache.value(mapName) && !this->mapCache.value(mapName)->isPersistedToFile()) { + // This is a new map that hasn't been saved yet, don't add it to the global map groups list yet. + continue; + } groupArr.push_back(mapName); } mapGroupsObj[groupName] = groupArr; @@ -1136,6 +1140,7 @@ void Project::saveMap(Map *map, bool skipLayout) { if (!map->isPersistedToFile()) { if (!QDir::root().mkpath(fullPath)) { logError(QString("Failed to create directory for new map: '%1'").arg(fullPath)); + return; } // Create file data/maps//scripts.inc From ded9f724dc21ab390e3a7d7f447e8237f1215edb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 13:10:57 -0500 Subject: [PATCH 219/364] Parser filter lists to QSet --- include/core/parseutil.h | 14 +++++------ src/core/parseutil.cpp | 18 +++++++------- src/project.cpp | 51 ++++++++++++++-------------------------- 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index b8ba9f55..b5fc5dd2 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -54,9 +54,9 @@ public: QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error = nullptr); - QMap readCDefinesByName(const QString &filename, const QStringList &names, QString *error = nullptr); - QStringList readCDefineNames(const QString &filename, const QStringList ®exList, QString *error = nullptr); + QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); + QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); + QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -101,10 +101,10 @@ private: QMap expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; - ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error); - QMap evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error); - bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const; - bool defineNameMatchesFilter(const QString &name, const QList &filterList) const; + ParsedDefines readCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + QMap evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; + bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; static const QRegularExpression re_incScriptLabel; static const QRegularExpression re_globalIncScriptLabel; diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9769d86e..22880562 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -368,11 +368,11 @@ QStringList ParseUtil::readCIncbinArray(const QString &filename, const QString & return paths; } -bool ParseUtil::defineNameMatchesFilter(const QString &name, const QStringList &filterList) const { +bool ParseUtil::defineNameMatchesFilter(const QString &name, const QSet &filterList) const { return filterList.contains(name); } -bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList &filterList) const { +bool ParseUtil::defineNameMatchesFilter(const QString &name, const QSet &filterList) const { for (auto filter : filterList) { if (filter.match(name).hasMatch()) return true; @@ -380,7 +380,7 @@ bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList &filterList, bool useRegex, QString *error) { ParsedDefines result; this->file = filename; @@ -402,10 +402,10 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const return result; // If necessary, construct regular expressions from filter list - QList filterList_Regex; + QSet filterList_Regex; if (useRegex) { for (auto filter : filterList) { - filterList_Regex.append(QRegularExpression(filter)); + filterList_Regex.insert(QRegularExpression(filter)); } } @@ -463,7 +463,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const } // Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on). -QMap ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) { +QMap ParseUtil::evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error) { ParsedDefines defines = readCDefines(filename, filterList, useRegex, error); // Evaluate defines @@ -483,19 +483,19 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS } // Find and evaluate a specific set of defines with known names. -QMap ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names, QString *error) { +QMap ParseUtil::readCDefinesByName(const QString &filename, const QSet &names, QString *error) { return evaluateCDefines(filename, names, false, error); } // Find and evaluate an unknown list of defines with a known name pattern. -QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error) { +QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error) { return evaluateCDefines(filename, regexList, true, error); } // Find an unknown list of defines with a known name pattern. // Similar to readCDefinesByRegex, but for cases where we only need to show a list of define names. // We can skip evaluating any expressions (and by extension skip reporting any errors from this process). -QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList, QString *error) { +QStringList ParseUtil::readCDefineNames(const QString &filename, const QSet ®exList, QString *error) { return readCDefines(filename, regexList, true, error).filteredNames; } diff --git a/src/project.cpp b/src/project.cpp index 0832cb08..19af47c8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1496,7 +1496,7 @@ bool Project::readTilesetMetatileLabels() { QString metatileLabelsFilename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels); fileWatcher.addPath(root + "/" + metatileLabelsFilename); - const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; + const QSet regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; QMap defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); for (QString label : defines.keys()) { @@ -2088,7 +2088,7 @@ bool Project::readFieldmapProperties() { const QString numPalsTotalName = projectConfig.getIdentifier(ProjectIdentifier::define_pals_total); const QString maxMapSizeName = projectConfig.getIdentifier(ProjectIdentifier::define_map_size); const QString numTilesPerMetatileName = projectConfig.getIdentifier(ProjectIdentifier::define_tiles_per_metatile); - const QStringList names = { + const QSet names = { numTilesPrimaryName, numTilesTotalName, numMetatilesPrimaryName, @@ -2172,7 +2172,7 @@ bool Project::readFieldmapMasks() { const QString elevationMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_elevation); const QString behaviorMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior); const QString layerTypeMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer); - const QStringList searchNames = { + const QSet searchNames = { metatileIdMaskName, collisionMaskName, elevationMaskName, @@ -2429,44 +2429,40 @@ bool Project::readHealLocations() { } bool Project::readItemNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items); fileWatcher.addPath(root + "/" + filename); QString error; - this->itemNames = parser.readCDefineNames(filename, regexList, &error); + this->itemNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read item constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readFlagNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags); fileWatcher.addPath(root + "/" + filename); QString error; - this->flagNames = parser.readCDefineNames(filename, regexList, &error); + this->flagNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read flag constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readVarNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars); fileWatcher.addPath(root + "/" + filename); QString error; - this->varNames = parser.readCDefineNames(filename, regexList, &error); + this->varNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read var constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readMovementTypes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement); fileWatcher.addPath(root + "/" + filename); QString error; - this->movementTypes = parser.readCDefineNames(filename, regexList, &error); + this->movementTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read movement type constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2483,33 +2479,30 @@ bool Project::readInitialFacingDirections() { } bool Project::readMapTypes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); fileWatcher.addPath(root + "/" + filename); QString error; - this->mapTypes = parser.readCDefineNames(filename, regexList, &error); + this->mapTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read map type constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readMapBattleScenes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); fileWatcher.addPath(root + "/" + filename); QString error; - this->mapBattleScenes = parser.readCDefineNames(filename, regexList, &error); + this->mapBattleScenes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read map battle scene constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readWeatherNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); fileWatcher.addPath(root + "/" + filename); QString error; - this->weatherNames = parser.readCDefineNames(filename, regexList, &error); + this->weatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read weather constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2519,11 +2512,10 @@ bool Project::readCoordEventWeatherNames() { if (!projectConfig.eventWeatherTriggerEnabled) return true; - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); fileWatcher.addPath(root + "/" + filename); QString error; - this->coordEventWeatherNames = parser.readCDefineNames(filename, regexList, &error); + this->coordEventWeatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read coord event weather constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2533,33 +2525,30 @@ bool Project::readSecretBaseIds() { if (!projectConfig.eventSecretBaseEnabled) return true; - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases); fileWatcher.addPath(root + "/" + filename); QString error; - this->secretBaseIds = parser.readCDefineNames(filename, regexList, &error); + this->secretBaseIds = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read secret base id constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readBgEventFacingDirections() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg); fileWatcher.addPath(root + "/" + filename); QString error; - this->bgEventFacingDirections = parser.readCDefineNames(filename, regexList, &error); + this->bgEventFacingDirections = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read bg event facing direction constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readTrainerTypes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types); fileWatcher.addPath(root + "/" + filename); QString error; - this->trainerTypes = parser.readCDefineNames(filename, regexList, &error); + this->trainerTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read trainer type constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2569,11 +2558,10 @@ bool Project::readMetatileBehaviors() { this->metatileBehaviorMap.clear(); this->metatileBehaviorMapInverse.clear(); - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors); fileWatcher.addPath(root + "/" + filename); QString error; - QMap defines = parser.readCDefinesByRegex(filename, regexList, &error); + QMap defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); if (defines.isEmpty() && projectConfig.metatileBehaviorMask) { // Not having any metatile behavior names is ok (their values will be displayed instead) // but if the user's metatiles can have nonzero values then warn them, as they likely want names. @@ -2592,11 +2580,10 @@ bool Project::readMetatileBehaviors() { } bool Project::readSongNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs); fileWatcher.addPath(root + "/" + filename); QString error; - this->songNames = parser.readCDefineNames(filename, regexList, &error); + this->songNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read song names from '%1': %2").arg(filename).arg(error)); @@ -2608,11 +2595,10 @@ bool Project::readSongNames() { } bool Project::readObjEventGfxConstants() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events); fileWatcher.addPath(root + "/" + filename); QString error; - this->gfxDefines = parser.readCDefinesByRegex(filename, regexList, &error); + this->gfxDefines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2952,10 +2938,9 @@ bool Project::readSpeciesIconPaths() { // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); - const QStringList regexList = {QString("\\b%1").arg(speciesPrefix)}; const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); fileWatcher.addPath(root + "/" + constantsFilename); - QStringList speciesNames = parser.readCDefineNames(constantsFilename, regexList); + QStringList speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); if (speciesNames.isEmpty()) speciesNames = monIconNames.keys(); From 6d8b4f21d8e326c290ecb83f4f4438016c09df70 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 13:32:48 -0500 Subject: [PATCH 220/364] Move hex string conversions to Util --- include/core/utility.h | 1 + include/ui/tilemaptileselector.h | 3 ++- src/config.cpp | 21 +++++++++++---------- src/core/metatile.cpp | 19 ++++++++++--------- src/core/utility.cpp | 6 +++++- src/editor.cpp | 2 +- src/project.cpp | 6 +++--- src/ui/noscrollcombobox.cpp | 3 ++- src/ui/regionmapeditor.cpp | 4 ++-- src/ui/tileseteditor.cpp | 5 ++--- 10 files changed, 39 insertions(+), 31 deletions(-) diff --git a/include/core/utility.h b/include/core/utility.h index b1dbe8bc..1b9277ab 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -8,6 +8,7 @@ namespace Util { void numericalModeSort(QStringList &list); int roundUp(int numToRound, int multiple); QString toDefineCase(QString input); + QString toHexString(uint32_t value, int minLength = 0); } #endif // UTILITY_H diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 867f6302..5c3b8dac 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -5,6 +5,7 @@ #include "selectablepixmapitem.h" #include "paletteutil.h" #include "imageproviders.h" +#include "utility.h" #include using std::shared_ptr; @@ -66,7 +67,7 @@ public: } virtual QString info() const { - return QString("Tile: 0x") + QString("%1 ").arg(this->id(), 4, 16, QChar('0')).toUpper(); + return QString("Tile: %1 ").arg(Util::toHexString(this->id(), 4)); } }; diff --git a/src/config.cpp b/src/config.cpp index 8c3e5c43..232d7e66 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,6 +3,7 @@ #include "shortcut.h" #include "map.h" #include "validator.h" +#include "utility.h" #include #include #include @@ -877,16 +878,16 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); - map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper()); - map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper()); - map.insert("metatile_encounter_type_mask", "0x" + QString::number(this->metatileEncounterTypeMask, 16).toUpper()); - map.insert("metatile_layer_type_mask", "0x" + QString::number(this->metatileLayerTypeMask, 16).toUpper()); - map.insert("block_metatile_id_mask", "0x" + QString::number(this->blockMetatileIdMask, 16).toUpper()); - map.insert("block_collision_mask", "0x" + QString::number(this->blockCollisionMask, 16).toUpper()); - map.insert("block_elevation_mask", "0x" + QString::number(this->blockElevationMask, 16).toUpper()); - map.insert("unused_tile_normal", "0x" + QString::number(this->unusedTileNormal, 16).toUpper()); - map.insert("unused_tile_covered", "0x" + QString::number(this->unusedTileCovered, 16).toUpper()); - map.insert("unused_tile_split", "0x" + QString::number(this->unusedTileSplit, 16).toUpper()); + map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); + map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask)); + map.insert("metatile_encounter_type_mask", Util::toHexString(this->metatileEncounterTypeMask)); + map.insert("metatile_layer_type_mask", Util::toHexString(this->metatileLayerTypeMask)); + map.insert("block_metatile_id_mask", Util::toHexString(this->blockMetatileIdMask)); + map.insert("block_collision_mask", Util::toHexString(this->blockCollisionMask)); + map.insert("block_elevation_mask", Util::toHexString(this->blockElevationMask)); + map.insert("unused_tile_normal", Util::toHexString(this->unusedTileNormal)); + map.insert("unused_tile_covered", Util::toHexString(this->unusedTileCovered)); + map.insert("unused_tile_split", Util::toHexString(this->unusedTileSplit)); map.insert("enable_map_allow_flags", QString::number(this->mapAllowFlagsEnabled)); map.insert("event_icon_path_object", this->eventIconPaths[Event::Group::Object]); map.insert("event_icon_path_warp", this->eventIconPaths[Event::Group::Warp]); diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 5f79129c..09852a12 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -1,6 +1,7 @@ #include "metatile.h" #include "tileset.h" #include "project.h" +#include "utility.h" // Stores how each attribute should be laid out for all metatiles, according to the vanilla games. // Used to set default config values and import maps with AdvanceMap. @@ -42,7 +43,7 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { static int numMetatileIdChars = 4; QString Metatile::getMetatileIdString(uint16_t metatileId) { - return "0x" + QString("%1").arg(metatileId, numMetatileIdChars, 16, QChar('0')).toUpper(); + return Util::toHexString(metatileId, numMetatileIdChars); }; QString Metatile::getMetatileIdStrings(const QList metatileIds) { @@ -127,8 +128,8 @@ void Metatile::setLayout(Project * project) { if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) { uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey(); if (packer.clamp(maxBehavior) != maxBehavior) - logWarn(QString("Metatile Behavior mask '0x%1' is insufficient to contain all available options.") - .arg(QString::number(behaviorMask, 16).toUpper())); + logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.") + .arg(Util::toHexString(behaviorMask))); } attributePackers.insert(Metatile::Attr::Behavior, packer); @@ -136,8 +137,8 @@ void Metatile::setLayout(Project * project) { packer.setMask(terrainTypeMask); const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1; if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) { - logWarn(QString("Metatile Terrain Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(terrainTypeMask, 16).toUpper()) + logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(terrainTypeMask)) .arg(maxTerrainType + 1)); } attributePackers.insert(Metatile::Attr::TerrainType, packer); @@ -146,8 +147,8 @@ void Metatile::setLayout(Project * project) { packer.setMask(encounterTypeMask); const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1; if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) { - logWarn(QString("Metatile Encounter Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(encounterTypeMask, 16).toUpper()) + logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(encounterTypeMask)) .arg(maxEncounterType + 1)); } attributePackers.insert(Metatile::Attr::EncounterType, packer); @@ -156,8 +157,8 @@ void Metatile::setLayout(Project * project) { packer.setMask(layerTypeMask); const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1; if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) { - logWarn(QString("Metatile Layer Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(layerTypeMask, 16).toUpper()) + logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(layerTypeMask)) .arg(maxLayerType + 1)); } attributePackers.insert(Metatile::Attr::LayerType, packer); diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 60f0089d..1053f879 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -5,7 +5,7 @@ // Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. // QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable -// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). +// effect (e.g. 'ROUTE_1, ROUTE_10, ROUTE_2,...' instead of 'ROUTE_1, ROUTE_2,... ROUTE_10'). // We can use QCollator to sort these lists with better handling for numbers. void Util::numericalModeSort(QStringList &list) { static QCollator collator; @@ -38,3 +38,7 @@ QString Util::toDefineCase(QString input) { return input.toUpper(); } + +QString Util::toHexString(uint32_t value, int minLength) { + return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper(); +} diff --git a/src/editor.cpp b/src/editor.cpp index d61a4661..e9111770 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -987,7 +987,7 @@ QString Editor::getMetatileDisplayMessage(uint16_t metatileId) { if (label.size()) message += QString(" \"%1\"").arg(label); if (metatile && metatile->behavior() != 0) { // Skip MB_NORMAL - const QString behaviorStr = this->project->metatileBehaviorMapInverse.value(metatile->behavior(), "0x" + QString::number(metatile->behavior(), 16)); + const QString behaviorStr = this->project->metatileBehaviorMapInverse.value(metatile->behavior(), Util::toHexString(metatile->behavior())); message += QString(", Behavior: %1").arg(behaviorStr); } return message; diff --git a/src/project.cpp b/src/project.cpp index 19af47c8..749e2676 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2197,10 +2197,10 @@ bool Project::readFieldmapMasks() { return false; *value = static_cast(it.value()); if (*value != it.value()){ - logWarn(QString("Value for %1 truncated from '0x%2' to '0x%3'") + logWarn(QString("Value for %1 truncated from '%2' to '%3'") .arg(name) - .arg(QString::number(it.value(), 16).toUpper()) - .arg(QString::number(*value, 16).toUpper())); + .arg(Util::toHexString(it.value())) + .arg(Util::toHexString(*value))); } return true; }; diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index e6e21a4e..21de55a8 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -1,4 +1,5 @@ #include "noscrollcombobox.h" +#include "utility.h" #include #include @@ -82,7 +83,7 @@ void NoScrollComboBox::setNumberItem(int value) void NoScrollComboBox::setHexItem(uint32_t value) { - this->setItem(this->findData(value), "0x" + QString::number(value, 16).toUpper()); + this->setItem(this->findData(value), Util::toHexString(value)); } void NoScrollComboBox::setClearButtonEnabled(bool enabled) { diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 94b7c8c6..2febccd8 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -6,6 +6,7 @@ #include "shortcut.h" #include "config.h" #include "log.h" +#include "utility.h" #include #include @@ -793,8 +794,7 @@ void RegionMapEditor::onRegionMapTileSelectorSelectedTileChanged(unsigned id) { } void RegionMapEditor::onRegionMapTileSelectorHoveredTileChanged(unsigned tileId) { - QString message = QString("Tile: 0x") + QString("%1").arg(tileId, 4, 16, QChar('0')).toUpper(); - this->ui->statusbar->showMessage(message); + this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tileId, 4))); } void RegionMapEditor::onRegionMapTileSelectorHoveredTileCleared() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index f88e6205..835877da 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -10,6 +10,7 @@ #include "filedialog.h" #include "validator.h" #include "eventfilters.h" +#include "utility.h" #include #include #include @@ -420,9 +421,7 @@ void TilesetEditor::queueMetatileReload(uint16_t metatileId) { } void TilesetEditor::onHoveredTileChanged(uint16_t tile) { - QString message = QString("Tile: 0x%1") - .arg(QString("%1").arg(tile, 3, 16, QChar('0')).toUpper()); - this->ui->statusbar->showMessage(message); + this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tile, 3))); } void TilesetEditor::onHoveredTileCleared() { From e260c642b0ddeac9ceb47f432d01bdacc7f7357f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 14:44:19 -0500 Subject: [PATCH 221/364] Speed improvements for Project::readSpeciesIconPaths --- include/project.h | 8 +- src/project.cpp | 167 ++++++++++++++++------------- src/ui/encountertabledelegates.cpp | 2 +- src/ui/projectsettingseditor.cpp | 8 +- src/ui/wildmonsearch.cpp | 2 +- 5 files changed, 106 insertions(+), 81 deletions(-) diff --git a/include/project.h b/include/project.h index 73bb5eeb..5330b792 100644 --- a/include/project.h +++ b/include/project.h @@ -51,6 +51,7 @@ public: QStringList itemNames; QStringList flagNames; QStringList varNames; + QStringList speciesNames; QStringList movementTypes; QStringList mapTypes; QStringList mapBattleScenes; @@ -142,8 +143,8 @@ public: QVector extraEncounterGroups; bool readSpeciesIconPaths(); - QPixmap getSpeciesIcon(const QString &species) const; - QMap speciesToIconPath; + QString getDefaultSpeciesIconPath(const QString &species); + QPixmap getSpeciesIcon(const QString &species); void addNewMapsec(const QString &idName); void removeMapsec(const QString &idName); @@ -255,6 +256,7 @@ private: QMap mapSectionDisplayNames; QMap modifiedFileTimestamps; QMap facingDirections; + QMap speciesToIconPath; struct EventGraphics { @@ -275,6 +277,8 @@ private: void ignoreWatchedFileTemporarily(QString filepath); void recordFileChange(const QString &filepath); + QString findSpeciesIconPath(const QStringList &names) const; + int maxEventsPerGroup; int maxObjectEvents; static int num_tiles_primary; diff --git a/src/project.cpp b/src/project.cpp index 749e2676..5ee65254 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2924,101 +2924,122 @@ QPixmap Project::getEventPixmap(Event::Group group) { bool Project::readSpeciesIconPaths() { this->speciesToIconPath.clear(); + this->speciesNames.clear(); // Read map of species constants to icon names const QString srcfilename = projectConfig.getFilePath(ProjectFilePath::pokemon_icon_table); - fileWatcher.addPath(root + "/" + srcfilename); + fileWatcher.addPath(this->root + "/" + srcfilename); const QString tableName = projectConfig.getIdentifier(ProjectIdentifier::symbol_pokemon_icon_table); const QMap monIconNames = parser.readNamedIndexCArray(srcfilename, tableName); - // Read map of icon names to filepaths - const QString incfilename = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx); - fileWatcher.addPath(root + "/" + incfilename); - const QMap iconIncbins = parser.readCIncbinMulti(incfilename); - // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); - fileWatcher.addPath(root + "/" + constantsFilename); - QStringList speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); - if (speciesNames.isEmpty()) - speciesNames = monIconNames.keys(); + fileWatcher.addPath(this->root + "/" + constantsFilename); + this->speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); + if (this->speciesNames.isEmpty()) { + this->speciesNames = monIconNames.keys(); + } + this->speciesNames.sort(); - // For each species, use the information gathered above to find the icon image. - bool missingIcons = false; - for (auto species : speciesNames) { - QString path = QString(); - if (monIconNames.contains(species) && iconIncbins.contains(monIconNames.value(species))) { - // We have the icon filepath from the icon table - path = QString("%1/%2").arg(root).arg(this->fixGraphicPath(iconIncbins[monIconNames.value(species)])); - } else { - // Failed to read icon filepath from the icon table, check filepaths where icons are normally located. - // Try to use the icon name (if we have it) to determine the directory, then try the species name. - // The name permuting is overkill, but it's making up for some of the fragility in the way we find icon paths. - QStringList possibleDirNames; - if (monIconNames.contains(species)) { - // Ex: For 'gMonIcon_QuestionMark' try 'question_mark' - static const QRegularExpression re("([a-z])([A-Z0-9])"); - QString iconName = monIconNames.value(species); - iconName = iconName.mid(iconName.indexOf("_") + 1); // jump past prefix ('gMonIcon') - possibleDirNames.append(iconName.replace(re, "\\1_\\2").toLower()); - } - - // Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz' - possibleDirNames.append(species.mid(speciesPrefix.length()).toLower()); - - // Permute paths with underscores. - // Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo' - QStringList permutedNames; - for (auto dir : possibleDirNames) { - if (!dir.contains("_")) continue; - for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) { - QString temp = dir; - permutedNames.prepend(temp.replace(i, 1, "/")); - permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last + // If we successfully found the species icon table we can use this data to get the filepath for each species icon. + // For any species not in the table, or if we failed to find the table at all, we will have to predict where the icon file is. + // That can require checking a lot of files (especially for projects with many species), so to save time on startup we only + // do this on request in Project::getDefaultSpeciesIconPath. + if (!monIconNames.isEmpty()) { + const QString iconGraphicsFile = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx); + fileWatcher.addPath(this->root + "/" + iconGraphicsFile); + QMap iconNameToFilepath = parser.readCIncbinMulti(iconGraphicsFile); + + for (auto i = monIconNames.constBegin(); i != monIconNames.constEnd(); i++) { + QString path; + QString species = i.key(); + QString iconName = i.value(); + if (iconNameToFilepath.contains(iconName)) { + path = fixGraphicPath(iconNameToFilepath.value(iconName)); + } else { + // We have an icon name for this species, but we haven't found its filepath. + // Try to find the icon file using the full icon name, and the icon name if we assume it has a prefix. + // Ex: For 'gMonIcon_QuestionMark' search for files by permuting through directories using 'question_mark' and 'g_mon_icon_question_mark. + static const QRegularExpression re_caseChange("([a-z])([A-Z0-9])"); + QStringList dirNames; + if (iconName.contains("_")) { + QString iconNameNoPrefix = iconName.mid(iconName.indexOf("_") + 1); + dirNames.append(iconNameNoPrefix.replace(re_caseChange, "\\1_\\2").toLower()); } - permutedNames.prepend(dir.remove("_")); + QString iconNameWithPrefix = iconName; // Leave iconName unchanged by .replace + dirNames.append(iconNameWithPrefix.replace(re_caseChange, "\\1_\\2").toLower()); + path = iconNameToFilepath[iconName] = findSpeciesIconPath(dirNames); } - possibleDirNames.append(permutedNames); - - possibleDirNames.removeDuplicates(); - for (auto dir : possibleDirNames) { - if (dir.isEmpty()) continue; - const QString stdPath = QString("%1/%2%3/icon.png") - .arg(root) - .arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx)) - .arg(dir); - if (QFile::exists(stdPath)) { - // Icon found at a normal filepath - path = stdPath; - break; - } - } - - if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) { - // Failed to find icon, this species will use a placeholder icon. - logWarn(QString("Failed to find Pokémon icon for '%1'").arg(species)); - missingIcons = true; + if (!path.isEmpty()) { + this->speciesToIconPath.insert(species, QString("%1/%2").arg(this->root).arg(path)); } } - this->speciesToIconPath.insert(species, path); } - - // Logging this alongside every warning (if there are multiple) is obnoxious, just do it once at the end. - if (missingIcons) logInfo("Pokémon icon filepaths can be specified under 'Options->Project Settings'"); - return true; } -QPixmap Project::getSpeciesIcon(const QString &species) const { +QString Project::getDefaultSpeciesIconPath(const QString &species) { + if (this->speciesToIconPath.contains(species)) { + // We already know the icon path for this species (either because we read it from the project, or we found it already). + return this->speciesToIconPath.value(species); + } + if (!this->speciesNames.contains(species)) { + // Don't bother searching for a path if we don't recognize the species name. + return QString(); + } + + // Ex: For 'SPECIES_FOO_BAR_BAZ' search for files by permuting through directories using 'foo_bar_baz'. + const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); + const QString path = findSpeciesIconPath({species.mid(speciesPrefix.length()).toLower()}); + this->speciesToIconPath.insert(species, path); + + // We failed to find a default icon path, this species will use a placeholder icon. + // If the user has no custom icon path for this species, tell them they can provide one. + if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) { + logWarn(QString("Failed to find Pokémon icon for '%1'. The filepath can be specified under 'Options->Project Settings'").arg(species)); + } + return path; +} + +// The name permuting in here is overkill, but it's making up for some of the fragility in the way we find pokémon icon paths. +// For pokeemerald-expansion in particular this function is solely responsible for finding pokémon icons, because they have no icon table. +QString Project::findSpeciesIconPath(const QStringList &names) const { + QStringList possibleDirNames = names; + + // Permute paths with underscores. + // Ex: For a base name of 'foo_bar_baz', try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'. + QStringList permutedNames; + for (auto dir : possibleDirNames) { + if (!dir.contains("_")) continue; + for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) { + QString temp = dir; + permutedNames.prepend(temp.replace(i, 1, "/")); + permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last + } + permutedNames.prepend(dir.remove("_")); + } + possibleDirNames.append(permutedNames); + possibleDirNames.removeDuplicates(); + + const QString basePath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx)); + for (const auto &dir : possibleDirNames) { + if (dir.isEmpty()) continue; + + const QString path = QString("%1%2/icon.png").arg(basePath).arg(dir); + if (QFile::exists(path)) + return path; + } + return QString(); +} + +QPixmap Project::getSpeciesIcon(const QString &species) { QPixmap pixmap; if (!QPixmapCache::find(species, &pixmap)) { // Prefer path from config. If not present, use the path parsed from project files - QString path = projectConfig.getPokemonIconPath(species); + QString path = Project::getExistingFilepath(projectConfig.getPokemonIconPath(species)); if (path.isEmpty()) { - path = this->speciesToIconPath.value(species); - } else { - path = Project::getExistingFilepath(path); + path = getDefaultSpeciesIconPath(species); } QImage img(path); diff --git a/src/ui/encountertabledelegates.cpp b/src/ui/encountertabledelegates.cpp index 4ffe9e0d..2d46f54f 100644 --- a/src/ui/encountertabledelegates.cpp +++ b/src/ui/encountertabledelegates.cpp @@ -21,7 +21,7 @@ void SpeciesComboDelegate::paint(QPainter *painter, const QStyleOptionViewItem & QWidget *SpeciesComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { NoScrollComboBox *editor = new NoScrollComboBox(parent); editor->setFrame(false); - editor->addItems(this->project->speciesToIconPath.keys()); + editor->addItems(this->project->speciesNames); return editor; } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 54aeada2..fa84f3e0 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -104,7 +104,7 @@ void ProjectSettingsEditor::initUi() { if (project) { ui->comboBox_DefaultPrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_DefaultSecondaryTileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_IconSpecies->addItems(project->speciesToIconPath.keys()); + ui->comboBox_IconSpecies->addItems(project->speciesNames); ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys()); } ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings); @@ -278,11 +278,11 @@ void ProjectSettingsEditor::updatePokemonIconPath(const QString &newSpecies) { if (!project) return; // If user was editing a path for a valid species, record filepath text before we wipe it. - if (!this->prevIconSpecies.isEmpty() && this->project->speciesToIconPath.contains(this->prevIconSpecies)) + if (!this->prevIconSpecies.isEmpty() && this->project->speciesNames.contains(this->prevIconSpecies)) this->editedPokemonIconPaths[this->prevIconSpecies] = ui->lineEdit_PokemonIcon->text(); QString editedPath = this->editedPokemonIconPaths.value(newSpecies); - QString defaultPath = this->project->speciesToIconPath.value(newSpecies); + QString defaultPath = this->project->getDefaultSpeciesIconPath(newSpecies); ui->lineEdit_PokemonIcon->setText(this->stripProjectDir(editedPath)); ui->lineEdit_PokemonIcon->setPlaceholderText(this->stripProjectDir(defaultPath)); @@ -567,7 +567,7 @@ void ProjectSettingsEditor::save() { // Save pokemon icon paths const QString species = ui->comboBox_IconSpecies->currentText(); - if (this->project->speciesToIconPath.contains(species)) + if (this->project->speciesNames.contains(species)) this->editedPokemonIconPaths.insert(species, ui->lineEdit_PokemonIcon->text()); for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++) projectConfig.setPokemonIconPath(i.key(), i.value()); diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index f43a0dab..056e7565 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -22,7 +22,7 @@ WildMonSearch::WildMonSearch(Project *project, QWidget *parent) : ui->setupUi(this); // Set up species combo box - ui->comboBox_Search->addItems(project->speciesToIconPath.keys()); + ui->comboBox_Search->addItems(project->speciesNames); ui->comboBox_Search->setCurrentText(QString()); ui->comboBox_Search->lineEdit()->setPlaceholderText(Project::getEmptySpeciesName()); connect(ui->comboBox_Search, &QComboBox::currentTextChanged, this, &WildMonSearch::updateResults); From 8f7e6b94b633ca1ffba17e4eefdca1aea8808e22 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 21:47:03 -0500 Subject: [PATCH 222/364] Cache metatile images for each render pass --- src/core/maplayout.cpp | 50 +++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 79e3b832..ee7040a0 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -322,44 +322,58 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { bool changed_any = false; int width_ = getWidth(); int height_ = getHeight(); - if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) { - image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + if (this->image.isNull() || this->image.width() != width_ * 16 || this->image.height() != height_ * 16) { + this->image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); changed_any = true; } if (this->blockdata.isEmpty() || !width_ || !height_) { - pixmap = pixmap.fromImage(image); - return pixmap; + this->pixmap = this->pixmap.fromImage(this->image); + return this->pixmap; } - QPainter painter(&image); + // There are a lot of external changes that can invalidate a general metatile image cache. + // However, during a single pass at rendering the layout there shouldn't be any changes to + // the tiles, tileset palettes, or metatile layer order/opacity, and layouts often have + // many repeated metatile IDs, so we create a cache for each request to render the layout. + QHash imageCache; + + QPainter painter(&this->image); for (int i = 0; i < this->blockdata.length(); i++) { if (!ignoreCache && !layoutBlockChanged(i, this->cached_blockdata)) { continue; } - changed_any = true; int map_y = width_ ? i / width_ : 0; int map_x = width_ ? i % width_ : 0; if (bounds.isValid() && !bounds.contains(map_x, map_y)) { continue; } - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - Block block = this->blockdata.at(i); - QImage metatile_image = getMetatileImage( - block.metatileId(), - fromLayout ? fromLayout->tileset_primary : this->tileset_primary, - fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary, - metatileLayerOrder, - metatileLayerOpacity - ); - painter.drawImage(metatile_origin, metatile_image); + + uint16_t metatileId = this->blockdata.at(i).metatileId(); + QImage metatileImage; + if (imageCache.contains(metatileId)) { + metatileImage = imageCache.value(metatileId); + } else { + metatileImage = getMetatileImage( + metatileId, + fromLayout ? fromLayout->tileset_primary : this->tileset_primary, + fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary, + metatileLayerOrder, + metatileLayerOpacity + ); + imageCache.insert(metatileId, metatileImage); + } + + QPoint metatileOrigin = QPoint(map_x * 16, map_y * 16); + painter.drawImage(metatileOrigin, metatileImage); + changed_any = true; } painter.end(); if (changed_any) { cacheBlockdata(); - pixmap = pixmap.fromImage(image); + this->pixmap = this->pixmap.fromImage(this->image); } - return pixmap; + return this->pixmap; } QPixmap Layout::renderCollision(bool ignoreCache) { From e0de7fa73f7788caa7a4f632a68521d815339a37 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 28 Feb 2025 13:38:04 -0500 Subject: [PATCH 223/364] Stop unnecessary full redraws of metatile selectors --- include/ui/metatileselector.h | 20 ++++--- include/ui/selectablepixmapitem.h | 6 +-- include/ui/tileseteditormetatileselector.h | 23 +++++--- src/ui/metatileselector.cpp | 24 +++++---- src/ui/tileseteditor.cpp | 6 +-- src/ui/tileseteditormetatileselector.cpp | 62 +++++++++++++++++----- 6 files changed, 97 insertions(+), 44 deletions(-) diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index fba0993d..7bb3f131 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -42,8 +42,10 @@ public: this->cellPos = QPoint(-1, -1); setAcceptHoverEvents(true); } - QPoint getSelectionDimensions(); - void draw(); + + QPoint getSelectionDimensions() override; + void draw() override; + bool select(uint16_t metatile); void selectFromMap(uint16_t metatileId, uint16_t collision, uint16_t elevation); void setTilesets(Tileset*, Tileset*); @@ -53,15 +55,18 @@ public: QPoint getMetatileIdCoordsOnWidget(uint16_t); void setLayout(Layout *layout); bool isInternalSelection() const { return (!this->externalSelection && !this->prefabSelection); } + Tileset *primaryTileset; Tileset *secondaryTileset; protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + void drawSelection() override; private: + QPixmap basePixmap; bool externalSelection; bool prefabSelection; int numMetatilesWide; @@ -72,6 +77,7 @@ private: MetatileSelection selection; QPoint cellPos; + void updateBasePixmap(); void updateSelectedMetatiles(); void updateExternalSelectedMetatiles(); uint16_t getMetatileId(int x, int y) const; diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h index 97a47c43..2681e485 100644 --- a/include/ui/selectablepixmapitem.h +++ b/include/ui/selectablepixmapitem.h @@ -31,9 +31,9 @@ protected: void select(int, int, int, int); void updateSelection(int, int); QPoint getCellPos(QPointF); - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; virtual void drawSelection(); signals: diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index afc77ffe..8a3fdfa7 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -11,9 +11,13 @@ class TilesetEditorMetatileSelector: public SelectablePixmapItem { public: TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout); Layout *layout = nullptr; - void draw(); + + void draw() override; + void drawMetatile(uint16_t metatileId); + void drawSelectedMetatile(); + bool select(uint16_t metatileId); - void setTilesets(Tileset*, Tileset*, bool draw = true); + void setTilesets(Tileset*, Tileset*); uint16_t getSelectedMetatileId(); void updateSelectedMetatile(); QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId); @@ -27,18 +31,21 @@ public: bool showDivider = false; protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; private: + QImage baseImage; + QPixmap basePixmap; Tileset *primaryTileset = nullptr; Tileset *secondaryTileset = nullptr; - uint16_t selectedMetatile; + uint16_t selectedMetatileId; int numMetatilesWide; int numMetatilesHigh; + void updateBasePixmap(); uint16_t getMetatileId(int x, int y); QPoint getMetatileIdCoords(uint16_t); bool shouldAcceptEvent(QGraphicsSceneMouseEvent*); diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index b8bafefc..012b6609 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -14,11 +14,7 @@ int MetatileSelector::numPrimaryMetatilesRounded() const { return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; } -void MetatileSelector::draw() { - if (!this->primaryTileset || !this->secondaryTileset) { - this->setPixmap(QPixmap()); - } - +void MetatileSelector::updateBasePixmap() { int primaryLength = this->numPrimaryMetatilesRounded(); int length_ = primaryLength + this->secondaryTileset->numMetatiles(); int height_ = length_ / this->numMetatilesWide; @@ -39,12 +35,20 @@ void MetatileSelector::draw() { QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); painter.drawImage(metatile_origin, metatile_image); } - painter.end(); - this->setPixmap(QPixmap::fromImage(image)); + this->basePixmap = QPixmap::fromImage(image); +} +void MetatileSelector::draw() { + if (this->basePixmap.isNull()) + updateBasePixmap(); + setPixmap(this->basePixmap); + drawSelection(); +} + +void MetatileSelector::drawSelection() { if (!this->prefabSelection && (!this->externalSelection || (this->externalSelectionWidth == 1 && this->externalSelectionHeight == 1))) { - this->drawSelection(); + SelectablePixmapItem::drawSelection(); } } @@ -76,7 +80,9 @@ void MetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTi this->updateExternalSelectedMetatiles(); else this->updateSelectedMetatiles(); - this->draw(); + + updateBasePixmap(); + draw(); } MetatileSelection MetatileSelector::getMetatileSelection() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index f88e6205..fd9c7919 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -473,7 +473,7 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { } } - this->metatileSelector->draw(); + this->metatileSelector->drawSelectedMetatile(); this->metatileLayersItem->draw(); this->tileSelector->draw(); this->commitMetatileChange(prevMetatile); @@ -601,7 +601,7 @@ void TilesetEditor::on_comboBox_layerType_activated(int layerType) Metatile *prevMetatile = new Metatile(*this->metatile); this->metatile->setLayerType(layerType); this->commitMetatileChange(prevMetatile); - this->metatileSelector->draw(); // Changing the layer type can affect how fully transparent metatiles appear + this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear } } @@ -858,7 +858,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, Q this->metatile = dest; *this->metatile = *src; this->metatileSelector->select(metatileId); - this->metatileSelector->draw(); + this->metatileSelector->drawMetatile(metatileId); this->metatileLayersItem->draw(); this->metatileLayersItem->clearLastModifiedCoords(); this->metatileLayersItem->clearLastHoveredCoords(); diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 07e50bba..4e09cf34 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -5,7 +5,8 @@ TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) : SelectablePixmapItem(32, 32, 1, 1) { - this->setTilesets(primaryTileset, secondaryTileset, false); + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; this->numMetatilesWide = 8; this->layout = layout; setAcceptHoverEvents(true); @@ -72,42 +73,75 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet return image; } +void TilesetEditorMetatileSelector::drawMetatile(uint16_t metatileId) { + QPoint pos = getMetatileIdCoords(metatileId); + + QPainter painter(&this->baseImage); + QImage metatile_image = getMetatileImage( + metatileId, + this->primaryTileset, + this->secondaryTileset, + this->layout->metatileLayerOrder, + this->layout->metatileLayerOpacity, + true) + .scaled(this->cellWidth, this->cellHeight); + painter.drawImage(QPoint(pos.x() * this->cellWidth, pos.y() * this->cellHeight), metatile_image); + painter.end(); + + this->basePixmap = QPixmap::fromImage(this->baseImage); + draw(); +} + +void TilesetEditorMetatileSelector::drawSelectedMetatile() { + drawMetatile(this->selectedMetatileId); +} + +void TilesetEditorMetatileSelector::updateBasePixmap() { + this->baseImage = buildAllMetatilesImage(); + this->basePixmap = QPixmap::fromImage(this->baseImage); +} + void TilesetEditorMetatileSelector::draw() { - this->setPixmap(QPixmap::fromImage(this->buildAllMetatilesImage())); - this->drawGrid(); - this->drawDivider(); - this->drawSelection(); - this->drawFilters(); + if (this->basePixmap.isNull()) + updateBasePixmap(); + setPixmap(this->basePixmap); + + drawGrid(); + drawDivider(); + drawFilters(); + + drawSelection(); } bool TilesetEditorMetatileSelector::select(uint16_t metatileId) { if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) return false; QPoint coords = this->getMetatileIdCoords(metatileId); SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); - this->selectedMetatile = metatileId; + this->selectedMetatileId = metatileId; emit selectedMetatileChanged(metatileId); return true; } -void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset, bool draw) { +void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; - if (draw) this->draw(); + updateBasePixmap(); + draw(); } void TilesetEditorMetatileSelector::updateSelectedMetatile() { QPoint origin = this->getSelectionStart(); uint16_t metatileId = this->getMetatileId(origin.x(), origin.y()); if (Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) - this->selectedMetatile = metatileId; + this->selectedMetatileId = metatileId; else - this->selectedMetatile = Project::getNumMetatilesPrimary() + this->secondaryTileset->numMetatiles() - 1; - emit selectedMetatileChanged(this->selectedMetatile); + this->selectedMetatileId = Project::getNumMetatilesPrimary() + this->secondaryTileset->numMetatiles() - 1; + emit selectedMetatileChanged(this->selectedMetatileId); } uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() { - return this->selectedMetatile; + return this->selectedMetatileId; } uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) { @@ -135,7 +169,7 @@ void TilesetEditorMetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *eve if (!shouldAcceptEvent(event)) return; SelectablePixmapItem::mouseMoveEvent(event); this->updateSelectedMetatile(); - emit hoveredMetatileChanged(this->selectedMetatile); + emit hoveredMetatileChanged(this->selectedMetatileId); } void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { From 87193158aec9907e75bdeacb9d262525c0fe3dde Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 28 Feb 2025 15:17:36 -0500 Subject: [PATCH 224/364] Fix crash when launching in layout-only mode --- src/mainwindow.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 14b176a4..57daee1d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2739,6 +2739,9 @@ void MainWindow::on_actionPreferences_triggered() { connect(preferenceEditor, &PreferenceEditor::themeChanged, this, &MainWindow::setTheme); connect(preferenceEditor, &PreferenceEditor::themeChanged, editor, &Editor::maskNonVisibleConnectionTiles); connect(preferenceEditor, &PreferenceEditor::preferencesSaved, this, &MainWindow::togglePreferenceSpecificUi); + // Changes to porymapConfig.loadAllEventScripts or porymapConfig.eventSelectionShapeMode + // require us to repopulate the EventFrames and redraw event pixmaps, respectively. + connect(preferenceEditor, &PreferenceEditor::preferencesSaved, editor, &Editor::updateEvents); connect(preferenceEditor, &PreferenceEditor::scriptSettingsChanged, editor->project, &Project::readEventScriptLabels); } @@ -2753,10 +2756,6 @@ void MainWindow::togglePreferenceSpecificUi() { if (this->updatePromoter) this->updatePromoter->updatePreferences(); - - // Changes to porymapConfig.loadAllEventScripts or porymapConfig.eventSelectionShapeMode - // require us to repopulate the EventFrames and redraw event pixmaps, respectively. - this->editor->updateEvents(); } void MainWindow::openProjectSettingsEditor(int tab) { From edd2cce110160ca6b9a06cab7b265a1e7abe7136 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 2 Mar 2025 18:05:33 -0500 Subject: [PATCH 225/364] New event button refactor --- include/core/events.h | 5 +- include/ui/neweventtoolbutton.h | 31 +++---- src/core/events.cpp | 47 ++++++++--- src/mainwindow.cpp | 39 +++------ src/project.cpp | 2 +- src/ui/neweventtoolbutton.cpp | 142 +++++++++----------------------- 6 files changed, 99 insertions(+), 167 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 6ecad0c1..fc0b90d8 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -66,7 +66,6 @@ public: Trigger, WeatherTrigger, Sign, HiddenItem, SecretBase, HealLocation, - Generic, None, }; @@ -168,7 +167,9 @@ public: static QString groupToString(Event::Group group); static QString typeToString(Event::Type type); - static Event::Type typeFromString(QString type); + static QString typeToJsonKey(Event::Type type); + static Event::Type typeFromJsonKey(QString type); + static QList types(); // protected attributes protected: diff --git a/include/ui/neweventtoolbutton.h b/include/ui/neweventtoolbutton.h index 721fe2f2..f956bea9 100644 --- a/include/ui/neweventtoolbutton.h +++ b/include/ui/neweventtoolbutton.h @@ -9,31 +9,20 @@ class NewEventToolButton : public QToolButton Q_OBJECT public: explicit NewEventToolButton(QWidget *parent = nullptr); - Event::Type getSelectedEventType(); - QAction *newObjectAction; - QAction *newCloneObjectAction; - QAction *newWarpAction; - QAction *newHealLocationAction; - QAction *newTriggerAction; - QAction *newWeatherTriggerAction; - QAction *newSignAction; - QAction *newHiddenItemAction; - QAction *newSecretBaseAction; -public slots: - void newObject(); - void newCloneObject(); - void newWarp(); - void newHealLocation(); - void newTrigger(); - void newWeatherTrigger(); - void newSign(); - void newHiddenItem(); - void newSecretBase(); + + Event::Type getSelectedEventType() const { return this->selectedEventType; } + bool selectEventType(Event::Type type); + void setEventTypeVisible(Event::Type type, bool visible); + signals: void newEventAdded(Event::Type); + private: + QMap typeToAction; Event::Type selectedEventType; - void init(); + QMenu* menu; + + void addEventType(Event::Type type); }; #endif // NEWEVENTTOOLBUTTON_H diff --git a/src/core/events.cpp b/src/core/events.cpp index c124e58d..eedf0d7f 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -73,19 +73,20 @@ void Event::modify() { this->map->modify(); } -const QMap groupToStringMap = { - {Event::Group::Object, "Object"}, - {Event::Group::Warp, "Warp"}, - {Event::Group::Coord, "Trigger"}, - {Event::Group::Bg, "BG"}, - {Event::Group::Heal, "Heal Location"}, -}; - QString Event::groupToString(Event::Group group) { + static const QMap groupToStringMap = { + {Event::Group::Object, "Object"}, + {Event::Group::Warp, "Warp"}, + {Event::Group::Coord, "Trigger"}, + {Event::Group::Bg, "BG"}, + {Event::Group::Heal, "Heal Location"}, + }; return groupToStringMap.value(group); } -const QMap typeToStringMap = { +// These are the expected key names used in the map.json files. +// We re-use them for key names in the copy/paste JSON data, +const QMap typeToJsonKeyMap = { {Event::Type::Object, "object"}, {Event::Type::CloneObject, "clone_object"}, {Event::Type::Warp, "warp"}, @@ -97,12 +98,32 @@ const QMap typeToStringMap = { {Event::Type::HealLocation, "heal_location"}, }; -QString Event::typeToString(Event::Type type) { - return typeToStringMap.value(type); +QString Event::typeToJsonKey(Event::Type type) { + return typeToJsonKeyMap.value(type); } -Event::Type Event::typeFromString(QString type) { - return typeToStringMap.key(type, Event::Type::None); +Event::Type Event::typeFromJsonKey(QString type) { + return typeToJsonKeyMap.key(type, Event::Type::None); +} + +QList Event::types() { + static QList typeList = typeToJsonKeyMap.keys(); + return typeList; +} + +QString Event::typeToString(Event::Type type) { + const QMap typeToStringMap = { + {Event::Type::Object, "Object"}, + {Event::Type::CloneObject, "Clone Object"}, + {Event::Type::Warp, "Warp"}, + {Event::Type::Trigger, "Trigger"}, + {Event::Type::WeatherTrigger, "Weather"}, + {Event::Type::Sign, "Sign"}, + {Event::Type::HiddenItem, "Hidden Item"}, + {Event::Type::SecretBase, "Secret Base"}, + {Event::Type::HealLocation, "Heal Location"}, + }; + return typeToStringMap.value(type); } void Event::loadPixmap(Project *project) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 57daee1d..c9f06b0e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1100,9 +1100,9 @@ bool MainWindow::setProjectUI() { // Wild Encounters tab ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, editor->project->wildEncountersLoaded); - ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.eventWeatherTriggerEnabled); - ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled); - ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled); + ui->newEventToolButton->setEventTypeVisible(Event::Type::WeatherTrigger, projectConfig.eventWeatherTriggerEnabled); + ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled); + ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); @@ -1638,7 +1638,7 @@ void MainWindow::copy() { OrderedJson::array eventsArray; for (const auto &event : this->editor->selectedEvents) { OrderedJson::object eventContainer; - eventContainer["event_type"] = Event::typeToString(event->getEventType()); + eventContainer["event_type"] = Event::typeToJsonKey(event->getEventType()); OrderedJson::object eventJson = event->buildEventJson(editor->project); eventContainer["event"] = eventJson; eventsArray.append(eventContainer); @@ -1749,7 +1749,7 @@ void MainWindow::paste() { QJsonArray events = pasteObject["events"].toArray(); for (QJsonValue event : events) { // paste the event to the map - Event::Type type = Event::typeFromString(event["event_type"].toString()); + Event::Type type = Event::typeFromJsonKey(event["event_type"].toString()); Event *pasteEvent = Event::create(type); if (!pasteEvent) continue; @@ -1981,7 +1981,7 @@ void MainWindow::resetMapViewScale() { void MainWindow::tryAddEventTab(QWidget * tab) { auto group = getEventGroupFromTabWidget(tab); - if (editor->map->getNumEvents(group)) + if (this->editor->map && this->editor->map->getNumEvents(group)) ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::groupToString(group))); } @@ -2109,6 +2109,12 @@ void MainWindow::updateSelectedEvents() { ui->tabWidget_EventType->setCurrentWidget(ui->tab_Multiple); } + // (Currently 'events' can't be empty here, but we'll check anyway in case that changes) + if (!events.isEmpty()) { + // Set the 'New Event' button to be the type of the most recently-selected event + ui->newEventToolButton->selectEventType(events.constLast()->getEventType()); + } + this->isProgrammaticEventTabChange = false; QList frames; @@ -2168,27 +2174,6 @@ void MainWindow::eventTabChanged(int index) { if (editor->map) { Event::Group group = getEventGroupFromTabWidget(ui->tabWidget_EventType->widget(index)); Event *selectedEvent = this->lastSelectedEvent.value(group, nullptr); - - switch (group) { - case Event::Group::Object: - ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newObjectAction); - break; - case Event::Group::Warp: - ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newWarpAction); - break; - case Event::Group::Coord: - ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newTriggerAction); - break; - case Event::Group::Bg: - ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newSignAction); - break; - case Event::Group::Heal: - ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newHealLocationAction); - break; - default: - break; - } - if (!isProgrammaticEventTabChange) { if (!selectedEvent) selectedEvent = this->editor->map->getEvent(group, 0); this->editor->selectMapEvent(selectedEvent); diff --git a/src/project.cpp b/src/project.cpp index 006e5b63..3ba41027 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -206,7 +206,7 @@ bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { bool Project::loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType) { QString typeString = ParseUtil::jsonToQString(json["type"]); - Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromString(typeString); + Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromJsonKey(typeString); Event* event = Event::create(type); if (!event) { return false; diff --git a/src/ui/neweventtoolbutton.cpp b/src/ui/neweventtoolbutton.cpp index f1a70b42..d1cea0a7 100644 --- a/src/ui/neweventtoolbutton.cpp +++ b/src/ui/neweventtoolbutton.cpp @@ -8,117 +8,53 @@ NewEventToolButton::NewEventToolButton(QWidget *parent) : { setPopupMode(QToolButton::MenuButtonPopup); QObject::connect(this, &NewEventToolButton::triggered, this, &NewEventToolButton::setDefaultAction); - this->init(); + + this->menu = new QMenu(this); + for (const auto &type : Event::types()) { + addEventType(type); + } + setMenu(this->menu); + setDefaultAction(this->menu->actions().constFirst()); } -void NewEventToolButton::init() -{ - // Add a context menu to select different types of map events. - this->newObjectAction = new QAction("New Object", this); - this->newObjectAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newObjectAction, &QAction::triggered, this, &NewEventToolButton::newObject); +void NewEventToolButton::addEventType(Event::Type type) { + if (this->typeToAction.contains(type)) + return; - this->newCloneObjectAction = new QAction("New Clone Object", this); - this->newCloneObjectAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newCloneObjectAction, &QAction::triggered, this, &NewEventToolButton::newCloneObject); + auto action = new QAction(QStringLiteral("New ") + Event::typeToString(type), this); + action->setIcon(QIcon(QStringLiteral(":/icons/add.ico"))); + connect(action, &QAction::triggered, [this, type] { + this->selectedEventType = type; + emit newEventAdded(this->selectedEventType); + }); - this->newWarpAction = new QAction("New Warp", this); - this->newWarpAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newWarpAction, &QAction::triggered, this, &NewEventToolButton::newWarp); - - this->newHealLocationAction = new QAction("New Heal Location", this); - this->newHealLocationAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newHealLocationAction, &QAction::triggered, this, &NewEventToolButton::newHealLocation); - - this->newTriggerAction = new QAction("New Trigger", this); - this->newTriggerAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newTriggerAction, &QAction::triggered, this, &NewEventToolButton::newTrigger); - - this->newWeatherTriggerAction = new QAction("New Weather Trigger", this); - this->newWeatherTriggerAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newWeatherTriggerAction, &QAction::triggered, this, &NewEventToolButton::newWeatherTrigger); - - this->newSignAction = new QAction("New Sign", this); - this->newSignAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newSignAction, &QAction::triggered, this, &NewEventToolButton::newSign); - - this->newHiddenItemAction = new QAction("New Hidden Item", this); - this->newHiddenItemAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newHiddenItemAction, &QAction::triggered, this, &NewEventToolButton::newHiddenItem); - - this->newSecretBaseAction = new QAction("New Secret Base", this); - this->newSecretBaseAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newSecretBaseAction, &QAction::triggered, this, &NewEventToolButton::newSecretBase); - - QMenu *alignMenu = new QMenu(this); - alignMenu->addAction(this->newObjectAction); - alignMenu->addAction(this->newCloneObjectAction); - alignMenu->addAction(this->newWarpAction); - alignMenu->addAction(this->newHealLocationAction); - alignMenu->addAction(this->newTriggerAction); - alignMenu->addAction(this->newWeatherTriggerAction); - alignMenu->addAction(this->newSignAction); - alignMenu->addAction(this->newHiddenItemAction); - alignMenu->addAction(this->newSecretBaseAction); - this->setMenu(alignMenu); - this->setDefaultAction(this->newObjectAction); + this->typeToAction.insert(type, action); + this->menu->addAction(action); } -Event::Type NewEventToolButton::getSelectedEventType() -{ - return this->selectedEventType; +bool NewEventToolButton::selectEventType(Event::Type type) { + auto action = this->typeToAction.value(type); + if (!action || !action->isVisible()) + return false; + + this->selectedEventType = type; + setDefaultAction(action); + return true; } -void NewEventToolButton::newObject() -{ - this->selectedEventType = Event::Type::Object; - emit newEventAdded(this->selectedEventType); -} +void NewEventToolButton::setEventTypeVisible(Event::Type type, bool visible) { + auto action = this->typeToAction.value(type); + if (!action) + return; -void NewEventToolButton::newCloneObject() -{ - this->selectedEventType = Event::Type::CloneObject; - emit newEventAdded(this->selectedEventType); -} + action->setVisible(visible); -void NewEventToolButton::newWarp() -{ - this->selectedEventType = Event::Type::Warp; - emit newEventAdded(this->selectedEventType); -} - -void NewEventToolButton::newHealLocation() -{ - this->selectedEventType = Event::Type::HealLocation; - emit newEventAdded(this->selectedEventType); -} - -void NewEventToolButton::newTrigger() -{ - this->selectedEventType = Event::Type::Trigger; - emit newEventAdded(this->selectedEventType); -} - -void NewEventToolButton::newWeatherTrigger() -{ - this->selectedEventType = Event::Type::WeatherTrigger; - emit newEventAdded(this->selectedEventType); -} - -void NewEventToolButton::newSign() -{ - this->selectedEventType = Event::Type::Sign; - emit newEventAdded(this->selectedEventType); -} - -void NewEventToolButton::newHiddenItem() -{ - this->selectedEventType = Event::Type::HiddenItem; - emit newEventAdded(this->selectedEventType); -} - -void NewEventToolButton::newSecretBase() -{ - this->selectedEventType = Event::Type::SecretBase; - emit newEventAdded(this->selectedEventType); + // If we just hid the currently-selected type we need to pick a new type. + if (this->selectedEventType == type) { + for (const auto &newType : Event::types()) { + if (newType != type && selectEventType(newType)){ + break; + } + } + } } From 75dbd256308b79d48eff12061dfefcb8a0e84232 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 2 Mar 2025 18:20:18 -0500 Subject: [PATCH 226/364] Fix events not being cleared --- src/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor.cpp b/src/editor.cpp index d61a4661..6559765e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1148,6 +1148,7 @@ void Editor::unsetMap() { this->map->pruneEditHistory(); this->map->disconnect(this); } + clearMapEvents(); clearMapConnections(); this->map = nullptr; From 4df7b9319b65580fbc3787ef13d6390e09d15881 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 2 Mar 2025 18:33:50 -0500 Subject: [PATCH 227/364] Remove erroneous comment, add missing static --- src/core/events.cpp | 2 +- src/mainwindow.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index eedf0d7f..9186e5c0 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -112,7 +112,7 @@ QList Event::types() { } QString Event::typeToString(Event::Type type) { - const QMap typeToStringMap = { + static const QMap typeToStringMap = { {Event::Type::Object, "Object"}, {Event::Type::CloneObject, "Clone Object"}, {Event::Type::Warp, "Warp"}, diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c9f06b0e..d30fd12b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2109,7 +2109,6 @@ void MainWindow::updateSelectedEvents() { ui->tabWidget_EventType->setCurrentWidget(ui->tab_Multiple); } - // (Currently 'events' can't be empty here, but we'll check anyway in case that changes) if (!events.isEmpty()) { // Set the 'New Event' button to be the type of the most recently-selected event ui->newEventToolButton->selectEventType(events.constLast()->getEventType()); From 7cfd9fa0f86ed22e4bfd494f6bbf1148b5fec2e0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 2 Mar 2025 19:23:19 -0500 Subject: [PATCH 228/364] Fix changing tilesets not marking layouts as unsaved --- include/core/maplayout.h | 1 + include/mainwindow.h | 1 + src/core/maplayout.cpp | 2 +- src/mainwindow.cpp | 15 ++++++++++++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index fb4c0118..c6231fee 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -25,6 +25,7 @@ public: bool loaded = false; + bool hasUnsavedDataChanges = false; QString id; QString name; diff --git a/include/mainwindow.h b/include/mainwindow.h index 64d5e38f..69e56b3c 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -200,6 +200,7 @@ private slots: void applyUserShortcuts(); void markMapEdited(); void markSpecificMapEdited(Map*); + void markLayoutEdited(); void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 79e3b832..4fae085c 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -438,5 +438,5 @@ QPixmap Layout::getLayoutItemPixmap() { } bool Layout::hasUnsavedChanges() const { - return !this->editHistory.isClean() || !this->newFolderPath.isEmpty(); + return !this->editHistory.isClean() || this->hasUnsavedDataChanges || !this->newFolderPath.isEmpty(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 57daee1d..c2e9881a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -339,7 +339,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); - connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); }); + connect(this->editor, &Editor::wildMonTableEdited, this, &MainWindow::markMapEdited); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); @@ -512,6 +512,15 @@ void MainWindow::markSpecificMapEdited(Map* map) { updateMapList(); } +void MainWindow::markLayoutEdited() { + if (!this->editor->layout) + return; + this->editor->layout->hasUnsavedDataChanges = true; + + updateWindowTitle(); + updateMapList(); +} + void MainWindow::loadUserSettings() { // Better Cursors ui->actionBetter_Cursors->setChecked(porymapConfig.prettyCursors); @@ -2585,7 +2594,7 @@ void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &ti redrawMapScene(); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); - markMapEdited(); + markLayoutEdited(); } } @@ -2596,7 +2605,7 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & redrawMapScene(); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); - markMapEdited(); + markLayoutEdited(); } } From 36aac9304fc395c22203807b077dab12faddd3f3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 2 Mar 2025 19:27:25 -0500 Subject: [PATCH 229/364] Add Layout::setClean --- include/core/maplayout.h | 1 + src/core/maplayout.cpp | 5 +++++ src/project.cpp | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index c6231fee..604e2b3d 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -114,6 +114,7 @@ public: void clearBorderCache(); void cacheBorder(); + void setClean(); bool hasUnsavedChanges() const; bool layoutBlockChanged(int i, const Blockdata &cache); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 4fae085c..36eada7b 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -437,6 +437,11 @@ QPixmap Layout::getLayoutItemPixmap() { return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); } +void Layout::setClean() { + this->editHistory.setClean(); + this->hasUnsavedDataChanges = false; +} + bool Layout::hasUnsavedChanges() const { return !this->editHistory.isClean() || this->hasUnsavedDataChanges || !this->newFolderPath.isEmpty(); } diff --git a/src/project.cpp b/src/project.cpp index 006e5b63..3ba99112 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1288,7 +1288,7 @@ void Project::saveLayout(Layout *layout) { // Update global data structures with current map data. updateLayout(layout); - layout->editHistory.setClean(); + layout->setClean(); } void Project::updateLayout(Layout *layout) { From d37864fa827eb138b7ed4315555b9d6d268d5588 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 3 Mar 2025 13:28:20 -0500 Subject: [PATCH 230/364] Fix empty metatile labels being disallowed --- include/core/validator.h | 3 +++ src/core/tileset.cpp | 8 +++++--- src/core/validator.cpp | 4 ++++ src/ui/tileseteditor.cpp | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/include/core/validator.h b/include/core/validator.h index f5f2ef91..2419de5d 100644 --- a/include/core/validator.h +++ b/include/core/validator.h @@ -52,8 +52,11 @@ public: : PrefixValidator(prefix, re_identifier, parent) {}; ~IdentifierValidator() {}; + void setAllowEmpty(bool allowEmpty); + private: static const QRegularExpression re_identifier; + static const QRegularExpression re_identifierOrEmpty; }; class UppercaseValidator : public QValidator { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 20590369..da5fcf54 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -182,9 +182,11 @@ bool Tileset::setMetatileLabel(int metatileId, QString label, Tileset *primaryTi if (!tileset) return false; - IdentifierValidator validator; - if (!validator.isValid(label)) - return false; + if (!label.isEmpty()) { + IdentifierValidator validator; + if (!validator.isValid(label)) + return false; + } tileset->metatileLabels[metatileId] = label; return true; diff --git a/src/core/validator.cpp b/src/core/validator.cpp index f682efc3..6f7a72a7 100644 --- a/src/core/validator.cpp +++ b/src/core/validator.cpp @@ -2,7 +2,11 @@ // Identifiers must only contain word characters, and cannot start with a digit. const QRegularExpression IdentifierValidator::re_identifier = QRegularExpression("[A-Za-z_]+[\\w]*"); +const QRegularExpression IdentifierValidator::re_identifierOrEmpty = QRegularExpression("(^$|[A-Za-z_]+[\\w]*)"); +void IdentifierValidator::setAllowEmpty(bool allowEmpty) { + this->setRegularExpression(allowEmpty ? re_identifierOrEmpty : re_identifier); +} bool PrefixValidator::missingPrefix(const QString &input) const { return !m_prefix.isEmpty() && !input.startsWith(m_prefix); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 835877da..9668f1cb 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -34,7 +34,10 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->spinBox_paletteSelector->setMinimum(0); ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); - ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this)); + + auto validator = new IdentifierValidator(this); + validator->setAllowEmpty(true); + ui->lineEdit_metatileLabel->setValidator(validator); ActiveWindowFilter *filter = new ActiveWindowFilter(this); connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated); From 7ab7f09fe396fb66e853ad2a452b3f028af44491 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 6 Mar 2025 01:44:37 -0500 Subject: [PATCH 231/364] Expose file extension regex to config --- docsrc/manual/project-files.rst | 4 ++++ include/config.h | 4 ++++ include/project.h | 3 +++ src/config.cpp | 5 +++++ src/core/tileset.cpp | 7 ++++--- src/project.cpp | 12 +++++------- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index d4c47950..4917be1d 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -131,3 +131,7 @@ In addition to these files, there are some specific symbol and macro names that ``regex_sign_facing_directions``, ``\bBG_EVENT_PLAYER_FACING_``, regex to find sign facing direction macro names ``regex_trainer_types``, ``\bTRAINER_TYPE_``, regex to find trainer type macro names ``regex_music``, ``\b(SE|MUS)_``, regex to find music macro names + ``regex_gbapal``, ``\.gbapal(\.[\w]+)?$``, regex to get the expected file extension for ``.pal`` data files + ``regex_bpp``, ``\.[\d]+bpp(\.[\w]+)?$``, regex to get the expected file extension for ``.png`` data files + ``pals_output_extension``, ``.gbapal``, the file extension to output for a new tileset's palette data files + ``tiles_output_extension``, ``.4bpp.lz``, the file extension to output for a new tileset's tiles image data file diff --git a/include/config.h b/include/config.h index b800fe1b..1df1b4ef 100644 --- a/include/config.h +++ b/include/config.h @@ -242,6 +242,10 @@ enum ProjectIdentifier { regex_sign_facing_directions, regex_trainer_types, regex_music, + regex_gbapal, + regex_bpp, + pals_output_extension, + tiles_output_extension, }; enum ProjectFilePath { diff --git a/include/project.h b/include/project.h index 5330b792..f7a4b5cc 100644 --- a/include/project.h +++ b/include/project.h @@ -258,6 +258,9 @@ private: QMap facingDirections; QMap speciesToIconPath; + const QRegularExpression re_gbapalExtension; + const QRegularExpression re_bppExtension; + struct EventGraphics { QString filepath; diff --git a/src/config.cpp b/src/config.cpp index 232d7e66..1847cfcf 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -125,6 +125,11 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::regex_sign_facing_directions, {"regex_sign_facing_directions", "\\bBG_EVENT_PLAYER_FACING_"}}, {ProjectIdentifier::regex_trainer_types, {"regex_trainer_types", "\\bTRAINER_TYPE_"}}, {ProjectIdentifier::regex_music, {"regex_music", "\\b(SE|MUS)_"}}, + {ProjectIdentifier::regex_gbapal, {"regex_gbapal", "\\.gbapal(\\.[\\w]+)?$"}}, + {ProjectIdentifier::regex_bpp, {"regex_bpp", "\\.[\\d]+bpp(\\.[\\w]+)?$"}}, + // Other + {ProjectIdentifier::pals_output_extension, {"pals_output_extension", ".gbapal"}}, + {ProjectIdentifier::tiles_output_extension, {"tiles_output_extension", ".4bpp.lz"}}, }; const QMap> ProjectConfig::defaultPaths = { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index da5fcf54..f6ce6a2e 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -304,8 +304,9 @@ bool Tileset::appendToGraphics(QString root, QString friendlyName, bool usingAsm } const QString tilesetDir = this->getExpectedDir(); - const QString tilesPath = tilesetDir + "/tiles.4bpp.lz"; + const QString tilesPath = QString("%1/tiles%2").arg(tilesetDir).arg(projectConfig.getIdentifier(ProjectIdentifier::tiles_output_extension)); const QString palettesPath = tilesetDir + "/palettes/"; + const QString palettesExt = projectConfig.getIdentifier(ProjectIdentifier::pals_output_extension); QString dataString = "\n"; if (usingAsm) { @@ -313,7 +314,7 @@ bool Tileset::appendToGraphics(QString root, QString friendlyName, bool usingAsm dataString.append("\t.align 2\n"); dataString.append(QString("gTilesetPalettes_%1::\n").arg(friendlyName)); for (int i = 0; i < Project::getNumPalettesTotal(); i++) - dataString.append(QString("\t.incbin \"%1%2.gbapal\"\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0'))); + dataString.append(QString("\t.incbin \"%1%2%3\"\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0')).arg(palettesExt)); dataString.append("\n\t.align 2\n"); dataString.append(QString("gTilesetTiles_%1::\n").arg(friendlyName)); dataString.append(QString("\t.incbin \"%1\"\n").arg(tilesPath)); @@ -321,7 +322,7 @@ bool Tileset::appendToGraphics(QString root, QString friendlyName, bool usingAsm // Append to C file dataString.append(QString("const u16 gTilesetPalettes_%1[][16] =\n{\n").arg(friendlyName)); for (int i = 0; i < Project::getNumPalettesTotal(); i++) - dataString.append(QString(" INCBIN_U16(\"%1%2.gbapal\"),\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0'))); + dataString.append(QString(" INCBIN_U16(\"%1%2%3\"),\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0')).arg(palettesExt)); dataString.append("};\n"); dataString.append(QString("\nconst u32 gTilesetTiles_%1[] = INCBIN_U32(\"%2\");\n").arg(friendlyName, tilesPath)); } diff --git a/src/project.cpp b/src/project.cpp index 7ed80174..c1a0cd07 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -33,7 +33,9 @@ int Project::max_map_data_size = 10240; // 0x2800 int Project::default_map_dimension = 20; Project::Project(QObject *parent) : - QObject(parent) + QObject(parent), + re_gbapalExtension(projectConfig.getIdentifier(ProjectIdentifier::regex_gbapal)), + re_bppExtension(projectConfig.getIdentifier(ProjectIdentifier::regex_bpp)) { QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::recordFileChange); } @@ -2654,16 +2656,12 @@ void Project::insertGlobalScriptLabels(QStringList &scriptLabels) const { } QString Project::fixPalettePath(QString path) { - static const QRegularExpression re_gbapal("\\.gbapal$"); - path = path.replace(re_gbapal, ".pal"); + path.replace(this->re_gbapalExtension, ".pal"); return path; } QString Project::fixGraphicPath(QString path) { - static const QRegularExpression re_lz("\\.lz$"); - path = path.replace(re_lz, ""); - static const QRegularExpression re_bpp("\\.[1248]bpp$"); - path = path.replace(re_bpp, ".png"); + path.replace(this->re_bppExtension, ".png"); return path; } From 21b823792d03e6a68131761ad3df17dfee232dae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 6 Mar 2025 16:56:37 -0500 Subject: [PATCH 232/364] Remove redundant key sequence list --- include/ui/multikeyedit.h | 5 ++-- src/ui/multikeyedit.cpp | 54 +++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/include/ui/multikeyedit.h b/include/ui/multikeyedit.h index 27231330..9a8faa10 100644 --- a/include/ui/multikeyedit.h +++ b/include/ui/multikeyedit.h @@ -37,15 +37,14 @@ signals: void customContextMenuRequested(const QPoint &pos); private: - QVector keySequenceEdit_vec; - QList keySequence_list; // Used to track changes + QVector keySequenceEdits; void addNewKeySequenceEdit(); void alignKeySequencesLeft(); void setFocusToLastNonEmptyKeySequenceEdit(); private slots: - void onEditingFinished(); + void onEditingFinished(QKeySequenceEdit *sender); void showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos); }; diff --git a/src/ui/multikeyedit.cpp b/src/ui/multikeyedit.cpp index fcf38480..cf11210f 100644 --- a/src/ui/multikeyedit.cpp +++ b/src/ui/multikeyedit.cpp @@ -8,9 +8,7 @@ MultiKeyEdit::MultiKeyEdit(QWidget *parent, int fieldCount) : - QWidget(parent), - keySequenceEdit_vec(QVector()), - keySequence_list(QList()) + QWidget(parent) { setLayout(new QHBoxLayout(this)); layout()->setContentsMargins(0, 0, 0, 0); @@ -48,36 +46,36 @@ bool MultiKeyEdit::eventFilter(QObject *watched, QEvent *event) { } int MultiKeyEdit::fieldCount() const { - return keySequenceEdit_vec.count(); + return this->keySequenceEdits.count(); } void MultiKeyEdit::setFieldCount(int count) { if (count < 1) count = 1; - while (keySequenceEdit_vec.count() < count) + while (this->keySequenceEdits.count() < count) addNewKeySequenceEdit(); - while (keySequenceEdit_vec.count() > count) - delete keySequenceEdit_vec.takeLast(); + while (this->keySequenceEdits.count() > count) + delete this->keySequenceEdits.takeLast(); alignKeySequencesLeft(); } QList MultiKeyEdit::keySequences() const { QList current_keySequences; - for (auto *kse : keySequenceEdit_vec) + for (auto *kse : this->keySequenceEdits) if (!kse->keySequence().isEmpty()) current_keySequences.append(kse->keySequence()); return current_keySequences; } bool MultiKeyEdit::removeOne(const QKeySequence &keySequence) { - for (auto *keySequenceEdit : keySequenceEdit_vec) { + for (auto *keySequenceEdit : this->keySequenceEdits) { if (keySequenceEdit->keySequence() == keySequence) { - keySequence_list.removeOne(keySequence); keySequenceEdit->clear(); alignKeySequencesLeft(); + setFocusToLastNonEmptyKeySequenceEdit(); return true; } } @@ -108,29 +106,28 @@ void MultiKeyEdit::setClearButtonEnabled(bool enable) { } void MultiKeyEdit::clear() { - for (auto *keySequenceEdit : keySequenceEdit_vec) + for (auto *keySequenceEdit : this->keySequenceEdits) keySequenceEdit->clear(); - keySequence_list.clear(); } void MultiKeyEdit::setKeySequences(const QList &keySequences) { clear(); - keySequence_list = keySequences; - int minCount = qMin(keySequenceEdit_vec.count(), keySequence_list.count()); + int minCount = qMin(this->keySequenceEdits.count(), keySequences.count()); for (int i = 0; i < minCount; ++i) - keySequenceEdit_vec[i]->setKeySequence(keySequence_list[i]); + this->keySequenceEdits[i]->setKeySequence(keySequences.at(i)); } void MultiKeyEdit::addKeySequence(const QKeySequence &keySequence) { - keySequenceEdit_vec.last()->setKeySequence(keySequence); + this->keySequenceEdits.last()->setKeySequence(keySequence); alignKeySequencesLeft(); } void MultiKeyEdit::addNewKeySequenceEdit() { auto *keySequenceEdit = new QKeySequenceEdit(this); keySequenceEdit->installEventFilter(this); - connect(keySequenceEdit, &QKeySequenceEdit::editingFinished, - this, &MultiKeyEdit::onEditingFinished); + connect(keySequenceEdit, &QKeySequenceEdit::editingFinished, [this, keySequenceEdit] { + onEditingFinished(keySequenceEdit); + }); connect(keySequenceEdit, &QKeySequenceEdit::keySequenceChanged, this, &MultiKeyEdit::keySequenceChanged); @@ -149,7 +146,7 @@ void MultiKeyEdit::addNewKeySequenceEdit() { } layout()->addWidget(keySequenceEdit); - keySequenceEdit_vec.append(keySequenceEdit); + this->keySequenceEdits.append(keySequenceEdit); } // Shift all key sequences left if there are any empty QKeySequenceEdit's. @@ -160,7 +157,7 @@ void MultiKeyEdit::alignKeySequencesLeft() { } void MultiKeyEdit::setFocusToLastNonEmptyKeySequenceEdit() { - for (auto it = keySequenceEdit_vec.rbegin(); it != keySequenceEdit_vec.rend(); ++it) { + for (auto it = this->keySequenceEdits.rbegin(); it != this->keySequenceEdits.rend(); ++it) { if (!(*it)->keySequence().isEmpty()) { (*it)->setFocus(); return; @@ -168,12 +165,19 @@ void MultiKeyEdit::setFocusToLastNonEmptyKeySequenceEdit() { } } -void MultiKeyEdit::onEditingFinished() { - auto *keySequenceEdit = qobject_cast(sender()); - if (keySequenceEdit && keySequence_list.contains(keySequenceEdit->keySequence())) - removeOne(keySequenceEdit->keySequence()); +void MultiKeyEdit::onEditingFinished(QKeySequenceEdit *sender) { + if (!sender) return; + alignKeySequencesLeft(); - setFocusToLastNonEmptyKeySequenceEdit(); + + // Remove duplicate key sequences, if any + if (!sender->keySequence().isEmpty()) { + for (const auto &edit : this->keySequenceEdits) { + if (edit != sender && edit->keySequence() == sender->keySequence()) { + removeOne(edit->keySequence()); + } + } + } emit editingFinished(); } From 1663ce7bafc46bfcc03fd178b39795121872f1a2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 11 Mar 2025 15:11:10 -0400 Subject: [PATCH 233/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0544760d..05d5aec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix some problems with tileset detection when importing maps from AdvanceMap. - Fix certain input fields allowing invalid identifiers, like names starting with numbers. - Fix crash in the Shortcuts Editor when applying changes after closing certain windows. +- Fix the Shortcuts Editor clearing shortcuts after selecting them. - Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. - The Metatile / Tile usage counts in the Tileset Editor now update to reflect changes. - Fix regression that stopped the map zoom from centering on the cursor. From a0a8c710964ab8515393806a2c44fde1992685c6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 14:45:28 -0400 Subject: [PATCH 234/364] Replace various map constant arrays with data stored in Map objects --- include/core/map.h | 9 +-- include/project.h | 22 +++--- src/core/events.cpp | 8 +- src/core/map.cpp | 9 --- src/core/mapconnection.cpp | 2 +- src/mainwindow.cpp | 2 +- src/project.cpp | 154 ++++++++++++++++--------------------- src/ui/maplistmodels.cpp | 12 +-- 8 files changed, 96 insertions(+), 122 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index db81fc93..22c21d96 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -48,12 +48,9 @@ public: static QString mapConstantFromName(const QString &name); QString expectedConstantName() const { return Map::mapConstantFromName(m_name); } - void setLayout(Layout *layout); + void setLayout(Layout *layout) { m_layout = layout; } Layout* layout() const { return m_layout; } - void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; } - QString layoutId() const { return m_layoutId; } - int getWidth() const; int getHeight() const; int getBorderWidth() const; @@ -71,10 +68,12 @@ public: void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } + void setLoaded(bool loaded) { m_loaded = loaded; } bool needsHealLocation() const { return m_needsHealLocation; } bool isPersistedToFile() const { return m_isPersistedToFile; } bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } + bool loaded() const { return m_loaded; } void resetEvents(); QList getEvents(Event::Group group = Event::Group::None) const; @@ -109,7 +108,6 @@ public: private: QString m_name; QString m_constantName; - QString m_layoutId; QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; @@ -123,6 +121,7 @@ private: bool m_hasUnsavedDataChanges = false; bool m_needsHealLocation = false; bool m_scriptsLoaded = false; + bool m_loaded = false; QMap> m_events; QSet m_ownedEvents; // for memory management diff --git a/include/project.h b/include/project.h index f7a4b5cc..d393fbbb 100644 --- a/include/project.h +++ b/include/project.h @@ -37,9 +37,6 @@ public: QStringList healLocationSaveOrder; QMap> healLocations; QMap mapConstantsToMapNames; - QMap mapNamesToMapConstants; - QMap mapNameToLayoutId; - QMap mapNameToMapSectionName; QString layoutsLabel; QStringList layoutIds; QStringList layoutIdsMaster; @@ -81,7 +78,7 @@ public: void set_root(QString); - void clearMapCache(); + void clearMaps(); void clearTilesetCache(); void clearMapLayouts(); void clearEventGraphics(); @@ -90,9 +87,10 @@ public: bool sanityCheck(); bool load(); - QMap mapCache; - Map* loadMap(QString); - Map* getMap(QString); + Map* loadMap(const QString &mapName); + + // Note: This does not guarantee the map is loaded. + Map* getMap(const QString &mapName) { return this->maps.value(mapName); } QMap tilesetCache; Tileset* loadTileset(QString, Tileset *tileset = nullptr); @@ -111,7 +109,10 @@ public: bool readMapGroups(); void addNewMapGroup(const QString &groupName); - QString mapNameToMapGroup(const QString &mapName); + QString mapNameToMapGroup(const QString &mapName) const; + QString getMapConstant(const QString &mapName, const QString &defaultValue = QString()) const; + QString getMapLayoutId(const QString &mapName, const QString &defaultValue = QString()) const; + QString getMapLocation(const QString &mapName, const QString &defaultValue = QString()) const; struct NewMapSettings { QString name; @@ -253,10 +254,11 @@ public: static QString getMapGroupPrefix(); private: - QMap mapSectionDisplayNames; + QHash mapSectionDisplayNames; QMap modifiedFileTimestamps; QMap facingDirections; - QMap speciesToIconPath; + QHash speciesToIconPath; + QHash maps; const QRegularExpression re_gbapalExtension; const QRegularExpression re_bppExtension; diff --git a/src/core/events.cpp b/src/core/events.cpp index 9186e5c0..48c28c8f 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -285,7 +285,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["y"] = this->getY(); cloneJson["target_local_id"] = this->getTargetID(); const QString mapName = this->getTargetMap(); - cloneJson["target_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + cloneJson["target_map"] = project->getMapConstant(mapName, mapName); this->addCustomAttributesTo(&cloneJson); return cloneJson; @@ -333,7 +333,7 @@ QSet CloneObjectEvent::getExpectedFields() { void CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone int eventIndex = this->targetID - 1; - Map *clonedMap = project->getMap(this->targetMap); + Map *clonedMap = project->loadMap(this->targetMap); Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { @@ -380,7 +380,7 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["y"] = this->getY(); warpJson["elevation"] = this->getElevation(); const QString mapName = this->getDestinationMap(); - warpJson["dest_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + warpJson["dest_map"] = project->getMapConstant(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); this->addCustomAttributesTo(&warpJson); @@ -839,7 +839,7 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { healLocationJson["y"] = this->getY(); if (projectConfig.healLocationRespawnDataEnabled) { const QString mapName = this->getRespawnMapName(); - healLocationJson["respawn_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + healLocationJson["respawn_map"] = project->getMapConstant(mapName, mapName); healLocationJson["respawn_npc"] = this->getRespawnNPC(); } diff --git a/src/core/map.cpp b/src/core/map.cpp index 7335c5bb..dc328cf0 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -23,7 +23,6 @@ Map::Map(QObject *parent) : QObject(parent) Map::Map(const Map &other, QObject *parent) : Map(parent) { m_name = other.m_name; m_constantName = other.m_constantName; - m_layoutId = other.m_layoutId; m_sharedEventsMap = other.m_sharedEventsMap; m_sharedScriptsMap = other.m_sharedScriptsMap; m_customAttributes = other.m_customAttributes; @@ -48,14 +47,6 @@ Map::~Map() { deleteConnections(); } -// Note: Map does not take ownership of layout -void Map::setLayout(Layout *layout) { - m_layout = layout; - if (layout) { - m_layoutId = layout->id; - } -} - // We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID. QString Map::mapConstantFromName(const QString &name) { return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index db2755e9..c478003b 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -53,7 +53,7 @@ void MapConnection::markMapEdited() { } Map* MapConnection::getMap(const QString& mapName) const { - return project ? project->getMap(mapName) : nullptr; + return project ? project->loadMap(mapName) : nullptr; } Map* MapConnection::targetMap() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ed7e7d77..85db9697 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1384,7 +1384,7 @@ void MainWindow::openNewMapDialog() { } void MainWindow::openDuplicateMapDialog(const QString &mapName) { - const Map *map = this->editor->project->getMap(mapName); + const Map *map = this->editor->project->loadMap(mapName); if (map) { auto dialog = new NewMapDialog(this->editor->project, map, this); dialog->open(); diff --git a/src/project.cpp b/src/project.cpp index 6590aac7..6b24ffb7 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -42,7 +42,7 @@ Project::Project(QObject *parent) : Project::~Project() { - clearMapCache(); + clearMaps(); clearTilesetCache(); clearMapLayouts(); clearEventGraphics(); @@ -127,9 +127,9 @@ QString Project::getProjectTitle() const { } } -void Project::clearMapCache() { - qDeleteAll(this->mapCache); - this->mapCache.clear(); +void Project::clearMaps() { + qDeleteAll(this->maps); + this->maps.clear(); } void Project::clearTilesetCache() { @@ -137,31 +137,18 @@ void Project::clearTilesetCache() { this->tilesetCache.clear(); } -Map* Project::loadMap(QString mapName) { - if (mapName == getDynamicMapName()) +Map* Project::loadMap(const QString &mapName) { + Map* map = this->maps.value(mapName); + if (!map) return nullptr; - Map *map; - if (mapCache.contains(mapName)) { - map = mapCache.value(mapName); - // TODO: uncomment when undo/redo history is fully implemented for all actions. - if (true/*map->hasUnsavedChanges()*/) { - return map; - } - } else { - map = new Map; - map->setName(mapName); - } + if (map->loaded()) + return map; - if (!(loadMapData(map) && loadMapLayout(map))){ - delete map; + if (!(loadMapData(map) && loadMapLayout(map))) return nullptr; - } - // If the map's MAPSEC value in the header changes, update our global array to keep it in sync. - connect(map->header(), &MapHeader::locationChanged, [this, map] { this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); }); - - mapCache.insert(mapName, map); + map->setLoaded(true); emit mapLoaded(map); return map; } @@ -235,11 +222,18 @@ bool Project::loadMapData(Map* map) { // We should already know the map constant ID from the initial project launch, but we'll ensure it's correct here anyway. map->setConstantName(ParseUtil::jsonToQString(mapObj["id"])); - this->mapNamesToMapConstants.insert(map->name(), map->constantName()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); + const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + Layout* layout = this->mapLayouts.value(layoutId); + if (!layout) { + // We've already verified layout IDs on project launch and ignored maps with invalid IDs, so this shouldn't happen. + logError(QString("Cannot load map with unknown layout ID '%1'").arg(layoutId)); + return false; + } + map->setLayout(layout); + map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); - map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); map->header()->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); @@ -361,12 +355,9 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t this->mapNames.insert(mapNamePos, map->name()); this->groupNameToMapNames[settings.group].append(map->name()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - this->mapNamesToMapConstants.insert(map->name(), map->constantName()); - this->mapNameToLayoutId.insert(map->name(), map->layoutId()); - this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); map->setIsPersistedToFile(false); - this->mapCache.insert(map->name(), map); + this->maps.insert(map->name(), map); emit mapCreated(map, settings.group); @@ -446,18 +437,7 @@ Layout *Project::loadLayout(QString layoutId) { } bool Project::loadMapLayout(Map* map) { - if (!map->isPersistedToFile()) { - return true; - } - - Layout *layout = this->mapLayouts.value(map->layoutId()); - if (!layout) { - logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); - return false; - } - map->setLayout(layout); - - if (map->hasUnsavedChanges()) { + if (!map->isPersistedToFile() || map->hasUnsavedChanges()) { return true; } else { return loadLayout(map->layout()); @@ -667,7 +647,7 @@ void Project::saveMapGroups() { for (const auto &groupName : this->groupNames) { OrderedJson::array groupArr; for (const auto &mapName : this->groupNameToMapNames.value(groupName)) { - if (this->mapCache.value(mapName) && !this->mapCache.value(mapName)->isPersistedToFile()) { + if (this->maps.value(mapName) && !this->maps.value(mapName)->isPersistedToFile()) { // This is a new map that hasn't been saved yet, don't add it to the global map groups list yet. continue; } @@ -1127,7 +1107,7 @@ void Project::writeBlockdata(QString path, const Blockdata &blockdata) { } void Project::saveAll() { - for (auto map : this->mapCache) { + for (auto map : this->maps) { saveMap(map, true); // Avoid double-saving the layouts } for (auto layout : this->mapLayouts) { @@ -1137,6 +1117,8 @@ void Project::saveAll() { } void Project::saveMap(Map *map, bool skipLayout) { + if (!map || !map->loaded()) return; + // Create/Modify a few collateral files for brand new maps. const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); @@ -1198,7 +1180,7 @@ void Project::saveMap(Map *map, bool skipLayout) { OrderedJson::array connectionsArr; for (const auto &connection : connections) { OrderedJson::object connectionObj; - connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName(), connection->targetMapName()); + connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); connectionsArr.append(connectionObj); @@ -1548,15 +1530,6 @@ Blockdata Project::readBlockdata(QString path, bool *ok) { return blockdata; } -Map* Project::getMap(QString map_name) { - if (mapCache.contains(map_name)) { - return mapCache.value(map_name); - } else { - Map *map = loadMap(map_name); - return map; - } -} - Tileset* Project::getTileset(QString label, bool forceLoad) { Tileset *existingTileset = nullptr; if (tilesetCache.contains(label)) { @@ -1760,8 +1733,8 @@ bool Project::readWildMonData() { } bool Project::readMapGroups() { + clearMaps(); this->mapConstantsToMapNames.clear(); - this->mapNamesToMapConstants.clear(); this->mapNames.clear(); this->groupNames.clear(); this->groupNameToMapNames.clear(); @@ -1780,8 +1753,10 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + // Save special "Dynamic" constant const QString dynamicMapName = getDynamicMapName(); - const QString dynamicMapConstant = getDynamicMapDefineName(); + this->mapConstantsToMapNames.insert(getDynamicMapDefineName(), dynamicMapName); + this->mapNames.append(dynamicMapName); // Process the map group lists QStringList failedMapNames; @@ -1793,11 +1768,6 @@ bool Project::readMapGroups() { // Process the names in this map group for (int j = 0; j < mapNamesJson.size(); j++) { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); - if (mapName == dynamicMapName) { - logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); - failedMapNames.append(mapName); - continue; - } if (this->mapNames.contains(mapName)) { logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); failedMapNames.append(mapName); @@ -1819,11 +1789,6 @@ bool Project::readMapGroups() { failedMapNames.append(mapName); continue; } - if (mapConstant == dynamicMapConstant) { - logWarn(QString("Ignoring map with reserved \"id\" value '%1'.").arg(mapName)); - failedMapNames.append(mapName); - continue; - } const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); if (!mapConstant.startsWith(expectedPrefix)) { logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); @@ -1855,22 +1820,28 @@ bool Project::readMapGroups() { logWarn(QString("Map '%1' has unknown \"region_map_section\" value '%2'.").arg(mapName).arg(mapSectionName)); } - // Success, save the constants to the project + // Success, create the Map object + auto map = new Map; + map->setName(mapName); + map->setConstantName(mapConstant); + map->setLayout(this->mapLayouts.value(layoutId)); + map->header()->setLocation(mapSectionName); + this->maps.insert(mapName, map); + this->mapNames.append(mapName); this->groupNameToMapNames[groupName].append(mapName); this->mapConstantsToMapNames.insert(mapConstant, mapName); - this->mapNamesToMapConstants.insert(mapName, mapConstant); - this->mapNameToLayoutId.insert(mapName, layoutId); - this->mapNameToMapSectionName.insert(mapName, mapSectionName); } } + // TODO: This might be ok now that we have layout-only mode? if (this->groupNames.isEmpty()) { logError(QString("Failed to find any map groups in %1").arg(filepath)); return false; } - if (this->mapNames.isEmpty()) { - logError(QString("Failed to find any map names in %1").arg(filepath)); + // TODO: This might be ok now that we have layout-only mode? + if (this->maps.isEmpty()) { + logError(QString("Failed to find any maps in %1").arg(filepath)); return false; } @@ -1880,11 +1851,6 @@ bool Project::readMapGroups() { emit mapsExcluded(failedMapNames); } - // Save special "Dynamic" constant - this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); - this->mapNamesToMapConstants.insert(dynamicMapName, dynamicMapConstant); - this->mapNames.append(dynamicMapName); - return true; } @@ -1899,7 +1865,7 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } -QString Project::mapNameToMapGroup(const QString &mapName) { +QString Project::mapNameToMapGroup(const QString &mapName) const { for (auto it = this->groupNameToMapNames.constBegin(); it != this->groupNameToMapNames.constEnd(); it++) { const QStringList mapNames = it.value(); if (mapNames.contains(mapName)) { @@ -1909,6 +1875,23 @@ QString Project::mapNameToMapGroup(const QString &mapName) { return QString(); } +QString Project::getMapConstant(const QString &mapName, const QString &defaultValue) const { + if (mapName == getDynamicMapName()) return getDynamicMapDefineName(); + + Map* map = this->maps.value(mapName); + return map ? map->constantName() : defaultValue; +} + +QString Project::getMapLayoutId(const QString &mapName, const QString &defaultValue) const { + Map* map = this->maps.value(mapName); + return (map && map->layout()) ? map->layout()->id : defaultValue; +} + +QString Project::getMapLocation(const QString &mapName, const QString &defaultValue) const { + Map* map = this->maps.value(mapName); + return map ? map->header()->location() : defaultValue; +} + // When we ask the user to provide a new identifier for something (like a map name or MAPSEC 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. @@ -1937,7 +1920,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { if (this->encounterGroupLabels.contains(identifier)) return false; // Check event IDs - for (const auto &map : this->mapCache) { + for (const auto &map : this->maps) { + if (!map->loaded()) continue; auto events = map->getEvents(); for (const auto &event : events) { QString idName = event->getIdName(); @@ -3202,16 +3186,14 @@ bool Project::hasUnsavedChanges() { return true; // Check layouts for unsaved changes - for (auto i = this->mapLayouts.constBegin(); i != this->mapLayouts.constEnd(); i++) { - auto layout = i.value(); - if (layout && layout->hasUnsavedChanges()) + for (const auto &layout : this->mapLayouts) { + if (layout->hasUnsavedChanges()) return true; } - // Check loaded maps for unsaved changes - for (auto i = this->mapCache.constBegin(); i != this->mapCache.constEnd(); i++) { - auto map = i.value(); - if (map && map->hasUnsavedChanges()) + // Check maps for unsaved changes + for (const auto &map : this->maps) { + if (map->hasUnsavedChanges()) return true; } return false; diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a8c330d2..1bdc43aa 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -104,7 +104,7 @@ QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); - map->setToolTip(this->project->mapNamesToMapConstants.value(mapName)); + map->setToolTip(this->project->getMapConstant(mapName)); this->mapItems.insert(mapName, map); return map; } @@ -164,10 +164,10 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { if (name == this->activeItemName) return this->mapOpenedIcon; - const Map* map = this->project->mapCache.value(name); - if (!map) + const Map* map = this->project->getMap(name); + if (!map || !map->loaded()) return this->mapGrayIcon; - return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; + return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } else if (type == this->folderTypeName) { // Decorating map folder in the map list return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon; @@ -446,7 +446,7 @@ MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListM insertMapFolderItem(idName); } for (const auto &mapName : this->project->mapNames) { - insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); + insertMapItem(mapName, this->project->getMapLocation(mapName)); } } @@ -470,7 +470,7 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListMod insertMapFolderItem(layoutId); } for (const auto &mapName : this->project->mapNames) { - insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); + insertMapItem(mapName, this->project->getMapLayoutId(mapName)); } } From 763382a8119037bee4f5ad1b4b6ce7b3ac6a52a0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 15:00:58 -0400 Subject: [PATCH 235/364] Allow projects with no maps or map groups --- src/project.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 6b24ffb7..cf779d5c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -467,11 +467,6 @@ bool Project::readMapLayouts() { } QJsonObject layoutsObj = layoutsDoc.object(); - QJsonArray layouts = layoutsObj["layouts"].toArray(); - if (layouts.size() == 0) { - logError(QString("'layouts' array is missing from %1.").arg(layoutsFilepath)); - return false; - } this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); if (this->layoutsLabel.isEmpty()) { @@ -481,6 +476,7 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } + QJsonArray layouts = layoutsObj["layouts"].toArray(); for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) @@ -563,6 +559,11 @@ bool Project::readMapLayouts() { this->layoutIdsMaster.append(layout->id); } + if (this->mapLayouts.isEmpty()) { + logError(QString("Failed to read any map layouts from '%1'. At least one map layout is required.").arg(layoutsFilepath)); + return false; + } + return true; } @@ -1834,16 +1835,7 @@ bool Project::readMapGroups() { } } - // TODO: This might be ok now that we have layout-only mode? - if (this->groupNames.isEmpty()) { - logError(QString("Failed to find any map groups in %1").arg(filepath)); - return false; - } - // TODO: This might be ok now that we have layout-only mode? - if (this->maps.isEmpty()) { - logError(QString("Failed to find any maps in %1").arg(filepath)); - return false; - } + // Note: Not successfully reading any maps or map groups is ok. We only require at least 1 map layout. if (!failedMapNames.isEmpty()) { // At least 1 map was excluded due to an error. @@ -1951,7 +1943,7 @@ QString Project::toUniqueIdentifier(const QString &identifier) const { void Project::initNewMapSettings() { this->newMapSettings.name = QString(); - this->newMapSettings.group = this->groupNames.at(0); + this->newMapSettings.group = this->groupNames.value(0); this->newMapSettings.canFlyTo = false; this->newMapSettings.layout.folderName = this->newMapSettings.name; From 7cfbcda9c25684966c24bfda80227eeb0a28978d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 15:53:15 -0400 Subject: [PATCH 236/364] Create new MAPSECs automatically on save if needed --- include/core/validator.h | 2 +- include/project.h | 4 ++-- src/core/validator.cpp | 5 +++-- src/project.cpp | 30 +++++++++++++++++++++--------- src/ui/newlocationdialog.cpp | 6 +----- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/include/core/validator.h b/include/core/validator.h index 2419de5d..46b916ce 100644 --- a/include/core/validator.h +++ b/include/core/validator.h @@ -34,7 +34,7 @@ public: QString prefix() const { return m_prefix; } void setPrefix(const QString &prefix); - bool isValid(QString &input) const; + bool isValid(const QString &input) const; private: QString m_prefix; diff --git a/include/project.h b/include/project.h index d393fbbb..23cd0f57 100644 --- a/include/project.h +++ b/include/project.h @@ -131,7 +131,7 @@ public: Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); Tileset *createNewTileset(QString name, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; - bool isValidNewIdentifier(QString identifier) const; + bool isValidNewIdentifier(const QString &identifier) const; QString toUniqueIdentifier(const QString &identifier) const; QString getProjectTitle() const; QString getNewHealLocationName(const Map* map) const; @@ -147,7 +147,7 @@ public: QString getDefaultSpeciesIconPath(const QString &species); QPixmap getSpeciesIcon(const QString &species); - void addNewMapsec(const QString &idName); + bool addNewMapsec(const QString &idName, const QString &displayName = QString()); void removeMapsec(const QString &idName); QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); } void setMapsecDisplayName(const QString &idName, const QString &displayName); diff --git a/src/core/validator.cpp b/src/core/validator.cpp index 6f7a72a7..2b0fcf0b 100644 --- a/src/core/validator.cpp +++ b/src/core/validator.cpp @@ -35,7 +35,8 @@ void PrefixValidator::fixup(QString &input) const { input.prepend(m_prefix); } -bool PrefixValidator::isValid(QString &input) const { +bool PrefixValidator::isValid(const QString &input) const { int pos = 0; - return validate(input, pos) == QValidator::Acceptable; + QString s(input); + return validate(s, pos) == QValidator::Acceptable; } diff --git a/src/project.cpp b/src/project.cpp index cf779d5c..5dc4e67d 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -346,11 +346,8 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t } map->setLayout(layout); - const QString location = map->header()->location(); - if (!this->mapSectionIdNames.contains(location) && isValidNewIdentifier(location)) { - // Unrecognized MAPSEC name, we can automatically add a new MAPSEC for it. - addNewMapsec(location); - } + // Try to record the MAPSEC name in case this is a new name. + addNewMapsec(map->header()->location()); this->mapNames.insert(mapNamePos, map->name()); this->groupNameToMapNames[settings.group].append(map->name()); @@ -1251,6 +1248,9 @@ void Project::saveMap(Map *map, bool skipLayout) { if (!skipLayout) saveLayout(map->layout()); + // Try to record the MAPSEC name in case this is a new name. + addNewMapsec(map->header()->location()); + map->setClean(); } @@ -1925,8 +1925,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { } // For some arbitrary string, return true if it's both a valid identifier name and not one that's already in-use. -bool Project::isValidNewIdentifier(QString identifier) const { - IdentifierValidator validator; +bool Project::isValidNewIdentifier(const QString &identifier) const { + static const IdentifierValidator validator; return validator.isValid(identifier) && isIdentifierUnique(identifier); } @@ -2332,8 +2332,18 @@ QString Project::getMapGroupPrefix() { return QStringLiteral("gMapGroup_"); } -// This function assumes a valid and unique name -void Project::addNewMapsec(const QString &idName) { +bool Project::addNewMapsec(const QString &idName, const QString &displayName) { + if (this->mapSectionIdNames.contains(idName)) { + // Already added + return false; + } + + IdentifierValidator validator(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); + if (!validator.isValid(idName)) { + logWarn(QString("Cannot add new MAPSEC with invalid name '%1'").arg(idName)); + return false; + } + if (this->mapSectionIdNamesSaveOrder.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. this->mapSectionIdNamesSaveOrder.insert(this->mapSectionIdNames.length() - 1, idName); @@ -2348,6 +2358,8 @@ void Project::addNewMapsec(const QString &idName) { emit mapSectionAdded(idName); emit mapSectionIdNamesChanged(this->mapSectionIdNames); + if (!displayName.isEmpty()) setMapsecDisplayName(idName, displayName); + return true; } void Project::removeMapsec(const QString &idName) { diff --git a/src/ui/newlocationdialog.cpp b/src/ui/newlocationdialog.cpp index d0586d99..b21904fd 100644 --- a/src/ui/newlocationdialog.cpp +++ b/src/ui/newlocationdialog.cpp @@ -69,11 +69,7 @@ void NewLocationDialog::accept() { if (!validateIdName()) return; - const QString idName = ui->lineEdit_IdName->text(); - const QString displayName = ui->lineEdit_DisplayName->text(); - - this->project->addNewMapsec(idName); - this->project->setMapsecDisplayName(idName, displayName); + this->project->addNewMapsec(ui->lineEdit_IdName->text(), ui->lineEdit_DisplayName->text()); QDialog::accept(); } From f4d4980aadc0e3fec824a0c03c778417c56f34fc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 16:12:35 -0400 Subject: [PATCH 237/364] Fix crash in layout selector --- src/mainwindow.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 85db9697..0f28ead1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1064,13 +1064,26 @@ void MainWindow::displayMapProperties() { } void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { - if (editor && editor->project && editor->map) { - if (editor->project->mapLayouts.contains(text)) { - editor->map->setLayout(editor->project->loadLayout(text)); - setMap(editor->map->name()); - markMapEdited(); - } + if (!this->editor || !this->editor->project || !this->editor->map) + return; + + if (!this->editor->project->mapLayouts.contains(text)) { + // User may be in the middle of typing the name of a layout, don't bother trying to load it. + return; } + + Layout* layout = this->editor->project->loadLayout(text); + if (!layout) { + RecentErrorMessage::show(QString("Unable to set layout '%1'.").arg(text), this); + + // New layout failed to load, restore previous layout + const QSignalBlocker b(ui->comboBox_LayoutSelector); + ui->comboBox_LayoutSelector->setCurrentText(this->editor->map->layout()->id); + return; + } + this->editor->map->setLayout(layout); + setMap(this->editor->map->name()); + markMapEdited(); } // Update the UI using information we've read from the user's project files. From 1375572be14741814d558d48aa8570e785beb0b3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 18:53:18 -0400 Subject: [PATCH 238/364] Update map list when MAPSEC/layout is changed --- include/core/map.h | 3 ++- include/core/maplayout.h | 4 +--- include/mainwindow.h | 3 ++- src/core/map.cpp | 8 ++++++++ src/core/maplayout.cpp | 8 ++------ src/editor.cpp | 2 +- src/mainwindow.cpp | 29 +++++++++++++++++++++-------- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 22c21d96..a90105b0 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -48,7 +48,7 @@ public: static QString mapConstantFromName(const QString &name); QString expectedConstantName() const { return Map::mapConstantFromName(m_name); } - void setLayout(Layout *layout) { m_layout = layout; } + void setLayout(Layout *layout); Layout* layout() const { return m_layout; } int getWidth() const; @@ -143,6 +143,7 @@ signals: void openScriptRequested(QString label); void connectionAdded(MapConnection*); void connectionRemoved(MapConnection*); + void layoutChanged(); }; #endif // MAP_H diff --git a/include/core/maplayout.h b/include/core/maplayout.h index cbe719b7..9b45a689 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -141,9 +141,7 @@ private: void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); signals: - void layoutChanged(Layout *layout); - //void modified(); - void layoutDimensionsChanged(const QSize &size); + void dimensionsChanged(const QSize &size); void needsRedrawing(); }; diff --git a/include/mainwindow.h b/include/mainwindow.h index 69e56b3c..8e57621a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -186,7 +186,6 @@ private slots: void copy(); void paste(); - void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); void onNewMapCreated(Map *newMap, const QString &groupName); @@ -383,6 +382,8 @@ private: void refreshRecentProjectsMenu(); + void rebuildMapList_Locations(); + void rebuildMapList_Layouts(); void updateMapList(); void openMapListItem(const QModelIndex &index); void onMapListTabChanged(int index); diff --git a/src/core/map.cpp b/src/core/map.cpp index dc328cf0..330132a1 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -47,6 +47,14 @@ Map::~Map() { deleteConnections(); } +// Note: Map does not take ownership of layout +void Map::setLayout(Layout *layout) { + if (layout == m_layout) + return; + m_layout = layout; + emit layoutChanged(); +} + // We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID. QString Map::mapConstantFromName(const QString &name) { return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index d2083203..e443a635 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -168,8 +168,7 @@ void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bo Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); } - emit layoutChanged(this); - emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); + emit dimensionsChanged(QSize(getWidth(), getHeight())); } void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { @@ -194,8 +193,7 @@ void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { this->width = newWidth; this->height = newHeight; - emit layoutChanged(this); - emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); + emit dimensionsChanged(QSize(getWidth(), getHeight())); } void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { @@ -211,8 +209,6 @@ void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockda if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); } - - emit layoutChanged(this); } void Layout::setNewDimensionsBlockdata(int newWidth, int newHeight) { diff --git a/src/editor.cpp b/src/editor.cpp index bb79f98c..d8c13d62 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1204,7 +1204,7 @@ bool Editor::setLayout(QString layoutId) { editGroup.addStack(&this->layout->editHistory); map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); - connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); + connect(this->layout, &Layout::dimensionsChanged, map_ruler, &MapRuler::setMapDimensions); ui->comboBox_PrimaryTileset->blockSignals(true); ui->comboBox_SecondaryTileset->blockSignals(true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0f28ead1..840c2920 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -926,7 +926,11 @@ bool MainWindow::setMap(QString map_name) { connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); - connect(editor->layout, &Layout::layoutChanged, this, &MainWindow::onLayoutChanged, Qt::UniqueConnection); + // If the map's MAPSEC / layout changes, update the map's position in the map list. + // These are doing more work than necessary, rather than rebuilding the entire list they should find and relocate the appropriate row. + connect(editor->map, &Map::layoutChanged, this, &MainWindow::rebuildMapList_Layouts, Qt::UniqueConnection); + connect(editor->map->header(), &MapHeader::locationChanged, this, &MainWindow::rebuildMapList_Locations, Qt::UniqueConnection); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); userConfig.recentMapOrLayout = map_name; @@ -1539,6 +1543,19 @@ void MainWindow::openMapListItem(const QModelIndex &index) { if (toolbar) toolbar->setFilterLocked(false); } +void MainWindow::rebuildMapList_Locations() { + this->mapLocationModel->deleteLater(); + this->mapLocationModel = new MapLocationModel(this->editor->project); + this->locationListProxyModel->setSourceModel(this->mapLocationModel); + resetMapListFilters(); +} +void MainWindow::rebuildMapList_Layouts() { + this->layoutTreeModel->deleteLater(); + this->layoutTreeModel = new LayoutTreeModel(this->editor->project); + this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); + resetMapListFilters(); +} + void MainWindow::updateMapList() { // Get the name of the open map/layout (or clear the relevant selection if there is none). QString activeItemName; @@ -1583,9 +1600,9 @@ void MainWindow::save(bool currentOnly) { if (!porymapConfig.shownInGameReloadMessage) { // Show a one-time warning that the user may need to reload their map to see their new changes. - static const QString message = QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, " - "the changes may not appear until you leave the map and return."); - InfoMessage::show(message, this); + InfoMessage::show(QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, " + "the changes may not appear until you leave the map and return."), + this); porymapConfig.shownInGameReloadMessage = true; } @@ -2420,10 +2437,6 @@ void MainWindow::onOpenConnectedMap(MapConnection *connection) { editor->setSelectedConnection(connection->findMirror()); } -void MainWindow::onLayoutChanged(Layout *) { - updateMapList(); -} - void MainWindow::onMapLoaded(Map *map) { connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(map); }); } From 4b1332609e8b325687dbfaff0941f75bc6b18ad2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 23:23:40 -0400 Subject: [PATCH 239/364] Restore layout selector if left in invalid state --- include/mainwindow.h | 1 + src/mainwindow.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/include/mainwindow.h b/include/mainwindow.h index 8e57621a..1320f649 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -204,6 +204,7 @@ private slots: void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); void on_action_Exit_triggered(); + void onLayoutSelectorEditingFinished(); void on_comboBox_LayoutSelector_currentTextChanged(const QString &text); void on_actionShortcuts_triggered(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 840c2920..6195025c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -298,6 +298,7 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog); connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); + connect(ui->comboBox_LayoutSelector->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -1090,6 +1091,18 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te markMapEdited(); } +void MainWindow::onLayoutSelectorEditingFinished() { + if (!this->editor || !this->editor->project || !this->editor->layout) + return; + + // If the user left the layout selector in an invalid state, restore it so that it displays the current layout. + const QString text = ui->comboBox_LayoutSelector->currentText(); + if (!this->editor->project->mapLayouts.contains(text)) { + const QSignalBlocker b(ui->comboBox_LayoutSelector); + ui->comboBox_LayoutSelector->setCurrentText(this->editor->layout->id); + } +} + // Update the UI using information we've read from the user's project files. bool MainWindow::setProjectUI() { Project *project = editor->project; From e6f4e64aa4e4d0609e91f874ed05c71d823372bd Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 14 Mar 2025 23:24:17 -0400 Subject: [PATCH 240/364] Add onLayoutOpened API callback --- docsrc/manual/scripting-capabilities.rst | 11 +++- include/scripting.h | 4 ++ include/ui/mapview.h | 6 +- resources/text/script_template.txt | 5 ++ src/editor.cpp | 6 ++ src/mainwindow.cpp | 10 +--- src/scriptapi/apioverlay.cpp | 76 +++++++++++++----------- src/scriptapi/scripting.cpp | 10 ++++ 8 files changed, 82 insertions(+), 46 deletions(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index b645f49a..ac1cf521 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -153,11 +153,18 @@ Callbacks .. js:function:: onMapOpened(mapName) - Called when a map or layout is opened. + Called when a map is opened. - :param mapName: the name of the opened map or layout + :param mapName: the name of the opened map :type mapName: string +.. js:function:: onLayoutOpened(layoutName) + + Called when a layout is opened, either by selecting a new map/layout in the map list or swapping the layout for the current map. + + :param layoutName: the name of the opened layout + :type layoutName: string + .. js:function:: onBlockChanged(x, y, prevBlock, newBlock) Called when a block is changed on the map. For example, this is called when a user paints a new tile or changes the collision property of a block. diff --git a/include/scripting.h b/include/scripting.h index c08bbd74..6870f159 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -9,6 +9,8 @@ #include #include +// !! New callback functions or changes to existing callback function names/arguments +// should be synced to resources/text/script_template.txt and docsrc/manual/scripting-capabilities.rst enum CallbackType { OnProjectOpened, OnProjectClosed, @@ -17,6 +19,7 @@ enum CallbackType { OnBlockHoverChanged, OnBlockHoverCleared, OnMapOpened, + OnLayoutOpened, OnMapResized, OnBorderResized, OnMapShifted, @@ -43,6 +46,7 @@ public: static void cb_BlockHoverChanged(int x, int y); static void cb_BlockHoverCleared(); static void cb_MapOpened(QString mapName); + static void cb_LayoutOpened(QString layoutName); static void cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_MapShifted(int xDelta, int yDelta); diff --git a/include/ui/mapview.h b/include/ui/mapview.h index 7355da9d..aa271757 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -70,11 +70,13 @@ public: Q_INVOKABLE void addTileImage(int x, int y, QJSValue tileObj, bool setTransparency = false, int layer = 0); Q_INVOKABLE void addMetatileImage(int x, int y, int metatileId, bool setTransparency = false, int layer = 0); -private: - QMap overlayMap; protected: virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void keyPressEvent(QKeyEvent*) override; +private: + QMap overlayMap; + + void updateScene(); }; #endif // GRAPHICSVIEW_H diff --git a/resources/text/script_template.txt b/resources/text/script_template.txt index fdd1949f..4b5134d1 100644 --- a/resources/text/script_template.txt +++ b/resources/text/script_template.txt @@ -13,6 +13,11 @@ export function onMapOpened(mapName) { } +// Called when a layout is opened, either by selecting a new map/layout in the map list or swapping the layout for the current map. +export function onLayoutOpened(layoutName) { + +} + // Called when a block is changed on the map. For example, this is called when a user paints a new tile or changes the collision property of a block. export function onBlockChanged(x, y, prevBlock, newBlock) { diff --git a/src/editor.cpp b/src/editor.cpp index d8c13d62..2e290f8a 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1191,6 +1191,9 @@ bool Editor::setLayout(QString layoutId) { return false; } + QString prevLayoutName; + if (this->layout) prevLayoutName = this->layout->name; + Layout *loadedLayout = this->project->loadLayout(layoutId); if (!loadedLayout) { return false; @@ -1218,6 +1221,9 @@ bool Editor::setLayout(QString layoutId) { if (index < 0) index = 0; this->ui->comboBox_LayoutSelector->setCurrentIndex(index); + if (this->layout->name != prevLayoutName) + Scripting::cb_LayoutOpened(this->layout->name); + return true; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6195025c..85e0df3e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -988,7 +988,6 @@ bool MainWindow::setLayout(QString layoutId) { connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); - Scripting::cb_MapOpened(layout->name); updateTilesetEditor(); userConfig.recentMapOrLayout = layoutId; @@ -2848,13 +2847,10 @@ void MainWindow::reloadScriptEngine() { // Lying to the scripts here, simulating a project reload Scripting::cb_ProjectOpened(projectConfig.projectDir); if (this->editor) { - QString curName; + if (this->editor->layout) + Scripting::cb_LayoutOpened(this->editor->layout->name); if (this->editor->map) - curName = this->editor->map->name(); - else if (editor->layout) - curName = this->editor->layout->name; - - Scripting::cb_MapOpened(curName); + Scripting::cb_MapOpened(this->editor->map->name()); } } diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index b12f5f09..4b1f75b8 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -2,15 +2,21 @@ #include "scripting.h" #include "imageproviders.h" +void MapView::updateScene() { + if (this->scene()) { + this->scene()->update(); + } +} + void MapView::clear(int layer) { this->getOverlay(layer)->clearItems(); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, clear all layers void MapView::clear() { this->clearOverlayMap(); - this->scene()->update(); + this->updateScene(); } void MapView::hide(int layer) { @@ -37,14 +43,14 @@ bool MapView::getVisibility(int layer) { void MapView::setVisibility(bool visible, int layer) { this->getOverlay(layer)->setHidden(!visible); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set visibility of all layers void MapView::setVisibility(bool visible) { foreach (Overlay * layer, this->overlayMap) layer->setHidden(!visible); - this->scene()->update(); + this->updateScene(); } int MapView::getX(int layer) { @@ -57,49 +63,49 @@ int MapView::getY(int layer) { void MapView::setX(int x, int layer) { this->getOverlay(layer)->setX(x); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set x of all layers void MapView::setX(int x) { foreach (Overlay * layer, this->overlayMap) layer->setX(x); - this->scene()->update(); + this->updateScene(); } void MapView::setY(int y, int layer) { this->getOverlay(layer)->setY(y); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set y of all layers void MapView::setY(int y) { foreach (Overlay * layer, this->overlayMap) layer->setY(y); - this->scene()->update(); + this->updateScene(); } void MapView::setClippingRect(int x, int y, int width, int height, int layer) { this->getOverlay(layer)->setClippingRect(QRectF(x, y, width, height)); - this->scene()->update(); + this->updateScene(); } void MapView::setClippingRect(int x, int y, int width, int height) { QRectF rect = QRectF(x, y, width, height); foreach (Overlay * layer, this->overlayMap) layer->setClippingRect(rect); - this->scene()->update(); + this->updateScene(); } void MapView::clearClippingRect(int layer) { this->getOverlay(layer)->clearClippingRect(); - this->scene()->update(); + this->updateScene(); } void MapView::clearClippingRect() { foreach (Overlay * layer, this->overlayMap) layer->clearClippingRect(); - this->scene()->update(); + this->updateScene(); } QJSValue MapView::getPosition(int layer) { @@ -109,26 +115,26 @@ QJSValue MapView::getPosition(int layer) { void MapView::setPosition(int x, int y, int layer) { this->getOverlay(layer)->setPosition(x, y); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set position of all layers void MapView::setPosition(int x, int y) { foreach (Overlay * layer, this->overlayMap) layer->setPosition(x, y); - this->scene()->update(); + this->updateScene(); } void MapView::move(int deltaX, int deltaY, int layer) { this->getOverlay(layer)->move(deltaX, deltaY); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, move all layers void MapView::move(int deltaX, int deltaY) { foreach (Overlay * layer, this->overlayMap) layer->move(deltaX, deltaY); - this->scene()->update(); + this->updateScene(); } int MapView::getOpacity(int layer) { @@ -137,14 +143,14 @@ int MapView::getOpacity(int layer) { void MapView::setOpacity(int opacity, int layer) { this->getOverlay(layer)->setOpacity(opacity); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set opacity of all layers void MapView::setOpacity(int opacity) { foreach (Overlay * layer, this->overlayMap) layer->setOpacity(opacity); - this->scene()->update(); + this->updateScene(); } qreal MapView::getHorizontalScale(int layer) { @@ -157,38 +163,38 @@ qreal MapView::getVerticalScale(int layer) { void MapView::setHorizontalScale(qreal scale, int layer) { this->getOverlay(layer)->setHScale(scale); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set horizontal scale of all layers void MapView::setHorizontalScale(qreal scale) { foreach (Overlay * layer, this->overlayMap) layer->setHScale(scale); - this->scene()->update(); + this->updateScene(); } void MapView::setVerticalScale(qreal scale, int layer) { this->getOverlay(layer)->setVScale(scale); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set vertical scale of all layers void MapView::setVerticalScale(qreal scale) { foreach (Overlay * layer, this->overlayMap) layer->setVScale(scale); - this->scene()->update(); + this->updateScene(); } void MapView::setScale(qreal hScale, qreal vScale, int layer) { this->getOverlay(layer)->setScale(hScale, vScale); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set scale of all layers void MapView::setScale(qreal hScale, qreal vScale) { foreach (Overlay * layer, this->overlayMap) layer->setScale(hScale, vScale); - this->scene()->update(); + this->updateScene(); } int MapView::getRotation(int layer) { @@ -197,41 +203,41 @@ int MapView::getRotation(int layer) { void MapView::setRotation(int angle, int layer) { this->getOverlay(layer)->setRotation(angle); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, set rotation of all layers void MapView::setRotation(int angle) { foreach (Overlay * layer, this->overlayMap) layer->setRotation(angle); - this->scene()->update(); + this->updateScene(); } void MapView::rotate(int degrees, int layer) { this->getOverlay(layer)->rotate(degrees); - this->scene()->update(); + this->updateScene(); } // Overload. No layer provided, rotate all layers void MapView::rotate(int degrees) { foreach (Overlay * layer, this->overlayMap) layer->rotate(degrees); - this->scene()->update(); + this->updateScene(); } void MapView::addText(QString text, int x, int y, QString color, int fontSize, int layer) { this->getOverlay(layer)->addText(text, x, y, color, fontSize); - this->scene()->update(); + this->updateScene(); } void MapView::addRect(int x, int y, int width, int height, QString borderColor, QString fillColor, int rounding, int layer) { if (this->getOverlay(layer)->addRect(x, y, width, height, borderColor, fillColor, rounding)) - this->scene()->update(); + this->updateScene(); } void MapView::addPath(QList xCoords, QList yCoords, QString borderColor, QString fillColor, int layer) { if (this->getOverlay(layer)->addPath(xCoords, yCoords, borderColor, fillColor)) - this->scene()->update(); + this->updateScene(); } void MapView::addPath(QList> coords, QString borderColor, QString fillColor, int layer) { @@ -250,7 +256,7 @@ void MapView::addPath(QList> coords, QString borderColor, QString fil void MapView::addImage(int x, int y, QString filepath, int layer, bool useCache) { if (this->getOverlay(layer)->addImage(x, y, filepath, useCache)) - this->scene()->update(); + this->updateScene(); } void MapView::createImage(int x, int y, QString filepath, int width, int height, int xOffset, int yOffset, qreal hScale, qreal vScale, int paletteId, bool setTransparency, int layer, bool useCache) { @@ -260,7 +266,7 @@ void MapView::createImage(int x, int y, QString filepath, int width, int height, if (paletteId != -1) palette = Tileset::getPalette(paletteId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->getOverlay(layer)->addImage(x, y, filepath, useCache, width, height, xOffset, yOffset, hScale, vScale, palette, setTransparency)) - this->scene()->update(); + this->updateScene(); } void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int paletteId, bool setTransparency, int layer) { @@ -274,7 +280,7 @@ void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) - this->scene()->update(); + this->updateScene(); } void MapView::addTileImage(int x, int y, QJSValue tileObj, bool setTransparency, int layer) { @@ -293,5 +299,5 @@ void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparenc if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) - this->scene()->update(); + this->updateScene(); } diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 3eb9afd6..05fd31d9 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -12,6 +12,7 @@ const QMap callbackFunctions = { {OnBlockHoverChanged, "onBlockHoverChanged"}, {OnBlockHoverCleared, "onBlockHoverCleared"}, {OnMapOpened, "onMapOpened"}, + {OnLayoutOpened, "onLayoutOpened"}, {OnMapResized, "onMapResized"}, {OnBorderResized, "onBorderResized"}, {OnMapShifted, "onMapShifted"}, @@ -258,6 +259,15 @@ void Scripting::cb_MapOpened(QString mapName) { instance->invokeCallback(OnMapOpened, args); } +void Scripting::cb_LayoutOpened(QString layoutName) { + if (!instance) return; + + QJSValueList args { + layoutName, + }; + instance->invokeCallback(OnLayoutOpened, args); +} + void Scripting::cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight) { if (!instance) return; From 4b8bbe94008190720a56b9a4bbaa698c07d0dabc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 16 Mar 2025 16:11:39 -0400 Subject: [PATCH 241/364] Bump Porymap version --- porymap.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/porymap.pro b/porymap.pro index 2675e700..35fd6af2 100644 --- a/porymap.pro +++ b/porymap.pro @@ -30,7 +30,7 @@ win32 { DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\" -VERSION = 5.4.1 +VERSION = 6.0.0 DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" SOURCES += src/core/advancemapparser.cpp \ From 854880f9f802d732949a54287cee25856bd6e8c6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 16 Mar 2025 17:53:09 -0400 Subject: [PATCH 242/364] Save map list settings in config --- include/config.h | 4 +++ include/mainwindow.h | 1 + include/ui/maplisttoolbar.h | 2 ++ src/config.cpp | 20 +++++++++++-- src/mainwindow.cpp | 37 +++++++++++++++++++++--- src/ui/maplisttoolbar.cpp | 56 ++++++++++++++++++------------------- 6 files changed, 85 insertions(+), 35 deletions(-) diff --git a/include/config.h b/include/config.h index 1df1b4ef..c10c6545 100644 --- a/include/config.h +++ b/include/config.h @@ -52,6 +52,8 @@ public: this->projectManuallyClosed = false; this->reopenOnLaunch = true; this->mapListTab = 0; + this->mapListEditGroupsEnabled = false; + this->mapListHideEmptyEnabled.clear(); this->prettyCursors = true; this->mirrorConnectingMaps = true; this->showDiveEmergeMaps = false; @@ -110,6 +112,8 @@ public: bool reopenOnLaunch; bool projectManuallyClosed; int mapListTab; + bool mapListEditGroupsEnabled; + QMap mapListHideEmptyEnabled; bool prettyCursors; bool mirrorConnectingMaps; bool showDiveEmergeMaps; diff --git a/include/mainwindow.h b/include/mainwindow.h index 1320f649..d8fe18a3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -423,6 +423,7 @@ private: double getMetatilesZoomScale(); void redrawMetatileSelection(); void scrollMetatileSelectorToSelection(); + MapListToolBar* getMapListToolBar(int tab); MapListToolBar* getCurrentMapListToolBar(); MapTree* getCurrentMapList(); void setLocationComboBoxes(const QStringList &locations); diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h index 9890b584..655ce414 100644 --- a/include/ui/maplisttoolbar.h +++ b/include/ui/maplisttoolbar.h @@ -42,6 +42,8 @@ public: signals: void filterCleared(MapTree*); void addFolderClicked(); + void editsAllowedChanged(bool allowed); + void emptyFoldersVisibleChanged(bool visible); private: Ui::MapListToolBar *ui; diff --git a/src/config.cpp b/src/config.cpp index 1847cfcf..dc217117 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -304,6 +304,16 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->prettyCursors = getConfigBool(key, value); } else if (key == "map_list_tab") { this->mapListTab = getConfigInteger(key, value, 0, 2, 0); + } else if (key == "map_list_edit_groups_enabled") { + this->mapListEditGroupsEnabled = getConfigBool(key, value); + } else if (key.startsWith("map_list_hide_empty_enabled/")) { + bool ok; + int tab = key.mid(QStringLiteral("map_list_hide_empty_enabled/").length()).toInt(&ok, 0); + if (!ok) { + logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); + return; + } + this->mapListHideEmptyEnabled.insert(tab, getConfigBool(key, value)); } else if (key == "main_window_geometry") { this->mainWindowGeometry = bytesFromString(value); } else if (key == "main_window_state") { @@ -445,6 +455,10 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("reopen_on_launch", this->reopenOnLaunch ? "1" : "0"); map.insert("pretty_cursors", this->prettyCursors ? "1" : "0"); map.insert("map_list_tab", QString::number(this->mapListTab)); + map.insert("map_list_edit_groups_enabled", this->mapListEditGroupsEnabled ? "1" : "0"); + for (auto i = this->mapListHideEmptyEnabled.constBegin(); i != this->mapListHideEmptyEnabled.constEnd(); i++) { + map.insert(QStringLiteral("map_list_hide_empty_enabled/") + QString::number(i.key()), i.value() ? "1" : "0"); + } map.insert("main_window_geometry", stringFromByteArray(this->mainWindowGeometry)); map.insert("main_window_state", stringFromByteArray(this->mainWindowState)); map.insert("map_splitter_state", stringFromByteArray(this->mapSplitterState)); @@ -769,14 +783,14 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { userConfig.parseCustomScripts(value); #endif } else if (key.startsWith("path/")) { - auto k = reverseDefaultPaths(key.mid(5)); + auto k = reverseDefaultPaths(key.mid(QStringLiteral("path/").length())); if (k != static_cast(-1)) { this->setFilePath(k, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } } else if (key.startsWith("ident/")) { - auto identifierId = reverseDefaultIdentifier(key.mid(6)); + auto identifierId = reverseDefaultIdentifier(key.mid(QStringLiteral("ident/").length())); if (identifierId != static_cast(-1)) { this->setIdentifier(identifierId, value); } else { @@ -803,7 +817,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "event_icon_path_heal") { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { - this->pokemonIconPaths.insert(key.mid(18).toUpper(), value); + this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()).toUpper(), value); } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 85e0df3e..7aaa9ae0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -449,6 +449,27 @@ void MainWindow::initMapList() { ui->mapListToolBar_Locations->setEditsAllowedButtonVisible(false); ui->mapListToolBar_Layouts->setEditsAllowedButtonVisible(false); + // Initialize settings from config + ui->mapListToolBar_Groups->setEditsAllowed(porymapConfig.mapListEditGroupsEnabled); + for (auto i = porymapConfig.mapListHideEmptyEnabled.constBegin(); i != porymapConfig.mapListHideEmptyEnabled.constEnd(); i++) { + auto toolbar = getMapListToolBar(i.key()); + if (toolbar) toolbar->setEmptyFoldersVisible(!i.value()); + } + + // Update config if map list settings change + connect(ui->mapListToolBar_Groups, &MapListToolBar::editsAllowedChanged, [](bool allowed) { + porymapConfig.mapListEditGroupsEnabled = allowed; + }); + connect(ui->mapListToolBar_Groups, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) { + porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups] = !visible; + }); + connect(ui->mapListToolBar_Locations, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) { + porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations] = !visible; + }); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) { + porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts] = !visible; + }); + // When map list search filter is cleared we want the current map/layout in the editor to be visible in the list. connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); connect(ui->mapListToolBar_Locations, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); @@ -1149,7 +1170,8 @@ bool MainWindow::setProjectUI() { // map models this->mapGroupModel = new MapGroupModel(editor->project); this->groupListProxyModel = new FilterChildrenProxyModel(); - groupListProxyModel->setSourceModel(this->mapGroupModel); + this->groupListProxyModel->setSourceModel(this->mapGroupModel); + this->groupListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups]); ui->mapList->setModel(groupListProxyModel); this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this)); @@ -1157,13 +1179,16 @@ bool MainWindow::setProjectUI() { this->mapLocationModel = new MapLocationModel(editor->project); this->locationListProxyModel = new FilterChildrenProxyModel(); - locationListProxyModel->setSourceModel(this->mapLocationModel); + this->locationListProxyModel->setSourceModel(this->mapLocationModel); + this->locationListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations]); + ui->locationList->setModel(locationListProxyModel); ui->locationList->sortByColumn(0, Qt::SortOrder::AscendingOrder); this->layoutTreeModel = new LayoutTreeModel(editor->project); this->layoutListProxyModel = new FilterChildrenProxyModel(); this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); + this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts]); ui->layoutList->setModel(layoutListProxyModel); ui->layoutList->sortByColumn(0, Qt::SortOrder::AscendingOrder); @@ -2708,8 +2733,8 @@ void MainWindow::initTilesetEditor() { connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } -MapListToolBar* MainWindow::getCurrentMapListToolBar() { - switch (ui->mapListContainer->currentIndex()) { +MapListToolBar* MainWindow::getMapListToolBar(int tab) { + switch (tab) { case MapListTab::Groups: return ui->mapListToolBar_Groups; case MapListTab::Locations: return ui->mapListToolBar_Locations; case MapListTab::Layouts: return ui->mapListToolBar_Layouts; @@ -2717,6 +2742,10 @@ MapListToolBar* MainWindow::getCurrentMapListToolBar() { } } +MapListToolBar* MainWindow::getCurrentMapListToolBar() { + return getMapListToolBar(ui->mapListContainer->currentIndex()); +} + MapTree* MainWindow::getCurrentMapList() { auto toolbar = getCurrentMapListToolBar(); if (toolbar) diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index d35e4656..2584c4fd 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -4,11 +4,6 @@ #include -/* - TODO: The button states for each tool bar (just the two toggleable buttons, hide empty folders and allow editing) - should be saved in the config. This will be cleaner/easier once the config is JSON, so holding off on that for now. -*/ - MapListToolBar::MapListToolBar(QWidget *parent) : QFrame(parent) , ui(new Ui::MapListToolBar) @@ -18,7 +13,7 @@ MapListToolBar::MapListToolBar(QWidget *parent) ui->button_ToggleEmptyFolders->setChecked(!m_emptyFoldersVisible); ui->button_ToggleEdit->setChecked(m_editsAllowed); - connect(ui->button_AddFolder, &QAbstractButton::clicked, this, &MapListToolBar::addFolderClicked); // TODO: Tool tip + connect(ui->button_AddFolder, &QAbstractButton::clicked, this, &MapListToolBar::addFolderClicked); connect(ui->button_ExpandAll, &QAbstractButton::clicked, this, &MapListToolBar::expandList); connect(ui->button_CollapseAll, &QAbstractButton::clicked, this, &MapListToolBar::collapseList); connect(ui->button_ToggleEdit, &QAbstractButton::clicked, this, &MapListToolBar::toggleEditsAllowed); @@ -57,28 +52,30 @@ void MapListToolBar::toggleEditsAllowed() { } void MapListToolBar::setEditsAllowed(bool allowed) { - m_editsAllowed = allowed; + if (m_list) { + if (allowed) { + m_list->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_list->setDragEnabled(true); + m_list->setAcceptDrops(true); + m_list->setDropIndicatorShown(true); + m_list->setDragDropMode(QAbstractItemView::InternalMove); + m_list->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + } else { + m_list->setSelectionMode(QAbstractItemView::NoSelection); + m_list->setDragEnabled(false); + m_list->setAcceptDrops(false); + m_list->setDropIndicatorShown(false); + m_list->setDragDropMode(QAbstractItemView::NoDragDrop); + m_list->setEditTriggers(QAbstractItemView::NoEditTriggers); + } + } - const QSignalBlocker b(ui->button_ToggleEdit); - ui->button_ToggleEdit->setChecked(allowed); + const QSignalBlocker b(ui->button_ToggleEdit); + ui->button_ToggleEdit->setChecked(allowed); - if (!m_list) - return; - - if (allowed) { - m_list->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_list->setDragEnabled(true); - m_list->setAcceptDrops(true); - m_list->setDropIndicatorShown(true); - m_list->setDragDropMode(QAbstractItemView::InternalMove); - m_list->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); - } else { - m_list->setSelectionMode(QAbstractItemView::NoSelection); - m_list->setDragEnabled(false); - m_list->setAcceptDrops(false); - m_list->setDropIndicatorShown(false); - m_list->setDragDropMode(QAbstractItemView::NoDragDrop); - m_list->setEditTriggers(QAbstractItemView::NoEditTriggers); + if (m_editsAllowed != allowed) { + m_editsAllowed = allowed; + emit editsAllowedChanged(allowed); } } @@ -87,8 +84,6 @@ void MapListToolBar::toggleEmptyFolders() { } void MapListToolBar::setEmptyFoldersVisible(bool visible) { - m_emptyFoldersVisible = visible; - if (m_list) { auto model = static_cast(m_list->model()); if (model) { @@ -103,6 +98,11 @@ void MapListToolBar::setEmptyFoldersVisible(bool visible) { const QSignalBlocker b(ui->button_ToggleEmptyFolders); ui->button_ToggleEmptyFolders->setChecked(!visible); + + if (m_emptyFoldersVisible != visible) { + m_emptyFoldersVisible = visible; + emit emptyFoldersVisibleChanged(visible); + } } void MapListToolBar::expandList() { From 8dcb66ca52bd6fc345b78705a18f9999a4da7a7a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 16 Mar 2025 19:29:26 -0400 Subject: [PATCH 243/364] Move map/layout loaded state to Project --- include/core/map.h | 3 --- include/core/maplayout.h | 1 - include/project.h | 14 ++++++++++++++ src/project.cpp | 25 +++++++++++++------------ src/ui/maplistmodels.cpp | 4 ++-- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index a90105b0..c1a14b04 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -68,12 +68,10 @@ public: void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } - void setLoaded(bool loaded) { m_loaded = loaded; } bool needsHealLocation() const { return m_needsHealLocation; } bool isPersistedToFile() const { return m_isPersistedToFile; } bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } - bool loaded() const { return m_loaded; } void resetEvents(); QList getEvents(Event::Group group = Event::Group::None) const; @@ -121,7 +119,6 @@ private: bool m_hasUnsavedDataChanges = false; bool m_needsHealLocation = false; bool m_scriptsLoaded = false; - bool m_loaded = false; QMap> m_events; QSet m_ownedEvents; // for memory management diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 9b45a689..57da8840 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -22,7 +22,6 @@ public: static QString layoutConstantFromName(const QString &name); - bool loaded = false; bool hasUnsavedDataChanges = false; QString id; diff --git a/include/project.h b/include/project.h index 23cd0f57..eca85273 100644 --- a/include/project.h +++ b/include/project.h @@ -92,6 +92,11 @@ public: // Note: This does not guarantee the map is loaded. Map* getMap(const QString &mapName) { return this->maps.value(mapName); } + bool isMapLoaded(const Map *map) const { return map && isMapLoaded(map->name()); } + bool isMapLoaded(const QString &mapName) const { return this->loadedMapNames.contains(mapName); } + bool isLayoutLoaded(const Layout *layout) const { return layout && isLayoutLoaded(layout->id); } + bool isLayoutLoaded(const QString &layoutId) const { return this->loadedLayoutIds.contains(layoutId); } + QMap tilesetCache; Tileset* loadTileset(QString, Tileset *tileset = nullptr); Tileset* getTileset(QString, bool forceLoad = false); @@ -260,6 +265,15 @@ private: QHash speciesToIconPath; QHash maps; + // Maps/layouts represented in these sets have been fully loaded from the project. + // If a valid map name / layout id is not in these sets, a Map / Layout object exists + // for it in Project::maps / Project::mapLayouts, but it has been minimally populated + // (i.e. for a map layout it only has the data read from layouts.json, none of its assets + // have been loaded, and for a map it only has the data needed to identify it in the map + // list, none of the rest of its data in map.json). + QSet loadedMapNames; + QSet loadedLayoutIds; + const QRegularExpression re_gbapalExtension; const QRegularExpression re_bppExtension; diff --git a/src/project.cpp b/src/project.cpp index 5dc4e67d..cd9d13d7 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -130,6 +130,7 @@ QString Project::getProjectTitle() const { void Project::clearMaps() { qDeleteAll(this->maps); this->maps.clear(); + this->loadedMapNames.clear(); } void Project::clearTilesetCache() { @@ -142,14 +143,15 @@ Map* Project::loadMap(const QString &mapName) { if (!map) return nullptr; - if (map->loaded()) + if (isMapLoaded(map)) return map; if (!(loadMapData(map) && loadMapLayout(map))) return nullptr; - map->setLoaded(true); + this->loadedMapNames.insert(mapName); emit mapLoaded(map); + return map; } @@ -393,14 +395,14 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout } // No need for a full load, we already have all the blockdata. - layout->loaded = loadLayoutTilesets(layout); - if (!layout->loaded) { + if (!loadLayoutTilesets(layout)) { delete layout; return nullptr; } this->mapLayouts.insert(layout->id, layout); this->layoutIds.append(layout->id); + this->loadedLayoutIds.insert(layout->id); emit layoutCreated(layout); @@ -408,14 +410,14 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout } bool Project::loadLayout(Layout *layout) { - if (!layout->loaded) { + if (!isLayoutLoaded(layout)) { // Force these to run even if one fails bool loadedTilesets = loadLayoutTilesets(layout); bool loadedBlockdata = loadBlockdata(layout); bool loadedBorder = loadLayoutBorder(layout); if (loadedTilesets && loadedBlockdata && loadedBorder) { - layout->loaded = true; + this->loadedLayoutIds.insert(layout->id); return true; } else { return false; @@ -448,6 +450,7 @@ void Project::clearMapLayouts() { this->mapLayoutsMaster.clear(); this->layoutIds.clear(); this->layoutIdsMaster.clear(); + this->loadedLayoutIds.clear(); } bool Project::readMapLayouts() { @@ -1115,7 +1118,7 @@ void Project::saveAll() { } void Project::saveMap(Map *map, bool skipLayout) { - if (!map || !map->loaded()) return; + if (!map || !isMapLoaded(map)) return; // Create/Modify a few collateral files for brand new maps. const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); @@ -1255,7 +1258,7 @@ void Project::saveMap(Map *map, bool skipLayout) { } void Project::saveLayout(Layout *layout) { - if (!layout || !layout->loaded) + if (!layout || !isLayoutLoaded(layout)) return; if (!layout->newFolderPath.isEmpty()) { @@ -1912,10 +1915,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { if (this->encounterGroupLabels.contains(identifier)) return false; // Check event IDs - for (const auto &map : this->maps) { - if (!map->loaded()) continue; - auto events = map->getEvents(); - for (const auto &event : events) { + for (const auto &mapName : this->loadedMapNames) { + for (const auto &event : this->maps.value(mapName)->getEvents()) { QString idName = event->getIdName(); if (!idName.isEmpty() && idName == identifier) return false; diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 1bdc43aa..118c7c3d 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -165,7 +165,7 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { return this->mapOpenedIcon; const Map* map = this->project->getMap(name); - if (!map || !map->loaded()) + if (!this->project->isMapLoaded(map)) return this->mapGrayIcon; return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } else if (type == this->folderTypeName) { @@ -510,7 +510,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { return this->mapOpenedIcon; const Layout* layout = this->project->mapLayouts.value(name); - if (!layout || !layout->loaded) + if (!this->project->isLayoutLoaded(layout)) return this->mapGrayIcon; return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } From 77134072da17fd53c52dacc0c892f11c13b39404 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 17 Mar 2025 03:57:31 -0400 Subject: [PATCH 244/364] Fix some clazy warnings (container-anti-pattern, incorrect-emit, and unused-non-trivial-variable) --- src/core/editcommands.cpp | 4 ++-- src/core/events.cpp | 4 ++-- src/core/parseutil.cpp | 1 - src/editor.cpp | 6 +++--- src/mainwindow.cpp | 6 +++--- src/project.cpp | 29 +++++++++++++++-------------- src/ui/resizelayoutpopup.cpp | 2 +- src/ui/tileseteditor.cpp | 6 +++--- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 0cc378fc..38b6a0d5 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -220,7 +220,7 @@ void ResizeLayout::redo() { layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - layout->needsRedrawing(); + emit layout->needsRedrawing(); } void ResizeLayout::undo() { @@ -237,7 +237,7 @@ void ResizeLayout::undo() { layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - layout->needsRedrawing(); + emit layout->needsRedrawing(); QUndoCommand::undo(); } diff --git a/src/core/events.cpp b/src/core/events.cpp index 9186e5c0..f35e1fe7 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -206,7 +206,7 @@ bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) { } void ObjectEvent::setDefaultValues(Project *project) { - this->setGfx(project->gfxDefines.keys().value(0, "0")); + this->setGfx(project->gfxDefines.key(0, "0")); this->setMovement(project->movementTypes.value(0, "0")); this->setScript("NULL"); this->setTrainerType(project->trainerTypes.value(0, "0")); @@ -310,7 +310,7 @@ bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { } void CloneObjectEvent::setDefaultValues(Project *project) { - this->setGfx(project->gfxDefines.keys().value(0, "0")); + this->setGfx(project->gfxDefines.key(0, "0")); this->setTargetID(1); if (this->getMap()) this->setTargetMap(this->getMap()->name()); } diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 22880562..8a3ad534 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -677,7 +677,6 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath, QS } bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error) { - QString err; QString jsonTxt = readTextFile(filepath, error); if (error && !error->isEmpty()) { return false; diff --git a/src/editor.cpp b/src/editor.cpp index bb79f98c..0fac0602 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -865,7 +865,7 @@ void Editor::displayDivingConnection(MapConnection *connection) { } void Editor::renderDivingConnections() { - for (auto item : diving_map_items.values()) + for (auto &item : diving_map_items) item->updatePixmap(); } @@ -1696,7 +1696,7 @@ void Editor::removeEventPixmapItem(Event *event) { } void Editor::clearMapConnections() { - for (auto item : connection_items) { + for (auto &item : connection_items) { if (item->scene()) item->scene()->removeItem(item); delete item; @@ -1708,7 +1708,7 @@ void Editor::clearMapConnections() { ui->comboBox_DiveMap->setCurrentText(""); ui->comboBox_EmergeMap->setCurrentText(""); - for (auto item : diving_map_items.values()) { + for (auto &item : diving_map_items) { if (item->scene()) item->scene()->removeItem(item); delete item; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ed7e7d77..08350694 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1546,9 +1546,9 @@ void MainWindow::updateMapList() { this->mapLocationModel->setActiveItem(activeItemName); this->layoutTreeModel->setActiveItem(activeItemName); - this->groupListProxyModel->layoutChanged(); - this->locationListProxyModel->layoutChanged(); - this->layoutListProxyModel->layoutChanged(); + emit this->groupListProxyModel->layoutChanged(); + emit this->locationListProxyModel->layoutChanged(); + emit this->layoutListProxyModel->layoutChanged(); } void MainWindow::on_action_Save_Project_triggered() { diff --git a/src/project.cpp b/src/project.cpp index 6590aac7..429dd6c2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -692,7 +692,6 @@ void Project::saveRegionMapSections() { return; } - const QString emptyMapsecName = getEmptyMapsecName(); OrderedJson::array mapSectionArray; for (const auto &idName : this->mapSectionIdNamesSaveOrder) { OrderedJson::object mapSectionObj; @@ -889,11 +888,12 @@ void Project::updateTilesetMetatileLabels(Tileset *tileset) { // Erase old labels, then repopulate with new labels const QString prefix = tileset->getMetatileLabelPrefix(); this->metatileLabelsMap[tileset->name].clear(); - for (int metatileId : tileset->metatileLabels.keys()) { - if (tileset->metatileLabels[metatileId].isEmpty()) - continue; - QString label = prefix + tileset->metatileLabels[metatileId]; - this->metatileLabelsMap[tileset->name][label] = metatileId; + for (auto i = tileset->metatileLabels.constBegin(); i != tileset->metatileLabels.constEnd(); i++) { + uint16_t metatileId = i.key(); + QString label = i.value(); + if (!label.isEmpty()) { + this->metatileLabelsMap[tileset->name][prefix + label] = metatileId; + } } } @@ -932,11 +932,12 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second const QString guardName = "GUARD_METATILE_LABELS_H"; QString outputText = QString("#ifndef %1\n#define %1\n").arg(guardName); - for (QString tilesetName : metatileLabelsMap.keys()) { - if (metatileLabelsMap[tilesetName].size() == 0) + for (auto i = this->metatileLabelsMap.constBegin(); i != this->metatileLabelsMap.constEnd(); i++) { + const QString tilesetName = i.key(); + const QMap tilesetMetatileLabels = i.value(); + if (tilesetMetatileLabels.isEmpty()) continue; - outputText += QString("\n// %1\n").arg(tilesetName); - outputText += buildMetatileLabelsText(metatileLabelsMap[tilesetName]); + outputText += QString("\n// %1\n%2").arg(tilesetName).arg(buildMetatileLabelsText(tilesetMetatileLabels)); } if (unusedMetatileLabels.size() != 0) { @@ -1499,10 +1500,10 @@ bool Project::readTilesetMetatileLabels() { fileWatcher.addPath(root + "/" + metatileLabelsFilename); const QSet regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; - QMap defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); - - for (QString label : defines.keys()) { - uint32_t metatileId = static_cast(defines[label]); + const QMap defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); + for (auto i = defines.constBegin(); i != defines.constEnd(); i++) { + QString label = i.key(); + uint32_t metatileId = i.value(); if (metatileId > Block::maxValue) { metatileId &= Block::maxValue; logWarn(QString("Value of metatile label '%1' truncated to %2").arg(label).arg(Metatile::getMetatileIdString(metatileId))); diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 20a378b9..5629d8e9 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -172,7 +172,7 @@ void ResizeLayoutPopup::setupLayoutView() { scene->addItem(outline); layoutPixmap->setBoundary(outline); - this->outline->rectUpdated(outline->rect().toAlignedRect()); + emit this->outline->rectUpdated(outline->rect().toAlignedRect()); // TODO: is this an ideal size for all maps, or should this adjust based on starting dimensions? this->ui->graphicsView->setTransform(QTransform::fromScale(0.5, 0.5)); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 5d6dede9..64b03734 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -123,8 +123,8 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi void TilesetEditor::setAttributesUi() { // Behavior if (projectConfig.metatileBehaviorMask) { - for (int num : project->metatileBehaviorMapInverse.keys()) { - this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num); + for (auto i = project->metatileBehaviorMapInverse.constBegin(); i != project->metatileBehaviorMapInverse.constEnd(); i++) { + this->ui->comboBox_metatileBehaviors->addItem(i.value(), i.key()); } this->ui->comboBox_metatileBehaviors->setMinimumContentsLength(0); } else { @@ -1123,7 +1123,7 @@ void TilesetEditor::countTileUsage() { QSet primaryTilesets; QSet secondaryTilesets; - for (auto layout : this->project->mapLayouts.values()) { + for (auto &layout : this->project->mapLayouts) { this->project->loadLayoutTilesets(layout); if (layout->tileset_primary_label == this->primaryTileset->name || layout->tileset_secondary_label == this->secondaryTileset->name) { From a406f4c21005fe28fdbbba7a3c7942552a747279 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 17 Mar 2025 23:56:25 -0400 Subject: [PATCH 245/364] Restore Dynamic map error checks --- src/project.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index cd9d13d7..52657f90 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1757,10 +1757,8 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); - // Save special "Dynamic" constant const QString dynamicMapName = getDynamicMapName(); - this->mapConstantsToMapNames.insert(getDynamicMapDefineName(), dynamicMapName); - this->mapNames.append(dynamicMapName); + const QString dynamicMapConstant = getDynamicMapDefineName(); // Process the map group lists QStringList failedMapNames; @@ -1772,6 +1770,11 @@ bool Project::readMapGroups() { // Process the names in this map group for (int j = 0; j < mapNamesJson.size(); j++) { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); + if (mapName == dynamicMapName) { + logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); + failedMapNames.append(mapName); + continue; + } if (this->mapNames.contains(mapName)) { logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); failedMapNames.append(mapName); @@ -1793,6 +1796,11 @@ bool Project::readMapGroups() { failedMapNames.append(mapName); continue; } + if (mapConstant == dynamicMapConstant) { + logWarn(QString("Ignoring map with reserved \"id\" value '%1'.").arg(mapName)); + failedMapNames.append(mapName); + continue; + } const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); if (!mapConstant.startsWith(expectedPrefix)) { logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); @@ -1846,6 +1854,10 @@ bool Project::readMapGroups() { emit mapsExcluded(failedMapNames); } + // Save special "Dynamic" constant + this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); + this->mapNames.append(dynamicMapName); + return true; } From d42887e8155b34b88bdb91495c6b98199c1898e4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 18 Mar 2025 21:19:52 -0400 Subject: [PATCH 246/364] Fix regression to loading clone object events --- src/core/events.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index 9186e5c0..ac197bc7 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -88,7 +88,7 @@ QString Event::groupToString(Event::Group group) { // We re-use them for key names in the copy/paste JSON data, const QMap typeToJsonKeyMap = { {Event::Type::Object, "object"}, - {Event::Type::CloneObject, "clone_object"}, + {Event::Type::CloneObject, "clone"}, {Event::Type::Warp, "warp"}, {Event::Type::Trigger, "trigger"}, {Event::Type::WeatherTrigger, "weather"}, @@ -165,7 +165,7 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { OrderedJson::object objectJson; if (projectConfig.eventCloneObjectEnabled) { - objectJson["type"] = "object"; + objectJson["type"] = Event::typeToJsonKey(Event::Type::Object); } QString idName = this->getIdName(); if (!idName.isEmpty()) @@ -276,7 +276,7 @@ EventFrame *CloneObjectEvent::createEventFrame() { OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { OrderedJson::object cloneJson; - cloneJson["type"] = "clone"; + cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject); QString idName = this->getIdName(); if (!idName.isEmpty()) cloneJson["local_id"] = idName; @@ -458,7 +458,7 @@ EventFrame *TriggerEvent::createEventFrame() { OrderedJson::object TriggerEvent::buildEventJson(Project *) { OrderedJson::object triggerJson; - triggerJson["type"] = "trigger"; + triggerJson["type"] = Event::typeToJsonKey(Event::Type::Trigger); triggerJson["x"] = this->getX(); triggerJson["y"] = this->getY(); triggerJson["elevation"] = this->getElevation(); @@ -532,7 +532,7 @@ EventFrame *WeatherTriggerEvent::createEventFrame() { OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { OrderedJson::object weatherJson; - weatherJson["type"] = "weather"; + weatherJson["type"] = Event::typeToJsonKey(Event::Type::WeatherTrigger); weatherJson["x"] = this->getX(); weatherJson["y"] = this->getY(); weatherJson["elevation"] = this->getElevation(); @@ -599,7 +599,7 @@ EventFrame *SignEvent::createEventFrame() { OrderedJson::object SignEvent::buildEventJson(Project *) { OrderedJson::object signJson; - signJson["type"] = "sign"; + signJson["type"] = Event::typeToJsonKey(Event::Type::Sign); signJson["x"] = this->getX(); signJson["y"] = this->getY(); signJson["elevation"] = this->getElevation(); @@ -672,7 +672,7 @@ EventFrame *HiddenItemEvent::createEventFrame() { OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { OrderedJson::object hiddenItemJson; - hiddenItemJson["type"] = "hidden_item"; + hiddenItemJson["type"] = Event::typeToJsonKey(Event::Type::HiddenItem); hiddenItemJson["x"] = this->getX(); hiddenItemJson["y"] = this->getY(); hiddenItemJson["elevation"] = this->getElevation(); @@ -765,7 +765,7 @@ EventFrame *SecretBaseEvent::createEventFrame() { OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { OrderedJson::object secretBaseJson; - secretBaseJson["type"] = "secret_base"; + secretBaseJson["type"] = Event::typeToJsonKey(Event::Type::SecretBase); secretBaseJson["x"] = this->getX(); secretBaseJson["y"] = this->getY(); secretBaseJson["elevation"] = this->getElevation(); From d55534732300036ef850f8bdc4308310372bc4b8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 18 Mar 2025 23:51:38 -0400 Subject: [PATCH 247/364] Fix crash when adding first MAPSEC define --- src/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index 52657f90..343e732b 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2357,7 +2357,7 @@ bool Project::addNewMapsec(const QString &idName, const QString &displayName) { return false; } - if (this->mapSectionIdNamesSaveOrder.last() == getEmptyMapsecName()) { + if (!this->mapSectionIdNamesSaveOrder.isEmpty() && this->mapSectionIdNamesSaveOrder.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. this->mapSectionIdNamesSaveOrder.insert(this->mapSectionIdNames.length() - 1, idName); } else { From ef6eb69c72b2ff90cd343b87daed12d25b7cd52f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 19 Mar 2025 14:27:56 -0400 Subject: [PATCH 248/364] Add file cache to ParserUtil --- include/core/parseutil.h | 6 +++- include/project.h | 1 + src/core/parseutil.cpp | 57 +++++++++++++++++++++++++++-------- src/project.cpp | 64 ++++++++++++++++++++++++---------------- 4 files changed, 90 insertions(+), 38 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index b5fc5dd2..4c19a27c 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -45,7 +45,8 @@ public: ParseUtil(); void set_root(const QString &dir); static QString readTextFile(const QString &path, QString *error = nullptr); - void invalidateTextFile(const QString &path); + bool cacheFile(const QString &path, QString *error = nullptr); + void clearFileCache() { this->fileCache.clear(); } static int textFileLineCount(const QString &path); QList parseAsm(const QString &filename); QStringList readCArray(const QString &filename, const QString &label); @@ -87,6 +88,7 @@ private: QString text; QString file; QString curDefine; + QHash fileCache; QHash errorMap; int evaluateDefine(const QString&, const QString &, QMap*, QMap*); QList tokenizeExpression(QString, QMap*, QMap*); @@ -105,6 +107,8 @@ private: QMap evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; + QString loadTextFile(const QString &path, QString *error = nullptr); + QString pathWithRoot(const QString &path); static const QRegularExpression re_incScriptLabel; static const QRegularExpression re_globalIncScriptLabel; diff --git a/include/project.h b/include/project.h index f7a4b5cc..5fcb579c 100644 --- a/include/project.h +++ b/include/project.h @@ -279,6 +279,7 @@ private: void ignoreWatchedFileTemporarily(QString filepath); void recordFileChange(const QString &filepath); + void resetFileCache(); QString findSpeciesIconPath(const QStringList &names) const; diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 22880562..62735db3 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -37,6 +37,12 @@ void ParseUtil::set_root(const QString &dir) { this->root = dir; } +QString ParseUtil::pathWithRoot(const QString &path) { + if (this->root.isEmpty()) return path; + if (path.startsWith(this->root)) return path; + return QString("%1/%2").arg(this->root).arg(path); +} + void ParseUtil::recordError(const QString &message) { this->errorMap[this->curDefine].append(message); } @@ -85,6 +91,34 @@ QString ParseUtil::readTextFile(const QString &path, QString *error) { return text; } +// Load the specified text file, either from the cache or by reading the file. +// Note that this doesn't insert any parsed files into the file cache, and we don't +// want it to (we read a lot of files only once, storing them all is a waste of memory). +QString ParseUtil::loadTextFile(const QString &path, QString *error) { + auto it = this->fileCache.constFind(path); + if (it != this->fileCache.constEnd()) { + // Load text file from cache + //logWarn(QString("CACHE HIT ON %1").arg(path)); + return it.value(); + } + +/* TODO: Remove + static QSet parsedFiles; + if (parsedFiles.contains(path)) { + logWarn(QString("CACHE MISS ON %1").arg(path)); + } else { + parsedFiles.insert(path); + } +*/ + + return readTextFile(pathWithRoot(path), error); +} + +bool ParseUtil::cacheFile(const QString &path, QString *error) { + this->fileCache.insert(path, readTextFile(pathWithRoot(path), error)); + return !error || error->isEmpty(); +} + int ParseUtil::textFileLineCount(const QString &path) { const QString text = readTextFile(path); return text.split('\n').count() + 1; @@ -93,7 +127,7 @@ int ParseUtil::textFileLineCount(const QString &path) { QList ParseUtil::parseAsm(const QString &filename) { QList parsed; - this->text = readTextFile(this->root + '/' + filename); + this->text = loadTextFile(filename); const QStringList lines = removeLineComments(this->text, "@").split('\n'); for (const auto &line : lines) { const QString trimmedLine = line.trimmed(); @@ -295,7 +329,7 @@ QString ParseUtil::readCIncbin(const QString &filename, const QString &label) { return path; } - this->text = readTextFile(this->root + "/" + filename); + this->text = loadTextFile(filename); QRegularExpression re(QString( "\\b%1\\b" @@ -316,7 +350,7 @@ QMap ParseUtil::readCIncbinMulti(const QString &filepath) { QMap incbinMap; this->file = filepath; - this->text = readTextFile(this->root + "/" + filepath); + this->text = loadTextFile(filepath); static const QRegularExpression regex("(? diff --git a/include/core/editcommands.h b/include/core/editcommands.h index eabfacc0..9a54063c 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -47,6 +47,13 @@ enum CommandId { #define IDMask_EventType_Trigger (1 << 11) #define IDMask_EventType_Heal (1 << 12) +#define IDMask_ConnectionDirection_Up (1 << 8) +#define IDMask_ConnectionDirection_Down (1 << 9) +#define IDMask_ConnectionDirection_Left (1 << 10) +#define IDMask_ConnectionDirection_Right (1 << 11) +#define IDMask_ConnectionDirection_Dive (1 << 12) +#define IDMask_ConnectionDirection_Emerge (1 << 13) + /// Implements a command to commit metatile paint actions /// onto the map using the pencil tool. class PaintMetatile : public QUndoCommand { @@ -400,7 +407,7 @@ public: void redo() override; bool mergeWith(const QUndoCommand *command) override; - int id() const override { return CommandId::ID_MapConnectionMove; } + int id() const override; private: MapConnection *connection; @@ -421,7 +428,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionChangeDirection; } + int id() const override; private: QPointer connection; @@ -443,7 +450,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionChangeMap; } + int id() const override; private: QPointer connection; @@ -465,7 +472,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionAdd; } + int id() const override; private: Map *map = nullptr; @@ -485,7 +492,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionRemove; } + int id() const override; private: Map *map = nullptr; diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index ffd893e5..ffc4d272 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -21,10 +21,12 @@ struct ImageExporterSettings { bool showGrid = false; bool showBorder = false; bool showCollision = false; - bool previewActualSize = false; + bool disablePreviewScaling = false; + bool disablePreviewUpdates = false; int timelapseSkipAmount = 1; int timelapseDelayMs = 200; - QColor fillColor = Qt::transparent; // Not exposed as a setting in the UI atm. + // Not exposed as a setting in the UI atm (our color input widget has no alpha channel). + QColor fillColor = Qt::transparent; }; class MapImageExporter : public QDialog @@ -63,9 +65,10 @@ private: void setModeSpecificUi(); void setSelectionText(const QString &text); void updateMapSelection(); + void resetSettings(); QString getTitle(ImageExporterMode mode); QString getDescription(ImageExporterMode mode); - void updatePreview(); + void updatePreview(bool forceUpdate = false); void scalePreview(); bool eventsEnabled(); void setEventGroupEnabled(Event::Group group, bool enable); @@ -81,7 +84,7 @@ private: void paintEvents(QPainter *painter, const Map *map); void paintGrid(QPainter *painter, const Layout *layout = nullptr); QMargins getMargins(const Map *map); - QPixmap getExpandedPixmap(const QPixmap &pixmap, const QSize &minSize, const QColor &fillColor); + QPixmap getExpandedPixmap(const QPixmap &pixmap, const QSize &targetSize, const QColor &fillColor); bool currentHistoryAppliesToFrame(QUndoStack *historyStack); protected: @@ -102,15 +105,16 @@ private slots: void on_checkBox_ConnectionRight_stateChanged(int state); void on_checkBox_AllConnections_stateChanged(int state); - void on_checkBox_Elevation_stateChanged(int state); + void on_checkBox_Collision_stateChanged(int state); void on_checkBox_Grid_stateChanged(int state); void on_checkBox_Border_stateChanged(int state); void on_pushButton_Reset_pressed(); - void on_spinBox_TimelapseDelay_valueChanged(int delayMs); - void on_spinBox_FrameSkip_valueChanged(int skip); + void on_spinBox_TimelapseDelay_editingFinished(); + void on_spinBox_FrameSkip_editingFinished(); - void on_checkBox_ActualSize_stateChanged(int state); + void on_checkBox_DisablePreviewScaling_stateChanged(int state); + void on_checkBox_DisablePreviewUpdates_stateChanged(int state); }; #endif // MAPIMAGEEXPORTER_H diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 0cc378fc..801bdd43 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -5,7 +5,7 @@ #include -int getEventTypeMask(QList events) { +int getEventTypeMask(const QList &events) { int eventTypeMask = 0; for (auto event : events) { Event::Group groupType = event->getEventGroup(); @@ -24,6 +24,26 @@ int getEventTypeMask(QList events) { return eventTypeMask; } +int getConnectionDirectionMask(const QList &directions) { + int mask = 0; + for (auto direction : directions) { + if (direction == "up") { + mask |= IDMask_ConnectionDirection_Up; + } else if (direction == "down") { + mask |= IDMask_ConnectionDirection_Down; + } else if (direction == "left") { + mask |= IDMask_ConnectionDirection_Left; + } else if (direction == "right") { + mask |= IDMask_ConnectionDirection_Right; + } else if (direction == "dive") { + mask |= IDMask_ConnectionDirection_Dive; + } else if (direction == "emerge") { + mask |= IDMask_ConnectionDirection_Emerge; + } + } + return mask; +} + void renderBlocks(Layout *layout, bool ignoreCache = false) { layout->layoutItem->draw(ignoreCache); layout->collisionItem->draw(ignoreCache); @@ -587,6 +607,10 @@ bool MapConnectionMove::mergeWith(const QUndoCommand *command) { return true; } +int MapConnectionMove::id() const { + return CommandId::ID_MapConnectionMove | getConnectionDirectionMask({this->connection->direction()}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -629,6 +653,10 @@ void MapConnectionChangeDirection::undo() { QUndoCommand::undo(); } +int MapConnectionChangeDirection::id() const { + return CommandId::ID_MapConnectionChangeDirection | getConnectionDirectionMask({this->oldDirection, this->newDirection}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -664,6 +692,10 @@ void MapConnectionChangeMap::undo() { QUndoCommand::undo(); } +int MapConnectionChangeMap::id() const { + return CommandId::ID_MapConnectionChangeMap | getConnectionDirectionMask({this->connection->direction()}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -708,6 +740,10 @@ void MapConnectionAdd::undo() { QUndoCommand::undo(); } +int MapConnectionAdd::id() const { + return CommandId::ID_MapConnectionAdd | getConnectionDirectionMask({this->connection->direction()}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -745,3 +781,7 @@ void MapConnectionRemove::undo() { QUndoCommand::undo(); } + +int MapConnectionRemove::id() const { + return CommandId::ID_MapConnectionRemove | getConnectionDirectionMask({this->connection->direction()}); +} diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index c98fccce..23b1b8a0 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -96,22 +96,26 @@ void MapImageExporter::setModeSpecificUi() { } if (m_mode == ImageExporterMode::Timelapse) { - // At the moment edit history for events (and the DraggablePixmapItem class) - // depend on the editor and assume their map is the current map. - // Until this is resolved, the selected map and the editor's map must remain the same. + // TODO: At the moment edit history for events (and the DraggablePixmapItem class) + // explicitly depend on the editor and assume their map is currently open. + // Other edit commands rely on this more subtly, like triggering API callbacks or + // spending time rendering their layout (which can make creating timelapses very slow). + // Until this is resolved, the selected map/layout must remain the same as in the editor. + // We enforce this here by disabling the selector, and in MainWindow by programmatically + // changing the exporter's map/layout selection if the user opens a new one in the editor. ui->comboBox_MapSelection->setEnabled(false); ui->label_MapSelection->setEnabled(false); - - // Timelapse gif has artifacts with transparency, make sure it's disabled. - m_settings.fillColor.setAlpha(255); } + + // Update for any mode-specific default settings + resetSettings(); } // Allow the window to open before displaying the preview. void MapImageExporter::showEvent(QShowEvent *event) { QWidget::showEvent(event); if (!event->spontaneous()) - QTimer::singleShot(0, this, &MapImageExporter::updatePreview); + QTimer::singleShot(0, this, [this](){ updatePreview(); }); } void MapImageExporter::resizeEvent(QResizeEvent *event) { @@ -166,12 +170,12 @@ void MapImageExporter::updateMapSelection() { } void MapImageExporter::saveImage() { - // If the preview is empty it's because progress was canceled. - // Try again to create it, and if it's canceled again we'll stop the export. - if (m_preview->pixmap().isNull()) { - updatePreview(); + // If the preview is empty (because progress was canceled) or if updates were disabled + // then we should ensure the image in the preview is up-to-date before exporting. + if (m_preview->pixmap().isNull() || m_settings.disablePreviewUpdates) { + updatePreview(true); if (m_preview->pixmap().isNull()) - return; + return; // Canceled } if (m_mode == ImageExporterMode::Timelapse && !m_timelapseGifImage) { // Shouldn't happen. We have a preview for the timelapse, but no timelapse image. @@ -237,8 +241,15 @@ bool MapImageExporter::currentHistoryAppliesToFrame(QUndoStack *historyStack) { case CommandId::ID_MapConnectionChangeDirection: case CommandId::ID_MapConnectionChangeMap: case CommandId::ID_MapConnectionAdd: - case CommandId::ID_MapConnectionRemove: - return connectionsEnabled(); + case CommandId::ID_MapConnectionRemove: { + if (!connectionsEnabled()) + return false; + if (command->id() & IDMask_ConnectionDirection_Up) return m_settings.showConnections.contains("up"); + if (command->id() & IDMask_ConnectionDirection_Down) return m_settings.showConnections.contains("down"); + if (command->id() & IDMask_ConnectionDirection_Left) return m_settings.showConnections.contains("left"); + if (command->id() & IDMask_ConnectionDirection_Right) return m_settings.showConnections.contains("right"); + return false; + } case CommandId::ID_EventMove: case CommandId::ID_EventShift: case CommandId::ID_EventCreate: @@ -257,15 +268,18 @@ bool MapImageExporter::currentHistoryAppliesToFrame(QUndoStack *historyStack) { } } -QPixmap MapImageExporter::getExpandedPixmap(const QPixmap &pixmap, const QSize &minSize, const QColor &fillColor) { - if (pixmap.width() >= minSize.width() && pixmap.height() >= minSize.height()) +QPixmap MapImageExporter::getExpandedPixmap(const QPixmap &pixmap, const QSize &targetSize, const QColor &fillColor) { + if (pixmap.width() >= targetSize.width() && pixmap.height() >= targetSize.height()) return pixmap; - QPixmap resizedPixmap = QPixmap(minSize); + QPixmap resizedPixmap = QPixmap(targetSize); QPainter painter(&resizedPixmap); resizedPixmap.fill(fillColor); - painter.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap); - painter.end(); + + // Center the old pixmap in the new resized one. + int x = (targetSize.width() - pixmap.width()) / 2; + int y = (targetSize.height() - pixmap.height()) / 2; + painter.drawPixmap(x, y, pixmap.width(), pixmap.height(), pixmap); return resizedPixmap; } @@ -278,13 +292,11 @@ struct TimelapseStep { QGifImage* MapImageExporter::createTimelapseGifImage(QProgressDialog *progress) { // TODO: Timelapse will play in order of layout changes then map changes (events, connections). Potentially update in the future? QList steps; - if (m_layout) { - steps.append({ - .historyStack = &m_layout->editHistory, - .initialStackIndex = m_layout->editHistory.index(), - .name = "layout", - }); - } + steps.append({ + .historyStack = &m_layout->editHistory, + .initialStackIndex = m_layout->editHistory.index(), + .name = "layout", + }); if (m_map) { steps.append({ .historyStack = m_map->editHistory(), @@ -301,8 +313,8 @@ QGifImage* MapImageExporter::createTimelapseGifImage(QProgressDialog *progress) progress->setMaximum(step.initialStackIndex); progress->setValue(progress->minimum()); do { - if (currentHistoryAppliesToFrame(step.historyStack)) { - // This command is relevant, record the size of the map at this point. + if (currentHistoryAppliesToFrame(step.historyStack) || step.historyStack->index() == step.initialStackIndex) { + // Either this is relevant edit history, or it's the final frame (which is always rendered). Record the size of the map at this point. QMargins margins = getMargins(m_map); canvasSize = canvasSize.expandedTo(QSize(m_layout->getWidth() * 16 + margins.left() + margins.right(), m_layout->getHeight() * 16 + margins.top() + margins.bottom())); @@ -489,7 +501,10 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress) { return stitchedPixmap; } -void MapImageExporter::updatePreview() { +void MapImageExporter::updatePreview(bool forceUpdate) { + if (m_settings.disablePreviewUpdates && !forceUpdate) + return; + QProgressDialog progress("", "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); @@ -535,7 +550,7 @@ void MapImageExporter::updatePreview() { } void MapImageExporter::scalePreview() { - if (!m_preview || m_settings.previewActualSize) + if (!m_preview || m_settings.disablePreviewScaling) return; ui->graphicsView_Preview->fitInView(m_preview, Qt::KeepAspectRatioByExpanding); } @@ -689,7 +704,7 @@ void MapImageExporter::setEventGroupEnabled(Event::Group group, bool enable) { } bool MapImageExporter::connectionsEnabled() { - return !m_settings.showConnections.isEmpty(); + return !m_settings.showConnections.isEmpty() && m_mode != ImageExporterMode::Stitch; } void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool enable) { @@ -700,7 +715,7 @@ void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool en } } -void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) { +void MapImageExporter::on_checkBox_Collision_stateChanged(int state) { m_settings.showCollision = (state == Qt::Checked); updatePreview(); } @@ -819,21 +834,37 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { updatePreview(); } -void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) { - m_settings.previewActualSize = (state == Qt::Checked); - if (m_settings.previewActualSize) { +void MapImageExporter::on_checkBox_DisablePreviewScaling_stateChanged(int state) { + m_settings.disablePreviewScaling = (state == Qt::Checked); + if (m_settings.disablePreviewScaling) { ui->graphicsView_Preview->resetTransform(); } else { scalePreview(); } } +void MapImageExporter::on_checkBox_DisablePreviewUpdates_stateChanged(int state) { + m_settings.disablePreviewUpdates = (state == Qt::Checked); + if (m_settings.disablePreviewUpdates) { + if (m_timelapseMovie) { + m_timelapseMovie->stop(); + } + } else { + updatePreview(); + } +} + void MapImageExporter::on_pushButton_Reset_pressed() { - m_settings = {}; + resetSettings(); + updatePreview(); +} + +void MapImageExporter::resetSettings() { + m_settings = {}; for (auto widget : this->findChildren()) { const QSignalBlocker b(widget); // Prevent calls to updatePreview - widget->setChecked(false); + widget->setChecked(false); // This assumes the default state of all checkboxes settings is false. } const QSignalBlocker b_TimelapseDelay(ui->spinBox_TimelapseDelay); @@ -842,15 +873,27 @@ void MapImageExporter::on_pushButton_Reset_pressed() { const QSignalBlocker b_FrameSkip(ui->spinBox_FrameSkip); ui->spinBox_FrameSkip->setValue(m_settings.timelapseSkipAmount); - updatePreview(); + if (m_mode == ImageExporterMode::Timelapse) { + // Timelapse gif has artifacts with transparency, make sure it's disabled. + m_settings.fillColor.setAlpha(255); + } } -void MapImageExporter::on_spinBox_TimelapseDelay_valueChanged(int delayMs) { +// These spin boxes can be changed rapidly, so we wait for editing to finish before updating the preview. +void MapImageExporter::on_spinBox_TimelapseDelay_editingFinished() { + int delayMs = ui->spinBox_TimelapseDelay->value(); + if (delayMs == m_settings.timelapseDelayMs) + return; + m_settings.timelapseDelayMs = delayMs; updatePreview(); } -void MapImageExporter::on_spinBox_FrameSkip_valueChanged(int skip) { - m_settings.timelapseSkipAmount = skip; +void MapImageExporter::on_spinBox_FrameSkip_editingFinished() { + int skipAmount = ui->spinBox_FrameSkip->value(); + if (skipAmount == m_settings.timelapseSkipAmount) + return; + + m_settings.timelapseSkipAmount = skipAmount; updatePreview(); } From 44f3b27f2ded0e8e11102487a00b36b11214a51c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 29 Mar 2025 01:47:54 -0400 Subject: [PATCH 267/364] Fix older Qt builds --- include/core/events.h | 4 ++++ src/ui/mapimageexporter.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/core/events.h b/include/core/events.h index 322f09e8..17a510b0 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -615,6 +615,10 @@ private: }; +inline uint qHash(const Event::Group &key, uint seed = 0) { + return qHash(static_cast(key), seed); +} + /// /// Keeps track of scripts diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 23b1b8a0..36c1f7ff 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -57,7 +57,7 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, // Update the map selector when the text changes. // We don't use QComboBox::currentTextChanged to avoid unnecessary re-rendering. - connect(ui->comboBox_MapSelection, &QComboBox::currentIndexChanged, this, &MapImageExporter::updateMapSelection); + connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); ui->graphicsView_Preview->setFocus(); From d264188ef9115b8ce1eeb146e91220962b725da6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 29 Mar 2025 20:41:02 -0400 Subject: [PATCH 268/364] Read wild pokemon table name from project rather than settings --- CHANGELOG.md | 1 + docsrc/manual/project-files.rst | 1 - include/config.h | 1 - include/project.h | 1 + src/config.cpp | 2 -- src/project.cpp | 13 ++++++++++++- 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index febc3703..b992da85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - The prompt to reload the project when a file has changed will now only appear when Porymap is the active application. - `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting. - The options for `Encounter Type` and `Terrain Type` in the Tileset Editor are not hardcoded anymore, they're now read from the project. +- The `symbol_wild_encounters` setting was replaced; this value is now read from the project. - A project may now be opened even if it has no maps or map groups. A minimum of one map layout is required. - The file extensions that are expected for `.png` and `.pal` data files and the extensions outputted when creating a new tileset can now be customized. - Miscellaneous performance improvements, especially for opening projects. diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 4917be1d..caac85c2 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -83,7 +83,6 @@ In addition to these files, there are some specific symbol and macro names that ``symbol_facing_directions``, ``gInitialMovementTypeFacingDirections``, to set sprite frame for Object Events based on movement type ``symbol_obj_event_gfx_pointers``, ``gObjectEventGraphicsInfoPointers``, to map Object Event graphics IDs to graphics data ``symbol_pokemon_icon_table``, ``gMonIconTable``, to map species constants to icon images - ``symbol_wild_encounters``, ``gWildMonHeaders``, output as the ``label`` property for the top-level wild ecounters JSON object ``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab ``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names ``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic`` diff --git a/include/config.h b/include/config.h index 0786c761..eb9c9ac3 100644 --- a/include/config.h +++ b/include/config.h @@ -200,7 +200,6 @@ enum ProjectIdentifier { symbol_facing_directions, symbol_obj_event_gfx_pointers, symbol_pokemon_icon_table, - symbol_wild_encounters, symbol_attribute_table, symbol_tilesets_prefix, symbol_dynamic_map_name, diff --git a/include/project.h b/include/project.h index 855bda18..9a031c0d 100644 --- a/include/project.h +++ b/include/project.h @@ -146,6 +146,7 @@ public: bool readWildMonData(); tsl::ordered_map> wildMonData; + QString wildMonTableName; QVector wildMonFields; QVector encounterGroupLabels; QVector extraEncounterGroups; diff --git a/src/config.cpp b/src/config.cpp index 62ab31f3..d74adbcb 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -69,13 +69,11 @@ const QList defaultWarpBehaviors_FRLG = { 0x71, // MB_UNION_ROOM_WARP }; -// TODO: symbol_wild_encounters should ultimately be removed from the table below. We can determine this name when we read the project. const QMap> ProjectConfig::defaultIdentifiers = { // Symbols {ProjectIdentifier::symbol_facing_directions, {"symbol_facing_directions", "gInitialMovementTypeFacingDirections"}}, {ProjectIdentifier::symbol_obj_event_gfx_pointers, {"symbol_obj_event_gfx_pointers", "gObjectEventGraphicsInfoPointers"}}, {ProjectIdentifier::symbol_pokemon_icon_table, {"symbol_pokemon_icon_table", "gMonIconTable"}}, - {ProjectIdentifier::symbol_wild_encounters, {"symbol_wild_encounters", "gWildMonHeaders"}}, {ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}}, {ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}}, {ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}}, diff --git a/src/project.cpp b/src/project.cpp index bc2156ce..bbbc052c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -738,7 +738,7 @@ void Project::saveWildMonData() { OrderedJson::array wildEncounterGroups; OrderedJson::object monHeadersObject; - monHeadersObject["label"] = projectConfig.getIdentifier(ProjectIdentifier::symbol_wild_encounters); + monHeadersObject["label"] = this->wildMonTableName; monHeadersObject["for_maps"] = true; OrderedJson::array fieldsInfoArray; @@ -1597,6 +1597,7 @@ bool Project::readWildMonData() { this->extraEncounterGroups.clear(); this->wildMonFields.clear(); this->wildMonData.clear(); + this->wildMonTableName.clear(); this->encounterGroupLabels.clear(); this->pokemonMinLevel = 0; this->pokemonMaxLevel = 100; @@ -1658,6 +1659,16 @@ bool Project::readWildMonData() { continue; } + // If multiple "for_maps" data sets are found they will be collapsed into a single set. + QString label = mainArrayObject["label"].string_value(); + if (this->wildMonTableName.isEmpty()) { + this->wildMonTableName = label; + } else { + logWarn(QString("Wild encounters table '%1' will be combined with '%2'. Only one table with \"for_maps\" set to 'true' is expected.") + .arg(label) + .arg(this->wildMonTableName)); + } + // Parse the "fields" data. This is like the header for the wild encounters data. // Each element describes a type of wild encounter Porymap can expect to find, and we represent this data with an EncounterField. // They should contain a name ("type"), the number of encounter slots and the ratio at which they occur ("encounter_rates"), From c9b0d139b26e8c10dacb4ae0bf816b6a9a987631 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 29 Mar 2025 22:54:13 -0400 Subject: [PATCH 269/364] Preserve custom fields in the map_sections, layouts, and connections arrays --- include/core/mapconnection.h | 5 +++ include/core/maplayout.h | 2 ++ include/project.h | 1 + src/core/maplayout.cpp | 1 + src/project.cpp | 70 +++++++++++++++++++++++------------- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 21c00ac6..e95c25df 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -5,6 +5,7 @@ #include #include #include +#include class Project; class Map; @@ -29,6 +30,9 @@ public: int offset() const { return m_offset; } void setOffset(int offset, bool mirror = true); + QJsonObject customData() const { return m_customData; } + void setCustomData(const QJsonObject &customData) { m_customData = customData; } + MapConnection* findMirror(); MapConnection* createMirror(); @@ -49,6 +53,7 @@ private: QString m_targetMapName; QString m_direction; int m_offset; + QJsonObject m_customData; void markMapEdited(); Map* getMap(const QString& mapName) const; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 57da8840..119a2232 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -42,6 +42,8 @@ public: Tileset *tileset_primary = nullptr; Tileset *tileset_secondary = nullptr; + QJsonObject customData; + Blockdata blockdata; QImage image; diff --git a/include/project.h b/include/project.h index 9a031c0d..09a39bb6 100644 --- a/include/project.h +++ b/include/project.h @@ -263,6 +263,7 @@ public: private: QHash mapSectionDisplayNames; + QHash mapSectionCustomData; QMap modifiedFileTimestamps; QMap facingDirections; QHash speciesToIconPath; diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index e443a635..33408f6a 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -31,6 +31,7 @@ void Layout::copyFrom(const Layout *other) { this->tileset_secondary = other->tileset_secondary; this->blockdata = other->blockdata; this->border = other->border; + this->customData = other->customData; } QString Layout::layoutConstantFromName(const QString &name) { diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..8841cd7f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -308,10 +308,12 @@ bool Project::loadMapData(Map* map) { if (!connectionsArr.isEmpty()) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); - const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); - const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); - const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset)); + const QString direction = ParseUtil::jsonToQString(connectionObj.take("direction")); + const int offset = ParseUtil::jsonToInt(connectionObj.take("offset")); + const QString mapConstant = ParseUtil::jsonToQString(connectionObj.take("map")); + auto connection = new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset); + connection->setCustomData(connectionObj); + map->loadConnection(connection); } } @@ -503,7 +505,7 @@ bool Project::readMapLayouts() { if (layoutObj.isEmpty()) continue; Layout *layout = new Layout(); - layout->id = ParseUtil::jsonToQString(layoutObj["id"]); + layout->id = ParseUtil::jsonToQString(layoutObj.take("id")); if (layout->id.isEmpty()) { logError(QString("Missing 'id' value on layout %1 in %2").arg(i).arg(layoutsFilepath)); delete layout; @@ -514,20 +516,20 @@ bool Project::readMapLayouts() { delete layout; continue; } - layout->name = ParseUtil::jsonToQString(layoutObj["name"]); + layout->name = ParseUtil::jsonToQString(layoutObj.take("name")); if (layout->name.isEmpty()) { logError(QString("Missing 'name' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - int lwidth = ParseUtil::jsonToInt(layoutObj["width"]); + int lwidth = ParseUtil::jsonToInt(layoutObj.take("width")); if (lwidth <= 0) { logError(QString("Invalid 'width' value '%1' for %2 in %3. Must be greater than 0.").arg(lwidth).arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } layout->width = lwidth; - int lheight = ParseUtil::jsonToInt(layoutObj["height"]); + int lheight = ParseUtil::jsonToInt(layoutObj.take("height")); if (lheight <= 0) { logError(QString("Invalid 'height' value '%1' for %2 in %3. Must be greater than 0.").arg(lheight).arg(layout->id).arg(layoutsFilepath)); delete layout; @@ -535,12 +537,12 @@ bool Project::readMapLayouts() { } layout->height = lheight; if (projectConfig.useCustomBorderSize) { - int bwidth = ParseUtil::jsonToInt(layoutObj["border_width"]); + int bwidth = ParseUtil::jsonToInt(layoutObj.take("border_width")); if (bwidth <= 0) { // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG bwidth = DEFAULT_BORDER_WIDTH; } layout->border_width = bwidth; - int bheight = ParseUtil::jsonToInt(layoutObj["border_height"]); + int bheight = ParseUtil::jsonToInt(layoutObj.take("border_height")); if (bheight <= 0) { bheight = DEFAULT_BORDER_HEIGHT; } @@ -549,30 +551,31 @@ bool Project::readMapLayouts() { layout->border_width = DEFAULT_BORDER_WIDTH; layout->border_height = DEFAULT_BORDER_HEIGHT; } - layout->tileset_primary_label = ParseUtil::jsonToQString(layoutObj["primary_tileset"]); + layout->tileset_primary_label = ParseUtil::jsonToQString(layoutObj.take("primary_tileset")); if (layout->tileset_primary_label.isEmpty()) { logError(QString("Missing 'primary_tileset' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->tileset_secondary_label = ParseUtil::jsonToQString(layoutObj["secondary_tileset"]); + layout->tileset_secondary_label = ParseUtil::jsonToQString(layoutObj.take("secondary_tileset")); if (layout->tileset_secondary_label.isEmpty()) { logError(QString("Missing 'secondary_tileset' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->border_path = ParseUtil::jsonToQString(layoutObj["border_filepath"]); + layout->border_path = ParseUtil::jsonToQString(layoutObj.take("border_filepath")); if (layout->border_path.isEmpty()) { logError(QString("Missing 'border_filepath' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->blockdata_path = ParseUtil::jsonToQString(layoutObj["blockdata_filepath"]); + layout->blockdata_path = ParseUtil::jsonToQString(layoutObj.take("blockdata_filepath")); if (layout->blockdata_path.isEmpty()) { logError(QString("Missing 'blockdata_filepath' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } + layout->customData = layoutObj; this->mapLayouts.insert(layout->id, layout); this->mapLayoutsMaster.insert(layout->id, layout->copy()); @@ -615,6 +618,9 @@ void Project::saveMapLayouts() { layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; layoutObj["blockdata_filepath"] = layout->blockdata_path; + for (auto it = layout->customData.constBegin(); it != layout->customData.constEnd(); it++) { + layoutObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } layoutsArr.push_back(layoutObj); } @@ -711,6 +717,11 @@ void Project::saveRegionMapSections() { mapSectionObj["height"] = entry.height; } + QJsonObject customData = this->mapSectionCustomData.value(idName); + for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { + mapSectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } + mapSectionArray.append(mapSectionObj); } @@ -1203,6 +1214,10 @@ void Project::saveMap(Map *map, bool skipLayout) { connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); + auto customData = connection->customData(); + for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { + connectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; @@ -2320,6 +2335,7 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); this->mapSectionDisplayNames.clear(); + this->mapSectionCustomData.clear(); this->regionMapEntries.clear(); const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); @@ -2351,7 +2367,7 @@ bool Project::readRegionMapSections() { continue; } } - const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); + const QString idName = ParseUtil::jsonToQString(mapSectionObj.take(idField)); if (!idName.startsWith(requiredPrefix)) { logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(filepath).arg(requiredPrefix)); continue; @@ -2361,7 +2377,7 @@ bool Project::readRegionMapSections() { this->mapSectionIdNamesSaveOrder.append(idName); if (mapSectionObj.contains("name")) - this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj["name"])); + this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj.take("name"))); // Map sections may have additional data indicating their position on the region map. // If they have this data, we can add them to the region map entry list. @@ -2373,16 +2389,20 @@ bool Project::readRegionMapSections() { break; } } - if (!hasRegionMapData) - continue; + if (hasRegionMapData) { + MapSectionEntry entry; + entry.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); + entry.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); + entry.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); + entry.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); + entry.valid = true; + this->regionMapEntries[idName] = entry; + } - MapSectionEntry entry; - entry.x = ParseUtil::jsonToInt(mapSectionObj["x"]); - entry.y = ParseUtil::jsonToInt(mapSectionObj["y"]); - entry.width = ParseUtil::jsonToInt(mapSectionObj["width"]); - entry.height = ParseUtil::jsonToInt(mapSectionObj["height"]); - entry.valid = true; - this->regionMapEntries[idName] = entry; + // Preserve any remaining fields for when we save. + if (!mapSectionObj.isEmpty()) { + this->mapSectionCustomData[idName] = mapSectionObj; + } } // Make sure the default name is present in the list. From 7c107f3470e99a325528e913599133c3a3f9af93 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 30 Mar 2025 17:38:05 -0400 Subject: [PATCH 270/364] Fix timelapse not considering multiple command ID flags --- src/ui/mapimageexporter.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 36c1f7ff..39607bca 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -244,11 +244,12 @@ bool MapImageExporter::currentHistoryAppliesToFrame(QUndoStack *historyStack) { case CommandId::ID_MapConnectionRemove: { if (!connectionsEnabled()) return false; - if (command->id() & IDMask_ConnectionDirection_Up) return m_settings.showConnections.contains("up"); - if (command->id() & IDMask_ConnectionDirection_Down) return m_settings.showConnections.contains("down"); - if (command->id() & IDMask_ConnectionDirection_Left) return m_settings.showConnections.contains("left"); - if (command->id() & IDMask_ConnectionDirection_Right) return m_settings.showConnections.contains("right"); - return false; + uint32_t flags = 0; + if (m_settings.showConnections.contains("up")) flags |= IDMask_ConnectionDirection_Up; + if (m_settings.showConnections.contains("down")) flags |= IDMask_ConnectionDirection_Down; + if (m_settings.showConnections.contains("left")) flags |= IDMask_ConnectionDirection_Left; + if (m_settings.showConnections.contains("right")) flags |= IDMask_ConnectionDirection_Right; + return (command->id() & flags) != 0; } case CommandId::ID_EventMove: case CommandId::ID_EventShift: @@ -256,12 +257,15 @@ bool MapImageExporter::currentHistoryAppliesToFrame(QUndoStack *historyStack) { case CommandId::ID_EventPaste: case CommandId::ID_EventDelete: case CommandId::ID_EventDuplicate: { - if (command->id() & IDMask_EventType_Object) return m_settings.showEvents.contains(Event::Group::Object); - if (command->id() & IDMask_EventType_Warp) return m_settings.showEvents.contains(Event::Group::Warp); - if (command->id() & IDMask_EventType_BG) return m_settings.showEvents.contains(Event::Group::Bg); - if (command->id() & IDMask_EventType_Trigger) return m_settings.showEvents.contains(Event::Group::Coord); - if (command->id() & IDMask_EventType_Heal) return m_settings.showEvents.contains(Event::Group::Heal); - return false; + if (!eventsEnabled()) + return false; + uint32_t flags = 0; + if (m_settings.showEvents.contains(Event::Group::Object)) flags |= IDMask_EventType_Object; + if (m_settings.showEvents.contains(Event::Group::Warp)) flags |= IDMask_EventType_Warp; + if (m_settings.showEvents.contains(Event::Group::Bg)) flags |= IDMask_EventType_BG; + if (m_settings.showEvents.contains(Event::Group::Coord)) flags |= IDMask_EventType_Trigger; + if (m_settings.showEvents.contains(Event::Group::Heal)) flags |= IDMask_EventType_Heal; + return (command->id() & flags) != 0; } default: return false; From 9d77af6f20f6e1537befa8781069f149aadd4304 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 1 Apr 2025 21:50:33 -0400 Subject: [PATCH 271/364] Sync GitHub Actions workflow with dev branch --- .github/workflows/main.yml | 63 +++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 55edb7b6..ea31470d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,32 +9,26 @@ on: tags: - '*' pull_request: - branches: - - master # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: - build-qt5-linux: + build-linux: + strategy: + matrix: + qtversion: [5.14.2, 6.8.2] runs-on: ubuntu-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache + - uses: actions/checkout@v4 - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: - version: '5.14.2' - modules: 'qtwidgets qtqml' - cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: ${{ matrix.qtversion }} + modules: 'qtcharts' + cache: 'true' - name: Configure run: qmake porymap.pro @@ -43,23 +37,22 @@ jobs: run: make build-macos: - runs-on: macos-latest + strategy: + matrix: + os: [macos-latest, macos-13] + runs-on: ${{ matrix.os }} + env: + BUILD_NAME: porymap-${{ matrix.os }}-${{ github.ref_name }} steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache + - uses: actions/checkout@v4 - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '6.2.*' - cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: '6.8.2' + modules: 'qtcharts' + cache: 'true' - name: Configure run: qmake -config release porymap.pro @@ -74,19 +67,19 @@ jobs: - name: Prep Release Directory if: startsWith(github.ref, 'refs/tags/') run: | - mkdir porymap-macOS-${{ github.ref_name }} - cp porymap.dmg porymap-macOS-${{ github.ref_name }}/porymap.dmg - cp RELEASE-README.txt porymap-macOS-${{ github.ref_name }}/README.txt + mkdir $BUILD_NAME + cp porymap.dmg $BUILD_NAME/porymap.dmg + cp RELEASE-README.txt $BUILD_NAME/README.txt - name: Bundle Release Directory if: startsWith(github.ref, 'refs/tags/') - run: zip -r porymap-macOS-${{ github.ref_name }}.zip porymap-macOS-${{ github.ref_name }} + run: zip -r $BUILD_NAME.zip $BUILD_NAME - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: - files: porymap-macOS-${{ github.ref_name }}.zip + files: $BUILD_NAME.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -94,7 +87,7 @@ jobs: runs-on: windows-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dsaltares/fetch-gh-release-asset@master if: steps.cache-static-qt.outputs.cache-hit != 'true' @@ -152,7 +145,7 @@ jobs: run: powershell.exe -Command "Compress-Archive -Path porymap-windows-${{ github.ref_name }} -DestinationPath porymap-windows-${{ github.ref_name }}.zip" - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: porymap-windows-${{ github.ref_name }}.zip From 825cd5107374b5c6f85c13cd23c47bc784763bc1 Mon Sep 17 00:00:00 2001 From: "Danny Wang (ThePeeps191)" <74725787+ThePeeps191@users.noreply.github.com> Date: Tue, 1 Apr 2025 20:35:09 -0600 Subject: [PATCH 272/364] fix small typo in creating-new-maps.html (#712) --- docs/manual/creating-new-maps.html | 4 ++-- docsrc/manual/creating-new-maps.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/manual/creating-new-maps.html b/docs/manual/creating-new-maps.html index 516cdc8c..180b57e5 100644 --- a/docs/manual/creating-new-maps.html +++ b/docs/manual/creating-new-maps.html @@ -456,7 +456,7 @@ in order to add a new map to the folder.

Name
The name of the new map. This cannot be changed in porymap.
Group
-
Which map group the new map will beling to. This cannot be changed in porymap.
+
Which map group the new map will belong to. This cannot be changed in porymap.
Map Width
The width (in metatiles) of the map. This can be changed in porymap.
Map Height
@@ -538,4 +538,4 @@ in order to add a new map to the folder.

- \ No newline at end of file + diff --git a/docsrc/manual/creating-new-maps.rst b/docsrc/manual/creating-new-maps.rst index be660e37..4da20776 100644 --- a/docsrc/manual/creating-new-maps.rst +++ b/docsrc/manual/creating-new-maps.rst @@ -31,7 +31,7 @@ Name The name of the new map. This cannot be changed in porymap. Group - Which map group the new map will beling to. This cannot be changed in porymap. + Which map group the new map will belong to. This cannot be changed in porymap. Map Width The width (in metatiles) of the map. This can be changed in porymap. From a4508918a17c4bc820538cf2deb70ac1ae4939b9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 31 Mar 2025 19:33:31 -0400 Subject: [PATCH 273/364] Preserve custom global fields in layouts, heal_locations, region_map_sections, and map_groups json files --- include/project.h | 6 +++++ src/project.cpp | 56 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/include/project.h b/include/project.h index 09a39bb6..4ed95f06 100644 --- a/include/project.h +++ b/include/project.h @@ -269,6 +269,12 @@ private: QHash speciesToIconPath; QHash maps; + // Fields for preserving top-level JSON data that Porymap isn't expecting. + QJsonObject customLayoutsData; + QJsonObject customMapSectionsData; + QJsonObject customMapGroupsData; + QJsonObject customHealLocationsData; + // Maps/layouts represented in these sets have been fully loaded from the project. // If a valid map name / layout id is not in these sets, a Map / Layout object exists // for it in Project::maps / Project::mapLayouts, but it has been minimally populated diff --git a/src/project.cpp b/src/project.cpp index 8841cd7f..98bd7215 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -475,6 +475,7 @@ void Project::clearMapLayouts() { this->layoutIds.clear(); this->layoutIdsMaster.clear(); this->loadedLayoutIds.clear(); + this->customLayoutsData = QJsonObject(); } bool Project::readMapLayouts() { @@ -491,7 +492,7 @@ bool Project::readMapLayouts() { QJsonObject layoutsObj = layoutsDoc.object(); - this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); + this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj.take("layouts_table_label")); if (this->layoutsLabel.isEmpty()) { this->layoutsLabel = "gMapLayouts"; logWarn(QString("'layouts_table_label' value is missing from %1. Defaulting to %2") @@ -499,7 +500,7 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - QJsonArray layouts = layoutsObj["layouts"].toArray(); + QJsonArray layouts = layoutsObj.take("layouts").toArray(); for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) @@ -588,6 +589,8 @@ bool Project::readMapLayouts() { return false; } + this->customLayoutsData = layoutsObj; + return true; } @@ -623,10 +626,14 @@ void Project::saveMapLayouts() { } layoutsArr.push_back(layoutObj); } + layoutsObj["layouts"] = layoutsArr; + + for (auto it = this->customLayoutsData.constBegin(); it != this->customLayoutsData.constEnd(); it++) { + layoutsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(layoutsFilepath); - layoutsObj["layouts"] = layoutsArr; OrderedJson layoutJson(layoutsObj); OrderedJsonDoc jsonDoc(&layoutJson); jsonDoc.dump(&layoutsFile); @@ -683,6 +690,9 @@ void Project::saveMapGroups() { } mapGroupsObj[groupName] = groupArr; } + for (auto it = this->customMapGroupsData.constBegin(); it != this->customMapGroupsData.constEnd(); it++) { + mapGroupsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -727,6 +737,9 @@ void Project::saveRegionMapSections() { OrderedJson::object object; object["map_sections"] = mapSectionArray; + for (auto it = this->customMapSectionsData.constBegin(); it != this->customMapSectionsData.constEnd(); it++) { + object[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -881,6 +894,9 @@ void Project::saveHealLocations() { OrderedJson::object object; object["heal_locations"] = eventJsonArr; + for (auto it = this->customHealLocationsData.constBegin(); it != this->customHealLocationsData.constEnd(); it++) { + object[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -1787,6 +1803,7 @@ bool Project::readMapGroups() { this->mapNames.clear(); this->groupNames.clear(); this->groupNameToMapNames.clear(); + this->customMapGroupsData = QJsonObject(); this->initTopLevelMapFields(); @@ -1809,7 +1826,12 @@ bool Project::readMapGroups() { QStringList failedMapNames; for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); - const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); + if (this->groupNames.contains(groupName)) { + logWarn(QString("Ignoring repeated map group name '%1'.").arg(groupName)); + continue; + } + + const QJsonArray mapNamesJson = mapGroupsObj.take(groupName).toArray(); this->groupNames.append(groupName); // Process the names in this map group @@ -1903,6 +1925,12 @@ bool Project::readMapGroups() { this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); this->mapNames.append(dynamicMapName); + // Save custom JSON data. + // Chuck the "connections_include_order" field, this is only for matching. + // TODO: Setting not to do this, on the off chance someone wants this field. + mapGroupsObj.remove("connections_include_order"); + this->customMapGroupsData = mapGroupsObj; + return true; } @@ -2335,11 +2363,16 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); this->mapSectionDisplayNames.clear(); - this->mapSectionCustomData.clear(); this->regionMapEntries.clear(); const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); + // The first of these is the custom data for each individual map section object, + // the second is the custom top-level data in the map sections file. + // TODO: Clarify this by relocating the various map section data maps to a single class? + this->mapSectionCustomData.clear(); + this->customMapSectionsData = QJsonObject(); + QJsonDocument doc; const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); QString error; @@ -2349,7 +2382,8 @@ bool Project::readRegionMapSections() { } fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); - QJsonArray mapSections = doc.object()["map_sections"].toArray(); + QJsonObject mapSectionsGlobalObj = doc.object(); + QJsonArray mapSections = mapSectionsGlobalObj.take("map_sections").toArray(); for (int i = 0; i < mapSections.size(); i++) { QJsonObject mapSectionObj = mapSections.at(i).toObject(); @@ -2399,11 +2433,16 @@ bool Project::readRegionMapSections() { this->regionMapEntries[idName] = entry; } + // Chuck the "name_clone" field, this is only for matching. + // TODO: Setting not to do this, on the off chance someone wants this field. + mapSectionObj.remove("name_clone"); + // Preserve any remaining fields for when we save. if (!mapSectionObj.isEmpty()) { this->mapSectionCustomData[idName] = mapSectionObj; } } + this->customMapSectionsData = mapSectionsGlobalObj; // Make sure the default name is present in the list. if (!this->mapSectionIdNames.contains(defaultName)) { @@ -2477,6 +2516,7 @@ void Project::clearHealLocations() { } this->healLocations.clear(); this->healLocationSaveOrder.clear(); + this->customHealLocationsData = QJsonObject(); } bool Project::readHealLocations() { @@ -2491,7 +2531,8 @@ bool Project::readHealLocations() { } fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); - QJsonArray healLocations = doc.object()["heal_locations"].toArray(); + QJsonObject healLocationsObj = doc.object(); + QJsonArray healLocations = healLocationsObj.take("heal_locations").toArray(); for (int i = 0; i < healLocations.size(); i++) { QJsonObject healLocationObj = healLocations.at(i).toObject(); static const QString mapField = QStringLiteral("map"); @@ -2505,6 +2546,7 @@ bool Project::readHealLocations() { this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); this->healLocationSaveOrder.append(event->getIdName()); } + this->customHealLocationsData = healLocationsObj; return true; } From e94fce0c8d78100492fc58c7ca11c9679aa49814 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 13:24:50 -0400 Subject: [PATCH 274/364] Combine the 3 QMaps for MAPSEC data --- include/core/regionmap.h | 6 +-- include/core/regionmapeditcommands.h | 4 +- include/project.h | 17 +++++-- include/ui/regionmapeditor.h | 2 +- src/core/regionmapeditcommands.cpp | 2 +- src/project.cpp | 73 ++++++++++++++++------------ src/ui/regionmapeditor.cpp | 4 +- 7 files changed, 63 insertions(+), 45 deletions(-) diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 18362663..c8afb1b2 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -56,8 +56,8 @@ public: bool loadLayout(poryjson::Json); bool loadEntries(); - void setEntries(QMap *entries) { this->region_map_entries = entries; } - void setEntries(const QMap &entries) { *(this->region_map_entries) = entries; } + void setEntries(QHash *entries) { this->region_map_entries = entries; } + void setEntries(const QHash &entries) { *(this->region_map_entries) = entries; } void clearEntries() { this->region_map_entries->clear(); } MapSectionEntry getEntry(QString section); void setEntry(QString section, MapSectionEntry entry); @@ -151,7 +151,7 @@ signals: void mapNeedsDisplaying(); private: - QMap *region_map_entries = nullptr; + QHash *region_map_entries = nullptr; QString alias = ""; diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index 05b12bc3..e47fdb7b 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -153,7 +153,7 @@ private: /// ClearEntries class ClearEntries : public QUndoCommand { public: - ClearEntries(RegionMap *map, QMap, QUndoCommand *parent = nullptr); + ClearEntries(RegionMap *map, QHash, QUndoCommand *parent = nullptr); void undo() override; void redo() override; @@ -163,7 +163,7 @@ public: private: RegionMap *map; - QMap entries; + QHash entries; }; #endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/project.h b/include/project.h index 4ed95f06..44094aaa 100644 --- a/include/project.h +++ b/include/project.h @@ -62,7 +62,6 @@ public: QStringList mapSectionIdNames; QMap encounterTypeToName; QMap terrainTypeToName; - QMap regionMapEntries; QMap> metatileLabelsMap; QMap unusedMetatileLabels; QMap metatileBehaviorMap; @@ -157,7 +156,7 @@ public: bool addNewMapsec(const QString &idName, const QString &displayName = QString()); void removeMapsec(const QString &idName); - QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); } + QString getMapsecDisplayName(const QString &idName) const { return this->locationData.value(idName).displayName; } void setMapsecDisplayName(const QString &idName, const QString &displayName); bool hasUnsavedChanges(); @@ -240,6 +239,9 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + void setRegionMapEntries(const QHash &entries); + QHash getRegionMapEntries() const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); @@ -262,8 +264,6 @@ public: static QString getMapGroupPrefix(); private: - QHash mapSectionDisplayNames; - QHash mapSectionCustomData; QMap modifiedFileTimestamps; QMap facingDirections; QHash speciesToIconPath; @@ -298,6 +298,15 @@ private: }; QMap eventGraphicsMap; + // The extra data that can be associated with each MAPSEC name. + struct LocationData + { + MapSectionEntry map; + QString displayName; + QJsonObject custom; + }; + QHash locationData; + void updateLayout(Layout *); void setNewLayoutBlockdata(Layout *layout); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 9a838827..490324af 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -95,7 +95,7 @@ private: void saveConfig(); bool loadRegionMapEntries(); bool saveRegionMapEntries(); - QMap region_map_entries; + QHash region_map_entries; bool buildConfigDialog(); poryjson::Json configRegionMapDialog(); diff --git a/src/core/regionmapeditcommands.cpp b/src/core/regionmapeditcommands.cpp index 7a12cbc6..f21f1d72 100644 --- a/src/core/regionmapeditcommands.cpp +++ b/src/core/regionmapeditcommands.cpp @@ -260,7 +260,7 @@ void ResizeTilemap::undo() { /// -ClearEntries::ClearEntries(RegionMap *map, QMap entries, QUndoCommand *parent) +ClearEntries::ClearEntries(RegionMap *map, QHash entries, QUndoCommand *parent) : QUndoCommand(parent) { setText("Clear Entries"); diff --git a/src/project.cpp b/src/project.cpp index 98bd7215..891f709f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -712,23 +712,23 @@ void Project::saveRegionMapSections() { OrderedJson::array mapSectionArray; for (const auto &idName : this->mapSectionIdNamesSaveOrder) { + const LocationData location = this->locationData.value(idName); + OrderedJson::object mapSectionObj; mapSectionObj["id"] = idName; - if (this->mapSectionDisplayNames.contains(idName)) { - mapSectionObj["name"] = this->mapSectionDisplayNames.value(idName); + if (!location.displayName.isEmpty()) { + mapSectionObj["name"] = location.displayName; } - if (this->regionMapEntries.contains(idName)) { - MapSectionEntry entry = this->regionMapEntries.value(idName); - mapSectionObj["x"] = entry.x; - mapSectionObj["y"] = entry.y; - mapSectionObj["width"] = entry.width; - mapSectionObj["height"] = entry.height; + if (location.map.valid) { + mapSectionObj["x"] = location.map.x; + mapSectionObj["y"] = location.map.y; + mapSectionObj["width"] = location.map.width; + mapSectionObj["height"] = location.map.height; } - QJsonObject customData = this->mapSectionCustomData.value(idName); - for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { + for (auto it = location.custom.constBegin(); it != location.custom.constEnd(); it++) { mapSectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); } @@ -2360,19 +2360,14 @@ bool Project::readFieldmapMasks() { } bool Project::readRegionMapSections() { + this->locationData.clear(); this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); - this->mapSectionDisplayNames.clear(); - this->regionMapEntries.clear(); + this->customMapSectionsData = QJsonObject(); + const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); - // The first of these is the custom data for each individual map section object, - // the second is the custom top-level data in the map sections file. - // TODO: Clarify this by relocating the various map section data maps to a single class? - this->mapSectionCustomData.clear(); - this->customMapSectionsData = QJsonObject(); - QJsonDocument doc; const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); QString error; @@ -2410,8 +2405,10 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.append(idName); this->mapSectionIdNamesSaveOrder.append(idName); - if (mapSectionObj.contains("name")) - this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj.take("name"))); + LocationData location; + if (mapSectionObj.contains("name")) { + location.displayName = ParseUtil::jsonToQString(mapSectionObj.take("name")); + } // Map sections may have additional data indicating their position on the region map. // If they have this data, we can add them to the region map entry list. @@ -2424,13 +2421,11 @@ bool Project::readRegionMapSections() { } } if (hasRegionMapData) { - MapSectionEntry entry; - entry.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); - entry.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); - entry.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); - entry.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); - entry.valid = true; - this->regionMapEntries[idName] = entry; + location.map.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); + location.map.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); + location.map.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); + location.map.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); + location.map.valid = true; } // Chuck the "name_clone" field, this is only for matching. @@ -2438,9 +2433,9 @@ bool Project::readRegionMapSections() { mapSectionObj.remove("name_clone"); // Preserve any remaining fields for when we save. - if (!mapSectionObj.isEmpty()) { - this->mapSectionCustomData[idName] = mapSectionObj; - } + location.custom = mapSectionObj; + + this->locationData.insert(idName, location); } this->customMapSectionsData = mapSectionsGlobalObj; @@ -2453,6 +2448,20 @@ bool Project::readRegionMapSections() { return true; } +void Project::setRegionMapEntries(const QHash &entries) { + for (auto it = entries.constBegin(); it != entries.constEnd(); it++) { + this->locationData[it.key()].map = it.value(); + } +} + +QHash Project::getRegionMapEntries() const { + QHash entries; + for (auto it = this->locationData.constBegin(); it != this->locationData.constEnd(); it++) { + entries[it.key()] = it.value().map; + } + return entries; +} + QString Project::getEmptyMapsecName() { return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); } @@ -2503,9 +2512,9 @@ void Project::removeMapsec(const QString &idName) { } void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { - if (this->mapSectionDisplayNames.value(idName) == displayName) + if (getMapsecDisplayName(idName) == displayName) return; - this->mapSectionDisplayNames[idName] = displayName; + this->locationData[idName].displayName = displayName; this->hasUnsavedDataChanges = true; emit mapSectionDisplayNameChanged(idName, displayName); } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 2febccd8..27fb5f2f 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -108,12 +108,12 @@ void RegionMapEditor::applyUserShortcuts() { } bool RegionMapEditor::loadRegionMapEntries() { - this->region_map_entries = this->project->regionMapEntries; + this->region_map_entries = this->project->getRegionMapEntries(); return true; } bool RegionMapEditor::saveRegionMapEntries() { - this->project->regionMapEntries = this->region_map_entries; + this->project->setRegionMapEntries(this->region_map_entries); this->project->saveRegionMapSections(); return true; } From 011f6196b56ede6207df724d70887f0dafe10ed5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 13:43:54 -0400 Subject: [PATCH 275/364] Add setting to keep data only needed for matching --- forms/projectsettingseditor.ui | 14 ++++++++++++-- include/config.h | 2 ++ src/config.cpp | 3 +++ src/project.cpp | 13 ++++++++----- src/ui/projectsettingseditor.cpp | 2 ++ 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index a088d87e..179392dd 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -39,7 +39,7 @@ 0 0 559 - 568 + 589 @@ -66,6 +66,16 @@ + + + + <html><head/><body><p>If enabled, Porymap will not discard data like &quot;connections_include_order&quot; or &quot;name_clone&quot;, which serve no purpose other than recreating the original game.</p></body></html> + + + Preserve data only needed to match the original game + + + @@ -1084,7 +1094,7 @@ 0 0 559 - 788 + 840 diff --git a/include/config.h b/include/config.h index eb9c9ac3..6746a0f4 100644 --- a/include/config.h +++ b/include/config.h @@ -323,6 +323,7 @@ public: this->tilesetsHaveCallback = true; this->tilesetsHaveIsCompressed = true; this->setTransparentPixelsBlack = true; + this->preserveMatchingOnlyData = false; this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); @@ -389,6 +390,7 @@ public: bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; bool setTransparentPixelsBlack; + bool preserveMatchingOnlyData; int metatileAttributesSize; uint32_t metatileBehaviorMask; uint32_t metatileTerrainTypeMask; diff --git a/src/config.cpp b/src/config.cpp index d74adbcb..d76f17a5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -809,6 +809,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveIsCompressed = getConfigBool(key, value); } else if (key == "set_transparent_pixels_black") { this->setTransparentPixelsBlack = getConfigBool(key, value); + } else if (key == "preserve_matching_only_data") { + this->preserveMatchingOnlyData = getConfigBool(key, value); } else if (key == "event_icon_path_object") { this->eventIconPaths[Event::Group::Object] = value; } else if (key == "event_icon_path_warp") { @@ -899,6 +901,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); + map.insert("preserve_matching_only_data", QString::number(this->preserveMatchingOnlyData)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask)); diff --git a/src/project.cpp b/src/project.cpp index 891f709f..bef26614 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1925,10 +1925,12 @@ bool Project::readMapGroups() { this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); this->mapNames.append(dynamicMapName); - // Save custom JSON data. // Chuck the "connections_include_order" field, this is only for matching. - // TODO: Setting not to do this, on the off chance someone wants this field. - mapGroupsObj.remove("connections_include_order"); + if (!projectConfig.preserveMatchingOnlyData) { + mapGroupsObj.remove("connections_include_order"); + } + + // Preserve any remaining fields for when we save. this->customMapGroupsData = mapGroupsObj; return true; @@ -2429,8 +2431,9 @@ bool Project::readRegionMapSections() { } // Chuck the "name_clone" field, this is only for matching. - // TODO: Setting not to do this, on the off chance someone wants this field. - mapSectionObj.remove("name_clone"); + if (!projectConfig.preserveMatchingOnlyData) { + mapSectionObj.remove("name_clone"); + } // Preserve any remaining fields for when we save. location.custom = mapSectionObj; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index fa84f3e0..90951dd7 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -443,6 +443,7 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_OutputCallback->setChecked(projectConfig.tilesetsHaveCallback); ui->checkBox_OutputIsCompressed->setChecked(projectConfig.tilesetsHaveIsCompressed); ui->checkBox_DisableWarning->setChecked(porymapConfig.warpBehaviorWarningDisabled); + ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData); // Radio buttons if (projectConfig.setTransparentPixelsBlack) @@ -524,6 +525,7 @@ void ProjectSettingsEditor::save() { projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked(); porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked(); projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); + projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked(); // Save spin box settings projectConfig.defaultElevation = ui->spinBox_Elevation->value(); From cdd7e74e7f0ee54933372300f0a505991a29db0b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 14:51:49 -0400 Subject: [PATCH 276/364] Fix bug with Hidden Item duplication --- CHANGELOG.md | 1 + src/core/events.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b992da85..c8777a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). - Fix selections with multiple Events not always clearing when making a new selection. - Fix the new event button not updating correctly when selecting object events. +- Fix duplicated `Hidden Item` events not copying the `Requires Itemfinder` field. - Fix `About porymap` opening a new window each time it's activated. - Fix the `Edit History` window not raising to the front when reactivated. - New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded. diff --git a/src/core/events.cpp b/src/core/events.cpp index f9828dc1..afa96d3c 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -654,7 +654,7 @@ Event *HiddenItemEvent::duplicate() const { copy->setItem(this->getItem()); copy->setFlag(this->getFlag()); copy->setQuantity(this->getQuantity()); - copy->setQuantity(this->getQuantity()); + copy->setUnderfoot(this->getUnderfoot()); copy->setCustomAttributes(this->getCustomAttributes()); From a898791a70d834fe829c50f8f9142f6236b9b358 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 16:22:26 -0400 Subject: [PATCH 277/364] Remove expensive calls to QGraphicsItemGroup::removefromGroup --- src/editor.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index b29a47bc..08dc0146 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1673,10 +1673,9 @@ void Editor::clearMapEvents() { if (events_group->scene()) { events_group->scene()->removeItem(events_group); } - for (QGraphicsItem *child : events_group->childItems()) { - events_group->removeFromGroup(child); - delete child; - } + // events_group does not own its children, the childrens' parent + // is set to the group's parent (and our group has no parent). + qDeleteAll(events_group->childItems()); delete events_group; events_group = nullptr; } From e1aaf3c18e059a03805fb1a5555416ea7d57c674 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 18:40:39 -0400 Subject: [PATCH 278/364] Speed up opening maps with many warp events --- include/core/events.h | 2 ++ src/core/events.cpp | 6 +++++- src/ui/eventframes.cpp | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index fc0b90d8..7c81499f 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -336,10 +336,12 @@ public: QString getDestinationWarpID() const { return this->destinationWarpID; } void setWarningEnabled(bool enabled); + bool getWarningEnabled() const { return this->warningEnabled; } private: QString destinationMap; QString destinationWarpID; + bool warningEnabled = false; }; diff --git a/src/core/events.cpp b/src/core/events.cpp index afa96d3c..33d333e3 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -425,7 +425,11 @@ QSet WarpEvent::getExpectedFields() { } void WarpEvent::setWarningEnabled(bool enabled) { - WarpFrame * frame = static_cast(this->getEventFrame()); + this->warningEnabled = enabled; + + // Don't call getEventFrame here, because it may create the event frame. + // If the frame hasn't been created yet then we have nothing else to do. + auto frame = static_cast(this->eventFrame.data()); if (frame && frame->warning) frame->warning->setVisible(enabled); } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 82334722..065dbf19 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -516,7 +516,7 @@ void WarpFrame::setup() { this->warning = new QPushButton(warningText, this); this->warning->setFlat(true); this->warning->setStyleSheet("color: red; text-align: left"); - this->warning->setVisible(false); + this->warning->setVisible(this->warp->getWarningEnabled()); l_vbox_warning->addWidget(this->warning); this->layout_contents->addLayout(l_vbox_warning); From 029e959bfefd3fafe5b9364052ea84add7e6055f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 13:53:33 -0400 Subject: [PATCH 279/364] Simplify fromQJsonValue loops --- include/core/events.h | 42 ++-- include/core/map.h | 6 +- include/lib/orderedjson.h | 4 +- include/project.h | 2 +- include/ui/customattributestable.h | 4 +- src/core/events.cpp | 368 +++++++++++++---------------- src/lib/orderedjson.cpp | 35 +-- src/project.cpp | 85 +++---- src/ui/customattributestable.cpp | 8 +- 9 files changed, 250 insertions(+), 304 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index fc0b90d8..22b76434 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -10,6 +10,7 @@ #include #include "orderedjson.h" +#include "parseutil.h" class Project; @@ -139,15 +140,14 @@ public: Event::Type getEventType() const { return this->eventType; } virtual OrderedJson::object buildEventJson(Project *project) = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) = 0; virtual void setDefaultValues(Project *project); virtual QSet getExpectedFields() = 0; - void readCustomAttributes(const QJsonObject &json); - void addCustomAttributesTo(OrderedJson::object *obj) const; - const QMap getCustomAttributes() const { return this->customAttributes; } - void setCustomAttributes(const QMap newCustomAttributes) { this->customAttributes = newCustomAttributes; } + + QJsonObject getCustomAttributes() const { return this->customAttributes; } + void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } virtual void loadPixmap(Project *project); @@ -190,12 +190,16 @@ protected: // When deleting events like this we want to warn the user that the #define may also be deleted. QString idName; - QMap customAttributes; + QJsonObject customAttributes; QPixmap pixmap; DraggablePixmapItem *pixmapItem = nullptr; QPointer eventFrame; + + static QString readString(QJsonObject *object, const QString &key) { return ParseUtil::jsonToQString(object->take(key)); } + static int readInt(QJsonObject *object, const QString &key) { return ParseUtil::jsonToInt(object->take(key)); } + static bool readBool(QJsonObject *object, const QString &key) { return ParseUtil::jsonToBool(object->take(key)); } }; @@ -218,7 +222,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -285,7 +289,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -323,7 +327,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -358,7 +362,7 @@ public: virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -386,7 +390,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -426,7 +430,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -457,7 +461,7 @@ public: virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -484,7 +488,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -519,7 +523,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -564,7 +568,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -596,12 +600,15 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &, Project *) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; + void setHostMapName(QString newHostMapName) { this->hostMapName = newHostMapName; } + QString getHostMapName() const; + void setRespawnMapName(QString newRespawnMapName) { this->respawnMapName = newRespawnMapName; } QString getRespawnMapName() const { return this->respawnMapName; } @@ -611,6 +618,7 @@ public: private: QString respawnMapName; QString respawnNPC; + QString hostMapName; // Only needed if the host map fails to load. }; diff --git a/include/core/map.h b/include/core/map.h index c1a14b04..c2078134 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -100,8 +100,8 @@ public: bool hasUnsavedChanges() const; void pruneEditHistory(); - void setCustomAttributes(const QMap &attributes) { m_customAttributes = attributes; } - QMap customAttributes() const { return m_customAttributes; } + void setCustomAttributes(const QJsonObject &attributes) { m_customAttributes = attributes; } + QJsonObject customAttributes() const { return m_customAttributes; } private: QString m_name; @@ -110,7 +110,7 @@ private: QString m_sharedScriptsMap = ""; QStringList m_scriptsFileLabels; - QMap m_customAttributes; + QJsonObject m_customAttributes; MapHeader *m_header = nullptr; Layout *m_layout = nullptr; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 544112f1..73422a2b 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -132,7 +132,9 @@ public: int>::type = 0> Json(const V & v) : Json(array(v.begin(), v.end())) {} - static const Json fromQJsonValue(QJsonValue value); + static Json fromQJsonValue(const QJsonValue &value); + static void append(Json::array *array, const QJsonArray &qArray); + static void append(Json::object *object, const QJsonObject &qObject); // This prevents Json(some_pointer) from accidentally producing a bool. Use // Json(bool(some_pointer)) if that behavior is desired. diff --git a/include/project.h b/include/project.h index 44094aaa..5f37c5e7 100644 --- a/include/project.h +++ b/include/project.h @@ -164,7 +164,7 @@ public: void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); - bool loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType = Event::Type::None); + bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); diff --git a/include/ui/customattributestable.h b/include/ui/customattributestable.h index 21cac4de..780f441d 100644 --- a/include/ui/customattributestable.h +++ b/include/ui/customattributestable.h @@ -13,8 +13,8 @@ public: explicit CustomAttributesTable(QWidget *parent = nullptr); ~CustomAttributesTable() {}; - QMap getAttributes() const; - void setAttributes(const QMap &attributes); + QJsonObject getAttributes() const; + void setAttributes(const QJsonObject &attributes); void addNewAttribute(const QString &key, const QJsonValue &value); bool deleteSelectedAttributes(); diff --git a/src/core/events.cpp b/src/core/events.cpp index f9828dc1..ebb1ac49 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -51,24 +51,6 @@ void Event::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); } -void Event::readCustomAttributes(const QJsonObject &json) { - this->customAttributes.clear(); - const QSet expectedFields = this->getExpectedFields(); - for (auto i = json.constBegin(); i != json.constEnd(); i++) { - if (!expectedFields.contains(i.key())) { - this->customAttributes[i.key()] = i.value(); - } - } -} - -void Event::addCustomAttributesTo(OrderedJson::object *obj) const { - for (auto i = this->customAttributes.constBegin(); i != this->customAttributes.constEnd(); i++) { - if (!obj->contains(i.key())) { - (*obj)[i.key()] = OrderedJson::fromQJsonValue(i.value()); - } - } -} - void Event::modify() { this->map->modify(); } @@ -181,27 +163,26 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID(); objectJson["script"] = this->getScript(); objectJson["flag"] = this->getFlag(); - this->addCustomAttributesTo(&objectJson); + OrderedJson::append(&objectJson, this->getCustomAttributes()); return objectJson; } -bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setIdName(ParseUtil::jsonToQString(json["local_id"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setMovement(ParseUtil::jsonToQString(json["movement_type"])); - this->setRadiusX(ParseUtil::jsonToInt(json["movement_range_x"])); - this->setRadiusY(ParseUtil::jsonToInt(json["movement_range_y"])); - this->setTrainerType(ParseUtil::jsonToQString(json["trainer_type"])); - this->setSightRadiusBerryTreeID(ParseUtil::jsonToQString(json["trainer_sight_or_berry_tree_id"])); - this->setScript(ParseUtil::jsonToQString(json["script"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); +bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setMovement(readString(&json, "movement_type")); + this->setRadiusX(readInt(&json, "movement_range_x")); + this->setRadiusY(readInt(&json, "movement_range_y")); + this->setTrainerType(readString(&json, "trainer_type")); + this->setSightRadiusBerryTreeID(readString(&json, "trainer_sight_or_berry_tree_id")); + this->setScript(readString(&json, "script")); + this->setFlag(readString(&json, "flag")); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -216,26 +197,24 @@ void ObjectEvent::setDefaultValues(Project *project) { this->setSightRadiusBerryTreeID("0"); } -const QSet expectedObjectFields = { - "local_id", - "graphics_id", - "elevation", - "movement_type", - "movement_range_x", - "movement_range_y", - "trainer_type", - "trainer_sight_or_berry_tree_id", - "script", - "flag", -}; - QSet ObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedObjectFields; + QSet expectedFields = { + "x", + "y", + "local_id", + "graphics_id", + "elevation", + "movement_type", + "movement_range_x", + "movement_range_y", + "trainer_type", + "trainer_sight_or_berry_tree_id", + "script", + "flag", + }; if (projectConfig.eventCloneObjectEnabled) { expectedFields.insert("type"); } - expectedFields << "x" << "y"; return expectedFields; } @@ -286,26 +265,25 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["target_local_id"] = this->getTargetID(); const QString mapName = this->getTargetMap(); cloneJson["target_map"] = project->getMapConstant(mapName, mapName); - this->addCustomAttributesTo(&cloneJson); + OrderedJson::append(&cloneJson, this->getCustomAttributes()); return cloneJson; } -bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setIdName(ParseUtil::jsonToQString(json["local_id"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); +bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setTargetID(readInt(&json, "target_local_id")); // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); + const QString mapConstant = readString(&json, "target_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -315,18 +293,16 @@ void CloneObjectEvent::setDefaultValues(Project *project) { if (this->getMap()) this->setTargetMap(this->getMap()->name()); } -const QSet expectedCloneObjectFields = { - "type", - "local_id", - "graphics_id", - "target_local_id", - "target_map", -}; - QSet CloneObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedCloneObjectFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "local_id", + "graphics_id", + "target_local_id", + "target_map", + }; return expectedFields; } @@ -383,25 +359,23 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["dest_map"] = project->getMapConstant(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); - this->addCustomAttributesTo(&warpJson); - + OrderedJson::append(&warpJson, this->getCustomAttributes()); return warpJson; } -bool WarpEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"])); +bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setDestinationWarpID(readString(&json, "dest_warp_id")); // Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); + const QString mapConstant = readString(&json, "dest_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -411,16 +385,14 @@ void WarpEvent::setDefaultValues(Project *) { this->setElevation(0); } -const QSet expectedWarpFields = { - "elevation", - "dest_map", - "dest_warp_id", -}; - QSet WarpEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWarpFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "elevation", + "dest_map", + "dest_warp_id", + }; return expectedFields; } @@ -466,21 +438,19 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { triggerJson["var_value"] = this->getScriptVarValue(); triggerJson["script"] = this->getScriptLabel(); - this->addCustomAttributesTo(&triggerJson); - + OrderedJson::append(&triggerJson, this->getCustomAttributes()); return triggerJson; } -bool TriggerEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setScriptVar(ParseUtil::jsonToQString(json["var"])); - this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomAttributes(json); +bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setScriptVar(readString(&json, "var")); + this->setScriptVarValue(readString(&json, "var_value")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -491,18 +461,16 @@ void TriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedTriggerFields = { - "type", - "elevation", - "var", - "var_value", - "script", -}; - QSet TriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "var", + "var_value", + "script", + }; return expectedFields; } @@ -538,19 +506,17 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { weatherJson["elevation"] = this->getElevation(); weatherJson["weather"] = this->getWeather(); - this->addCustomAttributesTo(&weatherJson); - + OrderedJson::append(&weatherJson, this->getCustomAttributes()); return weatherJson; } -bool WeatherTriggerEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setWeather(ParseUtil::jsonToQString(json["weather"])); - - this->readCustomAttributes(json); +bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setWeather(readString(&json, "weather")); + this->setCustomAttributes(json); return true; } @@ -559,16 +525,14 @@ void WeatherTriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedWeatherTriggerFields = { - "type", - "elevation", - "weather", -}; - QSet WeatherTriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWeatherTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "weather", + }; return expectedFields; } @@ -606,20 +570,18 @@ OrderedJson::object SignEvent::buildEventJson(Project *) { signJson["player_facing_dir"] = this->getFacingDirection(); signJson["script"] = this->getScriptLabel(); - this->addCustomAttributesTo(&signJson); - + OrderedJson::append(&signJson, this->getCustomAttributes()); return signJson; } -bool SignEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomAttributes(json); +bool SignEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setFacingDirection(readString(&json, "player_facing_dir")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -629,17 +591,15 @@ void SignEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSignFields = { - "type", - "elevation", - "player_facing_dir", - "script", -}; - QSet SignEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSignFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "player_facing_dir", + "script", + }; return expectedFields; } @@ -685,26 +645,24 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { hiddenItemJson["underfoot"] = this->getUnderfoot(); } - this->addCustomAttributesTo(&hiddenItemJson); - + OrderedJson::append(&hiddenItemJson, this->getCustomAttributes()); return hiddenItemJson; } -bool HiddenItemEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setItem(ParseUtil::jsonToQString(json["item"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); +bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setItem(readString(&json, "item")); + this->setFlag(readString(&json, "flag")); if (projectConfig.hiddenItemQuantityEnabled) { - this->setQuantity(ParseUtil::jsonToInt(json["quantity"])); + this->setQuantity(readInt(&json, "quantity")); } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { - this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"])); + this->setUnderfoot(readBool(&json, "underfoot")); } - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -719,23 +677,21 @@ void HiddenItemEvent::setDefaultValues(Project *project) { } } -const QSet expectedHiddenItemFields = { - "type", - "elevation", - "item", - "flag", -}; - QSet HiddenItemEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedHiddenItemFields; + QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "item", + "flag", + }; if (projectConfig.hiddenItemQuantityEnabled) { expectedFields << "quantity"; } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { expectedFields << "underfoot"; } - expectedFields << "x" << "y"; return expectedFields; } @@ -771,19 +727,17 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { secretBaseJson["elevation"] = this->getElevation(); secretBaseJson["secret_base_id"] = this->getBaseID(); - this->addCustomAttributesTo(&secretBaseJson); - + OrderedJson::append(&secretBaseJson, this->getCustomAttributes()); return secretBaseJson; } -bool SecretBaseEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"])); - - this->readCustomAttributes(json); +bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setBaseID(readString(&json, "secret_base_id")); + this->setCustomAttributes(json); return true; } @@ -792,16 +746,14 @@ void SecretBaseEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSecretBaseFields = { - "type", - "elevation", - "secret_base_id", -}; - QSet SecretBaseEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSecretBaseFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "secret_base_id", + }; return expectedFields; } @@ -829,12 +781,15 @@ EventFrame *HealLocationEvent::createEventFrame() { return this->eventFrame; } +QString HealLocationEvent::getHostMapName() const { + return this->getMap() ? this->getMap()->constantName() : this->hostMapName; +} + OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { OrderedJson::object healLocationJson; healLocationJson["id"] = this->getIdName(); - // This field doesn't need to be stored in the Event itself, so it's output only. - healLocationJson["map"] = this->getMap() ? this->getMap()->constantName() : QString(); + healLocationJson["map"] = this->getHostMapName(); healLocationJson["x"] = this->getX(); healLocationJson["y"] = this->getY(); if (projectConfig.healLocationRespawnDataEnabled) { @@ -843,26 +798,26 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { healLocationJson["respawn_npc"] = this->getRespawnNPC(); } - this->addCustomAttributesTo(&healLocationJson); - + OrderedJson::append(&healLocationJson, this->getCustomAttributes()); return healLocationJson; } -bool HealLocationEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setIdName(ParseUtil::jsonToQString(json["id"])); +bool HealLocationEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "id")); + this->setHostMapName(readString(&json, "map")); if (projectConfig.healLocationRespawnDataEnabled) { // Log a warning if "respawn_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["respawn_map"]); + const QString mapConstant = readString(&json, "respawn_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Respawn Map constant '%1'.").arg(mapConstant)); this->setRespawnMapName(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->setRespawnNPC(ParseUtil::jsonToQString(json["respawn_npc"])); + this->setRespawnNPC(readString(&json, "respawn_npc")); } - this->readCustomAttributes(json); + this->setCustomAttributes(json); return true; } @@ -875,16 +830,19 @@ void HealLocationEvent::setDefaultValues(Project *project) { } const QSet expectedHealLocationFields = { - "id", - "map" + }; QSet HealLocationEvent::getExpectedFields() { - QSet expectedFields = expectedHealLocationFields; + QSet expectedFields = { + "x", + "y", + "id", + "map", + }; if (projectConfig.healLocationRespawnDataEnabled) { expectedFields.insert("respawn_map"); expectedFields.insert("respawn_npc"); } - expectedFields << "x" << "y"; return expectedFields; } diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index 24bb264a..c501a596 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -311,32 +311,37 @@ const Json & JsonArray::operator[] (int i) const { else return m_value[i]; } -const Json Json::fromQJsonValue(QJsonValue value) { +Json Json::fromQJsonValue(const QJsonValue &value) { switch (value.type()) { case QJsonValue::String: return value.toString(); case QJsonValue::Double: return value.toInt(); case QJsonValue::Bool: return value.toBool(); - case QJsonValue::Array: - { - QJsonArray qArr = value.toArray(); - Json::array arr; - for (const auto &i: qArr) - arr.push_back(Json::fromQJsonValue(i)); - return arr; + case QJsonValue::Array: { + Json::array array; + Json::append(&array, value.toArray()); + return array; } - case QJsonValue::Object: - { - QJsonObject qObj = value.toObject(); - Json::object obj; - for (auto it = qObj.constBegin(); it != qObj.constEnd(); it++) - obj[it.key()] = Json::fromQJsonValue(it.value()); - return obj; + case QJsonValue::Object: { + Json::object object; + Json::append(&object, value.toObject()); + return object; } default: return static_null(); } } +void Json::append(Json::array *array, const QJsonArray &qArray) { + for (const auto &i: qArray) { + array->push_back(fromQJsonValue(i)); + } +} + +void Json::append(Json::object *object, const QJsonObject &qObject) { + for (auto it = qObject.constBegin(); it != qObject.constEnd(); it++) { + (*object)[it.key()] = fromQJsonValue(it.value()); + } +} /* * * * * * * * * * * * * * * * * * * * * Comparison diff --git a/src/project.cpp b/src/project.cpp index bef26614..a942d6fc 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -218,8 +218,8 @@ bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { return true; } -bool Project::loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType) { - QString typeString = ParseUtil::jsonToQString(json["type"]); +bool Project::loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType) { + QString typeString = ParseUtil::jsonToQString(json.take("type")); Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromJsonKey(typeString); Event* event = Event::create(type); if (!event) { @@ -245,10 +245,10 @@ bool Project::loadMapData(Map* map) { QJsonObject mapObj = mapDoc.object(); // We should already know the map constant ID from the initial project launch, but we'll ensure it's correct here anyway. - map->setConstantName(ParseUtil::jsonToQString(mapObj["id"])); + map->setConstantName(ParseUtil::jsonToQString(mapObj.take("id"))); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + const QString layoutId = ParseUtil::jsonToQString(mapObj.take("layout")); Layout* layout = this->mapLayouts.value(layoutId); if (!layout) { // We've already verified layout IDs on project launch and ignored maps with invalid IDs, so this shouldn't happen. @@ -257,24 +257,24 @@ bool Project::loadMapData(Map* map) { } map->setLayout(layout); - map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); - map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); - map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); - map->header()->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); - map->header()->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); - map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); + map->header()->setSong(ParseUtil::jsonToQString(mapObj.take("music"))); + map->header()->setLocation(ParseUtil::jsonToQString(mapObj.take("region_map_section"))); + map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj.take("requires_flash"))); + map->header()->setWeather(ParseUtil::jsonToQString(mapObj.take("weather"))); + map->header()->setType(ParseUtil::jsonToQString(mapObj.take("map_type"))); + map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj.take("show_map_name"))); + map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj.take("battle_scene"))); if (projectConfig.mapAllowFlagsEnabled) { - map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); - map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); - map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); + map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj.take("allow_cycling"))); + map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj.take("allow_escaping"))); + map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj.take("allow_running"))); } if (projectConfig.floorNumberEnabled) { - map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); + map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj.take("floor_number"))); } - map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); - map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); + map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj.take("shared_events_map"))); + map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj.take("shared_scripts_map"))); // Events map->resetEvents(); @@ -289,7 +289,7 @@ bool Project::loadMapData(Map* map) { for (auto i = defaultEventTypes.constBegin(); i != defaultEventTypes.constEnd(); i++) { QString eventGroupKey = i.key(); Event::Type defaultType = i.value(); - const QJsonArray eventsJsonArr = mapObj[eventGroupKey].toArray(); + const QJsonArray eventsJsonArr = mapObj.take(eventGroupKey).toArray(); for (int i = 0; i < eventsJsonArr.size(); i++) { if (!loadMapEvent(map, eventsJsonArr.at(i).toObject(), defaultType)) { logError(QString("Failed to load event for %1, in %2 at index %3.").arg(map->name()).arg(eventGroupKey).arg(i)); @@ -304,7 +304,7 @@ bool Project::loadMapData(Map* map) { } map->deleteConnections(); - QJsonArray connectionsArr = mapObj["connections"].toArray(); + QJsonArray connectionsArr = mapObj.take("connections").toArray(); if (!connectionsArr.isEmpty()) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); @@ -316,14 +316,7 @@ bool Project::loadMapData(Map* map) { map->loadConnection(connection); } } - - QMap customAttributes; - for (auto i = mapObj.constBegin(); i != mapObj.constEnd(); i++) { - if (!this->topLevelMapFields.contains(i.key())) { - customAttributes.insert(i.key(), i.value()); - } - } - map->setCustomAttributes(customAttributes); + map->setCustomAttributes(mapObj); return true; } @@ -621,16 +614,11 @@ void Project::saveMapLayouts() { layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; layoutObj["blockdata_filepath"] = layout->blockdata_path; - for (auto it = layout->customData.constBegin(); it != layout->customData.constEnd(); it++) { - layoutObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&layoutObj, layout->customData); layoutsArr.push_back(layoutObj); } layoutsObj["layouts"] = layoutsArr; - - for (auto it = this->customLayoutsData.constBegin(); it != this->customLayoutsData.constEnd(); it++) { - layoutsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&layoutsObj, this->customLayoutsData); ignoreWatchedFileTemporarily(layoutsFilepath); @@ -690,9 +678,7 @@ void Project::saveMapGroups() { } mapGroupsObj[groupName] = groupArr; } - for (auto it = this->customMapGroupsData.constBegin(); it != this->customMapGroupsData.constEnd(); it++) { - mapGroupsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&mapGroupsObj, this->customMapGroupsData); ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -727,19 +713,14 @@ void Project::saveRegionMapSections() { mapSectionObj["width"] = location.map.width; mapSectionObj["height"] = location.map.height; } - - for (auto it = location.custom.constBegin(); it != location.custom.constEnd(); it++) { - mapSectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&mapSectionObj, location.custom); mapSectionArray.append(mapSectionObj); } OrderedJson::object object; object["map_sections"] = mapSectionArray; - for (auto it = this->customMapSectionsData.constBegin(); it != this->customMapSectionsData.constEnd(); it++) { - object[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&object, this->customMapSectionsData); ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -894,9 +875,7 @@ void Project::saveHealLocations() { OrderedJson::object object; object["heal_locations"] = eventJsonArr; - for (auto it = this->customHealLocationsData.constBegin(); it != this->customHealLocationsData.constEnd(); it++) { - object[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&object, this->customHealLocationsData); ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -1230,10 +1209,7 @@ void Project::saveMap(Map *map, bool skipLayout) { connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); - auto customData = connection->customData(); - for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { - connectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&connectionObj, connection->customData()); connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; @@ -1289,10 +1265,7 @@ void Project::saveMap(Map *map, bool skipLayout) { this->healLocations[map->constantName()] = hlEvents; // Custom header fields. - const auto customAttributes = map->customAttributes(); - for (auto i = customAttributes.constBegin(); i != customAttributes.constEnd(); i++) { - mapObj[i.key()] = OrderedJson::fromQJsonValue(i.value()); - } + OrderedJson::append(&mapObj, map->customAttributes()); OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); @@ -2555,7 +2528,7 @@ bool Project::readHealLocations() { auto event = new HealLocationEvent(); event->loadFromJson(healLocationObj, this); - this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); + this->healLocations[event->getHostMapName()].append(event); this->healLocationSaveOrder.append(event->getIdName()); } this->customHealLocationsData = healLocationsObj; diff --git a/src/ui/customattributestable.cpp b/src/ui/customattributestable.cpp index 65381443..28153b4d 100644 --- a/src/ui/customattributestable.cpp +++ b/src/ui/customattributestable.cpp @@ -39,8 +39,8 @@ CustomAttributesTable::CustomAttributesTable(QWidget *parent) : }); } -QMap CustomAttributesTable::getAttributes() const { - QMap fields; +QJsonObject CustomAttributesTable::getAttributes() const { + QJsonObject fields; for (int row = 0; row < this->rowCount(); row++) { auto keyValuePair = this->getAttribute(row); if (!keyValuePair.first.isEmpty()) @@ -145,10 +145,10 @@ void CustomAttributesTable::addNewAttribute(const QString &key, const QJsonValue } // For programmatically populating the table -void CustomAttributesTable::setAttributes(const QMap &attributes) { +void CustomAttributesTable::setAttributes(const QJsonObject &attributes) { m_keys.clear(); this->setRowCount(0); // Clear old values - for (auto it = attributes.cbegin(); it != attributes.cend(); it++) + for (auto it = attributes.constBegin(); it != attributes.constEnd(); it++) this->addAttribute(it.key(), it.value()); this->resizeVertically(); } From f3a28848b9dcd226688b01805deb5a8d45e3ac08 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 11:54:39 -0400 Subject: [PATCH 280/364] Preserve custom fields in wild_encounters.json --- include/core/parseutil.h | 2 +- include/core/wildmoninfo.h | 10 +++-- include/lib/orderedjson.h | 19 +++++++-- include/lib/orderedmap.h | 11 ++++++ include/project.h | 6 ++- include/ui/regionmapeditor.h | 2 +- src/core/parseutil.cpp | 4 +- src/editor.cpp | 4 +- src/lib/orderedjson.cpp | 12 ------ src/project.cpp | 76 ++++++++++++++++++++++-------------- 10 files changed, 90 insertions(+), 56 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 4c19a27c..e02d0504 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -58,7 +58,7 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); - tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); + OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); bool tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error = nullptr); diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index 3c94fb17..a3665b7d 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -3,7 +3,7 @@ #define GUARD_WILDMONINFO_H #include -#include "orderedmap.h" +#include "orderedjson.h" class WildPokemon { public: @@ -13,22 +13,26 @@ public: int minLevel; int maxLevel; QString species; + OrderedJson::object customData; }; struct WildMonInfo { bool active = false; int encounterRate = 0; QVector wildPokemon; + OrderedJson::object customData; }; struct WildPokemonHeader { - tsl::ordered_map wildMons; + OrderedMap wildMons; + OrderedJson::object customData; }; struct EncounterField { QString name; // Ex: "fishing_mons" QVector encounterRates; - tsl::ordered_map> groups; // Ex: "good_rod", {2, 3, 4} + OrderedMap> groups; // Ex: "good_rod", {2, 3, 4} + OrderedJson::object customData; }; typedef QVector EncounterFields; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 73422a2b..cb5139d3 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -99,7 +99,7 @@ public: // Array and object typedefs typedef QVector array; - typedef tsl::ordered_map object; + typedef OrderedMap object; // Constructors for the various types of JSON value. Json() noexcept; // NUL @@ -133,8 +133,21 @@ public: Json(const V & v) : Json(array(v.begin(), v.end())) {} static Json fromQJsonValue(const QJsonValue &value); - static void append(Json::array *array, const QJsonArray &qArray); - static void append(Json::object *object, const QJsonObject &qObject); + + static void append(Json::array *array, const QJsonArray &addendum) { + for (const auto &i : addendum) array->push_back(fromQJsonValue(i)); + } + static void append(Json::array *array, const Json::array &addendum) { + for (const auto &i : addendum) array->push_back(i); + } + static void append(Json::object *object, const QJsonObject &addendum) { + for (auto it = addendum.constBegin(); it != addendum.constEnd(); it++) + (*object)[it.key()] = fromQJsonValue(it.value()); + } + static void append(Json::object *object, const Json::object &addendum) { + for (auto it = addendum.cbegin(); it != addendum.cend(); it++) + (*object)[it.key()] = it.value(); + } // This prevents Json(some_pointer) from accidentally producing a bool. Use // Json(bool(some_pointer)) if that behavior is desired. diff --git a/include/lib/orderedmap.h b/include/lib/orderedmap.h index 40882d9f..33fcfc50 100644 --- a/include/lib/orderedmap.h +++ b/include/lib/orderedmap.h @@ -1977,6 +1977,14 @@ public: size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } + + // Naive solution for take, should probably be replaced with one that does a single lookup and no unnecessary insertion. + // We want to mirror the behavior of QMap::take, which returns a default-constructed value if the key is not present. + T take(const key_type& key) { + typename ValueSelect::value_type value = m_ht[key]; + m_ht.erase(key); + return value; + } @@ -2404,4 +2412,7 @@ private: } // end namespace tsl +template +using OrderedMap = tsl::ordered_map; + #endif diff --git a/include/project.h b/include/project.h index 5f37c5e7..bcc53ae7 100644 --- a/include/project.h +++ b/include/project.h @@ -143,12 +143,11 @@ public: QString getNewHealLocationName(const Map* map) const; bool readWildMonData(); - tsl::ordered_map> wildMonData; + OrderedMap> wildMonData; QString wildMonTableName; QVector wildMonFields; QVector encounterGroupLabels; - QVector extraEncounterGroups; bool readSpeciesIconPaths(); QString getDefaultSpeciesIconPath(const QString &species); @@ -274,6 +273,9 @@ private: QJsonObject customMapSectionsData; QJsonObject customMapGroupsData; QJsonObject customHealLocationsData; + OrderedJson::object customWildMonData; + OrderedJson::object customWildMonGroupData; + OrderedJson::array extraEncounterGroups; // Maps/layouts represented in these sets have been fully loaded from the project. // If a valid map name / layout id is not in these sets, a Map / Layout object exists diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 490324af..8ecf0a76 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -54,7 +54,7 @@ private: Project *project; RegionMap *region_map = nullptr; - tsl::ordered_map region_maps; + OrderedMap region_maps; QString configFilepath; diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index e172ea42..da2a8f8e 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -610,12 +610,12 @@ bool ParseUtil::gameStringToBool(const QString &gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } -tsl::ordered_map> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { +OrderedMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { QString filePath = pathWithRoot(filename); auto cParser = fex::Parser(); auto tokens = fex::Lexer().LexFile(filePath); auto topLevelObjects = cParser.ParseTopLevelObjects(tokens); - tsl::ordered_map> structs; + OrderedMap> structs; for (auto it = topLevelObjects.begin(); it != topLevelObjects.end(); it++) { QString structLabel = QString::fromStdString(it->first); if (structLabel.isEmpty()) continue; diff --git a/src/editor.cpp b/src/editor.cpp index b29a47bc..19acbe60 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -596,7 +596,7 @@ void Editor::configureEncounterJSON(QWidget *window) { if (newNameDialog.exec() == QDialog::Accepted) { QString newFieldName = newNameEdit->text(); QVector newFieldRates(1, 100); - tempFields.append({newFieldName, newFieldRates, {}}); + tempFields.append({newFieldName, newFieldRates, {}, {}}); fieldChoices->addItem(newFieldName); fieldChoices->setCurrentIndex(fieldChoices->count() - 1); } @@ -675,7 +675,7 @@ void Editor::saveEncounterTabData() { if (!stack->count()) return; - tsl::ordered_map &encounterMap = project->wildMonData[map->constantName()]; + OrderedMap &encounterMap = project->wildMonData[map->constantName()]; for (int groupIndex = 0; groupIndex < stack->count(); groupIndex++) { MonTabWidget *tabWidget = static_cast(stack->widget(groupIndex)); diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index c501a596..d6ba4dbd 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -331,18 +331,6 @@ Json Json::fromQJsonValue(const QJsonValue &value) { } } -void Json::append(Json::array *array, const QJsonArray &qArray) { - for (const auto &i: qArray) { - array->push_back(fromQJsonValue(i)); - } -} - -void Json::append(Json::object *object, const QJsonObject &qObject) { - for (auto it = qObject.constBegin(); it != qObject.constEnd(); it++) { - (*object)[it.key()] = fromQJsonValue(it.value()); - } -} - /* * * * * * * * * * * * * * * * * * * * * Comparison */ diff --git a/src/project.cpp b/src/project.cpp index a942d6fc..871b8e2c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -747,7 +747,7 @@ void Project::saveWildMonData() { monHeadersObject["for_maps"] = true; OrderedJson::array fieldsInfoArray; - for (EncounterField fieldInfo : wildMonFields) { + for (EncounterField fieldInfo : this->wildMonFields) { OrderedJson::object fieldObject; OrderedJson::array rateArray; @@ -770,48 +770,52 @@ void Project::saveWildMonData() { } if (!groupsObject.empty()) fieldObject["groups"] = groupsObject; + OrderedJson::append(&fieldObject, fieldInfo.customData); fieldsInfoArray.append(fieldObject); } monHeadersObject["fields"] = fieldsInfoArray; OrderedJson::array encountersArray; - for (auto keyPair : wildMonData) { + for (auto keyPair : this->wildMonData) { QString key = keyPair.first; - for (auto grouplLabelPair : wildMonData[key]) { + for (auto grouplLabelPair : this->wildMonData[key]) { QString groupLabel = grouplLabelPair.first; OrderedJson::object encounterObject; encounterObject["map"] = key; encounterObject["base_label"] = groupLabel; - WildPokemonHeader encounterHeader = wildMonData[key][groupLabel]; + WildPokemonHeader encounterHeader = this->wildMonData[key][groupLabel]; for (auto fieldNamePair : encounterHeader.wildMons) { QString fieldName = fieldNamePair.first; - OrderedJson::object fieldObject; + OrderedJson::object monInfoObject; WildMonInfo monInfo = encounterHeader.wildMons[fieldName]; - fieldObject["encounter_rate"] = monInfo.encounterRate; + monInfoObject["encounter_rate"] = monInfo.encounterRate; OrderedJson::array monArray; for (WildPokemon wildMon : monInfo.wildPokemon) { OrderedJson::object monEntry; monEntry["min_level"] = wildMon.minLevel; monEntry["max_level"] = wildMon.maxLevel; monEntry["species"] = wildMon.species; + OrderedJson::append(&monEntry, wildMon.customData); monArray.push_back(monEntry); } - fieldObject["mons"] = monArray; - encounterObject[fieldName] = fieldObject; + monInfoObject["mons"] = monArray; + OrderedJson::append(&monInfoObject, monInfo.customData); + + encounterObject[fieldName] = monInfoObject; + OrderedJson::append(&encounterObject, encounterHeader.customData); } encountersArray.push_back(encounterObject); } } monHeadersObject["encounters"] = encountersArray; - wildEncounterGroups.push_back(monHeadersObject); + OrderedJson::append(&monHeadersObject, this->customWildMonGroupData); - // add extra Json objects that are not associated with maps to the file - for (auto extraObject : extraEncounterGroups) { - wildEncounterGroups.push_back(extraObject); - } + wildEncounterGroups.push_back(monHeadersObject); + OrderedJson::append(&wildEncounterGroups, this->extraEncounterGroups); wildEncountersObject["wild_encounter_groups"] = wildEncounterGroups; + OrderedJson::append(&wildEncountersObject, this->customWildMonData); ignoreWatchedFileTemporarily(wildEncountersJsonFilepath); OrderedJson encounterJson(wildEncountersObject); @@ -1607,6 +1611,8 @@ bool Project::readWildMonData() { this->pokemonMaxLevel = 100; this->maxEncounterRate = 2880/16; this->wildEncountersLoaded = false; + this->customWildMonData = OrderedJson::object(); + this->customWildMonGroupData = OrderedJson::object(); if (!userConfig.useEncounterJson) { return true; } @@ -1652,7 +1658,8 @@ bool Project::readWildMonData() { QMap> encounterRateFrequencyMaps; // Parse "wild_encounter_groups". This is the main object array containing all the data in this file. - for (OrderedJson mainArrayJson : wildMonObj["wild_encounter_groups"].array_items()) { + OrderedJson::array mainArray = wildMonObj.take("wild_encounter_groups").array_items(); + for (const OrderedJson &mainArrayJson : mainArray) { OrderedJson::object mainArrayObject = mainArrayJson.object_items(); // We're only interested in wild encounter data that's associated with maps ("for_maps" == true). @@ -1661,10 +1668,14 @@ bool Project::readWildMonData() { if (!mainArrayObject["for_maps"].bool_value()) { this->extraEncounterGroups.push_back(mainArrayObject); continue; + } else { + // Note: We don't call 'take' above, we don't want to strip data from extraEncounterGroups. + // We do want to strip it from the main group, because it shouldn't be treated as custom data. + mainArrayObject.erase("for_maps"); } // If multiple "for_maps" data sets are found they will be collapsed into a single set. - QString label = mainArrayObject["label"].string_value(); + QString label = mainArrayObject.take("label").string_value(); if (this->wildMonTableName.isEmpty()) { this->wildMonTableName = label; } else { @@ -1677,24 +1688,25 @@ bool Project::readWildMonData() { // Each element describes a type of wild encounter Porymap can expect to find, and we represent this data with an EncounterField. // They should contain a name ("type"), the number of encounter slots and the ratio at which they occur ("encounter_rates"), // and whether the encounters are divided into groups (like fishing rods). - for (const OrderedJson &fieldJson : mainArrayObject["fields"].array_items()) { + for (const OrderedJson &fieldJson : mainArrayObject.take("fields").array_items()) { OrderedJson::object fieldObject = fieldJson.object_items(); EncounterField encounterField; - encounterField.name = fieldObject["type"].string_value(); + encounterField.name = fieldObject.take("type").string_value(); - for (auto val : fieldObject["encounter_rates"].array_items()) { + for (auto val : fieldObject.take("encounter_rates").array_items()) { encounterField.encounterRates.append(val.int_value()); } // Each element of the "groups" array is an object with the group name as the key (e.g. "old_rod") // and an array of slot numbers indicating which encounter slots in this encounter type belong to that group. - for (auto groupPair : fieldObject["groups"].object_items()) { + for (auto groupPair : fieldObject.take("groups").object_items()) { const QString groupName = groupPair.first; for (auto slotNum : groupPair.second.array_items()) { encounterField.groups[groupName].append(slotNum.int_value()); } } + encounterField.customData = fieldObject; encounterRateFrequencyMaps.insert(encounterField.name, QMap()); this->wildMonFields.append(encounterField); @@ -1704,7 +1716,7 @@ bool Project::readWildMonData() { // Each element is an object that will tell us which map it's associated with, // its symbol name (which we will display in the Groups dropdown) and a list of // pokémon associated with any of the encounter types described by the data we parsed above. - for (const auto &encounterJson : mainArrayObject["encounters"].array_items()) { + for (const auto &encounterJson : mainArrayObject.take("encounters").array_items()) { OrderedJson::object encounterObj = encounterJson.object_items(); WildPokemonHeader header; @@ -1712,29 +1724,31 @@ bool Project::readWildMonData() { // Check for each possible encounter type. for (const EncounterField &monField : this->wildMonFields) { const QString field = monField.name; - if (encounterObj[field].is_null()) { + if (!encounterObj.contains(field)) { // Encounter type isn't present continue; } - OrderedJson::object encounterFieldObj = encounterObj[field].object_items(); + OrderedJson::object encounterFieldObj = encounterObj.take(field).object_items(); WildMonInfo monInfo; monInfo.active = true; // Read encounter rate - monInfo.encounterRate = encounterFieldObj["encounter_rate"].int_value(); + monInfo.encounterRate = encounterFieldObj.take("encounter_rate").int_value(); encounterRateFrequencyMaps[field][monInfo.encounterRate]++; // Read wild pokémon list - for (auto monJson : encounterFieldObj["mons"].array_items()) { + for (const auto &monJson : encounterFieldObj.take("mons").array_items()) { OrderedJson::object monObj = monJson.object_items(); WildPokemon newMon; - newMon.minLevel = monObj["min_level"].int_value(); - newMon.maxLevel = monObj["max_level"].int_value(); - newMon.species = monObj["species"].string_value(); + newMon.minLevel = monObj.take("min_level").int_value(); + newMon.maxLevel = monObj.take("max_level").int_value(); + newMon.species = monObj.take("species").string_value(); + newMon.customData = monObj; monInfo.wildPokemon.append(newMon); } + monInfo.customData = encounterFieldObj; // If the user supplied too few pokémon for this group then we fill in the rest with default values. for (int i = monInfo.wildPokemon.length(); i < monField.encounterRates.length(); i++) { @@ -1742,13 +1756,15 @@ bool Project::readWildMonData() { } header.wildMons[field] = monInfo; } - - const QString mapConstant = encounterObj["map"].string_value(); - const QString baseLabel = encounterObj["base_label"].string_value(); + const QString mapConstant = encounterObj.take("map").string_value(); + const QString baseLabel = encounterObj.take("base_label").string_value(); + header.customData = encounterObj; this->wildMonData[mapConstant].insert({baseLabel, header}); this->encounterGroupLabels.append(baseLabel); } + this->customWildMonGroupData = mainArrayObject; } + this->customWildMonData = wildMonObj; // For each encounter type, set default encounter rate to most common value. // Iterate over map of encounter type names to frequency maps... From db9e9d6f65077b230d14df7bfc779a7489956fde Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 13:09:14 -0400 Subject: [PATCH 281/364] Remove Project::topLevelMapFields --- include/project.h | 4 ++-- src/mainwindow.cpp | 2 +- src/project.cpp | 16 +++++++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/include/project.h b/include/project.h index bcc53ae7..c5aed0a3 100644 --- a/include/project.h +++ b/include/project.h @@ -71,7 +71,6 @@ public: QSet modifiedFiles; bool usingAsmTilesets; QSet disabledSettingsNames; - QSet topLevelMapFields; int pokemonMinLevel; int pokemonMaxLevel; int maxEncounterRate; @@ -161,7 +160,6 @@ public: bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; - void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); @@ -241,6 +239,8 @@ public: void setRegionMapEntries(const QHash &entries); QHash getRegionMapEntries() const; + QSet getTopLevelMapFields() const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index af7eaabe..28d7f996 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1192,7 +1192,7 @@ bool MainWindow::setProjectUI() { ui->layoutList->setModel(layoutListProxyModel); ui->layoutList->sortByColumn(0, Qt::SortOrder::AscendingOrder); - ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->topLevelMapFields); + ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->getTopLevelMapFields()); return true; } diff --git a/src/project.cpp b/src/project.cpp index 871b8e2c..f5499730 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -177,8 +177,8 @@ Map* Project::loadMap(const QString &mapName) { return map; } -void Project::initTopLevelMapFields() { - static const QSet defaultTopLevelMapFields = { +QSet Project::getTopLevelMapFields() const { + QSet fields = { "id", "name", "layout", @@ -197,15 +197,15 @@ void Project::initTopLevelMapFields() { "shared_events_map", "shared_scripts_map", }; - this->topLevelMapFields = defaultTopLevelMapFields; if (projectConfig.mapAllowFlagsEnabled) { - this->topLevelMapFields.insert("allow_cycling"); - this->topLevelMapFields.insert("allow_escaping"); - this->topLevelMapFields.insert("allow_running"); + fields.insert("allow_cycling"); + fields.insert("allow_escaping"); + fields.insert("allow_running"); } if (projectConfig.floorNumberEnabled) { - this->topLevelMapFields.insert("floor_number"); + fields.insert("floor_number"); } + return fields; } bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { @@ -1794,8 +1794,6 @@ bool Project::readMapGroups() { this->groupNameToMapNames.clear(); this->customMapGroupsData = QJsonObject(); - this->initTopLevelMapFields(); - const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_map_groups); fileWatcher.addPath(root + "/" + filepath); QJsonDocument mapGroupsDoc; From 5c9e84b4c0d0ee4f96d960d43f9730fe605f2fff Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 13:37:15 -0400 Subject: [PATCH 282/364] Add missing key take --- src/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index f5499730..6f225ff6 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1804,7 +1804,7 @@ bool Project::readMapGroups() { } QJsonObject mapGroupsObj = mapGroupsDoc.object(); - QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + QJsonArray mapGroupOrder = mapGroupsObj.take("group_order").toArray(); const QString dynamicMapName = getDynamicMapName(); const QString dynamicMapConstant = getDynamicMapDefineName(); From 8c6b1a1e7da43d4ed68e3c4c6f3465209f090c16 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 13:18:33 -0400 Subject: [PATCH 283/364] Update changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8777a50..da551cc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - The max encounter rate is now read from the project, rather than assuming the default value from RSE. - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. -- `Export Map Stitch Image` now shows a preview of the full image, not just the current map. +- `Export Map Stitch Image` and `Export Map Timelapse Image` now show a preview of the full image/gif, not just the current map. - `Custom Attributes` tables now display numbers using spin boxes. The `type` column was removed, because `value`'s type is now obvious. - Unrecognized map names in Event or Connections data will no longer be overwritten. - It's now possible to click on an event's sprite even if a different event's rectangle is overlapping it. The old selection behavior is available via a new setting. @@ -82,6 +82,12 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix a freeze on startup if project values are defined with mismatched parentheses. - Fix stitched map images sometimes rendering garbage - Fix the `Reset` button on `Export Map Timelapse Image` not resetting the Timelapse settings. +- Fix events in exported map stitch images being occluded by neighboring maps. +- Fix the map connections in exported map images coming from the map currently open in the editor, rather than the map shown in the export window. +- Fix crash when exporting a map stitch image if a map fails to load. +- Fix possible crash when exporting a timelapse that has events edit history. +- Fix exported timelapses excluding pasted events and certain map size changes. +- Fix exporting a timelapse sometimes altering the state of the current map's edit history. - Stop sliders in the Palette Editor from creating a bunch of edit history when used. - Fix scrolling on some containers locking up when the mouse stops over a spin box or combo box. - Fix some file dialogs returning to an incorrect window when closed. From ecad60843c9245ce2ceb885d30d91763a9de3e37 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 10:41:57 -0400 Subject: [PATCH 284/364] Remove old DraggablePixmapItem signals/slots --- include/ui/draggablepixmapitem.h | 24 ++++-------------------- src/ui/draggablepixmapitem.cpp | 1 - 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/include/ui/draggablepixmapitem.h b/include/ui/draggablepixmapitem.h index aeda4daf..debf1e41 100644 --- a/include/ui/draggablepixmapitem.h +++ b/include/ui/draggablepixmapitem.h @@ -42,29 +42,13 @@ signals: void positionChanged(Event *event); void xChanged(int); void yChanged(int); - void elevationChanged(int); void spriteChanged(QPixmap pixmap); - void onPropertyChanged(QString key, QString value); - -public slots: - void set_x(int x) { - event->setX(x); - updatePosition(); - } - void set_y(int y) { - event->setY(y); - updatePosition(); - } - void set_elevation(int z) { - event->setElevation(z); - updatePosition(); - } protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*); + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; }; #endif // DRAGGABLEPIXMAPITEM_H diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index f2fdeb91..31068fee 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -23,7 +23,6 @@ void DraggablePixmapItem::updatePosition() { void DraggablePixmapItem::emitPositionChanged() { emit xChanged(event->getX()); emit yChanged(event->getY()); - emit elevationChanged(event->getElevation()); } void DraggablePixmapItem::updatePixmap() { From 2d827f62f786f2de2b8ad7a4e08a41a23dc01fcc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 11:44:54 -0400 Subject: [PATCH 285/364] Support event lookup by ID name --- include/core/events.h | 6 ++-- include/core/map.h | 1 + include/editor.h | 2 +- include/mainwindow.h | 2 +- include/ui/draggablepixmapitem.h | 5 +-- src/core/events.cpp | 7 ++-- src/core/map.cpp | 15 +++++++++ src/editor.cpp | 1 + src/mainwindow.cpp | 57 ++++++++++++++++++++++++++------ src/project.cpp | 11 ++++-- src/ui/draggablepixmapitem.cpp | 28 ---------------- src/ui/eventframes.cpp | 5 ++- 12 files changed, 88 insertions(+), 52 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 22b76434..cff233cc 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -300,12 +300,12 @@ public: void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } QString getTargetMap() const { return this->targetMap; } - void setTargetID(int newTargetID) { this->targetID = newTargetID; } - int getTargetID() const { return this->targetID; } + void setTargetID(QString newTargetID) { this->targetID = newTargetID; } + QString getTargetID() const { return this->targetID; } private: QString targetMap; - int targetID = 0; + QString targetID; }; diff --git a/include/core/map.h b/include/core/map.h index c2078134..a3bb1bbf 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -76,6 +76,7 @@ public: void resetEvents(); QList getEvents(Event::Group group = Event::Group::None) const; Event* getEvent(Event::Group group, int index) const; + Event* getEvent(Event::Group group, const QString &idName) const; int getNumEvents(Event::Group group = Event::Group::None) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilePath() const; diff --git a/include/editor.h b/include/editor.h index 4c7bff02..b56f42d4 100644 --- a/include/editor.h +++ b/include/editor.h @@ -251,11 +251,11 @@ private slots: signals: void eventsChanged(); + void openEventMap(Event*); void openConnectedMap(MapConnection*); void wildMonTableOpened(EncounterTableModel*); void wildMonTableClosed(); void wildMonTableEdited(); - void warpEventDoubleClicked(QString, int, Event::Group); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); void tilesetUpdated(QString); diff --git a/include/mainwindow.h b/include/mainwindow.h index d8fe18a3..e3cdd62f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -177,7 +177,7 @@ private slots: void on_action_Save_Project_triggered(); void save(bool currentOnly = false); - void openWarpMap(QString map_name, int event_id, Event::Group event_group); + void openEventMap(Event *event); void duplicate(); void setClipboardData(poryjson::Json::object); diff --git a/include/ui/draggablepixmapitem.h b/include/ui/draggablepixmapitem.h index debf1e41..5c617099 100644 --- a/include/ui/draggablepixmapitem.h +++ b/include/ui/draggablepixmapitem.h @@ -42,13 +42,14 @@ signals: void positionChanged(Event *event); void xChanged(int); void yChanged(int); - void spriteChanged(QPixmap pixmap); + void spriteChanged(const QPixmap &pixmap); + void doubleClicked(Event *event); protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(this->event); } }; #endif // DRAGGABLEPIXMAPITEM_H diff --git a/src/core/events.cpp b/src/core/events.cpp index ebb1ac49..e132f277 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -275,7 +275,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { this->setY(readInt(&json, "y")); this->setIdName(readString(&json, "local_id")); this->setGfx(readString(&json, "graphics_id")); - this->setTargetID(readInt(&json, "target_local_id")); + this->setTargetID(readString(&json, "target_local_id")); // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. const QString mapConstant = readString(&json, "target_map"); @@ -289,7 +289,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { void CloneObjectEvent::setDefaultValues(Project *project) { this->setGfx(project->gfxDefines.key(0, "0")); - this->setTargetID(1); + this->setTargetID(QString::number(Event::getIndexOffset(Event::Group::Object))); if (this->getMap()) this->setTargetMap(this->getMap()->name()); } @@ -308,9 +308,8 @@ QSet CloneObjectEvent::getExpectedFields() { void CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone - int eventIndex = this->targetID - 1; Map *clonedMap = project->loadMap(this->targetMap); - Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr; + Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { // Get graphics data from cloned object diff --git a/src/core/map.cpp b/src/core/map.cpp index 330132a1..5e5d443f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -194,6 +194,21 @@ Event* Map::getEvent(Event::Group group, int index) const { return m_events[group].value(index, nullptr); } +Event* Map::getEvent(Event::Group group, const QString &idName) const { + bool idIsNumber; + int id = idName.toInt(&idIsNumber, 0); + if (idIsNumber) + return getEvent(group, id - Event::getIndexOffset(group)); + + auto events = getEvents(group); + for (const auto &event : events) { + if (event->getIdName() == idName) { + return event; + } + } + return nullptr; +} + int Map::getNumEvents(Event::Group group) const { if (group == Event::Group::None) { // Total number of events diff --git a/src/editor.cpp b/src/editor.cpp index 19acbe60..282ec52c 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1699,6 +1699,7 @@ void Editor::displayMapEvents() { DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); auto item = new DraggablePixmapItem(event, this); + connect(item, &DraggablePixmapItem::doubleClicked, this, &Editor::openEventMap); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28d7f996..237ca5c4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -338,7 +338,7 @@ void MainWindow::initEditor() { this->editor = new Editor(ui); connect(this->editor, &Editor::eventsChanged, this, &MainWindow::updateEvents); connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); - connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap); + connect(this->editor, &Editor::openEventMap, this, &MainWindow::openEventMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::wildMonTableEdited, this, &MainWindow::markMapEdited); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); @@ -1055,19 +1055,56 @@ void MainWindow::refreshCollisionSelector() { on_horizontalSlider_CollisionZoom_valueChanged(ui->horizontalSlider_CollisionZoom->value()); } -void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) { - // Open the destination map. - if (!userSetMap(map_name)) +// Some events (like warps) have data that refers to an event on a different map. +// This function opens that map, and selects the event it's referring to. +void MainWindow::openEventMap(Event *sourceEvent) { + if (!sourceEvent || !this->editor->map) return; + + QString targetMapName; + QString targetEventIdName; + Event::Group targetEventGroup; + + Event::Type eventType = sourceEvent->getEventType(); + if (eventType == Event::Type::Warp) { + // Warp events open to their destination warp event. + WarpEvent *warp = dynamic_cast(sourceEvent); + targetMapName = warp->getDestinationMap(); + targetEventIdName = warp->getDestinationWarpID(); + targetEventGroup = Event::Group::Warp; + } else if (eventType == Event::Type::CloneObject) { + // Clone object events open to their target object event. + CloneObjectEvent *clone = dynamic_cast(sourceEvent); + targetMapName = clone->getTargetMap(); + targetEventIdName = clone->getTargetID(); + targetEventGroup = Event::Group::Object; + } else if (eventType == Event::Type::SecretBase) { + // Secret Bases open to their secret base entrance + const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + SecretBaseEvent *base = dynamic_cast(sourceEvent); + QString baseId = base->getBaseID(); + targetMapName = this->editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); + targetEventIdName = "0"; + targetEventGroup = Event::Group::Warp; + } else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { + // Heal location events open to their respawn NPC + HealLocationEvent *heal = dynamic_cast(sourceEvent); + targetMapName = heal->getRespawnMapName(); + targetEventIdName = heal->getRespawnNPC(); + targetEventGroup = Event::Group::Object; + } else { + // Other event types have no target map to open. + return; + } + if (!userSetMap(targetMapName)) return; - // Select the target event. - int index = event_id - Event::getIndexOffset(event_group); - Event* event = this->editor->map->getEvent(event_group, index); - if (event) { - this->editor->selectMapEvent(event); + // Map opened successfully, now try to select the targeted event on that map. + Event* targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName); + if (targetEvent) { + this->editor->selectMapEvent(targetEvent); } else { // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name)); + logWarn(QString("%1 '%2' doesn't exist on map '%3'").arg(Event::groupToString(targetEventGroup)).arg(targetEventIdName).arg(targetMapName)); } } diff --git a/src/project.cpp b/src/project.cpp index 6f225ff6..e02db858 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -162,8 +162,10 @@ void Project::clearTilesetCache() { Map* Project::loadMap(const QString &mapName) { Map* map = this->maps.value(mapName); - if (!map) + if (!map) { + logError(QString("Unknown map name '%1'.").arg(mapName)); return nullptr; + } if (isMapLoaded(map)) return map; @@ -445,7 +447,12 @@ bool Project::loadLayout(Layout *layout) { Layout *Project::loadLayout(QString layoutId) { Layout *layout = this->mapLayouts.value(layoutId); - if (!layout || !loadLayout(layout)) { + if (!layout) { + logError(QString("Unknown layout ID '%1'.").arg(layoutId)); + return nullptr; + } + + if (!loadLayout(layout)) { logError(QString("Failed to load layout '%1'").arg(layoutId)); return nullptr; } diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 31068fee..53cdd90a 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -103,31 +103,3 @@ void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { this->editor->selectMapEvent(this->event); } } - -// Events with properties that specify a map will open that map when double-clicked. -void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { - Event::Type eventType = this->event->getEventType(); - if (eventType == Event::Type::Warp) { - WarpEvent *warp = dynamic_cast(this->event); - QString destMap = warp->getDestinationMap(); - int warpId = ParseUtil::gameStringToInt(warp->getDestinationWarpID()); - emit editor->warpEventDoubleClicked(destMap, warpId, Event::Group::Warp); - } - else if (eventType == Event::Type::CloneObject) { - CloneObjectEvent *clone = dynamic_cast(this->event); - emit editor->warpEventDoubleClicked(clone->getTargetMap(), clone->getTargetID(), Event::Group::Object); - } - else if (eventType == Event::Type::SecretBase) { - const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - SecretBaseEvent *base = dynamic_cast(this->event); - QString baseId = base->getBaseID(); - QString destMap = editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); - emit editor->warpEventDoubleClicked(destMap, 0, Event::Group::Warp); - } - else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { - HealLocationEvent *heal = dynamic_cast(this->event); - const QString localIdName = heal->getRespawnNPC(); - int localId = 0; // TODO: Get value from localIdName - emit editor->warpEventDoubleClicked(heal->getRespawnMapName(), localId, Event::Group::Object); - } -} diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 82334722..c69d35da 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -453,13 +453,16 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { }); // target id + // TODO: Replace spinner with combo box populated with local IDs from target map. this->spinner_target_id->disconnect(); + /* connect(this->spinner_target_id, QOverload::of(&QSpinBox::valueChanged), [this](int value) { this->clone->setTargetID(value); this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); + */ } void CloneObjectFrame::initialize() { @@ -474,7 +477,7 @@ void CloneObjectFrame::initialize() { // target id this->spinner_target_id->setMinimum(1); this->spinner_target_id->setMaximum(126); - this->spinner_target_id->setValue(this->clone->getTargetID()); + //this->spinner_target_id->setValue(this->clone->getTargetID()); // target map this->combo_target_map->setTextItem(this->clone->getTargetMap()); From 2256ded6c285f2d12eeefe99b7a6e3cd50b42862 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 12:31:45 -0400 Subject: [PATCH 286/364] Some event frame updates for local IDs --- include/ui/eventframes.h | 2 +- src/core/events.cpp | 2 +- src/core/map.cpp | 3 +++ src/mainwindow.cpp | 5 +++++ src/ui/eventframes.cpp | 48 ++++++++++++++-------------------------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 763d6baf..a5ae6765 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -109,7 +109,7 @@ public: public: NoScrollComboBox *combo_sprite; - NoScrollSpinBox *spinner_target_id; + NoScrollComboBox *combo_target_id; NoScrollComboBox *combo_target_map; private: diff --git a/src/core/events.cpp b/src/core/events.cpp index e132f277..8c5e5077 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -99,7 +99,7 @@ QString Event::typeToString(Event::Type type) { {Event::Type::CloneObject, "Clone Object"}, {Event::Type::Warp, "Warp"}, {Event::Type::Trigger, "Trigger"}, - {Event::Type::WeatherTrigger, "Weather"}, + {Event::Type::WeatherTrigger, "Weather Trigger"}, {Event::Type::Sign, "Sign"}, {Event::Type::HiddenItem, "Hidden Item"}, {Event::Type::SecretBase, "Secret Base"}, diff --git a/src/core/map.cpp b/src/core/map.cpp index 5e5d443f..fa69427b 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -195,6 +195,9 @@ Event* Map::getEvent(Event::Group group, int index) const { } Event* Map::getEvent(Event::Group group, const QString &idName) const { + if (idName.isEmpty()) + return nullptr; + bool idIsNumber; int id = idName.toInt(&idIsNumber, 0); if (idIsNumber) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 237ca5c4..1ed4ca04 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1081,8 +1081,13 @@ void MainWindow::openEventMap(Event *sourceEvent) { // Secret Bases open to their secret base entrance const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); SecretBaseEvent *base = dynamic_cast(sourceEvent); + + // Extract the map name from the secret base ID. QString baseId = base->getBaseID(); targetMapName = this->editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); + + // Just select the first warp. Normally the only warp event on every secret base map is the entrance/exit, so this is usually correct. + // The warp IDs for secret bases are specified in the project's C code, not in the map data, so we don't have an easy way to read the actual IDs. targetEventIdName = "0"; targetEventGroup = Event::Group::Warp; } else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index c69d35da..ea0cccae 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -74,6 +74,7 @@ void EventFrame::setup() { this->label_id = new QLabel("event_type"); l_vbox_1->addWidget(this->label_id); l_vbox_1->addLayout(l_layout_xyz); + this->label_id->setText(Event::typeToString(this->event->getEventType())); // icon / pixmap label this->label_icon = new QLabel(this); @@ -204,8 +205,6 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj void ObjectFrame::setup() { EventFrame::setup(); - this->label_id->setText("Object"); - // sprite combo QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); @@ -406,8 +405,6 @@ void ObjectFrame::populate(Project *project) { void CloneObjectFrame::setup() { EventFrame::setup(); - this->label_id->setText("Clone Object"); - this->spinner_z->setEnabled(false); // sprite combo (edits disabled) @@ -424,11 +421,12 @@ void CloneObjectFrame::setup() { l_form_dest_map->addRow("Target Map", this->combo_target_map); this->layout_contents->addLayout(l_form_dest_map); - // clone local id spinbox + // clone local id combo QFormLayout *l_form_dest_id = new QFormLayout(); - this->spinner_target_id = new NoScrollSpinBox(this); - this->spinner_target_id->setToolTip("event_object ID of the object being cloned."); - l_form_dest_id->addRow("Target Local ID", this->spinner_target_id); + this->combo_target_id = new NoScrollComboBox(this); + // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field + this->combo_target_id->setToolTip("event_object ID of the object being cloned."); + l_form_dest_id->addRow("Target Local ID", this->combo_target_id); this->layout_contents->addLayout(l_form_dest_id); // custom attributes @@ -450,19 +448,17 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); + // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. }); // target id - // TODO: Replace spinner with combo box populated with local IDs from target map. - this->spinner_target_id->disconnect(); - /* - connect(this->spinner_target_id, QOverload::of(&QSpinBox::valueChanged), [this](int value) { - this->clone->setTargetID(value); + this->combo_target_id->disconnect(); + connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) { + this->clone->setTargetID(text); this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); - */ } void CloneObjectFrame::initialize() { @@ -475,9 +471,7 @@ void CloneObjectFrame::initialize() { this->combo_sprite->setCurrentText(this->clone->getGfx()); // target id - this->spinner_target_id->setMinimum(1); - this->spinner_target_id->setMaximum(126); - //this->spinner_target_id->setValue(this->clone->getTargetID()); + this->combo_target_id->setCurrentText(this->clone->getTargetID()); // target map this->combo_target_map->setTextItem(this->clone->getTargetMap()); @@ -490,13 +484,12 @@ void CloneObjectFrame::populate(Project *project) { EventFrame::populate(project); this->combo_target_map->addItems(project->mapNames); + // TODO: Populate combo_target_id with local IDs from target map. } void WarpFrame::setup() { EventFrame::setup(); - this->label_id->setText("Warp"); - // desination map combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_dest_map = new NoScrollComboBox(this); @@ -537,6 +530,7 @@ void WarpFrame::connectSignals(MainWindow *window) { connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &text) { this->warp->setDestinationMap(text); this->warp->modify(); + // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. }); // dest id @@ -571,6 +565,7 @@ void WarpFrame::populate(Project *project) { EventFrame::populate(project); this->combo_dest_map->addItems(project->mapNames); + // TODO: Populate combo_dest_warp with local IDs from target map. } @@ -578,8 +573,6 @@ void WarpFrame::populate(Project *project) { void TriggerFrame::setup() { EventFrame::setup(); - this->label_id->setText("Trigger"); - // script combo QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); @@ -666,8 +659,6 @@ void TriggerFrame::populate(Project *project) { void WeatherTriggerFrame::setup() { EventFrame::setup(); - this->label_id->setText("Weather Trigger"); - // weather combo QFormLayout *l_form_weather = new QFormLayout(); this->combo_weather = new NoScrollComboBox(this); @@ -717,8 +708,6 @@ void WeatherTriggerFrame::populate(Project *project) { void SignFrame::setup() { EventFrame::setup(); - this->label_id->setText("Sign"); - // facing dir combo QFormLayout *l_form_facing_dir = new QFormLayout(); this->combo_facing_dir = new NoScrollComboBox(this); @@ -788,8 +777,6 @@ void SignFrame::populate(Project *project) { void HiddenItemFrame::setup() { EventFrame::setup(); - this->label_id->setText("Hidden Item"); - // item combo QFormLayout *l_form_item = new QFormLayout(); this->combo_item = new NoScrollComboBox(this); @@ -902,8 +889,6 @@ void HiddenItemFrame::populate(Project *project) { void SecretBaseFrame::setup() { EventFrame::setup(); - this->label_id->setText("Secret Base"); - this->spinner_z->setEnabled(false); // item combo @@ -955,8 +940,6 @@ void SecretBaseFrame::populate(Project *project) { void HealLocationFrame::setup() { EventFrame::setup(); - this->label_id->setText("Heal Location"); - this->hideable_label_z->setVisible(false); this->spinner_z->setVisible(false); @@ -982,6 +965,7 @@ void HealLocationFrame::setup() { QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc); l_form_respawn_npc->setContentsMargins(0, 0, 0, 0); this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc); + // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" "upon respawning after whiteout."); l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); @@ -1006,6 +990,7 @@ void HealLocationFrame::connectSignals(MainWindow *window) { connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { this->healLocation->setRespawnMapName(text); this->healLocation->modify(); + // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. }); this->combo_respawn_npc->disconnect(); @@ -1038,5 +1023,4 @@ void HealLocationFrame::populate(Project *project) { this->combo_respawn_map->addItems(project->mapNames); // TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map - // Same for warp IDs. } From 17949055f6d2fc364da091a5ace49619759fd0fe Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 13:49:26 -0400 Subject: [PATCH 287/364] Fix local ID being reordered in output JSON --- src/core/events.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index 8c5e5077..2dce4192 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -146,12 +146,13 @@ EventFrame *ObjectEvent::createEventFrame() { OrderedJson::object ObjectEvent::buildEventJson(Project *) { OrderedJson::object objectJson; - if (projectConfig.eventCloneObjectEnabled) { - objectJson["type"] = Event::typeToJsonKey(Event::Type::Object); - } QString idName = this->getIdName(); if (!idName.isEmpty()) objectJson["local_id"] = idName; + + if (projectConfig.eventCloneObjectEnabled) { + objectJson["type"] = Event::typeToJsonKey(Event::Type::Object); + } objectJson["graphics_id"] = this->getGfx(); objectJson["x"] = this->getX(); objectJson["y"] = this->getY(); @@ -255,10 +256,11 @@ EventFrame *CloneObjectEvent::createEventFrame() { OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { OrderedJson::object cloneJson; - cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject); QString idName = this->getIdName(); if (!idName.isEmpty()) cloneJson["local_id"] = idName; + + cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject); cloneJson["graphics_id"] = this->getGfx(); cloneJson["x"] = this->getX(); cloneJson["y"] = this->getY(); From bbd86673c5b994d7b346cc3269b933218a30dc65 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 16:13:12 -0400 Subject: [PATCH 288/364] Fix uses of QDir::separator, clean and strip root from config paths --- CHANGELOG.md | 1 + src/config.cpp | 13 ++++++++----- src/ui/customscriptseditor.cpp | 2 +- src/ui/projectsettingseditor.cpp | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da551cc1..0d64777f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. - Fix crash when saving tilesets with fewer palettes than the maximum. - Fix projects not opening on Windows if the project filepath contains certain characters. +- Fix custom project filepaths not converting Windows file separators. - Fix exported tile images containing garbage pixels after the end of the tiles. - Fix fully transparent pixels rendering with the incorrect color. - Fix the values for some config fields shuffling their order every save. diff --git a/src/config.cpp b/src/config.cpp index d74adbcb..42a59149 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -984,7 +984,7 @@ void ProjectConfig::setFilePath(const QString &pathId, const QString &path) { } QString ProjectConfig::getCustomFilePath(ProjectFilePath pathId) { - return this->filePaths.value(pathId); + return QDir::cleanPath(this->filePaths.value(pathId)); } QString ProjectConfig::getCustomFilePath(const QString &pathId) { @@ -992,14 +992,17 @@ QString ProjectConfig::getCustomFilePath(const QString &pathId) { } QString ProjectConfig::getFilePath(ProjectFilePath pathId) { - const QString customPath = this->getCustomFilePath(pathId); + QString customPath = this->getCustomFilePath(pathId); if (!customPath.isEmpty()) { // A custom filepath has been specified. If the file/folder exists, use that. - const QString absCustomPath = this->projectDir + QDir::separator() + customPath; - if (QFileInfo::exists(absCustomPath)) { + const QString baseDir = this->projectDir + "/"; + if (customPath.startsWith(baseDir)) { + customPath.remove(0, baseDir.length()); + } + if (QFileInfo::exists(QDir::cleanPath(baseDir + customPath))) { return customPath; } else { - logError(QString("Custom project filepath '%1' not found. Using default.").arg(absCustomPath)); + logError(QString("Custom project filepath '%1' not found. Using default.").arg(customPath)); } } return defaultPaths.contains(pathId) ? defaultPaths[pathId].second : QString(); diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index c3c412c2..eddbc7ae 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -11,7 +11,7 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) : QMainWindow(parent), ui(new Ui::CustomScriptsEditor), - baseDir(userConfig.projectDir + QDir::separator()) + baseDir(userConfig.projectDir + "/") { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index fa84f3e0..34987914 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -20,7 +20,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(QWidget *parent, Project *project) QMainWindow(parent), ui(new Ui::ProjectSettingsEditor), project(project), - baseDir(projectConfig.projectDir + QDir::separator()) + baseDir(projectConfig.projectDir + "/") { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); @@ -388,7 +388,7 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath) QString path; if (defaultFilepath.endsWith("/")){ // Default filepath is a folder, choose a new folder - path = FileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + QDir::separator(); + path = FileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + "/"; } else{ // Default filepath is not a folder, choose a new file path = FileDialog::getOpenFileName(this, "Choose Project File", startDir); From 69efdc34d0891a57dc8ff210434b735ff03287c6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 16:20:18 -0400 Subject: [PATCH 289/364] Stop warning when canceling custom filepath dialog --- src/ui/projectsettingseditor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 34987914..1c869dce 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -393,6 +393,8 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath) // Default filepath is not a folder, choose a new file path = FileDialog::getOpenFileName(this, "Choose Project File", startDir); } + if (path.isEmpty()) + return path; if (!path.startsWith(this->baseDir)){ // Most of Porymap's file-parsing code for project files will assume that filepaths From 374a2b67b83a985ef038546fde1685fbb89c75ae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 23:33:38 -0400 Subject: [PATCH 290/364] Remove some redundant event pixmap loading --- src/editor.cpp | 49 +++++++++++++++++++--------------- src/ui/draggablepixmapitem.cpp | 7 ----- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 282ec52c..9afc761b 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1987,31 +1987,36 @@ qreal Editor::getEventOpacity(const Event *event) const { } void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { - if (item && item->event && !item->event->getPixmap().isNull()) { - item->setOpacity(getEventOpacity(item->event)); - project->loadEventPixmap(item->event, true); - item->setPixmap(item->event->getPixmap()); - item->setShapeMode(porymapConfig.eventSelectionShapeMode); + if (!item || !item->event) + return; - if (this->editMode == EditMode::Events) { - if (this->selectedEvents.contains(item->event)) { - // Draw the selection rectangle - QImage image = item->pixmap().toImage(); - QPainter painter(&image); - painter.setPen(QColor(255, 0, 255)); - painter.drawRect(0, 0, image.width() - 1, image.height() - 1); - painter.end(); - item->setPixmap(QPixmap::fromImage(image)); - } - item->setAcceptedMouseButtons(Qt::AllButtons); - } else { - // Can't interact with event pixmaps outside of event editing mode. - // We could do setEnabled(false), but rather than ignoring the mouse events this - // would reject them, which would prevent painting on the map behind the events. - item->setAcceptedMouseButtons(Qt::NoButton); + project->loadEventPixmap(item->event, true); + + QPixmap pixmap = item->event->getPixmap(); + if (pixmap.isNull()) + return; + + qreal zValue = item->event->getY(); + if (this->editMode == EditMode::Events) { + if (this->selectedEvents.contains(item->event)) { + // Draw the selection rectangle + QPainter painter(&pixmap); + painter.setPen(Qt::magenta); + painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); + zValue++; } - item->updatePosition(); + item->setAcceptedMouseButtons(Qt::AllButtons); + } else { + // Can't interact with event pixmaps outside of event editing mode. + // We could do setEnabled(false), but rather than ignoring the mouse events this + // would reject them, which would prevent painting on the map behind the events. + item->setAcceptedMouseButtons(Qt::NoButton); } + item->setPixmap(pixmap); + item->setZValue(zValue); + item->setOpacity(getEventOpacity(item->event)); + item->setShapeMode(porymapConfig.eventSelectionShapeMode); + item->updatePosition(); } // Warp events display a warning if they're not positioned on a metatile with a warp behavior. diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 53cdd90a..a73a7ace 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -12,11 +12,6 @@ void DraggablePixmapItem::updatePosition() { int y = this->event->getPixelY(); setX(x); setY(y); - if (this->editor->selectedEvents.contains(this->event)) { - setZValue(event->getY() + 1); - } else { - setZValue(event->getY()); - } editor->updateWarpEventWarning(event); } @@ -26,8 +21,6 @@ void DraggablePixmapItem::emitPositionChanged() { } void DraggablePixmapItem::updatePixmap() { - editor->project->loadEventPixmap(event, true); - this->updatePosition(); editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); } From c53e9fcb284bafe9e77e33c5cadd1ce5243a839d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 21:11:58 -0400 Subject: [PATCH 291/364] Remove incorrect comment --- src/editor.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index a18e59da..93085d63 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1673,9 +1673,6 @@ void Editor::clearMapEvents() { if (events_group->scene()) { events_group->scene()->removeItem(events_group); } - // events_group does not own its children, the childrens' parent - // is set to the group's parent (and our group has no parent). - qDeleteAll(events_group->childItems()); delete events_group; events_group = nullptr; } From 714cce670fdbb2682e3f49f8402f10635ee02d8a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 8 Apr 2025 12:42:23 -0400 Subject: [PATCH 292/364] DraggaglePixmapItem -> EventPixmapItem --- include/core/editcommands.h | 2 +- include/core/events.h | 8 ++++---- include/editor.h | 6 +++--- ...draggablepixmapitem.h => eventpixmapitem.h} | 12 ++++++------ porymap.pro | 4 ++-- src/core/editcommands.cpp | 2 +- src/core/events.cpp | 2 +- src/editor.cpp | 14 +++++++------- src/mainwindow.cpp | 2 +- src/ui/eventframes.cpp | 10 +++++----- ...gablepixmapitem.cpp => eventpixmapitem.cpp} | 18 +++++++++--------- src/ui/mapimageexporter.cpp | 2 +- 12 files changed, 41 insertions(+), 41 deletions(-) rename include/ui/{draggablepixmapitem.h => eventpixmapitem.h} (77%) rename src/ui/{draggablepixmapitem.cpp => eventpixmapitem.cpp} (85%) diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 9a54063c..5dc4bf0c 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -14,7 +14,7 @@ class Map; class Layout; class Blockdata; class Event; -class DraggablePixmapItem; +class EventPixmapItem; class Editor; enum CommandId { diff --git a/include/core/events.h b/include/core/events.h index 2b2f5e40..3963e022 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -19,7 +19,7 @@ class EventFrame; class ObjectFrame; class CloneObjectFrame; class WarpFrame; -class DraggablePixmapItem; +class EventPixmapItem; class Event; class ObjectEvent; @@ -154,8 +154,8 @@ public: void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } QPixmap getPixmap() const { return this->pixmap; } - void setPixmapItem(DraggablePixmapItem *item); - DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; } + void setPixmapItem(EventPixmapItem *item); + EventPixmapItem *getPixmapItem() const { return this->pixmapItem; } void setUsesDefaultPixmap(bool newUsesDefaultPixmap) { this->usesDefaultPixmap = newUsesDefaultPixmap; } bool getUsesDefaultPixmap() const { return this->usesDefaultPixmap; } @@ -194,7 +194,7 @@ protected: QJsonObject customAttributes; QPixmap pixmap; - DraggablePixmapItem *pixmapItem = nullptr; + EventPixmapItem *pixmapItem = nullptr; QPointer eventFrame; diff --git a/include/editor.h b/include/editor.h index a4d3d6c3..60ead193 100644 --- a/include/editor.h +++ b/include/editor.h @@ -30,7 +30,7 @@ #include "mapruler.h" #include "encountertablemodel.h" -class DraggablePixmapItem; +class EventPixmapItem; class MetatilesPixmapItem; class Editor : public QObject @@ -107,7 +107,7 @@ public: void toggleBorderVisibility(bool visible, bool enableScriptCallback = true); void updateCustomMapAttributes(); - DraggablePixmapItem *addEventPixmapItem(Event *event); + EventPixmapItem *addEventPixmapItem(Event *event); void removeEventPixmapItem(Event *event); bool canAddEvents(const QList &events); void selectMapEvent(Event *event, bool toggle = false); @@ -116,7 +116,7 @@ public: void duplicateSelectedEvents(); void redrawAllEvents(); void redrawEvents(const QList &events); - void redrawEventPixmapItem(DraggablePixmapItem *item); + void redrawEventPixmapItem(EventPixmapItem *item); qreal getEventOpacity(const Event *event) const; void updateCursorRectPos(int x, int y); diff --git a/include/ui/draggablepixmapitem.h b/include/ui/eventpixmapitem.h similarity index 77% rename from include/ui/draggablepixmapitem.h rename to include/ui/eventpixmapitem.h index 5c617099..18813bc0 100644 --- a/include/ui/draggablepixmapitem.h +++ b/include/ui/eventpixmapitem.h @@ -1,5 +1,5 @@ -#ifndef DRAGGABLEPIXMAPITEM_H -#define DRAGGABLEPIXMAPITEM_H +#ifndef EVENTPIXMAPITEM_H +#define EVENTPIXMAPITEM_H #include #include @@ -12,12 +12,12 @@ class Editor; -class DraggablePixmapItem : public QObject, public QGraphicsPixmapItem { +class EventPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - DraggablePixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} + EventPixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} - DraggablePixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { + EventPixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { this->event = event; event->setPixmapItem(this); this->editor = editor; @@ -52,4 +52,4 @@ protected: virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(this->event); } }; -#endif // DRAGGABLEPIXMAPITEM_H +#endif // EVENTPIXMAPITEM_H diff --git a/porymap.pro b/porymap.pro index 35fd6af2..7374ac30 100644 --- a/porymap.pro +++ b/porymap.pro @@ -73,7 +73,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/customscriptseditor.cpp \ src/ui/customscriptslistitem.cpp \ src/ui/divingmappixmapitem.cpp \ - src/ui/draggablepixmapitem.cpp \ + src/ui/eventpixmapitem.cpp \ src/ui/bordermetatilespixmapitem.cpp \ src/ui/collisionpixmapitem.cpp \ src/ui/connectionpixmapitem.cpp \ @@ -184,7 +184,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/customscriptseditor.h \ include/ui/customscriptslistitem.h \ include/ui/divingmappixmapitem.h \ - include/ui/draggablepixmapitem.h \ + include/ui/eventpixmapitem.h \ include/ui/bordermetatilespixmapitem.h \ include/ui/collisionpixmapitem.h \ include/ui/connectionpixmapitem.h \ diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 8850448d..684d98c7 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -1,5 +1,5 @@ #include "editcommands.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "bordermetatilespixmapitem.h" #include "editor.h" diff --git a/src/core/events.cpp b/src/core/events.cpp index fbd4e568..694919c0 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -34,7 +34,7 @@ void Event::destroyEventFrame() { this->eventFrame = nullptr; } -void Event::setPixmapItem(DraggablePixmapItem *item) { +void Event::setPixmapItem(EventPixmapItem *item) { this->pixmapItem = item; if (this->eventFrame) { this->eventFrame->invalidateConnections(); diff --git a/src/editor.cpp b/src/editor.cpp index 93085d63..f618361c 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1,5 +1,5 @@ #include "editor.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "imageproviders.h" #include "log.h" #include "connectionslistitem.h" @@ -1692,10 +1692,10 @@ void Editor::displayMapEvents() { events_group->setHandlesChildEvents(false); } -DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { +EventPixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); - auto item = new DraggablePixmapItem(event, this); - connect(item, &DraggablePixmapItem::doubleClicked, this, &Editor::openEventMap); + auto item = new EventPixmapItem(event, this); + connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; @@ -1971,7 +1971,7 @@ qreal Editor::getEventOpacity(const Event *event) const { return event->getUsesDefaultPixmap() ? 0.7 : 1.0; } -void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { +void Editor::redrawEventPixmapItem(EventPixmapItem *item) { if (!item || !item->event) return; @@ -2287,8 +2287,8 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working } // It doesn't seem to be possible to prevent the mousePress event -// from triggering both event's DraggablePixmapItem and the background mousePress. -// Since the DraggablePixmapItem's event fires first, we can set a temp +// from triggering both event's EventPixmapItem and the background mousePress. +// Since the EventPixmapItem's event fires first, we can set a temp // variable "selectingEvent" so that we can detect whether or not the user // is clicking on the background instead of an event. void Editor::eventsView_onMousePress(QMouseEvent *event) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9bea0f4a..67f48be7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -10,7 +10,7 @@ #include "customattributesframe.h" #include "scripting.h" #include "adjustingstackedwidget.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "editcommands.h" #include "flowlayout.h" #include "shortcut.h" diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index d392017b..c8682aa7 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -1,7 +1,7 @@ #include "eventframes.h" #include "customattributesframe.h" #include "editcommands.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include using std::numeric_limits; @@ -114,7 +114,7 @@ void EventFrame::connectSignals(MainWindow *) { } }); - connect(this->event->getPixmapItem(), &DraggablePixmapItem::xChanged, this->spinner_x, &NoScrollSpinBox::setValue); + connect(this->event->getPixmapItem(), &EventPixmapItem::xChanged, this->spinner_x, &NoScrollSpinBox::setValue); this->spinner_y->disconnect(); connect(this->spinner_y, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -123,7 +123,7 @@ void EventFrame::connectSignals(MainWindow *) { this->event->getMap()->commit(new EventMove(QList() << this->event, 0, delta, this->spinner_y->getActionId())); } }); - connect(this->event->getPixmapItem(), &DraggablePixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue); + connect(this->event->getPixmapItem(), &EventPixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue); this->spinner_z->disconnect(); connect(this->spinner_z, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -297,7 +297,7 @@ void ObjectFrame::connectSignals(MainWindow *window) { this->object->getPixmapItem()->updatePixmap(); this->object->modify(); }); - connect(this->object->getPixmapItem(), &DraggablePixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->object->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); // movement this->combo_movement->disconnect(); @@ -439,7 +439,7 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { EventFrame::connectSignals(window); // update icon displayed in frame with target - connect(this->clone->getPixmapItem(), &DraggablePixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->clone->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); // target map this->combo_target_map->disconnect(); diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/eventpixmapitem.cpp similarity index 85% rename from src/ui/draggablepixmapitem.cpp rename to src/ui/eventpixmapitem.cpp index a73a7ace..cc0d76e1 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -1,4 +1,4 @@ -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "editor.h" #include "editcommands.h" #include "mapruler.h" @@ -7,7 +7,7 @@ static unsigned currentActionId = 0; -void DraggablePixmapItem::updatePosition() { +void EventPixmapItem::updatePosition() { int x = this->event->getPixelX(); int y = this->event->getPixelY(); setX(x); @@ -15,17 +15,17 @@ void DraggablePixmapItem::updatePosition() { editor->updateWarpEventWarning(event); } -void DraggablePixmapItem::emitPositionChanged() { +void EventPixmapItem::emitPositionChanged() { emit xChanged(event->getX()); emit yChanged(event->getY()); } -void DraggablePixmapItem::updatePixmap() { +void EventPixmapItem::updatePixmap() { editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); } -void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { if (this->active) return; this->active = true; @@ -49,21 +49,21 @@ void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { this->editor->selectingEvent = true; } -void DraggablePixmapItem::move(int dx, int dy) { +void EventPixmapItem::move(int dx, int dy) { event->setX(event->getX() + dx); event->setY(event->getY() + dy); updatePosition(); emitPositionChanged(); } -void DraggablePixmapItem::moveTo(const QPoint &pos) { +void EventPixmapItem::moveTo(const QPoint &pos) { event->setX(pos.x()); event->setY(pos.y()); updatePosition(); emitPositionChanged(); } -void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { if (!this->active) return; @@ -85,7 +85,7 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { this->releaseSelectionQueued = false; } -void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { if (!this->active) return; this->active = false; diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 39607bca..fe2865b0 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -96,7 +96,7 @@ void MapImageExporter::setModeSpecificUi() { } if (m_mode == ImageExporterMode::Timelapse) { - // TODO: At the moment edit history for events (and the DraggablePixmapItem class) + // TODO: At the moment edit history for events (and the EventPixmapItem class) // explicitly depend on the editor and assume their map is currently open. // Other edit commands rely on this more subtly, like triggering API callbacks or // spending time rendering their layout (which can make creating timelapses very slow). From 3d47d6b7e71c594a843c3406e2efff1674abb383 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 8 Apr 2025 13:12:10 -0400 Subject: [PATCH 293/364] Remove some Editor usage from EventPixmapItem --- include/editor.h | 7 ++-- include/ui/eventpixmapitem.h | 4 ++- include/ui/graphicsview.h | 21 ------------ include/ui/mapview.h | 11 +++++-- src/editor.cpp | 59 +++++++++++++++++----------------- src/ui/eventpixmapitem.cpp | 62 +++++++++++++++--------------------- src/ui/graphicsview.cpp | 17 +--------- src/ui/layoutpixmapitem.cpp | 15 +++++---- 8 files changed, 78 insertions(+), 118 deletions(-) diff --git a/include/editor.h b/include/editor.h index 60ead193..07a5dc68 100644 --- a/include/editor.h +++ b/include/editor.h @@ -122,6 +122,8 @@ public: void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); + void onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); + void onEventReleased(Event *event, const QPoint &position); void updateWarpEventWarning(Event *event); void updateWarpEventWarnings(); @@ -172,10 +174,7 @@ public: static QList> collisionIcons; int eventShiftActionId = 0; - - void eventsView_onMousePress(QMouseEvent *event); - - bool selectingEvent = false; + int eventMoveActionId = 0; void deleteSelectedEvents(); void shouldReselectEvents(); diff --git a/include/ui/eventpixmapitem.h b/include/ui/eventpixmapitem.h index 18813bc0..c44fc6bf 100644 --- a/include/ui/eventpixmapitem.h +++ b/include/ui/eventpixmapitem.h @@ -39,10 +39,12 @@ private: bool releaseSelectionQueued = false; signals: - void positionChanged(Event *event); void xChanged(int); void yChanged(int); void spriteChanged(const QPixmap &pixmap); + void selected(Event *event, bool toggle); + void dragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); + void released(Event *event, const QPoint &position); void doubleClicked(Event *event); protected: diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index 92771cf7..cac812b2 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -32,25 +32,4 @@ signals: void clicked(QMouseEvent *event); }; -class Editor; - -// TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView. -class GraphicsView : public QGraphicsView -{ -public: - GraphicsView() : QGraphicsView() {} - GraphicsView(QWidget *parent) : QGraphicsView(parent) {} - -public: -// GraphicsView_Object object; - Editor *editor; -protected: - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent *event) override; - virtual void moveEvent(QMoveEvent *event) override; -}; - -//Q_DECLARE_METATYPE(GraphicsView) - #endif // GRAPHICSVIEW_H diff --git a/include/ui/mapview.h b/include/ui/mapview.h index aa271757..d53e5cce 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -5,13 +5,17 @@ #include "graphicsview.h" #include "overlay.h" -class MapView : public GraphicsView +class Editor; + +class MapView : public QGraphicsView { Q_OBJECT public: - MapView() : GraphicsView() {} - MapView(QWidget *parent) : GraphicsView(parent) {} + MapView() : QGraphicsView() {} + MapView(QWidget *parent) : QGraphicsView(parent) {} + + Editor *editor; Overlay * getOverlay(int layer); void clearOverlayMap(); @@ -73,6 +77,7 @@ public: protected: virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void keyPressEvent(QKeyEvent*) override; + virtual void moveEvent(QMoveEvent *event) override; private: QMap overlayMap; diff --git a/src/editor.cpp b/src/editor.cpp index f618361c..cf9dd632 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1289,7 +1289,6 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { } void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { - // TODO: add event tab event painting tool buttons stuff here if (!item->getEditsEnabled()) { return; } @@ -1363,8 +1362,11 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i if (event && event->getPixmapItem()) event->getPixmapItem()->moveTo(pos); } - } else if (eventEditAction == EditAction::Select) { - // do nothing here, at least for now + } else if (eventEditAction == EditAction::Select && event->type() == QEvent::GraphicsSceneMousePress) { + if (!(event->modifiers() & Qt::ControlModifier) && this->selectedEvents.length() > 1) { + // User is clearing group selection by clicking on the background + selectMapEvent(this->selectedEvents.first()); + } } else if (eventEditAction == EditAction::Shift) { static QPoint selection_origin; @@ -1696,6 +1698,9 @@ EventPixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); auto item = new EventPixmapItem(event, this); connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap); + connect(item, &EventPixmapItem::dragged, this, &Editor::onEventDragged); + connect(item, &EventPixmapItem::released, this, &Editor::onEventReleased); + connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; @@ -2004,6 +2009,28 @@ void Editor::redrawEventPixmapItem(EventPixmapItem *item) { item->updatePosition(); } +void Editor::onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition) { + if (!this->map || !this->map_item) + return; + + this->map_item->hoveredMapMetatileChanged(newPosition); + + // Drag all the other selected events (if any) with it + QList draggedEvents; + if (this->selectedEvents.contains(event)) { + draggedEvents = this->selectedEvents; + } else { + draggedEvents.append(event); + } + + QPoint moveDistance = newPosition - oldPosition; + this->map->commit(new EventMove(draggedEvents, moveDistance.x(), moveDistance.y(), this->eventMoveActionId)); +} + +void Editor::onEventReleased(Event *, const QPoint &) { + this->eventMoveActionId++; +} + // Warp events display a warning if they're not positioned on a metatile with a warp behavior. void Editor::updateWarpEventWarning(Event *event) { if (porymapConfig.warpBehaviorWarningDisabled) @@ -2286,32 +2313,6 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working return process.startDetached(pid); } -// It doesn't seem to be possible to prevent the mousePress event -// from triggering both event's EventPixmapItem and the background mousePress. -// Since the EventPixmapItem's event fires first, we can set a temp -// variable "selectingEvent" so that we can detect whether or not the user -// is clicking on the background instead of an event. -void Editor::eventsView_onMousePress(QMouseEvent *event) { - // make sure we are in event editing mode - if (map_item && this->editMode != EditMode::Events) { - return; - } - if (this->eventEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { - this->eventEditAction = EditAction::Select; - this->settings->mapCursor = QCursor(); - this->cursorMapTileRect->setSingleTileMode(); - this->ui->toolButton_Paint->setChecked(false); - this->ui->toolButton_Select->setChecked(true); - } - - bool multiSelect = event->modifiers() & Qt::ControlModifier; - if (!selectingEvent && !multiSelect && this->selectedEvents.length() > 1) { - // User is clearing group selection by clicking on the background - this->selectMapEvent(this->selectedEvents.first()); - } - selectingEvent = false; -} - void Editor::setCollisionTabSpinBoxes(uint16_t collision, uint16_t elevation) { const QSignalBlocker blocker1(ui->spinBox_SelectedCollision); const QSignalBlocker blocker2(ui->spinBox_SelectedElevation); diff --git a/src/ui/eventpixmapitem.cpp b/src/ui/eventpixmapitem.cpp index cc0d76e1..1face67c 100644 --- a/src/ui/eventpixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -4,8 +4,19 @@ #include "mapruler.h" #include "metatile.h" -static unsigned currentActionId = 0; +void EventPixmapItem::move(int dx, int dy) { + event->setX(event->getX() + dx); + event->setY(event->getY() + dy); + updatePosition(); + emitPositionChanged(); +} +void EventPixmapItem::moveTo(const QPoint &pos) { + event->setX(pos.x()); + event->setY(pos.y()); + updatePosition(); + emitPositionChanged(); +} void EventPixmapItem::updatePosition() { int x = this->event->getPixelX(); @@ -25,17 +36,17 @@ void EventPixmapItem::updatePixmap() { emit spriteChanged(event->getPixmap()); } -void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (this->active) return; this->active = true; - this->lastPos = Metatile::coordFromPixmapCoord(mouse->scenePos()); + this->lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); - bool selectionToggle = mouse->modifiers() & Qt::ControlModifier; + bool selectionToggle = mouseEvent->modifiers() & Qt::ControlModifier; if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. - this->editor->selectMapEvent(this->event, selectionToggle); + emit selected(this->event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: // 1. This is the only selected event, and the selection is pointless. @@ -46,53 +57,30 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { // To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred. this->releaseSelectionQueued = true; } - this->editor->selectingEvent = true; + mouseEvent->accept(); } -void EventPixmapItem::move(int dx, int dy) { - event->setX(event->getX() + dx); - event->setY(event->getY() + dy); - updatePosition(); - emitPositionChanged(); -} - -void EventPixmapItem::moveTo(const QPoint &pos) { - event->setX(pos.x()); - event->setY(pos.y()); - updatePosition(); - emitPositionChanged(); -} - -void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (!this->active) return; - QPoint pos = Metatile::coordFromPixmapCoord(mouse->scenePos()); + QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); if (pos == this->lastPos) return; - QPoint moveDistance = pos - this->lastPos; - this->lastPos = pos; - emit this->editor->map_item->hoveredMapMetatileChanged(pos); - - QList selectedEvents; - if (this->editor->selectedEvents.contains(this->event)) { - selectedEvents = this->editor->selectedEvents; - } else { - selectedEvents.append(this->event); - } - editor->map->commit(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId)); this->releaseSelectionQueued = false; + emit dragged(this->event, this->lastPos, pos); + this->lastPos = pos; } -void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (!this->active) return; this->active = false; - currentActionId++; if (this->releaseSelectionQueued) { this->releaseSelectionQueued = false; - if (Metatile::coordFromPixmapCoord(mouse->scenePos()) == this->lastPos) - this->editor->selectMapEvent(this->event); + if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == this->lastPos) + emit selected(this->event, false); } + emit released(this->event, this->lastPos); } diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index 68479e98..6c4ccdb3 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -2,22 +2,7 @@ #include "mapview.h" #include "editor.h" -void GraphicsView::mousePressEvent(QMouseEvent *event) { - QGraphicsView::mousePressEvent(event); - if (editor) { - editor->eventsView_onMousePress(event); - } -} - -void GraphicsView::mouseMoveEvent(QMouseEvent *event) { - QGraphicsView::mouseMoveEvent(event); -} - -void GraphicsView::mouseReleaseEvent(QMouseEvent *event) { - QGraphicsView::mouseReleaseEvent(event); -} - -void GraphicsView::moveEvent(QMoveEvent *event) { +void MapView::moveEvent(QMoveEvent *event) { QGraphicsView::moveEvent(event); QLabel *label_MapRulerStatus = findChild("label_MapRulerStatus", Qt::FindDirectChildrenOnly); if (label_MapRulerStatus && label_MapRulerStatus->isVisible()) diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index f53cf275..3417a4ce 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -714,19 +714,20 @@ void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { } void LayoutPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - this->paint_tile_initial_x = this->straight_path_initial_x = pos.x(); - this->paint_tile_initial_y = this->straight_path_initial_y = pos.y(); + this->metatilePos = Metatile::coordFromPixmapCoord(event->pos()); + this->paint_tile_initial_x = this->straight_path_initial_x = this->metatilePos.x(); + this->paint_tile_initial_y = this->straight_path_initial_y = this->metatilePos.y(); emit startPaint(event, this); emit mouseEvent(event, this); } void LayoutPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (pos != this->metatilePos) { - this->metatilePos = pos; - emit this->hoveredMapMetatileChanged(pos); - } + if (pos == this->metatilePos) + return; + + this->metatilePos = pos; + emit hoveredMapMetatileChanged(pos); emit mouseEvent(event, this); } From 41d0b4261fd8d1fe878314cc3701a520c3896029 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 10 Apr 2025 12:33:56 -0400 Subject: [PATCH 294/364] Fix deprecated code as of Qt 6.9 --- include/core/utility.h | 7 ++++ include/mainwindow.h | 7 ++-- include/ui/mapheaderform.h | 10 ++--- include/ui/mapimageexporter.h | 37 ++++++++--------- include/ui/regionmapeditor.h | 5 ++- include/ui/shortcut.h | 3 -- include/ui/tileseteditor.h | 6 +-- src/core/utility.cpp | 7 ++++ src/editor.cpp | 4 ++ src/mainwindow.cpp | 30 ++++++++------ src/scriptapi/apioverlay.cpp | 5 +++ src/ui/customscriptseditor.cpp | 4 ++ src/ui/eventframes.cpp | 4 ++ src/ui/imageproviders.cpp | 7 +++- src/ui/mapheaderform.cpp | 18 ++++++--- src/ui/mapimageexporter.cpp | 68 ++++++++++++++++++++++++-------- src/ui/metatilelayersitem.cpp | 5 +++ src/ui/projectsettingseditor.cpp | 10 ++++- src/ui/regionmapeditor.cpp | 22 ++++++++--- src/ui/shortcut.cpp | 13 +----- src/ui/tilemaptileselector.cpp | 4 ++ src/ui/tileseteditor.cpp | 25 +++++++++--- src/ui/updatepromoter.cpp | 4 ++ 23 files changed, 210 insertions(+), 95 deletions(-) diff --git a/include/core/utility.h b/include/core/utility.h index 1b9277ab..d5b7900b 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -9,6 +9,13 @@ namespace Util { int roundUp(int numToRound, int multiple); QString toDefineCase(QString input); QString toHexString(uint32_t value, int minLength = 0); + Qt::Orientations getOrientation(bool xflip, bool yflip); } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + typedef Qt::CheckState CheckState; +#else + typedef int CheckState; +#endif + #endif // UTILITY_H diff --git a/include/mainwindow.h b/include/mainwindow.h index cdc641c2..9b1be78a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -248,8 +248,6 @@ private slots: void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); - void on_checkBox_smartPaths_stateChanged(int selected); - void on_checkBox_ToggleBorder_stateChanged(int selected); void resetMapViewScale(); @@ -260,7 +258,6 @@ private slots: void eventTabChanged(int index); - void on_checkBox_MirrorConnections_stateChanged(int selected); void on_actionDive_Emerge_Map_triggered(); void on_actionShow_Events_In_Map_View_triggered(); void on_groupBox_DiveMapOpacity_toggled(bool on); @@ -437,6 +434,10 @@ private: void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); + + void setSmartPathsEnabled(CheckState state); + void setBorderVisibility(CheckState state); + void setMirrorConnectionsEnabled(CheckState state); }; // These are namespaced in a struct to avoid colliding with e.g. class Map. diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 79f4f6c8..1b7bd66a 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -73,11 +73,11 @@ private: void onWeatherChanged(const QString &weather); void onTypeChanged(const QString &type); void onBattleSceneChanged(const QString &battleScene); - void onRequiresFlashChanged(int selected); - void onShowLocationNameChanged(int selected); - void onAllowRunningChanged(int selected); - void onAllowBikingChanged(int selected); - void onAllowEscapingChanged(int selected); + void onRequiresFlashChanged(CheckState selected); + void onShowLocationNameChanged(CheckState selected); + void onAllowRunningChanged(CheckState selected); + void onAllowBikingChanged(CheckState selected); + void onAllowEscapingChanged(CheckState selected); void onFloorNumberChanged(int offset); }; diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index ffc4d272..7b85beb9 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -91,30 +91,27 @@ protected: virtual void showEvent(QShowEvent *) override; virtual void resizeEvent(QResizeEvent *) override; -private slots: - void on_checkBox_Objects_stateChanged(int state); - void on_checkBox_Warps_stateChanged(int state); - void on_checkBox_BGs_stateChanged(int state); - void on_checkBox_Triggers_stateChanged(int state); - void on_checkBox_HealLocations_stateChanged(int state); - void on_checkBox_AllEvents_stateChanged(int state); - - void on_checkBox_ConnectionUp_stateChanged(int state); - void on_checkBox_ConnectionDown_stateChanged(int state); - void on_checkBox_ConnectionLeft_stateChanged(int state); - void on_checkBox_ConnectionRight_stateChanged(int state); - void on_checkBox_AllConnections_stateChanged(int state); - - void on_checkBox_Collision_stateChanged(int state); - void on_checkBox_Grid_stateChanged(int state); - void on_checkBox_Border_stateChanged(int state); +private: + void setShowGrid(CheckState state); + void setShowBorder(CheckState state); + void setShowObjects(CheckState state); + void setShowWarps(CheckState state); + void setShowBgs(CheckState state); + void setShowTriggers(CheckState state); + void setShowHealLocations(CheckState state); + void setShowAllEvents(CheckState state); + void setShowConnectionUp(CheckState state); + void setShowConnectionDown(CheckState state); + void setShowConnectionLeft(CheckState state); + void setShowConnectionRight(CheckState state); + void setShowAllConnections(CheckState state); + void setShowCollision(CheckState state); + void setDisablePreviewScaling(CheckState state); + void setDisablePreviewUpdates(CheckState state); void on_pushButton_Reset_pressed(); void on_spinBox_TimelapseDelay_editingFinished(); void on_spinBox_FrameSkip_editingFinished(); - - void on_checkBox_DisablePreviewScaling_stateChanged(int state); - void on_checkBox_DisablePreviewUpdates_stateChanged(int state); }; #endif // MAPIMAGEEXPORTER_H diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 9a838827..969b6e3a 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -121,6 +121,9 @@ private: void restoreWindowState(); void closeEvent(QCloseEvent* event); + void setTileHFlip(CheckState); + void setTileVFlip(CheckState); + private slots: void on_action_RegionMap_Save_triggered(); void on_actionSave_All_triggered(); @@ -145,8 +148,6 @@ private slots: void on_spinBox_RM_LayoutWidth_valueChanged(int); void on_spinBox_RM_LayoutHeight_valueChanged(int); void on_spinBox_tilePalette_valueChanged(int); - void on_checkBox_tileHFlip_stateChanged(int); - void on_checkBox_tileVFlip_stateChanged(int); void on_verticalSlider_Zoom_Map_Image_valueChanged(int); void on_verticalSlider_Zoom_Image_Tiles_valueChanged(int); void onHoveredRegionMapTileChanged(int x, int y); diff --git a/include/ui/shortcut.h b/include/ui/shortcut.h index 8989401d..5fdbfaee 100644 --- a/include/ui/shortcut.h +++ b/include/ui/shortcut.h @@ -49,9 +49,6 @@ public: void setAutoRepeat(bool on); bool autoRepeat() const; - int id() const; - QList ids() const; - inline QWidget *parentWidget() const { return static_cast(QObject::parent()); } diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index d659390a..ff2999ff 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -71,10 +71,6 @@ private slots: void on_spinBox_paletteSelector_valueChanged(int arg1); - void on_checkBox_xFlip_stateChanged(int arg1); - - void on_checkBox_yFlip_stateChanged(int arg1); - void on_actionSave_Tileset_triggered(); void on_actionImport_Primary_Tiles_triggered(); @@ -149,6 +145,8 @@ private: void commitTerrainType(); void commitLayerType(); void setRawAttributesVisible(bool visible); + void setXFlip(CheckState state); + void setYFlip(CheckState state); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 1053f879..55830b8a 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -42,3 +42,10 @@ QString Util::toDefineCase(QString input) { QString Util::toHexString(uint32_t value, int minLength) { return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper(); } + +Qt::Orientations Util::getOrientation(bool xflip, bool yflip) { + Qt::Orientations flags; + if (xflip) flags |= Qt::Orientation::Horizontal; + if (yflip) flags |= Qt::Orientation::Vertical; + return flags; +} diff --git a/src/editor.cpp b/src/editor.cpp index 29b6cb5d..a50e5622 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -296,7 +296,11 @@ void Editor::addNewWildMonGroup(QWidget *window) { form.addRow(new QLabel(monField.name), fieldCheckbox); } // Reading from ui here so not saving to disk before user. +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(copyCheckbox, &QCheckBox::checkStateChanged, [=](Qt::CheckState state){ +#else connect(copyCheckbox, &QCheckBox::stateChanged, [=](int state){ +#endif if (state == Qt::Checked) { int fieldIndex = 0; MonTabWidget *monWidget = static_cast(stack->widget(stack->currentIndex())); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 650acdaf..42c070f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -299,6 +299,16 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); connect(ui->comboBox_LayoutSelector->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_smartPaths, &QCheckBox::checkStateChanged, this, &MainWindow::setSmartPathsEnabled); + connect(ui->checkBox_ToggleBorder, &QCheckBox::checkStateChanged, this, &MainWindow::setBorderVisibility); + connect(ui->checkBox_MirrorConnections, &QCheckBox::checkStateChanged, this, &MainWindow::setMirrorConnectionsEnabled); +#else + connect(ui->checkBox_smartPaths, &QCheckBox::stateChanged, this, &MainWindow::setSmartPathsEnabled); + connect(ui->checkBox_ToggleBorder, &QCheckBox::stateChanged, this, &MainWindow::setBorderVisibility); + connect(ui->checkBox_MirrorConnections, &QCheckBox::stateChanged, this, &MainWindow::setMirrorConnectionsEnabled); +#endif } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -2711,25 +2721,21 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { } } -void MainWindow::on_checkBox_smartPaths_stateChanged(int selected) +void MainWindow::setSmartPathsEnabled(CheckState state) { - bool enabled = selected == Qt::Checked; - editor->settings->smartPathsEnabled = enabled; - if (enabled) { - editor->cursorMapTileRect->setSmartPathMode(true); - } else { - editor->cursorMapTileRect->setSmartPathMode(false); - } + bool enabled = (state == Qt::Checked); + this->editor->settings->smartPathsEnabled = enabled; + this->editor->cursorMapTileRect->setSmartPathMode(enabled); } -void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected) +void MainWindow::setBorderVisibility(CheckState state) { - editor->toggleBorderVisibility(selected != 0); + editor->toggleBorderVisibility(state == Qt::Checked); } -void MainWindow::on_checkBox_MirrorConnections_stateChanged(int selected) +void MainWindow::setMirrorConnectionsEnabled(CheckState state) { - porymapConfig.mirrorConnectingMaps = (selected == Qt::Checked); + porymapConfig.mirrorConnectingMaps = (state == Qt::Checked); } void MainWindow::on_actionTileset_Editor_triggered() diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index 4b1f75b8..00a34495 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -276,7 +276,12 @@ void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary, paletteId) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + .flipped(Util::getOrientation(xflip, yflip)); +#else .mirrored(xflip, yflip); +#endif + if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index eddbc7ae..9c166d43 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -105,7 +105,11 @@ void CustomScriptsEditor::displayScript(const QString &filepath, bool enabled) { connect(widget->ui->b_Choose, &QAbstractButton::clicked, [this, item](bool) { this->replaceScript(item); }); connect(widget->ui->b_Edit, &QAbstractButton::clicked, [this, item](bool) { this->openScript(item); }); connect(widget->ui->b_Delete, &QAbstractButton::clicked, [this, item](bool) { this->removeScript(item); }); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(widget->ui->checkBox_Enable, &QCheckBox::checkStateChanged, this, &CustomScriptsEditor::markEdited); +#else connect(widget->ui->checkBox_Enable, &QCheckBox::stateChanged, this, &CustomScriptsEditor::markEdited); +#endif connect(widget->ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsEditor::markEdited); // Per the Qt manual, for performance reasons QListWidget::setItemWidget shouldn't be used with non-static items. diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 065dbf19..e9c108d3 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -853,7 +853,11 @@ void HiddenItemFrame::connectSignals(MainWindow *window) { // underfoot this->check_itemfinder->disconnect(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(this->check_itemfinder, &QCheckBox::checkStateChanged, [=](Qt::CheckState state) { +#else connect(this->check_itemfinder, &QCheckBox::stateChanged, [=](int state) { +#endif this->hiddenItem->setUnderfoot(state == Qt::Checked); this->hiddenItem->modify(); }); diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 33ec3596..b1510195 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -127,7 +127,12 @@ QImage getMetatileImage( color.setAlpha(0); tile_image.setColor(0, color.rgba()); - metatile_painter.drawImage(origin, tile_image.mirrored(tile.xflip, tile.yflip)); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + tile_image.flip(Util::getOrientation(tile.xflip, tile.yflip)); +#else + tile_image = tile_image.mirrored(tile.xflip, tile.yflip); +#endif + metatile_painter.drawImage(origin, tile_image); } metatile_painter.end(); diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 09cb22c7..92fa8c7f 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -21,11 +21,19 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged); connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_RequiresFlash, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onRequiresFlashChanged); + connect(ui->checkBox_ShowLocationName, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onShowLocationNameChanged); + connect(ui->checkBox_AllowRunning, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowRunningChanged); + connect(ui->checkBox_AllowBiking, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowBikingChanged); + connect(ui->checkBox_AllowEscaping, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowEscapingChanged); +#else connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged); connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged); connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); +#endif connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); @@ -207,11 +215,11 @@ void MapHeaderForm::onSongUpdated(const QString &song) { if (m_hea void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); } void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); } void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); } -void MapHeaderForm::onRequiresFlashChanged(int selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } -void MapHeaderForm::onShowLocationNameChanged(int selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } -void MapHeaderForm::onAllowRunningChanged(int selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } -void MapHeaderForm::onAllowBikingChanged(int selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } -void MapHeaderForm::onAllowEscapingChanged(int selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } +void MapHeaderForm::onRequiresFlashChanged(CheckState selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } +void MapHeaderForm::onShowLocationNameChanged(CheckState selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } +void MapHeaderForm::onAllowRunningChanged(CheckState selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } +void MapHeaderForm::onAllowBikingChanged(CheckState selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } +void MapHeaderForm::onAllowEscapingChanged(CheckState selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 39607bca..cf1339c5 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -60,6 +60,42 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_Objects, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowObjects); + connect(ui->checkBox_Warps, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowWarps); + connect(ui->checkBox_BGs, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBgs); + connect(ui->checkBox_Triggers, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowTriggers); + connect(ui->checkBox_HealLocations, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowHealLocations); + connect(ui->checkBox_AllEvents, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllEvents); + connect(ui->checkBox_ConnectionUp, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionUp); + connect(ui->checkBox_ConnectionDown, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionDown); + connect(ui->checkBox_ConnectionLeft, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionLeft); + connect(ui->checkBox_ConnectionRight, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionRight); + connect(ui->checkBox_AllConnections, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllConnections); + connect(ui->checkBox_Collision, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowCollision); + connect(ui->checkBox_Grid, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowGrid); + connect(ui->checkBox_Border, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBorder); + connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewScaling); + connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewUpdates); +#else + connect(ui->checkBox_Objects, &QCheckBox::stateChanged, this, &MapImageExporter::setShowObjects); + connect(ui->checkBox_Warps, &QCheckBox::stateChanged, this, &MapImageExporter::setShowWarps); + connect(ui->checkBox_BGs, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBgs); + connect(ui->checkBox_Triggers, &QCheckBox::stateChanged, this, &MapImageExporter::setShowTriggers); + connect(ui->checkBox_HealLocations, &QCheckBox::stateChanged, this, &MapImageExporter::setShowHealLocations); + connect(ui->checkBox_AllEvents, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllEvents); + connect(ui->checkBox_ConnectionUp, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionUp); + connect(ui->checkBox_ConnectionDown, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionDown); + connect(ui->checkBox_ConnectionLeft, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionLeft); + connect(ui->checkBox_ConnectionRight, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionRight); + connect(ui->checkBox_AllConnections, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllConnections); + connect(ui->checkBox_Collision, &QCheckBox::stateChanged, this, &MapImageExporter::setShowCollision); + connect(ui->checkBox_Grid, &QCheckBox::stateChanged, this, &MapImageExporter::setShowGrid); + connect(ui->checkBox_Border, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBorder); + connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewScaling); + connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewUpdates); +#endif + ui->graphicsView_Preview->setFocus(); } @@ -719,48 +755,48 @@ void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool en } } -void MapImageExporter::on_checkBox_Collision_stateChanged(int state) { +void MapImageExporter::setShowCollision(CheckState state) { m_settings.showCollision = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Grid_stateChanged(int state) { +void MapImageExporter::setShowGrid(CheckState state) { m_settings.showGrid = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Border_stateChanged(int state) { +void MapImageExporter::setShowBorder(CheckState state) { m_settings.showBorder = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Objects_stateChanged(int state) { +void MapImageExporter::setShowObjects(CheckState state) { setEventGroupEnabled(Event::Group::Object, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Warps_stateChanged(int state) { +void MapImageExporter::setShowWarps(CheckState state) { setEventGroupEnabled(Event::Group::Warp, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_BGs_stateChanged(int state) { +void MapImageExporter::setShowBgs(CheckState state) { setEventGroupEnabled(Event::Group::Bg, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Triggers_stateChanged(int state) { +void MapImageExporter::setShowTriggers(CheckState state) { setEventGroupEnabled(Event::Group::Coord, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_HealLocations_stateChanged(int state) { +void MapImageExporter::setShowHealLocations(CheckState state) { setEventGroupEnabled(Event::Group::Heal, state == Qt::Checked); updatePreview(); } // Shortcut setting for enabling all events -void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { +void MapImageExporter::setShowAllEvents(CheckState state) { bool on = (state == Qt::Checked); const QSignalBlocker b_Objects(ui->checkBox_Objects); @@ -791,28 +827,28 @@ void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) { +void MapImageExporter::setShowConnectionUp(CheckState state) { setConnectionDirectionEnabled("up", state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) { +void MapImageExporter::setShowConnectionDown(CheckState state) { setConnectionDirectionEnabled("down", state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) { +void MapImageExporter::setShowConnectionLeft(CheckState state) { setConnectionDirectionEnabled("left", state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) { +void MapImageExporter::setShowConnectionRight(CheckState state) { setConnectionDirectionEnabled("right", state == Qt::Checked); updatePreview(); } // Shortcut setting for enabling all connection directions -void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { +void MapImageExporter::setShowAllConnections(CheckState state) { bool on = (state == Qt::Checked); const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); @@ -838,7 +874,7 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { updatePreview(); } -void MapImageExporter::on_checkBox_DisablePreviewScaling_stateChanged(int state) { +void MapImageExporter::setDisablePreviewScaling(CheckState state) { m_settings.disablePreviewScaling = (state == Qt::Checked); if (m_settings.disablePreviewScaling) { ui->graphicsView_Preview->resetTransform(); @@ -847,7 +883,7 @@ void MapImageExporter::on_checkBox_DisablePreviewScaling_stateChanged(int state) } } -void MapImageExporter::on_checkBox_DisablePreviewUpdates_stateChanged(int state) { +void MapImageExporter::setDisablePreviewUpdates(CheckState state) { m_settings.disablePreviewUpdates = (state == Qt::Checked); if (m_settings.disablePreviewUpdates) { if (m_timelapseMovie) { diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index 3050e761..87218f52 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -1,6 +1,7 @@ #include "config.h" #include "metatilelayersitem.h" #include "imageproviders.h" +#include "utility.h" #include static const QList tilePositions = { @@ -28,7 +29,11 @@ void MetatileLayersItem::draw() { for (int i = 0; i < numTiles; i++) { Tile tile = this->metatile->tiles.at(i); QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + .flipped(Util::getOrientation(tile.xflip, tile.yflip)) +#else .mirrored(tile.xflip, tile.yflip) +#endif .scaled(16, 16); painter.drawImage(tilePositions.at(i) * 16, tileImage); } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 1c869dce..b1c61151 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -45,7 +45,11 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->comboBox_BaseGameVersion, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::promptRestoreDefaults); connect(ui->comboBox_AttributesSize, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updateAttributeLimits); connect(ui->comboBox_IconSpecies, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updatePokemonIconPath); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { +#else connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::stateChanged, [this](int state) { +#endif bool customSize = (state == Qt::Checked); // When switching between the spin boxes or line edit for border metatiles we set // the newly-shown UI using the values from the hidden UI. @@ -82,8 +86,12 @@ void ProjectSettingsEditor::connectSignals() { connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited); } for (auto checkBox : ui->centralwidget->findChildren()) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(checkBox, &QCheckBox::checkStateChanged, this, &ProjectSettingsEditor::markEdited); +#else connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited); - for (auto radioButton : ui->centralwidget->findChildren()) +#endif + for (auto radioButton : ui->centralwidget->findChildren()) connect(radioButton, &QRadioButton::toggled, this, &ProjectSettingsEditor::markEdited); for (auto lineEdit : ui->centralwidget->findChildren()) connect(lineEdit, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited); diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 2febccd8..9495395f 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -27,6 +27,15 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->ui->setupUi(this); this->project = project; connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_tileHFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileHFlip); + connect(ui->checkBox_tileVFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileVFlip); +#else + connect(ui->checkBox_tileHFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileHFlip); + connect(ui->checkBox_tileVFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileVFlip); +#endif + + this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); this->initShortcuts(); this->restoreWindowState(); @@ -1030,15 +1039,18 @@ void RegionMapEditor::on_pushButton_RM_Options_delete_clicked() { } void RegionMapEditor::on_spinBox_tilePalette_valueChanged(int value) { - this->mapsquare_selector_item->selectPalette(value); + if (this->mapsquare_selector_item) + this->mapsquare_selector_item->selectPalette(value); } -void RegionMapEditor::on_checkBox_tileHFlip_stateChanged(int state) { - this->mapsquare_selector_item->selectHFlip(state == Qt::Checked); +void RegionMapEditor::setTileHFlip(CheckState state) { + if (this->mapsquare_selector_item) + this->mapsquare_selector_item->selectHFlip(state == Qt::Checked); } -void RegionMapEditor::on_checkBox_tileVFlip_stateChanged(int state) { - this->mapsquare_selector_item->selectVFlip(state == Qt::Checked); +void RegionMapEditor::setTileVFlip(CheckState state) { + if (this->mapsquare_selector_item) + this->mapsquare_selector_item->selectVFlip(state == Qt::Checked); } void RegionMapEditor::on_action_RegionMap_Resize_triggered() { diff --git a/src/ui/shortcut.cpp b/src/ui/shortcut.cpp index e94b4095..6b72da09 100644 --- a/src/ui/shortcut.cpp +++ b/src/ui/shortcut.cpp @@ -123,21 +123,10 @@ bool Shortcut::autoRepeat() const { return sc_vec.first()->autoRepeat(); } -int Shortcut::id() const { - return sc_vec.first()->id(); -} - -QList Shortcut::ids() const { - QList id_list; - for (auto *sc : sc_vec) - id_list.append(sc->id()); - return id_list; -} - bool Shortcut::event(QEvent *e) { if (isEnabled() && e->type() == QEvent::Shortcut) { auto se = static_cast(e); - if (ids().contains(se->shortcutId()) && keys().contains(se->key())) { + if (keys().contains(se->key())) { if (QWhatsThis::inWhatsThisMode()) { QWhatsThis::showText(QCursor::pos(), whatsThis()); } else { diff --git a/src/ui/tilemaptileselector.cpp b/src/ui/tilemaptileselector.cpp index a4ef5719..9093b694 100644 --- a/src/ui/tilemaptileselector.cpp +++ b/src/ui/tilemaptileselector.cpp @@ -93,7 +93,11 @@ QImage TilemapTileSelector::tileImg(shared_ptr tile) { // take a tile from the tileset QImage img = tilesetImage.copy(pos.x() * 8, pos.y() * 8, 8, 8); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + img.flip(Util::getOrientation(tile->hFlip(), tile->vFlip())); +#else img = img.mirrored(tile->hFlip(), tile->vFlip()); +#endif return img; } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 351771df..9281bd28 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -27,6 +27,14 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); ui->setupUi(this); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_xFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setXFlip); + connect(ui->checkBox_yFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setYFlip); +#else + connect(ui->checkBox_xFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setXFlip); + connect(ui->checkBox_yFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setYFlip); +#endif + this->tileXFlip = ui->checkBox_xFlip->isChecked(); this->tileYFlip = ui->checkBox_yFlip->isChecked(); this->paletteId = ui->spinBox_paletteSelector->value(); @@ -388,8 +396,13 @@ void TilesetEditor::drawSelectedTiles() { int tileIndex = 0; for (int j = 0; j < dimensions.y(); j++) { for (int i = 0; i < dimensions.x(); i++) { - QImage tileImage = getPalettedTileImage(tiles.at(tileIndex).tileId, this->primaryTileset, this->secondaryTileset, tiles.at(tileIndex).palette, true) - .mirrored(tiles.at(tileIndex).xflip, tiles.at(tileIndex).yflip) + auto tile = tiles.at(tileIndex); + QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + .flipped(Util::getOrientation(tile.xflip, tile.yflip)) +#else + .mirrored(tile.xflip, tile.yflip) +#endif .scaled(16, 16); tileIndex++; painter.drawImage(i * 16, j * 16, tileImage); @@ -540,17 +553,17 @@ void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId) this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::on_checkBox_xFlip_stateChanged(int checked) +void TilesetEditor::setXFlip(CheckState state) { - this->tileXFlip = checked; + this->tileXFlip = (state == Qt::Checked); this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::on_checkBox_yFlip_stateChanged(int checked) +void TilesetEditor::setYFlip(CheckState state) { - this->tileYFlip = checked; + this->tileYFlip = (state == Qt::Checked); this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); diff --git a/src/ui/updatepromoter.cpp b/src/ui/updatepromoter.cpp index f9331a16..27c37b0a 100644 --- a/src/ui/updatepromoter.cpp +++ b/src/ui/updatepromoter.cpp @@ -19,7 +19,11 @@ UpdatePromoter::UpdatePromoter(QWidget *parent, NetworkAccessManager *manager) // Set up "Do not alert me" check box this->updatePreferences(); ui->checkBox_StopAlerts->setVisible(false); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_StopAlerts, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { +#else connect(ui->checkBox_StopAlerts, &QCheckBox::stateChanged, [this](int state) { +#endif porymapConfig.checkForUpdates = (state != Qt::Checked); emit this->changedPreferences(); }); From c6e94eb6ab58207d730b990b088227cacbfeac5a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 10 Apr 2025 14:34:04 -0400 Subject: [PATCH 295/364] Replace stateChanged/checkStateChanged with toggled --- include/core/utility.h | 6 -- include/mainwindow.h | 6 +- include/ui/mapheaderform.h | 10 +- include/ui/mapimageexporter.h | 32 +++--- include/ui/regionmapeditor.h | 4 +- include/ui/tileseteditor.h | 4 +- src/editor.cpp | 10 +- src/mainwindow.cpp | 24 ++--- src/ui/customscriptseditor.cpp | 6 +- src/ui/eventframes.cpp | 8 +- src/ui/mapheaderform.cpp | 29 ++---- src/ui/mapimageexporter.cpp | 169 +++++++++++++------------------ src/ui/projectsettingseditor.cpp | 17 +--- src/ui/regionmapeditor.cpp | 17 ++-- src/ui/tileseteditor.cpp | 18 ++-- src/ui/updatepromoter.cpp | 8 +- 16 files changed, 143 insertions(+), 225 deletions(-) diff --git a/include/core/utility.h b/include/core/utility.h index d5b7900b..6613ee71 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -12,10 +12,4 @@ namespace Util { Qt::Orientations getOrientation(bool xflip, bool yflip); } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - typedef Qt::CheckState CheckState; -#else - typedef int CheckState; -#endif - #endif // UTILITY_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 9b1be78a..410bd6e3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -435,9 +435,9 @@ private: void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); - void setSmartPathsEnabled(CheckState state); - void setBorderVisibility(CheckState state); - void setMirrorConnectionsEnabled(CheckState state); + void setSmartPathsEnabled(bool enabled); + void setBorderVisibility(bool visible); + void setMirrorConnectionsEnabled(bool enabled); }; // These are namespaced in a struct to avoid colliding with e.g. class Map. diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 1b7bd66a..7f246d6d 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -73,11 +73,11 @@ private: void onWeatherChanged(const QString &weather); void onTypeChanged(const QString &type); void onBattleSceneChanged(const QString &battleScene); - void onRequiresFlashChanged(CheckState selected); - void onShowLocationNameChanged(CheckState selected); - void onAllowRunningChanged(CheckState selected); - void onAllowBikingChanged(CheckState selected); - void onAllowEscapingChanged(CheckState selected); + void onRequiresFlashChanged(bool enabled); + void onShowLocationNameChanged(bool enabled); + void onAllowRunningChanged(bool enabled); + void onAllowBikingChanged(bool enabled); + void onAllowEscapingChanged(bool enabled); void onFloorNumberChanged(int offset); }; diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 7b85beb9..51c1afe3 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -92,22 +92,22 @@ protected: virtual void resizeEvent(QResizeEvent *) override; private: - void setShowGrid(CheckState state); - void setShowBorder(CheckState state); - void setShowObjects(CheckState state); - void setShowWarps(CheckState state); - void setShowBgs(CheckState state); - void setShowTriggers(CheckState state); - void setShowHealLocations(CheckState state); - void setShowAllEvents(CheckState state); - void setShowConnectionUp(CheckState state); - void setShowConnectionDown(CheckState state); - void setShowConnectionLeft(CheckState state); - void setShowConnectionRight(CheckState state); - void setShowAllConnections(CheckState state); - void setShowCollision(CheckState state); - void setDisablePreviewScaling(CheckState state); - void setDisablePreviewUpdates(CheckState state); + void setShowGrid(bool checked); + void setShowBorder(bool checked); + void setShowObjects(bool checked); + void setShowWarps(bool checked); + void setShowBgs(bool checked); + void setShowTriggers(bool checked); + void setShowHealLocations(bool checked); + void setShowAllEvents(bool checked); + void setShowConnectionUp(bool checked); + void setShowConnectionDown(bool checked); + void setShowConnectionLeft(bool checked); + void setShowConnectionRight(bool checked); + void setShowAllConnections(bool checked); + void setShowCollision(bool checked); + void setDisablePreviewScaling(bool checked); + void setDisablePreviewUpdates(bool checked); void on_pushButton_Reset_pressed(); void on_spinBox_TimelapseDelay_editingFinished(); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 969b6e3a..97947872 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -121,8 +121,8 @@ private: void restoreWindowState(); void closeEvent(QCloseEvent* event); - void setTileHFlip(CheckState); - void setTileVFlip(CheckState); + void setTileHFlip(bool enabled); + void setTileVFlip(bool enabled); private slots: void on_action_RegionMap_Save_triggered(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index ff2999ff..fdd4751c 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -145,8 +145,8 @@ private: void commitTerrainType(); void commitLayerType(); void setRawAttributesVisible(bool visible); - void setXFlip(CheckState state); - void setYFlip(CheckState state); + void setXFlip(bool enabled); + void setYFlip(bool enabled); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/src/editor.cpp b/src/editor.cpp index a50e5622..a9067a79 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -296,12 +296,8 @@ void Editor::addNewWildMonGroup(QWidget *window) { form.addRow(new QLabel(monField.name), fieldCheckbox); } // Reading from ui here so not saving to disk before user. -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(copyCheckbox, &QCheckBox::checkStateChanged, [=](Qt::CheckState state){ -#else - connect(copyCheckbox, &QCheckBox::stateChanged, [=](int state){ -#endif - if (state == Qt::Checked) { + connect(copyCheckbox, &QCheckBox::toggled, [=](bool checked){ + if (checked) { int fieldIndex = 0; MonTabWidget *monWidget = static_cast(stack->widget(stack->currentIndex())); for (EncounterField monField : project->wildMonFields) { @@ -309,7 +305,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { fieldCheckboxes[fieldIndex]->setEnabled(false); fieldIndex++; } - } else if (state == Qt::Unchecked) { + } else { int fieldIndex = 0; for (EncounterField monField : project->wildMonFields) { fieldCheckboxes[fieldIndex]->setEnabled(true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42c070f8..a2cb1f67 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -299,16 +299,9 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); connect(ui->comboBox_LayoutSelector->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_smartPaths, &QCheckBox::checkStateChanged, this, &MainWindow::setSmartPathsEnabled); - connect(ui->checkBox_ToggleBorder, &QCheckBox::checkStateChanged, this, &MainWindow::setBorderVisibility); - connect(ui->checkBox_MirrorConnections, &QCheckBox::checkStateChanged, this, &MainWindow::setMirrorConnectionsEnabled); -#else - connect(ui->checkBox_smartPaths, &QCheckBox::stateChanged, this, &MainWindow::setSmartPathsEnabled); - connect(ui->checkBox_ToggleBorder, &QCheckBox::stateChanged, this, &MainWindow::setBorderVisibility); - connect(ui->checkBox_MirrorConnections, &QCheckBox::stateChanged, this, &MainWindow::setMirrorConnectionsEnabled); -#endif + connect(ui->checkBox_smartPaths, &QCheckBox::toggled, this, &MainWindow::setSmartPathsEnabled); + connect(ui->checkBox_ToggleBorder, &QCheckBox::toggled, this, &MainWindow::setBorderVisibility); + connect(ui->checkBox_MirrorConnections, &QCheckBox::toggled, this, &MainWindow::setMirrorConnectionsEnabled); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -2721,21 +2714,20 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { } } -void MainWindow::setSmartPathsEnabled(CheckState state) +void MainWindow::setSmartPathsEnabled(bool enabled) { - bool enabled = (state == Qt::Checked); this->editor->settings->smartPathsEnabled = enabled; this->editor->cursorMapTileRect->setSmartPathMode(enabled); } -void MainWindow::setBorderVisibility(CheckState state) +void MainWindow::setBorderVisibility(bool visible) { - editor->toggleBorderVisibility(state == Qt::Checked); + editor->toggleBorderVisibility(visible); } -void MainWindow::setMirrorConnectionsEnabled(CheckState state) +void MainWindow::setMirrorConnectionsEnabled(bool enabled) { - porymapConfig.mirrorConnectingMaps = (state == Qt::Checked); + porymapConfig.mirrorConnectingMaps = enabled; } void MainWindow::on_actionTileset_Editor_triggered() diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index 9c166d43..7cc89a14 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -105,11 +105,7 @@ void CustomScriptsEditor::displayScript(const QString &filepath, bool enabled) { connect(widget->ui->b_Choose, &QAbstractButton::clicked, [this, item](bool) { this->replaceScript(item); }); connect(widget->ui->b_Edit, &QAbstractButton::clicked, [this, item](bool) { this->openScript(item); }); connect(widget->ui->b_Delete, &QAbstractButton::clicked, [this, item](bool) { this->removeScript(item); }); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(widget->ui->checkBox_Enable, &QCheckBox::checkStateChanged, this, &CustomScriptsEditor::markEdited); -#else - connect(widget->ui->checkBox_Enable, &QCheckBox::stateChanged, this, &CustomScriptsEditor::markEdited); -#endif + connect(widget->ui->checkBox_Enable, &QCheckBox::toggled, this, &CustomScriptsEditor::markEdited); connect(widget->ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsEditor::markEdited); // Per the Qt manual, for performance reasons QListWidget::setItemWidget shouldn't be used with non-static items. diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index e9c108d3..bce55c66 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -853,12 +853,8 @@ void HiddenItemFrame::connectSignals(MainWindow *window) { // underfoot this->check_itemfinder->disconnect(); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(this->check_itemfinder, &QCheckBox::checkStateChanged, [=](Qt::CheckState state) { -#else - connect(this->check_itemfinder, &QCheckBox::stateChanged, [=](int state) { -#endif - this->hiddenItem->setUnderfoot(state == Qt::Checked); + connect(this->check_itemfinder, &QCheckBox::toggled, [=](bool checked) { + this->hiddenItem->setUnderfoot(checked); this->hiddenItem->modify(); }); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 92fa8c7f..65fe6ead 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -20,20 +20,11 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) connect(ui->comboBox_Weather, &QComboBox::currentTextChanged, this, &MapHeaderForm::onWeatherChanged); connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged); connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_RequiresFlash, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onRequiresFlashChanged); - connect(ui->checkBox_ShowLocationName, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onShowLocationNameChanged); - connect(ui->checkBox_AllowRunning, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowRunningChanged); - connect(ui->checkBox_AllowBiking, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowBikingChanged); - connect(ui->checkBox_AllowEscaping, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowEscapingChanged); -#else - connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged); - connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged); - connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); - connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); - connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); -#endif + connect(ui->checkBox_RequiresFlash, &QCheckBox::toggled, this, &MapHeaderForm::onRequiresFlashChanged); + connect(ui->checkBox_ShowLocationName, &QCheckBox::toggled, this, &MapHeaderForm::onShowLocationNameChanged); + connect(ui->checkBox_AllowRunning, &QCheckBox::toggled, this, &MapHeaderForm::onAllowRunningChanged); + connect(ui->checkBox_AllowBiking, &QCheckBox::toggled, this, &MapHeaderForm::onAllowBikingChanged); + connect(ui->checkBox_AllowEscaping, &QCheckBox::toggled, this, &MapHeaderForm::onAllowEscapingChanged); connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); @@ -215,11 +206,11 @@ void MapHeaderForm::onSongUpdated(const QString &song) { if (m_hea void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); } void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); } void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); } -void MapHeaderForm::onRequiresFlashChanged(CheckState selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } -void MapHeaderForm::onShowLocationNameChanged(CheckState selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } -void MapHeaderForm::onAllowRunningChanged(CheckState selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } -void MapHeaderForm::onAllowBikingChanged(CheckState selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } -void MapHeaderForm::onAllowEscapingChanged(CheckState selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } +void MapHeaderForm::onRequiresFlashChanged(bool enabled) { if (m_header) m_header->setRequiresFlash(enabled); } +void MapHeaderForm::onShowLocationNameChanged(bool enabled) { if (m_header) m_header->setShowsLocationName(enabled); } +void MapHeaderForm::onAllowRunningChanged(bool enabled) { if (m_header) m_header->setAllowsRunning(enabled); } +void MapHeaderForm::onAllowBikingChanged(bool enabled) { if (m_header) m_header->setAllowsBiking(enabled); } +void MapHeaderForm::onAllowEscapingChanged(bool enabled) { if (m_header) m_header->setAllowsEscaping(enabled); } void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index cf1339c5..58b63cbd 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -60,41 +60,22 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_Objects, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowObjects); - connect(ui->checkBox_Warps, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowWarps); - connect(ui->checkBox_BGs, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBgs); - connect(ui->checkBox_Triggers, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowTriggers); - connect(ui->checkBox_HealLocations, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowHealLocations); - connect(ui->checkBox_AllEvents, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllEvents); - connect(ui->checkBox_ConnectionUp, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionUp); - connect(ui->checkBox_ConnectionDown, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionDown); - connect(ui->checkBox_ConnectionLeft, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionLeft); - connect(ui->checkBox_ConnectionRight, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionRight); - connect(ui->checkBox_AllConnections, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllConnections); - connect(ui->checkBox_Collision, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowCollision); - connect(ui->checkBox_Grid, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowGrid); - connect(ui->checkBox_Border, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBorder); - connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewScaling); - connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewUpdates); -#else - connect(ui->checkBox_Objects, &QCheckBox::stateChanged, this, &MapImageExporter::setShowObjects); - connect(ui->checkBox_Warps, &QCheckBox::stateChanged, this, &MapImageExporter::setShowWarps); - connect(ui->checkBox_BGs, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBgs); - connect(ui->checkBox_Triggers, &QCheckBox::stateChanged, this, &MapImageExporter::setShowTriggers); - connect(ui->checkBox_HealLocations, &QCheckBox::stateChanged, this, &MapImageExporter::setShowHealLocations); - connect(ui->checkBox_AllEvents, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllEvents); - connect(ui->checkBox_ConnectionUp, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionUp); - connect(ui->checkBox_ConnectionDown, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionDown); - connect(ui->checkBox_ConnectionLeft, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionLeft); - connect(ui->checkBox_ConnectionRight, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionRight); - connect(ui->checkBox_AllConnections, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllConnections); - connect(ui->checkBox_Collision, &QCheckBox::stateChanged, this, &MapImageExporter::setShowCollision); - connect(ui->checkBox_Grid, &QCheckBox::stateChanged, this, &MapImageExporter::setShowGrid); - connect(ui->checkBox_Border, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBorder); - connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewScaling); - connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewUpdates); -#endif + connect(ui->checkBox_Objects, &QCheckBox::toggled, this, &MapImageExporter::setShowObjects); + connect(ui->checkBox_Warps, &QCheckBox::toggled, this, &MapImageExporter::setShowWarps); + connect(ui->checkBox_BGs, &QCheckBox::toggled, this, &MapImageExporter::setShowBgs); + connect(ui->checkBox_Triggers, &QCheckBox::toggled, this, &MapImageExporter::setShowTriggers); + connect(ui->checkBox_HealLocations, &QCheckBox::toggled, this, &MapImageExporter::setShowHealLocations); + connect(ui->checkBox_AllEvents, &QCheckBox::toggled, this, &MapImageExporter::setShowAllEvents); + connect(ui->checkBox_ConnectionUp, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionUp); + connect(ui->checkBox_ConnectionDown, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionDown); + connect(ui->checkBox_ConnectionLeft, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionLeft); + connect(ui->checkBox_ConnectionRight, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionRight); + connect(ui->checkBox_AllConnections, &QCheckBox::toggled, this, &MapImageExporter::setShowAllConnections); + connect(ui->checkBox_Collision, &QCheckBox::toggled, this, &MapImageExporter::setShowCollision); + connect(ui->checkBox_Grid, &QCheckBox::toggled, this, &MapImageExporter::setShowGrid); + connect(ui->checkBox_Border, &QCheckBox::toggled, this, &MapImageExporter::setShowBorder); + connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::toggled, this, &MapImageExporter::setDisablePreviewScaling); + connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::toggled, this, &MapImageExporter::setDisablePreviewUpdates); ui->graphicsView_Preview->setFocus(); } @@ -755,127 +736,123 @@ void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool en } } -void MapImageExporter::setShowCollision(CheckState state) { - m_settings.showCollision = (state == Qt::Checked); +void MapImageExporter::setShowCollision(bool checked) { + m_settings.showCollision = checked; updatePreview(); } -void MapImageExporter::setShowGrid(CheckState state) { - m_settings.showGrid = (state == Qt::Checked); +void MapImageExporter::setShowGrid(bool checked) { + m_settings.showGrid = checked; updatePreview(); } -void MapImageExporter::setShowBorder(CheckState state) { - m_settings.showBorder = (state == Qt::Checked); +void MapImageExporter::setShowBorder(bool checked) { + m_settings.showBorder = checked; updatePreview(); } -void MapImageExporter::setShowObjects(CheckState state) { - setEventGroupEnabled(Event::Group::Object, state == Qt::Checked); +void MapImageExporter::setShowObjects(bool checked) { + setEventGroupEnabled(Event::Group::Object, checked); updatePreview(); } -void MapImageExporter::setShowWarps(CheckState state) { - setEventGroupEnabled(Event::Group::Warp, state == Qt::Checked); +void MapImageExporter::setShowWarps(bool checked) { + setEventGroupEnabled(Event::Group::Warp, checked); updatePreview(); } -void MapImageExporter::setShowBgs(CheckState state) { - setEventGroupEnabled(Event::Group::Bg, state == Qt::Checked); +void MapImageExporter::setShowBgs(bool checked) { + setEventGroupEnabled(Event::Group::Bg, checked); updatePreview(); } -void MapImageExporter::setShowTriggers(CheckState state) { - setEventGroupEnabled(Event::Group::Coord, state == Qt::Checked); +void MapImageExporter::setShowTriggers(bool checked) { + setEventGroupEnabled(Event::Group::Coord, checked); updatePreview(); } -void MapImageExporter::setShowHealLocations(CheckState state) { - setEventGroupEnabled(Event::Group::Heal, state == Qt::Checked); +void MapImageExporter::setShowHealLocations(bool checked) { + setEventGroupEnabled(Event::Group::Heal, checked); updatePreview(); } // Shortcut setting for enabling all events -void MapImageExporter::setShowAllEvents(CheckState state) { - bool on = (state == Qt::Checked); - +void MapImageExporter::setShowAllEvents(bool checked) { const QSignalBlocker b_Objects(ui->checkBox_Objects); - ui->checkBox_Objects->setChecked(on); - ui->checkBox_Objects->setDisabled(on); - setEventGroupEnabled(Event::Group::Object, on); + ui->checkBox_Objects->setChecked(checked); + ui->checkBox_Objects->setDisabled(checked); + setEventGroupEnabled(Event::Group::Object, checked); const QSignalBlocker b_Warps(ui->checkBox_Warps); - ui->checkBox_Warps->setChecked(on); - ui->checkBox_Warps->setDisabled(on); - setEventGroupEnabled(Event::Group::Warp, on); + ui->checkBox_Warps->setChecked(checked); + ui->checkBox_Warps->setDisabled(checked); + setEventGroupEnabled(Event::Group::Warp, checked); const QSignalBlocker b_BGs(ui->checkBox_BGs); - ui->checkBox_BGs->setChecked(on); - ui->checkBox_BGs->setDisabled(on); - setEventGroupEnabled(Event::Group::Bg, on); + ui->checkBox_BGs->setChecked(checked); + ui->checkBox_BGs->setDisabled(checked); + setEventGroupEnabled(Event::Group::Bg, checked); const QSignalBlocker b_Triggers(ui->checkBox_Triggers); - ui->checkBox_Triggers->setChecked(on); - ui->checkBox_Triggers->setDisabled(on); - setEventGroupEnabled(Event::Group::Coord, on); + ui->checkBox_Triggers->setChecked(checked); + ui->checkBox_Triggers->setDisabled(checked); + setEventGroupEnabled(Event::Group::Coord, checked); const QSignalBlocker b_HealLocations(ui->checkBox_HealLocations); - ui->checkBox_HealLocations->setChecked(on); - ui->checkBox_HealLocations->setDisabled(on); - setEventGroupEnabled(Event::Group::Heal, on); + ui->checkBox_HealLocations->setChecked(checked); + ui->checkBox_HealLocations->setDisabled(checked); + setEventGroupEnabled(Event::Group::Heal, checked); updatePreview(); } -void MapImageExporter::setShowConnectionUp(CheckState state) { - setConnectionDirectionEnabled("up", state == Qt::Checked); +void MapImageExporter::setShowConnectionUp(bool checked) { + setConnectionDirectionEnabled("up", checked); updatePreview(); } -void MapImageExporter::setShowConnectionDown(CheckState state) { - setConnectionDirectionEnabled("down", state == Qt::Checked); +void MapImageExporter::setShowConnectionDown(bool checked) { + setConnectionDirectionEnabled("down", checked); updatePreview(); } -void MapImageExporter::setShowConnectionLeft(CheckState state) { - setConnectionDirectionEnabled("left", state == Qt::Checked); +void MapImageExporter::setShowConnectionLeft(bool checked) { + setConnectionDirectionEnabled("left", checked); updatePreview(); } -void MapImageExporter::setShowConnectionRight(CheckState state) { - setConnectionDirectionEnabled("right", state == Qt::Checked); +void MapImageExporter::setShowConnectionRight(bool checked) { + setConnectionDirectionEnabled("right", checked); updatePreview(); } // Shortcut setting for enabling all connection directions -void MapImageExporter::setShowAllConnections(CheckState state) { - bool on = (state == Qt::Checked); - +void MapImageExporter::setShowAllConnections(bool checked) { const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); - ui->checkBox_ConnectionUp->setChecked(on); - ui->checkBox_ConnectionUp->setDisabled(on); - setConnectionDirectionEnabled("up", on); + ui->checkBox_ConnectionUp->setChecked(checked); + ui->checkBox_ConnectionUp->setDisabled(checked); + setConnectionDirectionEnabled("up", checked); const QSignalBlocker b_Down(ui->checkBox_ConnectionDown); - ui->checkBox_ConnectionDown->setChecked(on); - ui->checkBox_ConnectionDown->setDisabled(on); - setConnectionDirectionEnabled("down", on); + ui->checkBox_ConnectionDown->setChecked(checked); + ui->checkBox_ConnectionDown->setDisabled(checked); + setConnectionDirectionEnabled("down", checked); const QSignalBlocker b_Left(ui->checkBox_ConnectionLeft); - ui->checkBox_ConnectionLeft->setChecked(on); - ui->checkBox_ConnectionLeft->setDisabled(on); - setConnectionDirectionEnabled("left", on); + ui->checkBox_ConnectionLeft->setChecked(checked); + ui->checkBox_ConnectionLeft->setDisabled(checked); + setConnectionDirectionEnabled("left", checked); const QSignalBlocker b_Right(ui->checkBox_ConnectionRight); - ui->checkBox_ConnectionRight->setChecked(on); - ui->checkBox_ConnectionRight->setDisabled(on); - setConnectionDirectionEnabled("right", on); + ui->checkBox_ConnectionRight->setChecked(checked); + ui->checkBox_ConnectionRight->setDisabled(checked); + setConnectionDirectionEnabled("right", checked); updatePreview(); } -void MapImageExporter::setDisablePreviewScaling(CheckState state) { - m_settings.disablePreviewScaling = (state == Qt::Checked); +void MapImageExporter::setDisablePreviewScaling(bool checked) { + m_settings.disablePreviewScaling = checked; if (m_settings.disablePreviewScaling) { ui->graphicsView_Preview->resetTransform(); } else { @@ -883,8 +860,8 @@ void MapImageExporter::setDisablePreviewScaling(CheckState state) { } } -void MapImageExporter::setDisablePreviewUpdates(CheckState state) { - m_settings.disablePreviewUpdates = (state == Qt::Checked); +void MapImageExporter::setDisablePreviewUpdates(bool checked) { + m_settings.disablePreviewUpdates = checked; if (m_settings.disablePreviewUpdates) { if (m_timelapseMovie) { m_timelapseMovie->stop(); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index b1c61151..29521974 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -45,16 +45,11 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->comboBox_BaseGameVersion, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::promptRestoreDefaults); connect(ui->comboBox_AttributesSize, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updateAttributeLimits); connect(ui->comboBox_IconSpecies, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updatePokemonIconPath); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { -#else - connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::stateChanged, [this](int state) { -#endif - bool customSize = (state == Qt::Checked); + connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::toggled, [this](bool enabled) { // When switching between the spin boxes or line edit for border metatiles we set // the newly-shown UI using the values from the hidden UI. - this->setBorderMetatileIds(customSize, this->getBorderMetatileIds(!customSize)); - this->setBorderMetatilesUi(customSize); + this->setBorderMetatileIds(enabled, this->getBorderMetatileIds(!enabled)); + this->setBorderMetatilesUi(enabled); }); connect(ui->button_AddWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(true); }); connect(ui->button_RemoveWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(false); }); @@ -86,11 +81,7 @@ void ProjectSettingsEditor::connectSignals() { connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited); } for (auto checkBox : ui->centralwidget->findChildren()) -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(checkBox, &QCheckBox::checkStateChanged, this, &ProjectSettingsEditor::markEdited); -#else - connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited); -#endif + connect(checkBox, &QCheckBox::toggled, this, &ProjectSettingsEditor::markEdited); for (auto radioButton : ui->centralwidget->findChildren()) connect(radioButton, &QRadioButton::toggled, this, &ProjectSettingsEditor::markEdited); for (auto lineEdit : ui->centralwidget->findChildren()) diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 9495395f..32d0be75 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -27,13 +27,8 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->ui->setupUi(this); this->project = project; connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_tileHFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileHFlip); - connect(ui->checkBox_tileVFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileVFlip); -#else - connect(ui->checkBox_tileHFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileHFlip); - connect(ui->checkBox_tileVFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileVFlip); -#endif + connect(ui->checkBox_tileHFlip, &QCheckBox::toggled, this, &RegionMapEditor::setTileHFlip); + connect(ui->checkBox_tileVFlip, &QCheckBox::toggled, this, &RegionMapEditor::setTileVFlip); this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); @@ -1043,14 +1038,14 @@ void RegionMapEditor::on_spinBox_tilePalette_valueChanged(int value) { this->mapsquare_selector_item->selectPalette(value); } -void RegionMapEditor::setTileHFlip(CheckState state) { +void RegionMapEditor::setTileHFlip(bool enabled) { if (this->mapsquare_selector_item) - this->mapsquare_selector_item->selectHFlip(state == Qt::Checked); + this->mapsquare_selector_item->selectHFlip(enabled); } -void RegionMapEditor::setTileVFlip(CheckState state) { +void RegionMapEditor::setTileVFlip(bool enabled) { if (this->mapsquare_selector_item) - this->mapsquare_selector_item->selectVFlip(state == Qt::Checked); + this->mapsquare_selector_item->selectVFlip(enabled); } void RegionMapEditor::on_action_RegionMap_Resize_triggered() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 9281bd28..3d610fb9 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -26,14 +26,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) setAttribute(Qt::WA_DeleteOnClose); setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); ui->setupUi(this); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_xFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setXFlip); - connect(ui->checkBox_yFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setYFlip); -#else - connect(ui->checkBox_xFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setXFlip); - connect(ui->checkBox_yFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setYFlip); -#endif + connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip); + connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip); this->tileXFlip = ui->checkBox_xFlip->isChecked(); this->tileYFlip = ui->checkBox_yFlip->isChecked(); @@ -553,17 +547,17 @@ void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId) this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::setXFlip(CheckState state) +void TilesetEditor::setXFlip(bool enabled) { - this->tileXFlip = (state == Qt::Checked); + this->tileXFlip = enabled; this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::setYFlip(CheckState state) +void TilesetEditor::setYFlip(bool enabled) { - this->tileYFlip = (state == Qt::Checked); + this->tileYFlip = enabled; this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); diff --git a/src/ui/updatepromoter.cpp b/src/ui/updatepromoter.cpp index 27c37b0a..8afc8d9c 100644 --- a/src/ui/updatepromoter.cpp +++ b/src/ui/updatepromoter.cpp @@ -19,12 +19,8 @@ UpdatePromoter::UpdatePromoter(QWidget *parent, NetworkAccessManager *manager) // Set up "Do not alert me" check box this->updatePreferences(); ui->checkBox_StopAlerts->setVisible(false); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_StopAlerts, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { -#else - connect(ui->checkBox_StopAlerts, &QCheckBox::stateChanged, [this](int state) { -#endif - porymapConfig.checkForUpdates = (state != Qt::Checked); + connect(ui->checkBox_StopAlerts, &QCheckBox::toggled, [this](bool stopAlerts) { + porymapConfig.checkForUpdates = !stopAlerts; emit this->changedPreferences(); }); From 55f44a6257f72abb233481aba105e12e76c36017 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 10 Apr 2025 22:37:32 -0400 Subject: [PATCH 296/364] Add project version check via git --- include/config.h | 2 + include/mainwindow.h | 2 +- include/project.h | 1 + src/config.cpp | 3 ++ src/mainwindow.cpp | 58 +++++++++++++++++------ src/project.cpp | 108 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 159 insertions(+), 15 deletions(-) diff --git a/include/config.h b/include/config.h index eb9c9ac3..4bfc2bc5 100644 --- a/include/config.h +++ b/include/config.h @@ -336,6 +336,7 @@ public: this->unusedTileCovered = 0x0000; this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; + this->forcedMajorVersion = 0; this->identifiers.clear(); this->readKeys.clear(); } @@ -406,6 +407,7 @@ public: int collisionSheetHeight; QList warpBehaviors; int maxEventsPerGroup; + int forcedMajorVersion; protected: virtual QString getConfigFilepath() override; diff --git a/include/mainwindow.h b/include/mainwindow.h index cdc641c2..b3df2e9b 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -355,7 +355,7 @@ private: void refreshCollisionSelector(); void setLayoutOnlyMode(bool layoutOnly); - bool checkProjectSanity(); + bool isInvalidProject(Project *project); bool loadProjectData(); bool setProjectUI(); void clearProjectUI(); diff --git a/include/project.h b/include/project.h index 9a031c0d..554fb3c7 100644 --- a/include/project.h +++ b/include/project.h @@ -87,6 +87,7 @@ public: void clearHealLocations(); bool sanityCheck(); + int getSupportedMajorVersion(QString *errorOut = nullptr); bool load(); Map* loadMap(const QString &mapName); diff --git a/src/config.cpp b/src/config.cpp index 42a59149..8a17a45c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -835,6 +835,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->warpBehaviors.append(getConfigUint32(key, s)); } else if (key == "max_events_per_group") { this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255); + } else if (key == "forced_major_version") { + this->forcedMajorVersion = getConfigInteger(key, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -931,6 +933,7 @@ QMap ProjectConfig::getKeyValueMap() { warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); map.insert("warp_behaviors", warpBehaviorStrs.join(",")); map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup)); + map.insert("forced_major_version", QString::number(this->forcedMajorVersion)); return map; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 650acdaf..6a3bdb56 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -668,7 +668,7 @@ bool MainWindow::openProject(QString dir, bool initial) { this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it - if (!checkProjectSanity()) { + if (isInvalidProject(this->editor->project)) { delete this->editor->project; return false; } @@ -708,22 +708,52 @@ bool MainWindow::loadProjectData() { return success; } -bool MainWindow::checkProjectSanity() { - if (editor->project->sanityCheck()) - return true; +bool MainWindow::isInvalidProject(Project *project) { + if (!project->sanityCheck()) { + logWarn(QString("The directory '%1' failed the project sanity check.").arg(project->root)); - logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root)); + ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), this); + msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" + "Make sure you selected the correct project directory " + "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(project->root)); + auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); + msgBox.exec(); - ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), this); - msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" - "Make sure you selected the correct project directory " - "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(editor->project->root)); - auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); - msgBox.exec(); - if (msgBox.clickedButton() == tryAnyway) { - // The user has chosen to try to load this project anyway. + // The user may choose to try to load this project anyway. // This will almost certainly fail, but they'll get a more specific error message. - return true; + if (msgBox.clickedButton() != tryAnyway){ + return true; + } + } + QString error; + int projectVersion = project->getSupportedMajorVersion(&error); + if (projectVersion <= 0) { + // Failed to identify a supported major version. + // We can't draw any conclusions from this, so we don't consider the project to be invalid. + logWarn(error.isEmpty() ? QStringLiteral("Unable to identify project's Porymap version.") : error); + } else { + logInfo(QString("Successful project version check. Supports at least Porymap v%1.").arg(projectVersion)); + + if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) { + // We were unable to find the necessary changes for Porymap's current major version. + // Warn the user that this might mean their project is missing breaking changes. + // Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting. + // It is very plausible that the user may have reproduced the required changes in an + // unknown commit, rather than merging the required changes directly from the base repo. + // In this case the 'projectVersion' may actually be too old to use for their repo. + ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), this); + msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2." + "") // TODO: Once we have a wiki or manual page describing breaking changes, link that here. + .arg(project->getProjectTitle()) + .arg(porymapVersion.majorVersion())); + auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); + msgBox.exec(); + if (msgBox.clickedButton() != tryAnyway){ + return true; + } + // User opted to try with this version anyway. Don't warn them about this version again. + projectConfig.forcedMajorVersion = porymapVersion.majorVersion(); + } } return false; } diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..1b165df2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -77,6 +77,114 @@ bool Project::sanityCheck() { return false; } +// Porymap projects have no standardized way for Porymap to determine whether they're compatible as of the latest breaking changes. +// We can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess. +// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project +// should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1. +// This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check +// is not important enough to significantly slow down project launch, we'd rather just timeout. +int Project::getSupportedMajorVersion(QString *errorOut) { + const int failureVersion = -1; + QString gitPath = QStandardPaths::findExecutable("git"); + if (gitPath.isEmpty()) { + if (errorOut) *errorOut = QStringLiteral("Failed to identify project history: Unable to locate git."); + return failureVersion; + } + + QProcess process; + process.setWorkingDirectory(this->root); + process.setProgram(gitPath); + process.setReadChannel(QProcess::StandardOutput); + process.setStandardInputFile(QProcess::nullDevice()); // We won't have any writing to do. + + // First we need to know which (if any) known git history this project belongs to. + // We'll get the root commit, then compare it to the known root commits for the base project repos. + static const QStringList args_getRootCommit = { "rev-list", "--max-parents=0", "HEAD" }; + process.setArguments(args_getRootCommit); + process.start(); + if (!process.waitForFinished(500) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) { + if (errorOut) { + *errorOut = QStringLiteral("Failed to identify project history"); + if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) { + errorOut->append(QString(": %1").arg(process.errorString())); + } else { + process.setReadChannel(QProcess::StandardError); + QString error = QString(process.readLine()).remove('\n'); + if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error)); + } + } + return failureVersion; + } + const QString rootCommit = QString(process.readLine()).remove('\n'); + + // The keys in this map are the hashes of the root commits for each of the 3 base repos. + // The values are a list of pairs, where the first element is a major version number, and the + // second element is the hash of the earliest commit that supports that major version. + static const QMap>> historyMap = { + // pokeemerald + {"33b799c967fd63d04afe82eecc4892f3e45781b3", { + {6, "07c897ad48c36b178093bde8ca360823127d812b"}, // TODO: Update to merge commit for pokeemerald's porymap-6 branch + {5, "c76beed98990a57c84d3930190fd194abfedf7e8"}, + {4, "cb5b8da77b9ba6837fcc8c5163bedc5008b12c2c"}, + {3, "204c431993dad29661a9ff47326787cd0cf381e6"}, + {2, "cdae0c1444bed98e652c87dc3e3edcecacfef8be"}, + {1, ""} + }}, + // pokefirered + {"670fef77ac4d9116d5fdc28c0da40622919a062b", { + {6, "7722e7a92ca5fa69925dcef82f6c89c35ec48171"}, // TODO: Update to merge commit for pokefirered's porymap-6 branch + {5, "52591dcee42933d64f60c59276fc13c3bb89c47b"}, + {4, "200c82e01a94dbe535e6ed8768d8afad4444d4d2"}, + }}, + // pokeruby + {"1362b60f3467f0894d55e82f3294980b6373021d", { + {6, "bc5aeaa64ecad03aa4ab9e1000ba94916276c936"}, // TODO: Update to merge commit for pokeruby's porymap-6 branch + {5, "d99cb43736dd1d4ee4820f838cb259d773d8bf25"}, + {4, "f302fcc134bf354c3655e3423be68fd7a99cb396"}, + {3, "b4f4d2c0f03462dcdf3492aad27890294600eb2e"}, + {2, "0e8ccfc4fd3544001f4c25fafd401f7558bdefba"}, + {1, ""} + }}, + }; + if (!historyMap.contains(rootCommit)) { + // Either this repo does not share history with one of the base repos, + // (that's ok, don't report an error) or we got some unexpected result. + return failureVersion; + } + + // We now know which base repo that the user's repo shares history with. + // Next we check to see if it contains the changes required to support particular major versions of Porymap. + // We'll start with the most recent latest version and work backwards. + for (const auto &pair : historyMap.value(rootCommit)) { + int versionNum = pair.first; + QString commitHash = pair.second; + if (commitHash.isEmpty()) { + // An empty commit hash means 'consider any point in the history a supported version' + return versionNum; + } + process.setArguments({ "merge-base", "--is-ancestor", commitHash, "HEAD" }); + process.start(); + if (!process.waitForFinished(500) || process.exitStatus() != QProcess::ExitStatus::NormalExit) { + if (errorOut) { + *errorOut = QStringLiteral("Failed to identify project's supported Porymap version"); + if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) { + errorOut->append(QString(": %1").arg(process.errorString())); + } else { + process.setReadChannel(QProcess::StandardError); + QString error = QString(process.readLine()).remove('\n'); + if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error)); + } + } + return failureVersion; + } + if (process.exitCode() == 0) { + // Identified a supported major version + return versionNum; + } + } + return failureVersion; +} + bool Project::load() { resetFileCache(); this->disabledSettingsNames.clear(); From 35d5851a8f5e6990e5d14fbe26b95d14f2ecce44 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 21:32:42 -0400 Subject: [PATCH 297/364] Read MAP_OFFSET_W, MAP_OFFSET_H from project --- include/config.h | 2 + include/project.h | 41 +++++++----- src/config.cpp | 2 + src/project.cpp | 119 ++++++++++------------------------- src/scriptapi/apimap.cpp | 6 +- src/ui/newlayoutform.cpp | 9 +-- src/ui/resizelayoutpopup.cpp | 17 ++--- 7 files changed, 78 insertions(+), 118 deletions(-) diff --git a/include/config.h b/include/config.h index eb9c9ac3..2ca56fd1 100644 --- a/include/config.h +++ b/include/config.h @@ -214,6 +214,8 @@ enum ProjectIdentifier { define_pals_total, define_tiles_per_metatile, define_map_size, + define_map_offset_width, + define_map_offset_height, define_mask_metatile, define_mask_collision, define_mask_elevation, diff --git a/include/project.h b/include/project.h index 9a031c0d..fe1c48f2 100644 --- a/include/project.h +++ b/include/project.h @@ -240,24 +240,27 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + int getMapDataSize(int width, int height) const; + int getMaxMapDataSize() const { return this->maxMapDataSize; } + int getMaxMapWidth() const; + int getMaxMapHeight() const; + bool mapDimensionsValid(int width, int height) const; + bool calculateDefaultMapSize(); + int getDefaultMapDimension() const { return this->defaultMapDimension; } + QSize getMapSizeAddition() const { return this->mapSizeAddition; } + + int getMaxEvents(Event::Group group) const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); - static int getNumTilesPrimary(); - static int getNumTilesTotal(); - static int getNumMetatilesPrimary(); - static int getNumMetatilesTotal(); - static int getNumPalettesPrimary(); - static int getNumPalettesTotal(); - static int getMaxMapDataSize(); - static int getDefaultMapDimension(); - static int getMaxMapWidth(); - static int getMaxMapHeight(); - static int getMapDataSize(int width, int height); - static bool mapDimensionsValid(int width, int height); - bool calculateDefaultMapSize(); - int getMaxEvents(Event::Group group); + static int getNumTilesPrimary() { return num_tiles_primary; } + static int getNumTilesTotal() { return num_tiles_total; } + static int getNumMetatilesPrimary() { return num_metatiles_primary; } + static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; } + static int getNumPalettesPrimary(){ return num_pals_primary; } + static int getNumPalettesTotal() { return num_pals_total; } static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); @@ -302,15 +305,19 @@ private: QString findSpeciesIconPath(const QStringList &names) const; - int maxEventsPerGroup; int maxObjectEvents; + QSize mapSizeAddition; + int maxMapDataSize; + int defaultMapDimension; + + // TODO: These really shouldn't be static, they're specific to a single project. + // We're making an assumption here that we only have one project open at a single time + // (which is true, but then if that's the case we should have some global Project instance instead) static int num_tiles_primary; static int num_tiles_total; static int num_metatiles_primary; static int num_pals_primary; static int num_pals_total; - static int max_map_data_size; - static int default_map_dimension; signals: void fileChanged(const QString &filepath); diff --git a/src/config.cpp b/src/config.cpp index 42a59149..fcd7f83b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -89,6 +89,8 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_pals_total, {"define_pals_total", "NUM_PALS_TOTAL"}}, {ProjectIdentifier::define_tiles_per_metatile, {"define_tiles_per_metatile", "NUM_TILES_PER_METATILE"}}, {ProjectIdentifier::define_map_size, {"define_map_size", "MAX_MAP_DATA_SIZE"}}, + {ProjectIdentifier::define_map_offset_width, {"define_map_offset_width", "MAP_OFFSET_W"}}, + {ProjectIdentifier::define_map_offset_height, {"define_map_offset_height", "MAP_OFFSET_H"}}, {ProjectIdentifier::define_mask_metatile, {"define_mask_metatile", "MAPGRID_METATILE_ID_MASK"}}, {ProjectIdentifier::define_mask_collision, {"define_mask_collision", "MAPGRID_COLLISION_MASK"}}, {ProjectIdentifier::define_mask_elevation, {"define_mask_elevation", "MAPGRID_ELEVATION_MASK"}}, diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..d33063e0 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -29,8 +29,6 @@ int Project::num_tiles_total = 1024; 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_dimension = 20; Project::Project(QObject *parent) : QObject(parent), @@ -2109,7 +2107,12 @@ bool Project::readFieldmapProperties() { const QString numPalsTotalName = projectConfig.getIdentifier(ProjectIdentifier::define_pals_total); const QString maxMapSizeName = projectConfig.getIdentifier(ProjectIdentifier::define_map_size); const QString numTilesPerMetatileName = projectConfig.getIdentifier(ProjectIdentifier::define_tiles_per_metatile); - const QSet names = { + const QString mapOffsetWidthName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_width); + const QString mapOffsetHeightName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_height); + + const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); + fileWatcher.addPath(root + "/" + filename); + const QMap defines = parser.readCDefinesByName(filename, { numTilesPrimaryName, numTilesTotalName, numMetatilesPrimaryName, @@ -2117,10 +2120,9 @@ bool Project::readFieldmapProperties() { numPalsTotalName, maxMapSizeName, numTilesPerMetatileName, - }; - const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); - fileWatcher.addPath(root + "/" + filename); - const QMap defines = parser.readCDefinesByName(filename, names); + mapOffsetWidthName, + mapOffsetHeightName, + }); auto loadDefine = [defines](const QString name, int * dest, int min, int max) { auto it = defines.find(name); @@ -2146,25 +2148,35 @@ bool Project::readFieldmapProperties() { // we don't actually know what the maximum number of metatiles is. loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary, 1, 0xFFFF - 1); + int w = 15, h = 14; // Default values of MAP_OFFSET_W, MAP_OFFSET_H + loadDefine(mapOffsetWidthName, &w, 0, INT_MAX); + loadDefine(mapOffsetHeightName, &h, 0, INT_MAX); + this->mapSizeAddition = QSize(w, h); + + this->maxMapDataSize = 10240; // Default value of MAX_MAP_DATA_SIZE + this->defaultMapDimension = 20; // Arbitrary default of 20x20. auto it = defines.find(maxMapSizeName); if (it != defines.end()) { int min = getMapDataSize(1, 1); if (it.value() >= min) { - Project::max_map_data_size = it.value(); - calculateDefaultMapSize(); + this->maxMapDataSize = it.value(); + if (getMapDataSize(this->defaultMapDimension, this->defaultMapDimension) > this->maxMapDataSize) { + // The specified map size is too small to use the default map dimensions. + // Calculate the largest square map size that we can use instead. + this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + } } else { - // must be large enough to support a 1x1 map - logWarn(QString("Value for map property '%1' is %2, must be at least %3. Using default (%4) instead.") + logWarn(QString("Value for map property '%1' of %2 is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") .arg(maxMapSizeName) .arg(it.value()) .arg(min) - .arg(Project::max_map_data_size)); + .arg(this->maxMapDataSize)); } } else { logWarn(QString("Value for map property '%1' not found. Using default (%2) instead.") .arg(maxMapSizeName) - .arg(Project::max_map_data_size)); + .arg(this->maxMapDataSize)); } it = defines.find(numTilesPerMetatileName); @@ -3112,91 +3124,28 @@ QPixmap Project::getSpeciesIcon(const QString &species) { return pixmap; } -int Project::getNumTilesPrimary() -{ - return Project::num_tiles_primary; +int Project::getMapDataSize(int width, int height) const { + return (width + this->mapSizeAddition.width()) + * (height + this->mapSizeAddition.height()); } -int Project::getNumTilesTotal() -{ - return Project::num_tiles_total; +int Project::getMaxMapWidth() const { + return (getMaxMapDataSize() / (1 + this->mapSizeAddition.height())) - this->mapSizeAddition.width(); } -int Project::getNumMetatilesPrimary() -{ - return Project::num_metatiles_primary; +int Project::getMaxMapHeight() const { + return (getMaxMapDataSize() / (1 + this->mapSizeAddition.width())) - this->mapSizeAddition.height(); } -int Project::getNumMetatilesTotal() -{ - return Block::getMaxMetatileId() + 1; -} - -int Project::getNumPalettesPrimary() -{ - return Project::num_pals_primary; -} - -int Project::getNumPalettesTotal() -{ - return Project::num_pals_total; -} - -int Project::getMaxMapDataSize() -{ - return Project::max_map_data_size; -} - -int Project::getMapDataSize(int width, int height) -{ - // + 15 and + 14 come from fieldmap.c in pokeruby/pokeemerald/pokefirered. - return (width + 15) * (height + 14); -} - -int Project::getDefaultMapDimension() -{ - return Project::default_map_dimension; -} - -int Project::getMaxMapWidth() -{ - return (getMaxMapDataSize() / (1 + 14)) - 15; -} - -int Project::getMaxMapHeight() -{ - return (getMaxMapDataSize() / (1 + 15)) - 14; -} - -bool Project::mapDimensionsValid(int width, int height) { +bool Project::mapDimensionsValid(int width, int height) const { return getMapDataSize(width, height) <= getMaxMapDataSize(); } -// Get largest possible square dimensions for a map up to maximum of 20x20 (arbitrary) -bool Project::calculateDefaultMapSize(){ - int max = getMaxMapDataSize(); - - if (max >= getMapDataSize(20, 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_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)) - .arg(max) - .arg(getMapDataSize(1, 1))); - return false; - } - return true; -} - // Object events have their own limit specified by ProjectIdentifier::define_obj_event_count. // The default value for this is 64. All events (object events included) are also limited by // the data types of the event counters in the project. This would normally be u8, so the limit is 255. // We let the users tell us this limit in case they change these data types. -int Project::getMaxEvents(Event::Group group) { +int Project::getMaxEvents(Event::Group group) const { if (group == Event::Group::Object) return qMin(this->maxObjectEvents, projectConfig.maxEventsPerGroup); return projectConfig.maxEventsPerGroup; diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 0ee3316e..08bfc9c5 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -227,7 +227,7 @@ int MainWindow::getHeight() { void MainWindow::setDimensions(int width, int height) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, height)) + if (this->editor->project && !this->editor->project->mapDimensionsValid(width, height)) return; this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); @@ -237,7 +237,7 @@ void MainWindow::setDimensions(int width, int height) { void MainWindow::setWidth(int width) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, this->editor->layout->getHeight())) + if (this->editor->project && !this->editor->project->mapDimensionsValid(width, this->editor->layout->getHeight())) return; this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); @@ -247,7 +247,7 @@ void MainWindow::setWidth(int width) { void MainWindow::setHeight(int height) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(this->editor->layout->getWidth(), height)) + if (this->editor->project && !this->editor->project->mapDimensionsValid(this->editor->layout->getWidth(), height)) return; this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index aa8178ce..b88b4f3f 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -86,17 +86,14 @@ bool NewLayoutForm::validateMapDimensions() { int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); int maxSize = m_project->getMaxMapDataSize(); - // TODO: Get from project - const int additionalWidth = 15; - const int additionalHeight = 14; - QString errorText; if (size > maxSize) { + QSize addition = m_project->getMapSizeAddition(); errorText = QString("The specified width and height are too large.\n" "The maximum map width and height is the following: (width + %1) * (height + %2) <= %3\n" "The specified map width and height was: (%4 + %1) * (%5 + %2) = %6") - .arg(additionalWidth) - .arg(additionalHeight) + .arg(addition.width()) + .arg(addition.height()) .arg(maxSize) .arg(ui->spinBox_MapWidth->value()) .arg(ui->spinBox_MapHeight->value()) diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 5629d8e9..00790ca8 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -145,15 +145,18 @@ void ResizeLayoutPopup::setupLayoutView() { // Upper limits: maximum metatiles in a map formula: // max = (width + 15) * (height + 14) // This limit can be found in fieldmap.c in pokeruby/pokeemerald/pokefirered. - int numMetatiles = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); - int maxMetatiles = editor->project->getMaxMapDataSize(); - if (numMetatiles > maxMetatiles) { - QString errorText = QString("The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) + int size = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); + int maxSize = editor->project->getMaxMapDataSize(); + if (size > maxSize) { + QSize addition = editor->project->getMapSizeAddition(); + QString errorText = QString("The maximum layout width and height is the following: (width + %1) * (height + %2) <= %3\n" + "The specified layout width and height was: (%4 + %1) * (%5 + %2) = %6") + .arg(addition.width()) + .arg(addition.height()) + .arg(maxSize) .arg(rect.width() / 16) .arg(rect.height() / 16) - .arg(numMetatiles); + .arg(size); QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setText("The specified width and height are too large."); From 900ff0afd9b40dcbdc498df3805802f2325d3d3c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 21:57:40 -0400 Subject: [PATCH 298/364] Fix incorrect log comments, update manual --- docsrc/manual/project-files.rst | 2 ++ src/project.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index caac85c2..93715d86 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -97,6 +97,8 @@ In addition to these files, there are some specific symbol and macro names that ``define_pals_total``, ``NUM_PALS_TOTAL``, ``define_tiles_per_metatile``, ``NUM_TILES_PER_METATILE``, to determine if triple-layer metatiles are in use. Values other than 8 or 12 are ignored ``define_map_size``, ``MAX_MAP_DATA_SIZE``, to limit map dimensions + ``define_map_offset_width``, ``MAP_OFFSET_W``, to limit map dimensions + ``define_map_offset_height``, ``MAP_OFFSET_H``, to limit map dimensions ``define_mask_metatile``, ``MAPGRID_METATILE_ID_MASK``, optionally read to get settings on ``Maps`` tab ``define_mask_collision``, ``MAPGRID_COLLISION_MASK``, optionally read to get settings on ``Maps`` tab ``define_mask_elevation``, ``MAPGRID_ELEVATION_MASK``, optionally read to get settings on ``Maps`` tab diff --git a/src/project.cpp b/src/project.cpp index d33063e0..20ff806a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2129,14 +2129,14 @@ bool Project::readFieldmapProperties() { if (it != defines.end()) { *dest = it.value(); if (*dest < min) { - logWarn(QString("Value for tileset property '%1' (%2) is below the minimum (%3). Defaulting to minimum.").arg(name).arg(*dest).arg(min)); + logWarn(QString("Value for '%1' (%2) is below the minimum (%3). Defaulting to minimum.").arg(name).arg(*dest).arg(min)); *dest = min; } else if (*dest > max) { - logWarn(QString("Value for tileset property '%1' (%2) is above the maximum (%3). Defaulting to maximum.").arg(name).arg(*dest).arg(max)); + logWarn(QString("Value for '%1' (%2) is above the maximum (%3). Defaulting to maximum.").arg(name).arg(*dest).arg(max)); *dest = max; } } else { - logWarn(QString("Value for tileset property '%1' not found. Using default (%2) instead.").arg(name).arg(*dest)); + logWarn(QString("Value for '%1' not found. Using default (%2) instead.").arg(name).arg(*dest)); } }; loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter. @@ -2166,7 +2166,7 @@ bool Project::readFieldmapProperties() { this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); } } else { - logWarn(QString("Value for map property '%1' of %2 is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") + logWarn(QString("Value for '%1' (%2) is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") .arg(maxMapSizeName) .arg(it.value()) .arg(min) @@ -2174,7 +2174,7 @@ bool Project::readFieldmapProperties() { } } else { - logWarn(QString("Value for map property '%1' not found. Using default (%2) instead.") + logWarn(QString("Value for '%1' not found. Using default (%2) instead.") .arg(maxMapSizeName) .arg(this->maxMapDataSize)); } From c630581453c15467d83d74633fc97ebd63a84784 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 22:22:30 -0400 Subject: [PATCH 299/364] Add setting for default map size --- forms/projectsettingseditor.ui | 162 +++++++++++++++---------------- include/config.h | 2 + include/project.h | 6 +- src/config.cpp | 10 +- src/project.cpp | 21 ++-- src/ui/projectsettingseditor.cpp | 5 + 6 files changed, 112 insertions(+), 94 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index a088d87e..00b015e1 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -369,7 +369,7 @@ 0 0 559 - 560 + 622 @@ -379,37 +379,6 @@ Map Data Defaults - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - The default metatile value that will be used to fill new maps - - - 0x - - - 16 - - - @@ -417,6 +386,44 @@ + + + + Width + + + + + + + The default elevation that will be used to fill new maps + + + + + + + Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file + + + Create separate text file + + + + + + + 1 + + + + + + + 1 + + + @@ -424,13 +431,10 @@ - - - - Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file - + + - Create separate text file + Collision @@ -441,6 +445,20 @@ + + + + The default metatile value that will be used to fill new maps + + + + + + + The default collision that will be used to fill new maps + + + @@ -481,79 +499,59 @@ 0 - + The default metatile value that will be used for the top-left border metatile on new maps. - - 0x - - - 16 - - + The default metatile value that will be used for the top-right border metatile on new maps. - - 0x - - - 16 - - + The default metatile value that will be used for the bottom-left border metatile on new maps. - - 0x - - - 16 - - + The default metatile value that will be used for the bottom-right border metatile on new maps. - - 0x - - - 16 - - - - - The default elevation that will be used to fill new maps - + + + + + 0 + + + 0 + + + 0 + + + 0 + + - - + + - Collision - - - - - - - The default collision that will be used to fill new maps + Height @@ -1084,7 +1082,7 @@ 0 0 559 - 788 + 840 diff --git a/include/config.h b/include/config.h index 2ca56fd1..d3c274e9 100644 --- a/include/config.h +++ b/include/config.h @@ -319,6 +319,7 @@ public: this->defaultMetatileId = 1; this->defaultElevation = 3; this->defaultCollision = 0; + this->defaultMapSize = QSize(20,20); this->defaultPrimaryTileset = "gTileset_General"; this->prefabFilepath = QString(); this->prefabImportPrompted = false; @@ -383,6 +384,7 @@ public: uint16_t defaultMetatileId; uint16_t defaultElevation; uint16_t defaultCollision; + QSize defaultMapSize; QList newMapBorderMetatileIds; QString defaultPrimaryTileset; QString defaultSecondaryTileset; diff --git a/include/project.h b/include/project.h index fe1c48f2..1e45fdbb 100644 --- a/include/project.h +++ b/include/project.h @@ -246,7 +246,7 @@ public: int getMaxMapHeight() const; bool mapDimensionsValid(int width, int height) const; bool calculateDefaultMapSize(); - int getDefaultMapDimension() const { return this->defaultMapDimension; } + QSize getDefaultMapSize() const { return this->defaultMapSize; } QSize getMapSizeAddition() const { return this->mapSizeAddition; } int getMaxEvents(Event::Group group) const; @@ -306,9 +306,9 @@ private: QString findSpeciesIconPath(const QStringList &names) const; int maxObjectEvents; - QSize mapSizeAddition; int maxMapDataSize; - int defaultMapDimension; + QSize defaultMapSize; + QSize mapSizeAddition; // TODO: These really shouldn't be static, they're specific to a single project. // We're making an assumption here that we only have one project open at a single time diff --git a/src/config.cpp b/src/config.cpp index fcd7f83b..a97ba5bc 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -265,7 +265,7 @@ int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, in int result = value.toInt(&ok, 0); if (!ok) { logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); - return defaultValue; + result = defaultValue; } return qMin(max, qMax(min, result)); } @@ -275,7 +275,7 @@ uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_ uint32_t result = value.toUInt(&ok, 0); if (!ok) { logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); - return defaultValue; + result = defaultValue; } return qMin(max, qMax(min, result)); } @@ -739,6 +739,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->defaultElevation = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "default_collision") { this->defaultCollision = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "default_map_width") { + this->defaultMapSize.setWidth(getConfigInteger(key, value, 1)); + } else if (key == "default_map_height") { + this->defaultMapSize.setHeight(getConfigInteger(key, value, 1)); } else if (key == "new_map_border_metatiles") { this->newMapBorderMetatileIds.clear(); QList metatileIds = value.split(","); @@ -890,6 +894,8 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("default_metatile", Metatile::getMetatileIdString(this->defaultMetatileId)); map.insert("default_elevation", QString::number(this->defaultElevation)); map.insert("default_collision", QString::number(this->defaultCollision)); + map.insert("default_map_width", QString::number(this->defaultMapSize.width())); + map.insert("default_map_height", QString::number(this->defaultMapSize.height())); map.insert("new_map_border_metatiles", Metatile::getMetatileIdStrings(this->newMapBorderMetatileIds)); map.insert("default_primary_tileset", this->defaultPrimaryTileset); map.insert("default_secondary_tileset", this->defaultSecondaryTileset); diff --git a/src/project.cpp b/src/project.cpp index 20ff806a..6190017a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1990,8 +1990,8 @@ void Project::initNewMapSettings() { this->newMapSettings.layout.folderName = this->newMapSettings.name; this->newMapSettings.layout.name = QString(); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); - this->newMapSettings.layout.width = getDefaultMapDimension(); - this->newMapSettings.layout.height = getDefaultMapDimension(); + this->newMapSettings.layout.width = this->defaultMapSize.width(); + this->newMapSettings.layout.height = this->defaultMapSize.height(); this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH; this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); @@ -2013,8 +2013,8 @@ void Project::initNewMapSettings() { void Project::initNewLayoutSettings() { this->newLayoutSettings.name = QString(); this->newLayoutSettings.id = Layout::layoutConstantFromName(this->newLayoutSettings.name); - this->newLayoutSettings.width = getDefaultMapDimension(); - this->newLayoutSettings.height = getDefaultMapDimension(); + this->newLayoutSettings.width = this->defaultMapSize.width(); + this->newLayoutSettings.height = this->defaultMapSize.height(); this->newLayoutSettings.borderWidth = DEFAULT_BORDER_WIDTH; this->newLayoutSettings.borderHeight = DEFAULT_BORDER_HEIGHT; this->newLayoutSettings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); @@ -2154,16 +2154,23 @@ bool Project::readFieldmapProperties() { this->mapSizeAddition = QSize(w, h); this->maxMapDataSize = 10240; // Default value of MAX_MAP_DATA_SIZE - this->defaultMapDimension = 20; // Arbitrary default of 20x20. + this->defaultMapSize = projectConfig.defaultMapSize; auto it = defines.find(maxMapSizeName); if (it != defines.end()) { int min = getMapDataSize(1, 1); if (it.value() >= min) { this->maxMapDataSize = it.value(); - if (getMapDataSize(this->defaultMapDimension, this->defaultMapDimension) > this->maxMapDataSize) { + if (getMapDataSize(this->defaultMapSize.width(), this->defaultMapSize.height()) > this->maxMapDataSize) { // The specified map size is too small to use the default map dimensions. // Calculate the largest square map size that we can use instead. - this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + int dimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + logWarn(QString("Value for '%1' (%2) is too small to support the default %3x%4 map. Default changed to %5x%5.") + .arg(maxMapSizeName) + .arg(it.value()) + .arg(this->defaultMapSize.width()) + .arg(this->defaultMapSize.height()) + .arg(dimension)); + this->defaultMapSize = QSize(dimension, dimension); } } else { logWarn(QString("Value for '%1' (%2) is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 29521974..7f84c5dc 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -136,6 +136,8 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue); ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue); ui->spinBox_MaxEvents->setMaximum(INT_MAX); + ui->spinBox_MapWidth->setMaximum(INT_MAX); + ui->spinBox_MapHeight->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -455,6 +457,8 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_Elevation->setValue(projectConfig.defaultElevation); ui->spinBox_Collision->setValue(projectConfig.defaultCollision); ui->spinBox_FillMetatile->setValue(projectConfig.defaultMetatileId); + ui->spinBox_MapWidth->setValue(projectConfig.defaultMapSize.width()); + ui->spinBox_MapHeight->setValue(projectConfig.defaultMapSize.height()); ui->spinBox_MaxElevation->setValue(projectConfig.collisionSheetHeight - 1); ui->spinBox_MaxCollision->setValue(projectConfig.collisionSheetWidth - 1); ui->spinBox_BehaviorMask->setValue(projectConfig.metatileBehaviorMask & ui->spinBox_BehaviorMask->maximum()); @@ -530,6 +534,7 @@ void ProjectSettingsEditor::save() { projectConfig.defaultElevation = ui->spinBox_Elevation->value(); projectConfig.defaultCollision = ui->spinBox_Collision->value(); projectConfig.defaultMetatileId = ui->spinBox_FillMetatile->value(); + projectConfig.defaultMapSize = QSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); projectConfig.collisionSheetHeight = ui->spinBox_MaxElevation->value() + 1; projectConfig.collisionSheetWidth = ui->spinBox_MaxCollision->value() + 1; projectConfig.metatileBehaviorMask = ui->spinBox_BehaviorMask->value(); From c54d875d3ca5c3a7777591dd0b052c6a0b374612 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 22:43:38 -0400 Subject: [PATCH 300/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d64777f..984f3879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Redesigned the new map dialog, including better error checking and a collapsible section for header data. - New maps/layouts are no longer saved automatically, and can be fully discarded by closing without saving. - Map groups and ``MAPSEC`` names specified when creating a new map will be added automatically if they don't already exist. +- Custom fields in JSON files that Porymap writes are no longer discarded. - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. - Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions. - A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening. From 168792b9c6d7da724967151e55cf8f5d7a5be8f3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 23:10:51 -0400 Subject: [PATCH 301/364] Fix comment typo --- src/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index 1b165df2..9ce9f6ed 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -154,7 +154,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) { // We now know which base repo that the user's repo shares history with. // Next we check to see if it contains the changes required to support particular major versions of Porymap. - // We'll start with the most recent latest version and work backwards. + // We'll start with the most recent major version and work backwards. for (const auto &pair : historyMap.value(rootCommit)) { int versionNum = pair.first; QString commitHash = pair.second; From e19932b90ced2c7cc2e5fdb168a1490bc260e2cc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 11:43:54 -0400 Subject: [PATCH 302/364] Allow custom map connection direction input --- include/ui/connectionslistitem.h | 13 +++-- src/ui/connectionslistitem.cpp | 79 ++++++++++++++++++------------- src/ui/newmapconnectiondialog.cpp | 1 - 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index b63922a9..bce05345 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -36,19 +36,18 @@ private: protected: virtual void mousePressEvent(QMouseEvent*) override; - virtual void focusInEvent(QFocusEvent*) override; virtual void keyPressEvent(QKeyEvent*) override; + virtual bool eventFilter(QObject*, QEvent *event) override; signals: void selected(); void openMapClicked(MapConnection*); -private slots: - void on_comboBox_Direction_currentTextChanged(QString direction); - void on_comboBox_Map_currentTextChanged(QString mapName); - void on_spinBox_Offset_valueChanged(int offset); - void on_button_Delete_clicked(); - void on_button_OpenMap_clicked(); +private: + void commitDirection(); + void commitMap(const QString &mapName); + void commitMove(int offset); + void commitRemove(); }; #endif // CONNECTIONSLISTITEM_H diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index 5b21a12c..f761ba12 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -7,44 +7,58 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) : QFrame(parent), - ui(new Ui::ConnectionsListItem) + ui(new Ui::ConnectionsListItem), + connection(connection), + map(connection->parentMap()) { ui->setupUi(this); setFocusPolicy(Qt::StrongFocus); - const QSignalBlocker blocker1(ui->comboBox_Direction); - const QSignalBlocker blocker2(ui->comboBox_Map); - const QSignalBlocker blocker3(ui->spinBox_Offset); - - ui->comboBox_Direction->setEditable(false); + // Direction + const QSignalBlocker b_Direction(ui->comboBox_Direction); ui->comboBox_Direction->setMinimumContentsLength(0); ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); + ui->comboBox_Direction->installEventFilter(this); + // We don't use QComboBox::currentTextChanged here to avoid unnecessary commits while typing. + connect(ui->comboBox_Direction, QOverload::of(&QComboBox::currentIndexChanged), this, &ConnectionsListItem::commitDirection); + connect(ui->comboBox_Direction->lineEdit(), &QLineEdit::editingFinished, this, &ConnectionsListItem::commitDirection); + + // Map + const QSignalBlocker b_Map(ui->comboBox_Map); ui->comboBox_Map->setMinimumContentsLength(6); ui->comboBox_Map->addItems(mapNames); ui->comboBox_Map->setFocusedScrollingEnabled(false); // Scrolling could cause rapid changes to many different maps ui->comboBox_Map->setInsertPolicy(QComboBox::NoInsert); + ui->comboBox_Map->installEventFilter(this); - ui->spinBox_Offset->setMinimum(INT_MIN); - ui->spinBox_Offset->setMaximum(INT_MAX); + // The map combo box only commits the change if it's a valid map name, so unlike Direction we can use QComboBox::currentTextChanged. + connect(ui->comboBox_Map, &QComboBox::currentTextChanged, this, &ConnectionsListItem::commitMap); // Invalid map names are not considered a change. If editing finishes with an invalid name, restore the previous name. connect(ui->comboBox_Map->lineEdit(), &QLineEdit::editingFinished, [this] { - const QSignalBlocker blocker(ui->comboBox_Map); - if (ui->comboBox_Map->findText(ui->comboBox_Map->currentText()) < 0) + const QSignalBlocker b(ui->comboBox_Map); + if (this->connection && ui->comboBox_Map->findText(ui->comboBox_Map->currentText()) < 0) ui->comboBox_Map->setTextItem(this->connection->targetMapName()); }); - // Distinguish between move actions for the edit history - connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); + // Offset + const QSignalBlocker b_Offset(ui->spinBox_Offset); + ui->spinBox_Offset->setMinimum(INT_MIN); + ui->spinBox_Offset->setMaximum(INT_MAX); + ui->spinBox_Offset->installEventFilter(this); + + connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); // Distinguish between move actions for the edit history + connect(ui->spinBox_Offset, &QSpinBox::valueChanged, this, &ConnectionsListItem::commitMove); // If the connection changes externally we want to update to reflect the change. connect(connection, &MapConnection::offsetChanged, this, &ConnectionsListItem::updateUI); connect(connection, &MapConnection::directionChanged, this, &ConnectionsListItem::updateUI); connect(connection, &MapConnection::targetMapNameChanged, this, &ConnectionsListItem::updateUI); - this->connection = connection; - this->map = connection->parentMap(); + connect(ui->button_Delete, &QToolButton::clicked, this, &ConnectionsListItem::commitRemove); + connect(ui->button_OpenMap, &QToolButton::clicked, [this] { emit openMapClicked(this->connection); }); + this->updateUI(); } @@ -66,13 +80,19 @@ void ConnectionsListItem::updateUI() { ui->spinBox_Offset->setValue(this->connection->offset()); } +bool ConnectionsListItem::eventFilter(QObject*, QEvent *event) { + if (event->type() == QEvent::FocusIn) + this->setSelected(true); + return false; +} + void ConnectionsListItem::setSelected(bool selected) { if (selected == this->isSelected) return; this->isSelected = selected; - this->setStyleSheet(selected ? ".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }" - : ".ConnectionsListItem { border-width: 1px; }"); + this->setStyleSheet(selected ? QStringLiteral(".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }") + : QStringLiteral(".ConnectionsListItem { border-width: 1px; }")); if (selected) emit this->selected(); } @@ -81,41 +101,32 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { this->setSelected(true); } -void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(QString direction) { - this->setSelected(true); - if (this->map) +void ConnectionsListItem::commitDirection() { + const QString direction = ui->comboBox_Direction->currentText(); + if (this->map && this->connection && this->connection->direction() != direction) { this->map->commit(new MapConnectionChangeDirection(this->connection, direction)); + } } -void ConnectionsListItem::on_comboBox_Map_currentTextChanged(QString mapName) { - this->setSelected(true); +void ConnectionsListItem::commitMap(const QString &mapName) { if (this->map && ui->comboBox_Map->findText(mapName) >= 0) this->map->commit(new MapConnectionChangeMap(this->connection, mapName)); } -void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) { - this->setSelected(true); +void ConnectionsListItem::commitMove(int offset) { if (this->map) this->map->commit(new MapConnectionMove(this->connection, offset, this->actionId)); } -void ConnectionsListItem::on_button_Delete_clicked() { +void ConnectionsListItem::commitRemove() { if (this->map) this->map->commit(new MapConnectionRemove(this->map, this->connection)); } -void ConnectionsListItem::on_button_OpenMap_clicked() { - emit openMapClicked(this->connection); -} - -void ConnectionsListItem::focusInEvent(QFocusEvent* event) { - this->setSelected(true); - QFrame::focusInEvent(event); -} - void ConnectionsListItem::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { - on_button_Delete_clicked(); + commitRemove(); + event->accept(); } else { QFrame::keyPressEvent(event); } diff --git a/src/ui/newmapconnectiondialog.cpp b/src/ui/newmapconnectiondialog.cpp index def341fd..a4f08496 100644 --- a/src/ui/newmapconnectiondialog.cpp +++ b/src/ui/newmapconnectiondialog.cpp @@ -8,7 +8,6 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->comboBox_Direction->setEditable(false); ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); ui->comboBox_Map->addItems(mapNames); From b6548fd49ccb66cfcfc408f180ef7d7e585f6708 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 12:40:28 -0400 Subject: [PATCH 303/364] Stop zoom behavior from regressing again --- forms/mainwindow.ui | 6 ------ src/mainwindow.cpp | 4 ++++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 86f50047..99e7d4b2 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -351,12 +351,6 @@ false - - QGraphicsView::ViewportAnchor::AnchorUnderMouse - - - QGraphicsView::ViewportAnchor::AnchorUnderMouse - diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..006546e5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -261,6 +261,10 @@ void MainWindow::initCustomUI() { // Create map header data widget this->mapHeaderForm = new MapHeaderForm(); ui->layout_HeaderData->addWidget(this->mapHeaderForm); + + // Center zooming on the mouse + ui->graphicsView_Map->setTransformationAnchor(QGraphicsView::ViewportAnchor::AnchorUnderMouse); + ui->graphicsView_Map->setResizeAnchor(QGraphicsView::ViewportAnchor::AnchorUnderMouse); } void MainWindow::initExtraSignals() { From d014eef9e8f6d4bbddc0eb4734a06e5bfec66e3e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 13:41:57 -0400 Subject: [PATCH 304/364] Add NoScrollComboBox::editingFinished, disable diving map buttons with no map --- forms/mainwindow.ui | 6 +++++ include/editor.h | 7 ++--- include/mainwindow.h | 2 -- include/ui/noscrollcombobox.h | 3 +++ src/editor.cpp | 47 ++++++++++++++++++++++++++-------- src/mainwindow.cpp | 16 ------------ src/ui/connectionslistitem.cpp | 5 +--- src/ui/divingmappixmapitem.cpp | 6 +---- src/ui/mapimageexporter.cpp | 5 +--- src/ui/newlayoutform.cpp | 4 +-- src/ui/noscrollcombobox.cpp | 5 ++++ src/ui/tileseteditor.cpp | 14 +++------- 12 files changed, 63 insertions(+), 57 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 99e7d4b2..8a8757a0 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2429,6 +2429,9 @@ + + false + Open the selected Dive Map @@ -2563,6 +2566,9 @@ + + false + Open the selected Emerge Map diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..8b8a18b0 100644 --- a/include/editor.h +++ b/include/editor.h @@ -98,8 +98,8 @@ public: void deleteWildMonGroup(); void configureEncounterJSON(QWidget *); EncounterTableModel* getCurrentWildMonTable(); - void updateDiveMap(QString mapName); - void updateEmergeMap(QString mapName); + bool setDivingMapName(const QString &mapName, const QString &direction); + QString getDivingMapName(const QString &direction) const; void setSelectedConnection(MapConnection *connection); void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false); @@ -218,8 +218,9 @@ private: void removeConnectionPixmap(MapConnection *connection); void displayConnection(MapConnection *connection); void displayDivingConnection(MapConnection *connection); - void setDivingMapName(QString mapName, QString direction); void removeDivingMapPixmap(MapConnection *connection); + void onDivingMapEditingFinished(NoScrollComboBox* combo, const QString &direction); + void updateDivingMapButton(QToolButton* button, const QString &mapName); void updateEncounterFields(EncounterFields newFields); QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); diff --git a/include/mainwindow.h b/include/mainwindow.h index 410bd6e3..abda3116 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -243,8 +243,6 @@ private slots: void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); void on_button_OpenEmergeMap_clicked(); - void on_comboBox_DiveMap_currentTextChanged(const QString &mapName); - void on_comboBox_EmergeMap_currentTextChanged(const QString &mapName); void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); diff --git a/include/ui/noscrollcombobox.h b/include/ui/noscrollcombobox.h index 32966b3a..0ae2487c 100644 --- a/include/ui/noscrollcombobox.h +++ b/include/ui/noscrollcombobox.h @@ -18,6 +18,9 @@ public: void setLineEdit(QLineEdit *edit); void setFocusedScrollingEnabled(bool enabled); +signals: + void editingFinished(); + private: void setItem(int index, const QString &text); diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..78cab8bb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -54,6 +54,19 @@ Editor::Editor(Ui::MainWindow* ui) connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this, &Editor::openProjectInTextEditor); connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::toggleGrid); connect(ui->mapCustomAttributesFrame->table(), &CustomAttributesTable::edited, this, &Editor::updateCustomMapAttributes); + + connect(ui->comboBox_DiveMap, &NoScrollComboBox::editingFinished, [this] { + onDivingMapEditingFinished(this->ui->comboBox_DiveMap, "dive"); + }); + connect(ui->comboBox_EmergeMap, &NoScrollComboBox::editingFinished, [this] { + onDivingMapEditingFinished(this->ui->comboBox_EmergeMap, "emerge"); + }); + connect(ui->comboBox_DiveMap, &NoScrollComboBox::currentTextChanged, [this] { + updateDivingMapButton(this->ui->button_OpenDiveMap, this->ui->comboBox_DiveMap->currentText()); + }); + connect(ui->comboBox_EmergeMap, &NoScrollComboBox::currentTextChanged, [this] { + updateDivingMapButton(this->ui->button_OpenEmergeMap, this->ui->comboBox_EmergeMap->currentText()); + }); } Editor::~Editor() @@ -914,21 +927,18 @@ void Editor::removeDivingMapPixmap(MapConnection *connection) { updateDivingMapsVisibility(); } -void Editor::updateDiveMap(QString mapName) { - setDivingMapName(mapName, "dive"); -} +bool Editor::setDivingMapName(const QString &mapName, const QString &direction) { + if (!mapName.isEmpty() && !this->project->mapNames.contains(mapName)) + return false; + if (!MapConnection::isDiving(direction)) + return false; -void Editor::updateEmergeMap(QString mapName) { - setDivingMapName(mapName, "emerge"); -} - -void Editor::setDivingMapName(QString mapName, QString direction) { auto pixmapItem = diving_map_items.value(direction); MapConnection *connection = pixmapItem ? pixmapItem->connection() : nullptr; if (connection) { if (mapName == connection->targetMapName()) - return; // No change + return true; // No change // Update existing connection if (mapName.isEmpty()) { @@ -940,6 +950,23 @@ void Editor::setDivingMapName(QString mapName, QString direction) { // Create new connection addConnection(new MapConnection(mapName, direction)); } + return true; +} + +QString Editor::getDivingMapName(const QString &direction) const { + auto pixmapItem = diving_map_items.value(direction); + return (pixmapItem && pixmapItem->connection()) ? pixmapItem->connection()->targetMapName() : QString(); +} + +void Editor::onDivingMapEditingFinished(NoScrollComboBox *combo, const QString &direction) { + if (!setDivingMapName(combo->currentText(), direction)) { + // If user input was invalid, restore the combo to the previously-valid text. + combo->setCurrentText(getDivingMapName(direction)); + } +} + +void Editor::updateDivingMapButton(QToolButton* button, const QString &mapName) { + if (this->project) button->setDisabled(!this->project->mapNames.contains(mapName)); } void Editor::updateDivingMapsVisibility() { @@ -1722,8 +1749,6 @@ void Editor::clearMapConnections() { } connection_items.clear(); - const QSignalBlocker blocker1(ui->comboBox_DiveMap); - const QSignalBlocker blocker2(ui->comboBox_EmergeMap); ui->comboBox_DiveMap->setCurrentText(""); ui->comboBox_EmergeMap->setCurrentText(""); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 006546e5..c539b654 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1217,10 +1217,7 @@ void MainWindow::clearProjectUI() { const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); ui->comboBox_SecondaryTileset->clear(); - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); - - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_EmergeMap->clear(); const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); @@ -1381,8 +1378,6 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { // (other combo boxes like for warp destinations are repopulated when the map changes). int mapIndex = this->editor->project->mapNames.indexOf(newMap->name()); if (mapIndex >= 0) { - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_DiveMap->insertItem(mapIndex, newMap->name()); ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } @@ -2646,17 +2641,6 @@ void MainWindow::on_button_OpenEmergeMap_clicked() { userSetMap(ui->comboBox_EmergeMap->currentText()); } -void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { - // Include empty names as an update (user is deleting the connection) - if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) - editor->updateDiveMap(mapName); -} - -void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName) { - if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) - editor->updateEmergeMap(mapName); -} - void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) { if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index f761ba12..b0fdf581 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -20,9 +20,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); ui->comboBox_Direction->installEventFilter(this); - // We don't use QComboBox::currentTextChanged here to avoid unnecessary commits while typing. - connect(ui->comboBox_Direction, QOverload::of(&QComboBox::currentIndexChanged), this, &ConnectionsListItem::commitDirection); - connect(ui->comboBox_Direction->lineEdit(), &QLineEdit::editingFinished, this, &ConnectionsListItem::commitDirection); + connect(ui->comboBox_Direction, &NoScrollComboBox::editingFinished, this, &ConnectionsListItem::commitDirection); // Map const QSignalBlocker b_Map(ui->comboBox_Map); @@ -32,7 +30,6 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->comboBox_Map->setInsertPolicy(QComboBox::NoInsert); ui->comboBox_Map->installEventFilter(this); - // The map combo box only commits the change if it's a valid map name, so unlike Direction we can use QComboBox::currentTextChanged. connect(ui->comboBox_Map, &QComboBox::currentTextChanged, this, &ConnectionsListItem::commitMap); // Invalid map names are not considered a change. If editing finishes with an invalid name, restore the previous name. diff --git a/src/ui/divingmappixmapitem.cpp b/src/ui/divingmappixmapitem.cpp index e20b8f25..b774b1e9 100644 --- a/src/ui/divingmappixmapitem.cpp +++ b/src/ui/divingmappixmapitem.cpp @@ -38,9 +38,5 @@ void DivingMapPixmapItem::onTargetMapChanged() { } void DivingMapPixmapItem::setComboText(const QString &text) { - if (!m_combo) - return; - - const QSignalBlocker blocker(m_combo); - m_combo->setCurrentText(text); + if (m_combo) m_combo->setCurrentText(text); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 58b63cbd..8d415819 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -55,10 +55,7 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage); connect(ui->pushButton_Cancel, &QPushButton::pressed, this, &MapImageExporter::close); - // Update the map selector when the text changes. - // We don't use QComboBox::currentTextChanged to avoid unnecessary re-rendering. - connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); - connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); + connect(ui->comboBox_MapSelection, &NoScrollComboBox::editingFinished, this, &MapImageExporter::updateMapSelection); connect(ui->checkBox_Objects, &QCheckBox::toggled, this, &MapImageExporter::setShowObjects); connect(ui->checkBox_Warps, &QCheckBox::toggled, this, &MapImageExporter::setShowWarps); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index aa8178ce..2bf4621f 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -18,8 +18,8 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); }); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); }); - connect(ui->comboBox_PrimaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validatePrimaryTileset(true); }); - connect(ui->comboBox_SecondaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validateSecondaryTileset(true); }); + connect(ui->comboBox_PrimaryTileset, &NoScrollComboBox::editingFinished, [this]{ validatePrimaryTileset(true); }); + connect(ui->comboBox_SecondaryTileset, &NoScrollComboBox::editingFinished, [this]{ validateSecondaryTileset(true); }); } NewLayoutForm::~NewLayoutForm() diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index 21de55a8..bd6b438b 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -23,6 +23,11 @@ NoScrollComboBox::NoScrollComboBox(QWidget *parent) static const QRegularExpression re("[^\\s]*"); QValidator *validator = new QRegularExpressionValidator(re, this); this->setValidator(validator); + + // QComboBox (as of writing) has no 'editing finished' signal to capture + // changes made either through the text edit or the drop-down. + connect(this, &QComboBox::activated, this, &NoScrollComboBox::editingFinished); + connect(this->lineEdit(), &QLineEdit::editingFinished, this, &NoScrollComboBox::editingFinished); } // On macOS QComboBox::setEditable and QComboBox::setLineEdit will override our changes to the focus policy, so we enforce it here. diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 3d610fb9..2671f445 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -125,16 +125,10 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi } void TilesetEditor::initAttributesUi() { - // Update the metatile's attributes values when the attribute combo boxes are edited. - // We avoid using the 'currentTextChanged' signal here, we want to know when we can clean up the input field and commit changes. - connect(ui->comboBox_metatileBehaviors->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitMetatileBehavior); - connect(ui->comboBox_encounterType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitEncounterType); - connect(ui->comboBox_terrainType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitTerrainType); - connect(ui->comboBox_layerType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitLayerType); - connect(ui->comboBox_metatileBehaviors, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitMetatileBehavior); - connect(ui->comboBox_encounterType, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitEncounterType); - connect(ui->comboBox_terrainType, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitTerrainType); - connect(ui->comboBox_layerType, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitLayerType); + connect(ui->comboBox_metatileBehaviors, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitMetatileBehavior); + connect(ui->comboBox_encounterType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitEncounterType); + connect(ui->comboBox_terrainType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitTerrainType); + connect(ui->comboBox_layerType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitLayerType); // Behavior if (projectConfig.metatileBehaviorMask) { From d30be0b9af530e913612e092f97e8d64d3ef7dd3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 15 Apr 2025 13:49:23 -0400 Subject: [PATCH 305/364] Fix some inputs moving user's cursor while typing --- include/ui/mapheaderform.h | 2 ++ src/ui/mapheaderform.cpp | 22 ++++++++++++++++------ src/ui/maplisttoolbar.cpp | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 7f246d6d..4f3dc775 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -64,6 +64,8 @@ private: QPointer m_project = nullptr; bool m_allowProjectChanges = true; + void setText(QComboBox *combo, const QString &text) const; + void setText(QLineEdit *lineEdit, const QString &text) const; void setLocations(const QStringList &locations); void updateLocationName(); diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 65fe6ead..a2a0b8b5 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -174,19 +174,29 @@ void MapHeaderForm::updateLocationName() { } // Set data in UI -void MapHeaderForm::setSong(const QString &song) { ui->comboBox_Song->setCurrentText(song); } -void MapHeaderForm::setLocation(const QString &location) { ui->comboBox_Location->setCurrentText(location); } -void MapHeaderForm::setLocationName(const QString &locationName) { ui->lineEdit_LocationName->setText(locationName); } +void MapHeaderForm::setSong(const QString &song) { setText(ui->comboBox_Song, song); } +void MapHeaderForm::setLocation(const QString &location) { setText(ui->comboBox_Location, location); } +void MapHeaderForm::setLocationName(const QString &locationName) { setText(ui->lineEdit_LocationName, locationName); } void MapHeaderForm::setRequiresFlash(bool requiresFlash) { ui->checkBox_RequiresFlash->setChecked(requiresFlash); } -void MapHeaderForm::setWeather(const QString &weather) { ui->comboBox_Weather->setCurrentText(weather); } -void MapHeaderForm::setType(const QString &type) { ui->comboBox_Type->setCurrentText(type); } -void MapHeaderForm::setBattleScene(const QString &battleScene) { ui->comboBox_BattleScene->setCurrentText(battleScene); } +void MapHeaderForm::setWeather(const QString &weather) { setText(ui->comboBox_Weather, weather); } +void MapHeaderForm::setType(const QString &type) { setText(ui->comboBox_Type, type); } +void MapHeaderForm::setBattleScene(const QString &battleScene) { setText(ui->comboBox_BattleScene, battleScene); } void MapHeaderForm::setShowsLocationName(bool showsLocationName) { ui->checkBox_ShowLocationName->setChecked(showsLocationName); } void MapHeaderForm::setAllowsRunning(bool allowsRunning) { ui->checkBox_AllowRunning->setChecked(allowsRunning); } void MapHeaderForm::setAllowsBiking(bool allowsBiking) { ui->checkBox_AllowBiking->setChecked(allowsBiking); } void MapHeaderForm::setAllowsEscaping(bool allowsEscaping) { ui->checkBox_AllowEscaping->setChecked(allowsEscaping); } void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_FloorNumber->setValue(floorNumber); } +// If we always call setText / setCurrentText the user's cursor may move to the end of the text while they're typing. +void MapHeaderForm::setText(QComboBox *combo, const QString &text) const { + if (combo->currentText() != text) + combo->setCurrentText(text); +} +void MapHeaderForm::setText(QLineEdit *lineEdit, const QString &text) const { + if (lineEdit->text() != text) + lineEdit->setText(text); +} + // Read data from UI QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); } QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); } diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 2584c4fd..304d6490 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -121,7 +121,9 @@ void MapListToolBar::applyFilter(const QString &filterText) { return; const QSignalBlocker b(ui->lineEdit_filterBox); - ui->lineEdit_filterBox->setText(filterText); + if (ui->lineEdit_filterBox->text() != filterText) { + ui->lineEdit_filterBox->setText(filterText); + } // The clear button does not properly disappear when filterText is empty. // It seems like this is because blocking the QLineEdit's signals prevents From ee0f5923cebb9b0eb3c3be0b56349dac7b7c0589 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 15 Apr 2025 15:33:07 -0400 Subject: [PATCH 306/364] Better error handling if saving fails --- include/config.h | 2 +- include/core/maplayout.h | 6 +- include/core/paletteutil.h | 2 +- include/core/tileset.h | 20 +-- include/editor.h | 6 +- include/mainwindow.h | 2 +- include/project.h | 39 +++--- include/ui/tileseteditor.h | 4 +- src/config.cpp | 12 +- src/core/maplayout.cpp | 49 +++++++- src/core/paletteutil.cpp | 20 +-- src/core/tileset.cpp | 72 ++++++----- src/editor.cpp | 23 ++-- src/mainwindow.cpp | 15 +-- src/project.cpp | 250 ++++++++++++++++--------------------- src/ui/tileseteditor.cpp | 25 ++-- 16 files changed, 294 insertions(+), 253 deletions(-) diff --git a/include/config.h b/include/config.h index 6746a0f4..0f1e8706 100644 --- a/include/config.h +++ b/include/config.h @@ -26,7 +26,7 @@ static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_ class KeyValueConfigBase { public: - void save(); + bool save(); void load(); virtual ~KeyValueConfigBase(); virtual void reset() = 0; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 40a3a035..3822b5cf 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -116,9 +116,12 @@ public: void clearBorderCache(); void cacheBorder(); - void setClean(); bool hasUnsavedChanges() const; + bool save(const QString &root); + bool saveBorder(const QString &root); + bool saveBlockdata(const QString &root); + bool layoutBlockChanged(int i, const Blockdata &cache); uint16_t getBorderMetatileId(int x, int y); @@ -143,6 +146,7 @@ public: private: void setNewDimensionsBlockdata(int newWidth, int newHeight); void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); + bool writeBlockdata(const QString &path, const Blockdata &blockdata) const; static int getBorderDrawDistance(int dimension, qreal minimum); diff --git a/include/core/paletteutil.h b/include/core/paletteutil.h index ce221026..34e9ae3f 100644 --- a/include/core/paletteutil.h +++ b/include/core/paletteutil.h @@ -7,7 +7,7 @@ namespace PaletteUtil { QList parse(QString filepath, bool *error); - void writeJASC(QString filepath, QVector colors, int offset, int nColors); + bool writeJASC(const QString &filepath, const QVector &colors, int offset, int nColors); } #endif // PALETTEUTIL_H diff --git a/include/core/tileset.h b/include/core/tileset.h index 32d18858..a05afdc3 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -55,17 +55,17 @@ public: static QString getExpectedDir(QString tilesetName, bool isSecondary); QString getExpectedDir(); - void load(); - void loadMetatiles(); - void loadMetatileAttributes(); - void loadTilesImage(QImage *importedImage = nullptr); - void loadPalettes(); + bool load(); + bool loadMetatiles(); + bool loadMetatileAttributes(); + bool loadTilesImage(QImage *importedImage = nullptr); + bool loadPalettes(); - void save(); - void saveMetatileAttributes(); - void saveMetatiles(); - void saveTilesImage(); - void savePalettes(); + bool save(); + bool saveMetatileAttributes(); + bool saveMetatiles(); + bool saveTilesImage(); + bool savePalettes(); bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..4d579093 100644 --- a/include/editor.h +++ b/include/editor.h @@ -57,8 +57,8 @@ public: GridSettings gridSettings; void setProject(Project * project); - void saveAll(); - void saveCurrent(); + bool saveAll(); + bool saveCurrent(); void saveEncounterTabData(); void closeProject(); @@ -199,7 +199,7 @@ private: EditMode editMode = EditMode::None; - void save(bool currentOnly); + bool save(bool currentOnly); void clearMap(); void clearMetatileSelector(); void clearMovementPermissionSelector(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 410bd6e3..de790d63 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -175,7 +175,7 @@ private slots: void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); void on_action_Save_Project_triggered(); - void save(bool currentOnly = false); + bool save(bool currentOnly = false); void openWarpMap(QString map_name, int event_id, Event::Group event_group); diff --git a/include/project.h b/include/project.h index c5aed0a3..d8ef4c36 100644 --- a/include/project.h +++ b/include/project.h @@ -108,10 +108,6 @@ public: bool loadBlockdata(Layout *); bool loadLayoutBorder(Layout *); - void saveTextFile(QString path, QString text); - void appendTextFile(QString path, QString text); - void deleteFile(QString path); - bool readMapGroups(); void addNewMapGroup(const QString &groupName); QString mapNameToMapGroup(const QString &mapName) const; @@ -168,25 +164,20 @@ public: bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); - void loadTilesetAssets(Tileset*); + bool loadTilesetAssets(Tileset*); void loadTilesetMetatileLabels(Tileset*); void readTilesetPaths(Tileset* tileset); - void saveAll(); - void saveGlobalData(); - void saveLayout(Layout *); - void saveLayoutBlockdata(Layout *); - void saveLayoutBorder(Layout *); - void writeBlockdata(QString, const Blockdata &); - void saveMap(Map *map, bool skipLayout = false); - void saveConfig(); - void saveMapLayouts(); - void saveMapGroups(); - void saveRegionMapSections(); - void saveWildMonData(); - void saveHealLocations(); - void saveTilesets(Tileset*, Tileset*); - void saveTilesetMetatileLabels(Tileset*, Tileset*); + bool saveAll(); + bool saveGlobalData(); + bool saveConfig(); + bool saveLayout(Layout *layout); + bool saveMap(Map *map, bool skipLayout = false); + bool saveTextFile(const QString &path, const QString &text); + bool saveRegionMapSections(); + bool saveTilesets(Tileset*, Tileset*); + bool saveTilesetMetatileLabels(Tileset*, Tileset*); + void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); @@ -309,8 +300,6 @@ private: }; QHash locationData; - void updateLayout(Layout *); - void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); @@ -318,6 +307,12 @@ private: void recordFileChange(const QString &filepath); void resetFileCache(); + bool saveMapLayouts(); + bool saveMapGroups(); + bool saveWildMonData(); + bool saveHealLocations(); + bool appendTextFile(const QString &path, const QString &text); + QString findSpeciesIconPath(const QStringList &names) const; int maxEventsPerGroup; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index fdd4751c..b6a60a61 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -71,8 +71,6 @@ private slots: void on_spinBox_paletteSelector_valueChanged(int arg1); - void on_actionSave_Tileset_triggered(); - void on_actionImport_Primary_Tiles_triggered(); void on_actionImport_Secondary_Tiles_triggered(); @@ -173,6 +171,8 @@ private: bool lockSelection = false; QSet metatileReloadQueue; + bool save(); + signals: void tilesetsSaved(QString, QString); }; diff --git a/src/config.cpp b/src/config.cpp index 5c5feb54..e6ac885b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -233,7 +233,7 @@ void KeyValueConfigBase::load() { file.close(); } -void KeyValueConfigBase::save() { +bool KeyValueConfigBase::save() { QString text = ""; QMap map = this->getKeyValueMap(); for (QMap::iterator it = map.begin(); it != map.end(); it++) { @@ -241,12 +241,14 @@ void KeyValueConfigBase::save() { } QFile file(this->getConfigFilepath()); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - file.close(); - } else { + if (!file.open(QIODevice::WriteOnly)) { logError(QString("Could not open config file '%1' for writing: ").arg(this->getConfigFilepath()) + file.errorString()); + return false; } + + file.write(text.toUtf8()); + file.close(); + return true; } bool KeyValueConfigBase::getConfigBool(QString key, QString value) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 45b35f91..e3d47422 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -467,11 +467,50 @@ QPixmap Layout::getLayoutItemPixmap() { return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); } -void Layout::setClean() { - this->editHistory.setClean(); - this->hasUnsavedDataChanges = false; -} - bool Layout::hasUnsavedChanges() const { return !this->editHistory.isClean() || this->hasUnsavedDataChanges || !this->newFolderPath.isEmpty(); } + +bool Layout::save(const QString &root) { + if (!this->newFolderPath.isEmpty()) { + // Layout directory doesn't exist yet, create it now. + const QString fullPath = QString("%1/%2").arg(root).arg(this->newFolderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); + return false; + } + this->newFolderPath = QString(); + } + + bool success = true; + if (!saveBorder(root)) success = false; + if (!saveBlockdata(root)) success = false; + if (!success) + return false; + + this->editHistory.setClean(); + this->hasUnsavedDataChanges = false; + return true; +} + +bool Layout::saveBorder(const QString &root) { + QString path = QString("%1/%2").arg(root).arg(this->border_path); + return writeBlockdata(path, this->border); +} + +bool Layout::saveBlockdata(const QString &root) { + QString path = QString("%1/%2").arg(root).arg(this->blockdata_path); + return writeBlockdata(path, this->blockdata); +} + +bool Layout::writeBlockdata(const QString &path, const Blockdata &blockdata) const { + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing: %2").arg(path).arg(file.errorString())); + return false; + } + + QByteArray data = blockdata.serialize(); + file.write(data); + return true; +} diff --git a/src/core/paletteutil.cpp b/src/core/paletteutil.cpp index 929336b2..76a3d0c4 100644 --- a/src/core/paletteutil.cpp +++ b/src/core/paletteutil.cpp @@ -38,14 +38,14 @@ QList PaletteUtil::parse(QString filepath, bool *error) { return QList(); } -void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, int nColors) { +bool PaletteUtil::writeJASC(const QString &filepath, const QVector &palette, int offset, int nColors) { if (!nColors) { - logWarn(QString("Cannot save a palette with no colors.")); - return; + logError(QString("Cannot save a palette with no colors.")); + return false; } if (offset > palette.size() || offset + nColors > palette.size()) { - logWarn("Palette offset out of range for color table."); - return; + logError("Palette offset out of range for color table."); + return false; } QString text = "JASC-PAL\r\n0100\r\n"; @@ -59,11 +59,13 @@ void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, } QFile file(filepath); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - } else { - logWarn(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); + return false; } + + file.write(text.toUtf8()); + return true; } QList parsePal(QString filepath, bool *error) { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index f6ce6a2e..0ad43d1a 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -402,13 +402,13 @@ QHash Tileset::getHeaderMemberMap(bool usingAsm) return map; } -void Tileset::loadMetatiles() { +bool Tileset::loadMetatiles() { clearMetatiles(); QFile metatiles_file(this->metatiles_path); if (!metatiles_file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1' for reading.").arg(this->metatiles_path)); - return; + logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(metatiles_file.errorString())); + return false; } QByteArray data = metatiles_file.readAll(); @@ -425,13 +425,14 @@ void Tileset::loadMetatiles() { } m_metatiles.append(metatile); } + return true; } -void Tileset::saveMetatiles() { +bool Tileset::saveMetatiles() { QFile metatiles_file(this->metatiles_path); if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - logError(QString("Could not open '%1' for writing.").arg(this->metatiles_path)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(metatiles_file.errorString())); + return false; } QByteArray data; @@ -444,13 +445,14 @@ void Tileset::saveMetatiles() { } } metatiles_file.write(data); + return true; } -void Tileset::loadMetatileAttributes() { +bool Tileset::loadMetatileAttributes() { QFile attrs_file(this->metatile_attrs_path); if (!attrs_file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1' for reading.").arg(this->metatile_attrs_path)); - return; + logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString())); + return false; } QByteArray data = attrs_file.readAll(); @@ -467,13 +469,14 @@ void Tileset::loadMetatileAttributes() { attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); m_metatiles.at(i)->setAttributes(attributes); } + return true; } -void Tileset::saveMetatileAttributes() { +bool Tileset::saveMetatileAttributes() { QFile attrs_file(this->metatile_attrs_path); if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - logError(QString("Could not open '%1' for writing.").arg(this->metatile_attrs_path)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString())); + return false; } QByteArray data; @@ -483,9 +486,10 @@ void Tileset::saveMetatileAttributes() { data.append(static_cast(attributes >> (8 * i))); } attrs_file.write(data); + return true; } -void Tileset::loadTilesImage(QImage *importedImage) { +bool Tileset::loadTilesImage(QImage *importedImage) { QImage image; if (importedImage) { image = *importedImage; @@ -520,23 +524,25 @@ void Tileset::loadTilesImage(QImage *importedImage) { } this->tilesImage = image; this->tiles = tiles; + return true; } -void Tileset::saveTilesImage() { +bool Tileset::saveTilesImage() { // Only write the tiles image if it was changed. // Porymap will only ever change an existing tiles image by importing a new one. if (!m_hasUnsavedTilesImage) - return; + return true; if (!this->tilesImage.save(this->tilesImagePath, "PNG")) { logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath)); - return; + return false; } m_hasUnsavedTilesImage = false; + return true; } -void Tileset::loadPalettes() { +bool Tileset::loadPalettes() { this->palettes.clear(); this->palettePreviews.clear(); @@ -559,26 +565,34 @@ void Tileset::loadPalettes() { this->palettes.append(palette); this->palettePreviews.append(palette); } + return true; } -void Tileset::savePalettes() { +bool Tileset::savePalettes() { + bool success = true; int numPalettes = qMin(this->palettePaths.length(), this->palettes.length()); for (int i = 0; i < numPalettes; i++) { - PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16); + if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16)) + success = false; } + return success; } -void Tileset::load() { - loadMetatiles(); - loadMetatileAttributes(); - loadTilesImage(); - loadPalettes(); +bool Tileset::load() { + bool success = true; + if (!loadMetatiles()) success = false; + if (!loadMetatileAttributes()) success = false; + if (!loadTilesImage()) success = false; + if (!loadPalettes()) success = false; + return success; } // Because metatile labels are global (and handled by the project) we don't save them here. -void Tileset::save() { - saveMetatiles(); - saveMetatileAttributes(); - saveTilesImage(); - savePalettes(); +bool Tileset::save() { + bool success = true; + if (!saveMetatiles()) success = false; + if (!saveMetatileAttributes()) success = false; + if (!saveTilesImage()) success = false; + if (!savePalettes()) success = false; + return success; } diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..d1347cf1 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -68,30 +68,33 @@ Editor::~Editor() closeProject(); } -void Editor::saveCurrent() { - save(true); +bool Editor::saveCurrent() { + return save(true); } -void Editor::saveAll() { - save(false); +bool Editor::saveAll() { + return save(false); } -void Editor::save(bool currentOnly) { +bool Editor::save(bool currentOnly) { if (!this->project) - return; + return true; saveEncounterTabData(); + bool success = true; if (currentOnly) { if (this->map) { - this->project->saveMap(this->map); + success = this->project->saveMap(this->map); } else if (this->layout) { - this->project->saveLayout(this->layout); + success = this->project->saveLayout(this->layout); } - this->project->saveGlobalData(); + if (!this->project->saveGlobalData()) + success = false; } else { - this->project->saveAll(); + success = this->project->saveAll(); } + return success; } void Editor::setProject(Project * project) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..f0ba072b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1634,16 +1634,15 @@ void MainWindow::on_action_Save_triggered() { save(true); } -void MainWindow::save(bool currentOnly) { - if (currentOnly) { - this->editor->saveCurrent(); - } else { - this->editor->saveAll(); +bool MainWindow::save(bool currentOnly) { + bool success = currentOnly ? this->editor->saveCurrent() : this->editor->saveAll(); + if (!success) { + RecentErrorMessage::show(QStringLiteral("Failed to save some project changes."), this); } updateWindowTitle(); updateMapList(); - if (!porymapConfig.shownInGameReloadMessage) { + if (success && !porymapConfig.shownInGameReloadMessage) { // Show a one-time warning that the user may need to reload their map to see their new changes. InfoMessage::show(QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, " "the changes may not appear until you leave the map and return."), @@ -1652,6 +1651,7 @@ void MainWindow::save(bool currentOnly) { } saveGlobalConfigs(); + return success; } void MainWindow::duplicate() { @@ -3048,7 +3048,8 @@ bool MainWindow::closeProject() { auto reply = msgBox.exec(); if (reply == QMessageBox::Yes) { - save(); + if (!save()) + return false; } else if (reply == QMessageBox::No) { logWarn("Closing project with unsaved changes."); } else if (reply == QMessageBox::Cancel) { diff --git a/src/project.cpp b/src/project.cpp index 6f225ff6..f478f904 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -162,8 +162,10 @@ void Project::clearTilesetCache() { Map* Project::loadMap(const QString &mapName) { Map* map = this->maps.value(mapName); - if (!map) + if (!map) { + logError(QString("Unknown map name '%1'.").arg(mapName)); return nullptr; + } if (isMapLoaded(map)) return map; @@ -445,8 +447,13 @@ bool Project::loadLayout(Layout *layout) { Layout *Project::loadLayout(QString layoutId) { Layout *layout = this->mapLayouts.value(layoutId); - if (!layout || !loadLayout(layout)) { - logError(QString("Failed to load layout '%1'").arg(layoutId)); + if (!layout) { + logError(QString("Unknown layout ID '%1'.").arg(layoutId)); + return nullptr; + } + + if (!loadLayout(layout)) { + // Error should already be logged. return nullptr; } return layout; @@ -587,12 +594,12 @@ bool Project::readMapLayouts() { return true; } -void Project::saveMapLayouts() { +bool Project::saveMapLayouts() { QString layoutsFilepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_layouts); QFile layoutsFile(layoutsFilepath); if (!layoutsFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(layoutsFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(layoutsFilepath).arg(layoutsFile.errorString())); + return false; } OrderedJson::object layoutsObj; @@ -626,6 +633,7 @@ void Project::saveMapLayouts() { OrderedJsonDoc jsonDoc(&layoutJson); jsonDoc.dump(&layoutsFile); layoutsFile.close(); + return true; } void Project::ignoreWatchedFileTemporarily(QString filepath) { @@ -651,12 +659,12 @@ void Project::recordFileChange(const QString &filepath) { emit fileChanged(filepath); } -void Project::saveMapGroups() { +bool Project::saveMapGroups() { QString mapGroupsFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups)); QFile mapGroupsFile(mapGroupsFilepath); if (!mapGroupsFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(mapGroupsFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(mapGroupsFilepath).arg(mapGroupsFile.errorString())); + return false; } OrderedJson::object mapGroupsObj; @@ -686,14 +694,15 @@ void Project::saveMapGroups() { OrderedJsonDoc jsonDoc(&mapGroupJson); jsonDoc.dump(&mapGroupsFile); mapGroupsFile.close(); + return true; } -void Project::saveRegionMapSections() { +bool Project::saveRegionMapSections() { const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); QFile file(filepath); if (!file.open(QIODevice::WriteOnly)) { - logError(QString("Could not open '%1' for writing").arg(filepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(filepath).arg(file.errorString())); + return false; } OrderedJson::array mapSectionArray; @@ -727,16 +736,17 @@ void Project::saveRegionMapSections() { OrderedJsonDoc jsonDoc(&json); jsonDoc.dump(&file); file.close(); + return true; } -void Project::saveWildMonData() { - if (!this->wildEncountersLoaded) return; +bool Project::saveWildMonData() { + if (!this->wildEncountersLoaded) return true; QString wildEncountersJsonFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_wild_encounters)); QFile wildEncountersFile(wildEncountersJsonFilepath); if (!wildEncountersFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(wildEncountersJsonFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(wildEncountersJsonFilepath).arg(wildEncountersFile.errorString())); + return false; } OrderedJson::object wildEncountersObject; @@ -822,6 +832,7 @@ void Project::saveWildMonData() { OrderedJsonDoc jsonDoc(&encounterJson); jsonDoc.dump(&wildEncountersFile); wildEncountersFile.close(); + return true; } // For a map with a constant of 'MAP_FOO', returns a unique 'HEAL_LOCATION_FOO'. @@ -838,12 +849,12 @@ QString Project::getNewHealLocationName(const Map* map) const { return toUniqueIdentifier(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + idName); } -void Project::saveHealLocations() { +bool Project::saveHealLocations() { const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_heal_locations)); QFile file(filepath); if (!file.open(QIODevice::WriteOnly)) { - logError(QString("Could not open '%1' for writing").arg(filepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(filepath).arg(file.errorString())); + return false; } // Build the JSON data for output. @@ -886,17 +897,21 @@ void Project::saveHealLocations() { OrderedJsonDoc jsonDoc(&json); jsonDoc.dump(&file); file.close(); + return true; } -void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { - saveTilesetMetatileLabels(primaryTileset, secondaryTileset); - if (primaryTileset) - primaryTileset->save(); - if (secondaryTileset) - secondaryTileset->save(); +bool Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { + bool success = saveTilesetMetatileLabels(primaryTileset, secondaryTileset); + if (primaryTileset && !primaryTileset->save()) + success = false; + if (secondaryTileset && !secondaryTileset->save()) + success = false; + return success; } void Project::updateTilesetMetatileLabels(Tileset *tileset) { + if (!tileset) return; + // Erase old labels, then repopulate with new labels const QString prefix = tileset->getMetatileLabelPrefix(); this->metatileLabelsMap[tileset->name].clear(); @@ -931,11 +946,11 @@ QString Project::buildMetatileLabelsText(const QMap defines) return output; } -void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *secondaryTileset) { +bool Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *secondaryTileset) { // Skip writing the file if there are no labels in both the new and old sets - if (metatileLabelsMap[primaryTileset->name].size() == 0 && primaryTileset->metatileLabels.size() == 0 - && metatileLabelsMap[secondaryTileset->name].size() == 0 && secondaryTileset->metatileLabels.size() == 0) - return; + if ((!primaryTileset || (metatileLabelsMap[primaryTileset->name].size() == 0 && primaryTileset->metatileLabels.size() == 0)) + && (!secondaryTileset || (metatileLabelsMap[secondaryTileset->name].size() == 0 && secondaryTileset->metatileLabels.size() == 0))) + return true; updateTilesetMetatileLabels(primaryTileset); updateTilesetMetatileLabels(secondaryTileset); @@ -962,42 +977,23 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels); ignoreWatchedFileTemporarily(root + "/" + filename); - saveTextFile(root + "/" + filename, outputText); + return saveTextFile(root + "/" + filename, outputText); } bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); - if (!layout->tileset_primary) { - QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); - layout->tileset_primary_label = defaultTileset; - layout->tileset_primary = getTileset(layout->tileset_primary_label); - if (!layout->tileset_primary) { - logError(QString("%1 has invalid primary tileset '%2'.").arg(layout->name).arg(layout->tileset_primary_label)); - return false; - } - logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset)); - } - layout->tileset_secondary = getTileset(layout->tileset_secondary_label); - if (!layout->tileset_secondary) { - QString defaultTileset = this->getDefaultSecondaryTilesetLabel(); - layout->tileset_secondary_label = defaultTileset; - layout->tileset_secondary = getTileset(layout->tileset_secondary_label); - if (!layout->tileset_secondary) { - logError(QString("%1 has invalid secondary tileset '%2'.").arg(layout->name).arg(layout->tileset_secondary_label)); - return false; - } - logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset)); - } - return true; + return layout->tileset_primary && layout->tileset_secondary; } Tileset* Project::loadTileset(QString label, Tileset *tileset) { auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets); if (this->usingAsmTilesets) { // Read asm tileset header. Backwards compatibility - const QStringList values = parser.getLabelValues(parser.parseAsm(projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm)), label); + const QString path = projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm); + const QStringList values = parser.getLabelValues(parser.parseAsm(path), label); if (values.isEmpty()) { + logError(QString("Failed to find header data in '%1' for tileset '%2'.").arg(path).arg(label)); return nullptr; } if (tileset == nullptr) { @@ -1011,8 +1007,10 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->metatile_attrs_label = values.value(memberMap.key("metatileAttributes")); } else { // Read C tileset header - auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap); + const QString path = projectConfig.getFilePath(ProjectFilePath::tilesets_headers); + auto structs = parser.readCStructs(path, label, memberMap); if (!structs.contains(label)) { + logError(QString("Failed to find header data in '%1' for tileset '%2'.").arg(path).arg(label)); return nullptr; } if (tileset == nullptr) { @@ -1027,7 +1025,11 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->metatile_attrs_label = tilesetAttributes.value("metatileAttributes"); } - loadTilesetAssets(tileset); + if (!loadTilesetAssets(tileset)) { + // Error should already be logged. + delete tileset; + return nullptr; + } tilesetCache.insert(label, tileset); return tileset; @@ -1116,38 +1118,22 @@ void Project::setNewLayoutBorder(Layout *layout) { layout->lastCommitBlocks.borderDimensions = QSize(width, height); } -void Project::saveLayoutBorder(Layout *layout) { - QString path = QString("%1/%2").arg(root).arg(layout->border_path); - writeBlockdata(path, layout->border); -} - -void Project::saveLayoutBlockdata(Layout *layout) { - QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); - writeBlockdata(path, layout->blockdata); -} - -void Project::writeBlockdata(QString path, const Blockdata &blockdata) { - QFile file(path); - if (file.open(QIODevice::WriteOnly)) { - QByteArray data = blockdata.serialize(); - file.write(data); - } else { - logError(QString("Failed to open blockdata file for writing: '%1'").arg(path)); - } -} - -void Project::saveAll() { +bool Project::saveAll() { + bool success = true; for (auto map : this->maps) { - saveMap(map, true); // Avoid double-saving the layouts + if (!saveMap(map, true)) // Avoid double-saving the layouts + success = false; } for (auto layout : this->mapLayouts) { - saveLayout(layout); + if (!saveLayout(layout)) + success = false; } - saveGlobalData(); + if (!saveGlobalData()) success = false; + return success; } -void Project::saveMap(Map *map, bool skipLayout) { - if (!map || !isMapLoaded(map)) return; +bool Project::saveMap(Map *map, bool skipLayout) { + if (!map || !isMapLoaded(map)) return true; // Create/Modify a few collateral files for brand new maps. const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); @@ -1155,7 +1141,7 @@ void Project::saveMap(Map *map, bool skipLayout) { if (!map->isPersistedToFile()) { if (!QDir::root().mkpath(fullPath)) { logError(QString("Failed to create directory for new map: '%1'").arg(fullPath)); - return; + return false; } // Create file data/maps//scripts.inc @@ -1179,8 +1165,8 @@ void Project::saveMap(Map *map, bool skipLayout) { QString mapFilepath = fullPath + "/map.json"; QFile mapFile(mapFilepath); if (!mapFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(mapFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(mapFilepath).arg(mapFile.errorString())); + return false; } OrderedJson::object mapObj; @@ -1276,72 +1262,61 @@ void Project::saveMap(Map *map, bool skipLayout) { jsonDoc.dump(&mapFile); mapFile.close(); - if (!skipLayout) saveLayout(map->layout()); - // Try to record the MAPSEC name in case this is a new name. addNewMapsec(map->header()->location()); - map->setClean(); + + if (!skipLayout && !saveLayout(map->layout())) + return false; + return true; } -void Project::saveLayout(Layout *layout) { +bool Project::saveLayout(Layout *layout) { if (!layout || !isLayoutLoaded(layout)) - return; + return true; - if (!layout->newFolderPath.isEmpty()) { - // Layout directory doesn't exist yet, create it now. - const QString fullPath = QString("%1/%2").arg(this->root).arg(layout->newFolderPath); - if (!QDir::root().mkpath(fullPath)) { - logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); - return; - } - layout->newFolderPath = QString(); - } - - saveLayoutBorder(layout); - saveLayoutBlockdata(layout); + if (!layout->save(this->root)) + return false; // Update global data structures with current map data. - updateLayout(layout); - - layout->setClean(); -} - -void Project::updateLayout(Layout *layout) { if (!this->layoutIdsMaster.contains(layout->id)) { this->layoutIdsMaster.append(layout->id); } if (this->mapLayoutsMaster.contains(layout->id)) { this->mapLayoutsMaster[layout->id]->copyFrom(layout); - } - else { + } else { this->mapLayoutsMaster.insert(layout->id, layout->copy()); } + return true; } -void Project::saveGlobalData() { - saveMapLayouts(); - saveMapGroups(); - saveRegionMapSections(); - saveHealLocations(); - saveWildMonData(); - saveConfig(); +bool Project::saveGlobalData() { + bool success = true; + if (!saveMapLayouts()) success = false; + if (!saveMapGroups()) success = false; + if (!saveRegionMapSections()) success = false; + if (!saveHealLocations()) success = false; + if (!saveWildMonData()) success = false; + if (!saveConfig()) success = false; + if (!success) + return false; + this->hasUnsavedDataChanges = false; + return true; } -void Project::saveConfig() { - projectConfig.save(); - userConfig.save(); +bool Project::saveConfig() { + bool success = true; + if (!projectConfig.save()) success = false; + if (!userConfig.save()) success = false; + return success; } -void Project::loadTilesetAssets(Tileset* tileset) { - if (tileset->name.isNull()) { - return; - } +bool Project::loadTilesetAssets(Tileset* tileset) { readTilesetPaths(tileset); loadTilesetMetatileLabels(tileset); - tileset->load(); + return tileset->load(); } void Project::readTilesetPaths(Tileset* tileset) { @@ -1535,6 +1510,8 @@ bool Project::readTilesetMetatileLabels() { } void Project::loadTilesetMetatileLabels(Tileset* tileset) { + if (!tileset || tileset->name.isEmpty()) return; + QString metatileLabelPrefix = tileset->getMetatileLabelPrefix(); // Reverse map for faster lookup by metatile id @@ -1576,29 +1553,24 @@ Tileset* Project::getTileset(QString label, bool forceLoad) { } } -void Project::saveTextFile(QString path, QString text) { +bool Project::saveTextFile(const QString &path, const QString &text) { QFile file(path); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - } else { + if (!file.open(QIODevice::WriteOnly)) { logError(QString("Could not open '%1' for writing: ").arg(path) + file.errorString()); + return false; } + file.write(text.toUtf8()); + return true; } -void Project::appendTextFile(QString path, QString text) { +bool Project::appendTextFile(const QString &path, const QString &text) { QFile file(path); - if (file.open(QIODevice::Append)) { - file.write(text.toUtf8()); - } else { + if (!file.open(QIODevice::Append)) { logError(QString("Could not open '%1' for appending: ").arg(path) + file.errorString()); + return false; } -} - -void Project::deleteFile(QString path) { - QFile file(path); - if (file.exists() && !file.remove()) { - logError(QString("Could not delete file '%1': ").arg(path) + file.errorString()); - } + file.write(text.toUtf8()); + return true; } bool Project::readWildMonData() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 3d610fb9..d21cb981 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -33,6 +33,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) this->tileYFlip = ui->checkBox_yFlip->isChecked(); this->paletteId = ui->spinBox_paletteSelector->value(); + connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save); + ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes); @@ -94,7 +96,7 @@ void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondar QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes); if (result == QMessageBox::Yes) - this->on_actionSave_Tileset_triggered(); + this->save(); } this->setTilesets(primaryTilesetLabel, secondaryTilesetLabel); this->refresh(); @@ -688,19 +690,23 @@ void TilesetEditor::commitLayerType() { this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear } -void TilesetEditor::on_actionSave_Tileset_triggered() -{ +bool TilesetEditor::save() { // Need this temporary flag to stop selection resetting after saving. // This is a workaround; redrawing the map's metatile selector shouldn't emit the same signal as when it's selected. this->lockSelection = true; - this->project->saveTilesets(this->primaryTileset, this->secondaryTileset); + + bool success = this->project->saveTilesets(this->primaryTileset, this->secondaryTileset); emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name); if (this->paletteEditor) { this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset); } - this->ui->statusbar->showMessage(QString("Saved primary and secondary Tilesets!"), 5000); - this->hasUnsavedChanges = false; + this->ui->statusbar->showMessage(success ? QStringLiteral("Saved primary and secondary Tilesets!") + : QStringLiteral("Failed to save tilesets! See log for details."), 5000); + if (success) { + this->hasUnsavedChanges = false; + } this->lockSelection = false; + return success; } void TilesetEditor::on_actionImport_Primary_Tiles_triggered() @@ -812,8 +818,11 @@ void TilesetEditor::closeEvent(QCloseEvent *event) QMessageBox::Yes); if (result == QMessageBox::Yes) { - this->on_actionSave_Tileset_triggered(); - event->accept(); + if (this->save()) { + event->accept(); + } else { + event->ignore(); + } } else if (result == QMessageBox::No) { this->reset(); event->accept(); From 428693a6c9ce41b6da4fa574ce810b2a41f9cf9c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 16 Apr 2025 13:44:46 -0400 Subject: [PATCH 307/364] Remove now-unnecessary tileset loading --- src/ui/tileseteditor.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index d21cb981..f9a09ce8 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -1152,12 +1152,6 @@ void TilesetEditor::countMetatileUsage() { this->metatileSelector->usedMetatiles.fill(0); for (auto layout : this->project->mapLayouts) { - // It's possible for a layout's tileset labels to change if they are invalid, - // so we need to load all the tilesets even if they aren't the tileset we're looking for. - // Otherwise the metatile usage counts may change because the layouts with invalid tilesets - // were updated to use a tileset we were looking for. - this->project->loadLayoutTilesets(layout); - bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name); bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name); @@ -1196,10 +1190,10 @@ void TilesetEditor::countTileUsage() { QSet secondaryTilesets; for (auto &layout : this->project->mapLayouts) { - this->project->loadLayoutTilesets(layout); if (layout->tileset_primary_label == this->primaryTileset->name || layout->tileset_secondary_label == this->secondaryTileset->name) { // need to check metatiles + this->project->loadLayoutTilesets(layout); if (layout->tileset_primary && layout->tileset_secondary) { primaryTilesets.insert(layout->tileset_primary); secondaryTilesets.insert(layout->tileset_secondary); From db246873600d126f4cc6085186715988a491ac5e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 20 Feb 2025 12:01:55 -0500 Subject: [PATCH 308/364] Add player view size settings --- forms/projectsettingseditor.ui | 43 ++++++++++++++++++++++++++++++++ include/config.h | 8 +++--- include/editor.h | 1 + include/ui/movablerect.h | 4 +-- src/config.cpp | 14 ++++++++--- src/editor.cpp | 16 ++++++++++-- src/mainwindow.cpp | 2 ++ src/project.cpp | 4 +-- src/ui/projectsettingseditor.cpp | 12 ++++++--- 9 files changed, 86 insertions(+), 18 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 614af3ea..696df9ac 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -280,6 +280,49 @@ + + + + Player View Size + + + + + + Width + + + + + + + <html><head/><body><p>The horizontal size in pixels of the area that the player can see in-game (normally, the full width of the GBA screen).</p></body></html> + + + 16 + + + + + + + Height + + + + + + + <html><head/><body><p>The vertical size in pixels of the area that the player can see in-game (normally, the full height of the GBA screen).</p></body></html> + + + 16 + + + + + + diff --git a/include/config.h b/include/config.h index 9ee3785b..18422e09 100644 --- a/include/config.h +++ b/include/config.h @@ -331,8 +331,8 @@ public: this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); this->collisionSheetPath = QString(); - this->collisionSheetWidth = 2; - this->collisionSheetHeight = 16; + this->collisionSheetSize = QSize(2, 16); + this->playerViewSize = QSize(240, 160); this->blockMetatileIdMask = 0x03FF; this->blockCollisionMask = 0x0C00; this->blockElevationMask = 0xF000; @@ -408,8 +408,8 @@ public: uint16_t unusedTileSplit; bool mapAllowFlagsEnabled; QString collisionSheetPath; - int collisionSheetWidth; - int collisionSheetHeight; + QSize collisionSheetSize; + QSize playerViewSize; QList warpBehaviors; int maxEventsPerGroup; diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..b1429ef2 100644 --- a/include/editor.h +++ b/include/editor.h @@ -119,6 +119,7 @@ public: void redrawEventPixmapItem(DraggablePixmapItem *item); qreal getEventOpacity(const Event *event) const; + void setPlayerViewSize(const QSize &size); void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 56798a0c..87f7a36e 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -15,8 +15,8 @@ public: qreal penWidth = 4; return QRectF(-penWidth, -penWidth, - 30 * 8 + penWidth * 2, - 20 * 8 + penWidth * 2); + this->rect().width() + penWidth * 2, + this->rect().height() + penWidth * 2); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { diff --git a/src/config.cpp b/src/config.cpp index d6e0d329..8c139942 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -832,9 +832,13 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { - this->collisionSheetWidth = getConfigUint32(key, value, 1, Block::maxValue); + this->collisionSheetSize.setWidth(getConfigInteger(key, value, 1, Block::maxValue)); } else if (key == "collision_sheet_height") { - this->collisionSheetHeight = getConfigUint32(key, value, 1, Block::maxValue); + this->collisionSheetSize.setHeight(getConfigInteger(key, value, 1, Block::maxValue)); + } else if (key == "player_view_width") { + this->playerViewSize.setWidth(getConfigInteger(key, value, 16, INT_MAX, 240)); + } else if (key == "player_view_height") { + this->playerViewSize.setHeight(getConfigInteger(key, value, 16, INT_MAX, 160)); } else if (key == "warp_behaviors") { this->warpBehaviors.clear(); value.remove(" "); @@ -935,8 +939,10 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); } map.insert("collision_sheet_path", this->collisionSheetPath); - map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth)); - map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight)); + map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); + map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); + map.insert("player_view_width", QString::number(this->playerViewSize.width())); + map.insert("player_view_height", QString::number(this->playerViewSize.height())); QStringList warpBehaviorStrs; for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..db4d4694 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1061,6 +1061,18 @@ void Editor::scaleMapView(int s) { ui->graphicsView_Connections->setTransform(transform); } +void Editor::setPlayerViewSize(const QSize &size) { + if (!this->playerViewRect) + return; + + auto rect = this->playerViewRect->rect(); + rect.setWidth(qMax(size.width(), 16)); + rect.setHeight(qMax(size.height(), 16)); + this->playerViewRect->setRect(rect); + if (ui->graphicsView_Map->scene()) + ui->graphicsView_Map->scene()->update(); +} + void Editor::updateCursorRectPos(int x, int y) { if (this->playerViewRect) this->playerViewRect->updateLocation(x, y); @@ -2338,8 +2350,8 @@ void Editor::setCollisionGraphics() { // Users are not required to provide an image that gives an icon for every elevation/collision combination. // Instead they tell us how many are provided in their image by specifying the number of columns and rows. - const int imgColumns = projectConfig.collisionSheetWidth; - const int imgRows = projectConfig.collisionSheetHeight; + const int imgColumns = projectConfig.collisionSheetSize.width(); + const int imgRows = projectConfig.collisionSheetSize.height(); // Create a pixmap for the selector on the Collision tab. If a project was previously opened we'll also need to refresh the selector. this->collisionSheetPixmap = QPixmap::fromImage(imgSheet).scaled(MovementPermissionsSelector::CellWidth * imgColumns, diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..076f8f8f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1171,6 +1171,8 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled); + this->editor->setPlayerViewSize(projectConfig.playerViewSize); + editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); diff --git a/src/project.cpp b/src/project.cpp index d209a890..158d4f02 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3268,8 +3268,8 @@ void Project::applyParsedLimits() { projectConfig.defaultMetatileId = qMin(projectConfig.defaultMetatileId, Block::getMaxMetatileId()); projectConfig.defaultElevation = qMin(projectConfig.defaultElevation, Block::getMaxElevation()); projectConfig.defaultCollision = qMin(projectConfig.defaultCollision, Block::getMaxCollision()); - projectConfig.collisionSheetHeight = qMin(qMax(projectConfig.collisionSheetHeight, 1), Block::getMaxElevation() + 1); - projectConfig.collisionSheetWidth = qMin(qMax(projectConfig.collisionSheetWidth, 1), Block::getMaxCollision() + 1); + projectConfig.collisionSheetSize.setHeight(qMin(qMax(projectConfig.collisionSheetSize.height(), 1), Block::getMaxElevation() + 1)); + projectConfig.collisionSheetSize.setWidth(qMin(qMax(projectConfig.collisionSheetSize.width(), 1), Block::getMaxCollision() + 1)); } bool Project::hasUnsavedChanges() { diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index e1c2becf..8b47913a 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -138,6 +138,8 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_MaxEvents->setMaximum(INT_MAX); ui->spinBox_MapWidth->setMaximum(INT_MAX); ui->spinBox_MapHeight->setMaximum(INT_MAX); + ui->spinBox_PlayerViewWidth->setMaximum(INT_MAX); + ui->spinBox_PlayerViewHeight->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -460,8 +462,8 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_FillMetatile->setValue(projectConfig.defaultMetatileId); ui->spinBox_MapWidth->setValue(projectConfig.defaultMapSize.width()); ui->spinBox_MapHeight->setValue(projectConfig.defaultMapSize.height()); - ui->spinBox_MaxElevation->setValue(projectConfig.collisionSheetHeight - 1); - ui->spinBox_MaxCollision->setValue(projectConfig.collisionSheetWidth - 1); + ui->spinBox_MaxElevation->setValue(projectConfig.collisionSheetSize.height() - 1); + ui->spinBox_MaxCollision->setValue(projectConfig.collisionSheetSize.width() - 1); ui->spinBox_BehaviorMask->setValue(projectConfig.metatileBehaviorMask & ui->spinBox_BehaviorMask->maximum()); ui->spinBox_EncounterTypeMask->setValue(projectConfig.metatileEncounterTypeMask & ui->spinBox_EncounterTypeMask->maximum()); ui->spinBox_LayerTypeMask->setValue(projectConfig.metatileLayerTypeMask & ui->spinBox_LayerTypeMask->maximum()); @@ -473,6 +475,8 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup); + ui->spinBox_PlayerViewWidth->setValue(projectConfig.playerViewSize.width()); + ui->spinBox_PlayerViewHeight->setValue(projectConfig.playerViewSize.height()); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -537,8 +541,7 @@ void ProjectSettingsEditor::save() { projectConfig.defaultCollision = ui->spinBox_Collision->value(); projectConfig.defaultMetatileId = ui->spinBox_FillMetatile->value(); projectConfig.defaultMapSize = QSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); - projectConfig.collisionSheetHeight = ui->spinBox_MaxElevation->value() + 1; - projectConfig.collisionSheetWidth = ui->spinBox_MaxCollision->value() + 1; + projectConfig.collisionSheetSize = QSize(ui->spinBox_MaxElevation->value() + 1, ui->spinBox_MaxCollision->value() + 1); projectConfig.metatileBehaviorMask = ui->spinBox_BehaviorMask->value(); projectConfig.metatileTerrainTypeMask = ui->spinBox_TerrainTypeMask->value(); projectConfig.metatileEncounterTypeMask = ui->spinBox_EncounterTypeMask->value(); @@ -550,6 +553,7 @@ void ProjectSettingsEditor::save() { projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value(); + projectConfig.playerViewSize = QSize(ui->spinBox_PlayerViewWidth->value(), ui->spinBox_PlayerViewHeight->value()); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); From a6ec91724f84e91427b8aa4faebea19d51962cb1 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 16 Apr 2025 15:01:58 -0400 Subject: [PATCH 309/364] Replace BORDER_DISTANCE with actual view distance --- include/core/map.h | 4 ---- include/core/maplayout.h | 1 + include/project.h | 2 ++ include/ui/movablerect.h | 8 ++++---- src/core/map.cpp | 9 +++++---- src/core/maplayout.cpp | 20 +++++++++++++++----- src/editor.cpp | 25 ++++++------------------- src/project.cpp | 15 +++++++++++++++ src/ui/mapimageexporter.cpp | 12 ++++++------ 9 files changed, 54 insertions(+), 42 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index aaf5b8b7..8c4bead4 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -22,10 +22,6 @@ #define MAX_BORDER_WIDTH 255 #define MAX_BORDER_HEIGHT 255 -// Number of metatiles to draw out from edge of map. Could allow modification of this in the future. -// porymap will reflect changes to it, but the value is hard-coded in the projects at the moment -#define BORDER_DISTANCE 7 - class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 40a3a035..8d1d64bc 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -98,6 +98,7 @@ public: int getBorderHeight() const { return border_height; } int getBorderDrawWidth() const; int getBorderDrawHeight() const; + QRect getVisibleRect() const; bool isWithinBounds(int x, int y) const; bool isWithinBounds(const QRect &rect) const; diff --git a/include/project.h b/include/project.h index 93e6b162..b9a94776 100644 --- a/include/project.h +++ b/include/project.h @@ -256,6 +256,8 @@ public: static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); + static QSize getViewDistance(); + static QSize getMetatileViewDistance(); static int getNumTilesPrimary() { return num_tiles_primary; } static int getNumTilesTotal() { return num_tiles_total; } static int getNumMetatilesPrimary() { return num_metatiles_primary; } diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 87f7a36e..a9f15917 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -22,10 +22,10 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { if (!(*enabled)) return; painter->setPen(this->color); - painter->drawRect(this->rect().x() - 2, this->rect().y() - 2, this->rect().width() + 3, this->rect().height() + 3); - painter->setPen(QColor(0, 0, 0)); - painter->drawRect(this->rect().x() - 3, this->rect().y() - 3, this->rect().width() + 5, this->rect().height() + 5); - painter->drawRect(this->rect().x() - 1, this->rect().y() - 1, this->rect().width() + 1, this->rect().height() + 1); + painter->drawRect(this->rect() + QMargins(1,1,1,1)); // Fill + painter->setPen(Qt::black); + painter->drawRect(this->rect() + QMargins(2,2,2,2)); // Outer border + painter->drawRect(this->rect()); // Inner border } void updateLocation(int x, int y); bool *enabled; diff --git a/src/core/map.cpp b/src/core/map.cpp index b9fe4c90..793090c0 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -83,16 +83,17 @@ QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) cons int x = 0, y = 0; int w = getWidth(), h = getHeight(); + QSize viewDistance = Project::getMetatileViewDistance(); if (direction == "up") { - h = qMin(h, BORDER_DISTANCE); + h = qMin(h, viewDistance.height()); y = getHeight() - h; } else if (direction == "down") { - h = qMin(h, BORDER_DISTANCE); + h = qMin(h, viewDistance.height()); } else if (direction == "left") { - w = qMin(w, BORDER_DISTANCE); + w = qMin(w, viewDistance.width()); x = getWidth() - w; } else if (direction == "right") { - w = qMin(w, BORDER_DISTANCE); + w = qMin(w, viewDistance.width()); } else if (MapConnection::isDiving(direction)) { if (fromLayout) { w = qMin(w, fromLayout->getWidth()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 45b35f91..e70d8040 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -64,16 +64,18 @@ bool Layout::isWithinBorderBounds(int x, int y) const { } int Layout::getBorderDrawWidth() const { - return getBorderDrawDistance(border_width, BORDER_DISTANCE); + return getBorderDrawDistance(border_width, Project::getMetatileViewDistance().width()); } int Layout::getBorderDrawHeight() const { - return getBorderDrawDistance(border_height, BORDER_DISTANCE); + return getBorderDrawDistance(border_height, Project::getMetatileViewDistance().height()); } -// We need to draw sufficient border blocks to fill the area that gets loaded around the player in-game (BORDER_DISTANCE). -// Note that this is not the same as the player's view distance. -// The result will be some multiple of the input dimension, because we only draw the border in increments of its full width/height. +// Calculate the distance away from the layout's edge that we need to start drawing border blocks. +// We need to fulfill two requirements here: +// - We should draw enough to fill the player's in-game view +// - The value should be some multiple of the border's dimension +// (otherwise the border won't be positioned the same as it would in-game). int Layout::getBorderDrawDistance(int dimension, qreal minimum) { if (dimension >= minimum) return dimension; @@ -82,6 +84,14 @@ int Layout::getBorderDrawDistance(int dimension, qreal minimum) { return dimension * qCeil(minimum / qMax(dimension, 1)); } +// Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border. +QRect Layout::getVisibleRect() const { + QRect area = QRect(0, 0, this->width * 16, this->height * 16); + QSize viewDistance = Project::getMetatileViewDistance() * 16; + area += QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); + return area; +} + bool Layout::getBlock(int x, int y, Block *out) { if (isWithinBounds(x, y)) { int i = y * getWidth() + x; diff --git a/src/editor.cpp b/src/editor.cpp index db4d4694..54fc9640 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1572,14 +1572,8 @@ void Editor::displayMapMetatiles() { map_item->draw(true); scene->addItem(map_item); - int tw = 16; - int th = 16; - scene->setSceneRect( - -BORDER_DISTANCE * tw, - -BORDER_DISTANCE * th, - map_item->pixmap().width() + BORDER_DISTANCE * 2 * tw, - map_item->pixmap().height() + BORDER_DISTANCE * 2 * th - ); + // Scene rect is the map plus a margin that gives enough space to scroll and see the edge of the player view rectangle. + scene->setSceneRect(this->layout->getVisibleRect() + QMargins(3,3,3,3)); } void Editor::clearMapMovementPermissions() { @@ -1772,18 +1766,13 @@ void Editor::clearConnectionMask() { } } -// Hides connected map tiles that cannot be seen from the current map (beyond BORDER_DISTANCE). +// Hides connected map tiles that cannot be seen from the current map void Editor::maskNonVisibleConnectionTiles() { clearConnectionMask(); QPainterPath mask; mask.addRect(scene->itemsBoundingRect().toRect()); - mask.addRect( - -BORDER_DISTANCE * 16, - -BORDER_DISTANCE * 16, - (layout->getWidth() + BORDER_DISTANCE * 2) * 16, - (layout->getHeight() + BORDER_DISTANCE * 2) * 16 - ); + mask.addRect(layout->getVisibleRect()); // Mask the tiles with the current theme's background color. QPen pen(ui->graphicsView_Map->palette().color(QPalette::Active, QPalette::Base)); @@ -1805,13 +1794,11 @@ void Editor::clearMapBorder() { void Editor::displayMapBorder() { clearMapBorder(); - int borderWidth = this->layout->getBorderWidth(); - int borderHeight = this->layout->getBorderHeight(); int borderHorzDist = this->layout->getBorderDrawWidth(); int borderVertDist = this->layout->getBorderDrawHeight(); QPixmap pixmap = this->layout->renderBorder(); - for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += borderHeight) - for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += borderWidth) { + for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += this->layout->getBorderHeight()) + for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += this->layout->getBorderWidth()) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); diff --git a/src/project.cpp b/src/project.cpp index 158d4f02..d91d63da 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3235,6 +3235,21 @@ QString Project::getEmptySpeciesName() { return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty); } +// Get the distance in pixels that the player is able to see from the space they're standing on. +// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 112x72. +QSize Project::getViewDistance() { + return ((projectConfig.playerViewSize) - QSize(16,16)) / 2; +} + +// Get the distance in metatiles that the player is able to see from the space they're standing on, rounded up. +// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 7x5 metatiles. +QSize Project::getMetatileViewDistance() { + QSize viewDistance = getViewDistance(); + viewDistance.setWidth(qCeil(viewDistance.width() / 16.0)); + viewDistance.setHeight(qCeil(viewDistance.height() / 16.0)); + return viewDistance; +} + // If the provided filepath is an absolute path to an existing file, return filepath. // If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path. // Otherwise return empty string. diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 58b63cbd..9a65af8f 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -606,9 +606,11 @@ QPixmap MapImageExporter::getFormattedMapPixmap() { QMargins MapImageExporter::getMargins(const Map *map) { QMargins margins; if (m_settings.showBorder) { - // The border may technically extend beyond BORDER_DISTANCE, but when the border is painted - // we will be limiting it to the visible sight range. - margins = QMargins(BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE) * 16; + // When we render map borders we render them in full increments of the border dimensions. + // This means for large border dimensions the painted area of the border may extend well beyond the area the player can see. + // When we call paintBorder we will clip the painting to this visible area, so we only need to consider the visible area here. + QSize viewDistance = m_project->getMetatileViewDistance() * 16; + margins = QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); } else if (map && connectionsEnabled()) { for (const auto &connection : map->getConnections()) { const QString dir = connection->direction(); @@ -649,10 +651,8 @@ void MapImageExporter::paintBorder(QPainter *painter, Layout *layout) { layout->renderBorder(true); // Clip parts of the border that would be beyond player visibility. - QRect visibleArea(0, 0, layout->getWidth() * 16, layout->getHeight() * 16); - visibleArea += (QMargins(BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE) * 16); painter->save(); - painter->setClipRect(visibleArea); + painter->setClipRect(layout->getVisibleRect()); int borderHorzDist = layout->getBorderDrawWidth(); int borderVertDist = layout->getBorderDrawHeight(); From 5d475513d50da50ca1dc21b70b3b97cbe90e5f4d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 11:21:23 -0400 Subject: [PATCH 310/364] Allow off-center player view size --- forms/projectsettingseditor.ui | 142 ++++++++++++++++++++----------- include/config.h | 12 +-- include/core/maplayout.h | 3 +- include/editor.h | 2 +- include/project.h | 3 +- include/ui/movablerect.h | 3 +- src/config.cpp | 26 ++++-- src/core/map.cpp | 10 +-- src/core/maplayout.cpp | 23 ++--- src/editor.cpp | 19 ++--- src/mainwindow.cpp | 2 +- src/project.cpp | 20 ++--- src/ui/mapimageexporter.cpp | 13 +-- src/ui/movablerect.cpp | 16 ++-- src/ui/projectsettingseditor.cpp | 17 ++-- 15 files changed, 180 insertions(+), 131 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 696df9ac..fe065be9 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -6,8 +6,8 @@ 0 0 - 631 - 600 + 642 + 609 @@ -38,8 +38,8 @@ 0 0 - 559 - 589 + 570 + 692 @@ -281,44 +281,86 @@ - + - Player View Size + Player View Distance - - - - - Width - - + + + + + + + North + + + + + + + South + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see North of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see South of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + + + - - - - <html><head/><body><p>The horizontal size in pixels of the area that the player can see in-game (normally, the full width of the GBA screen).</p></body></html> - - - 16 - - - - - - - Height - - - - - - - <html><head/><body><p>The vertical size in pixels of the area that the player can see in-game (normally, the full height of the GBA screen).</p></body></html> - - - 16 - - + + + + + + West + + + + + + + East + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see West of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see East of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + + + @@ -421,7 +463,7 @@ 0 0 - 559 + 561 622 @@ -792,7 +834,7 @@ 0 0 - 559 + 561 798 @@ -1134,7 +1176,7 @@ 0 0 - 559 + 561 840 @@ -1516,8 +1558,8 @@ 0 0 - 559 - 490 + 561 + 593 @@ -1563,8 +1605,8 @@ 0 0 - 533 - 428 + 535 + 531 @@ -1605,8 +1647,8 @@ 0 0 - 559 - 490 + 561 + 593 @@ -1652,8 +1694,8 @@ 0 0 - 533 - 428 + 535 + 531 diff --git a/include/config.h b/include/config.h index 18422e09..fe62377f 100644 --- a/include/config.h +++ b/include/config.h @@ -15,11 +15,11 @@ #include "events.h" -static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION); +extern const QVersionNumber porymapVersion; -// In both versions the default new map border is a generic tree -#define DEFAULT_BORDER_RSE (QList{0x1D4, 0x1D5, 0x1DC, 0x1DD}) -#define DEFAULT_BORDER_FRLG (QList{0x14, 0x15, 0x1C, 0x1D}) +// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels. +#define GBA_H_DIST_TO_CENTER ((240-16)/2) +#define GBA_V_DIST_TO_CENTER ((160-16)/2) #define CONFIG_BACKWARDS_COMPATABILITY @@ -332,7 +332,7 @@ public: this->pokemonIconPaths.clear(); this->collisionSheetPath = QString(); this->collisionSheetSize = QSize(2, 16); - this->playerViewSize = QSize(240, 160); + this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER); this->blockMetatileIdMask = 0x03FF; this->blockCollisionMask = 0x0C00; this->blockElevationMask = 0xF000; @@ -409,7 +409,7 @@ public: bool mapAllowFlagsEnabled; QString collisionSheetPath; QSize collisionSheetSize; - QSize playerViewSize; + QMargins playerViewDistance; QList warpBehaviors; int maxEventsPerGroup; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 8d1d64bc..8d8bb2e2 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -96,8 +96,7 @@ public: int getHeight() const { return height; } int getBorderWidth() const { return border_width; } int getBorderHeight() const { return border_height; } - int getBorderDrawWidth() const; - int getBorderDrawHeight() const; + QMargins getBorderMargins() const; QRect getVisibleRect() const; bool isWithinBounds(int x, int y) const; diff --git a/include/editor.h b/include/editor.h index b1429ef2..4a945daa 100644 --- a/include/editor.h +++ b/include/editor.h @@ -119,7 +119,7 @@ public: void redrawEventPixmapItem(DraggablePixmapItem *item); qreal getEventOpacity(const Event *event) const; - void setPlayerViewSize(const QSize &size); + void setPlayerViewRect(const QRectF &rect); void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); diff --git a/include/project.h b/include/project.h index b9a94776..0c67e85d 100644 --- a/include/project.h +++ b/include/project.h @@ -256,8 +256,7 @@ public: static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); - static QSize getViewDistance(); - static QSize getMetatileViewDistance(); + static QMargins getMetatileViewDistance(); static int getNumTilesPrimary() { return num_tiles_primary; } static int getNumTilesTotal() { return num_tiles_total; } static int getNumMetatilesPrimary() { return num_metatiles_primary; } diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index a9f15917..21edd21d 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -10,7 +10,7 @@ class MovableRect : public QGraphicsRectItem { public: - MovableRect(bool *enabled, int width, int height, QRgb color); + MovableRect(bool *enabled, const QRectF &rect, const QRgb &color); QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, @@ -31,6 +31,7 @@ public: bool *enabled; protected: + QRectF baseRect; QRgb color; }; diff --git a/src/config.cpp b/src/config.cpp index 8c139942..b8ad1232 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -18,6 +18,12 @@ #include #include +const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION); + +// In both versions the default new map border is a generic tree +const QList defaultBorder_RSE = {0x1D4, 0x1D5, 0x1DC, 0x1DD}; +const QList defaultBorder_FRLG = {0x14, 0x15, 0x1C, 0x1D}; + const QList defaultWarpBehaviors_RSE = { 0x0E, // MB_MOSSDEEP_GYM_WARP 0x0F, // MB_MT_PYRE_HOLE @@ -835,10 +841,14 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->collisionSheetSize.setWidth(getConfigInteger(key, value, 1, Block::maxValue)); } else if (key == "collision_sheet_height") { this->collisionSheetSize.setHeight(getConfigInteger(key, value, 1, Block::maxValue)); - } else if (key == "player_view_width") { - this->playerViewSize.setWidth(getConfigInteger(key, value, 16, INT_MAX, 240)); - } else if (key == "player_view_height") { - this->playerViewSize.setHeight(getConfigInteger(key, value, 16, INT_MAX, 160)); + } else if (key == "player_view_north") { + this->playerViewDistance.setTop(getConfigInteger(key, value, 0, INT_MAX, GBA_V_DIST_TO_CENTER)); + } else if (key == "player_view_south") { + this->playerViewDistance.setBottom(getConfigInteger(key, value, 0, INT_MAX, GBA_V_DIST_TO_CENTER)); + } else if (key == "player_view_west") { + this->playerViewDistance.setLeft(getConfigInteger(key, value, 0, INT_MAX, GBA_H_DIST_TO_CENTER)); + } else if (key == "player_view_east") { + this->playerViewDistance.setRight(getConfigInteger(key, value, 0, INT_MAX, GBA_H_DIST_TO_CENTER)); } else if (key == "warp_behaviors") { this->warpBehaviors.clear(); value.remove(" "); @@ -872,7 +882,7 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("enable_event_clone_object")) this->eventCloneObjectEnabled = isPokefirered; if (!readKeys.contains("enable_floor_number")) this->floorNumberEnabled = isPokefirered; if (!readKeys.contains("create_map_text_file")) this->createMapTextFileEnabled = (this->baseGameVersion != BaseGameVersion::pokeemerald); - if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE; + if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? defaultBorder_FRLG : defaultBorder_RSE; if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"; if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion); if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::Behavior); @@ -941,8 +951,10 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); - map.insert("player_view_width", QString::number(this->playerViewSize.width())); - map.insert("player_view_height", QString::number(this->playerViewSize.height())); + map.insert("player_view_north", QString::number(this->playerViewDistance.top())); + map.insert("player_view_south", QString::number(this->playerViewDistance.bottom())); + map.insert("player_view_west", QString::number(this->playerViewDistance.left())); + map.insert("player_view_east", QString::number(this->playerViewDistance.right())); QStringList warpBehaviorStrs; for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); diff --git a/src/core/map.cpp b/src/core/map.cpp index 793090c0..b067d6a5 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -83,17 +83,17 @@ QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) cons int x = 0, y = 0; int w = getWidth(), h = getHeight(); - QSize viewDistance = Project::getMetatileViewDistance(); + QMargins viewDistance = Project::getMetatileViewDistance(); if (direction == "up") { - h = qMin(h, viewDistance.height()); + h = qMin(h, viewDistance.top()); y = getHeight() - h; } else if (direction == "down") { - h = qMin(h, viewDistance.height()); + h = qMin(h, viewDistance.bottom()); } else if (direction == "left") { - w = qMin(w, viewDistance.width()); + w = qMin(w, viewDistance.left()); x = getWidth() - w; } else if (direction == "right") { - w = qMin(w, viewDistance.width()); + w = qMin(w, viewDistance.right()); } else if (MapConnection::isDiving(direction)) { if (fromLayout) { w = qMin(w, fromLayout->getWidth()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index e70d8040..b8c200af 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -63,14 +63,6 @@ bool Layout::isWithinBorderBounds(int x, int y) const { return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); } -int Layout::getBorderDrawWidth() const { - return getBorderDrawDistance(border_width, Project::getMetatileViewDistance().width()); -} - -int Layout::getBorderDrawHeight() const { - return getBorderDrawDistance(border_height, Project::getMetatileViewDistance().height()); -} - // Calculate the distance away from the layout's edge that we need to start drawing border blocks. // We need to fulfill two requirements here: // - We should draw enough to fill the player's in-game view @@ -83,13 +75,22 @@ int Layout::getBorderDrawDistance(int dimension, qreal minimum) { // Get first multiple of dimension >= the minimum return dimension * qCeil(minimum / qMax(dimension, 1)); } +QMargins Layout::getBorderMargins() const { + QMargins minimum = Project::getMetatileViewDistance(); + QMargins distance; + distance.setTop(getBorderDrawDistance(this->border_height, minimum.top())); + distance.setBottom(getBorderDrawDistance(this->border_height, minimum.bottom())); + distance.setLeft(getBorderDrawDistance(this->border_width, minimum.left())); + distance.setRight(getBorderDrawDistance(this->border_width, minimum.right())); + return distance; +} // Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border. +// At maximum, this is equal to the map size plus the border margins. +// If the border is large (and so beyond player the view) it may be smaller than that. QRect Layout::getVisibleRect() const { QRect area = QRect(0, 0, this->width * 16, this->height * 16); - QSize viewDistance = Project::getMetatileViewDistance() * 16; - area += QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); - return area; + return area += (Project::getMetatileViewDistance() * 16); } bool Layout::getBlock(int x, int y, Block *out) { diff --git a/src/editor.cpp b/src/editor.cpp index 54fc9640..d41a33a2 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -30,7 +30,6 @@ Editor::Editor(Ui::MainWindow* ui) { this->ui = ui; this->settings = new Settings(); - this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, 30 * 8, 20 * 8, qRgb(255, 255, 255)); this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255)); this->map_ruler = new MapRuler(4); connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged); @@ -1061,14 +1060,9 @@ void Editor::scaleMapView(int s) { ui->graphicsView_Connections->setTransform(transform); } -void Editor::setPlayerViewSize(const QSize &size) { - if (!this->playerViewRect) - return; - - auto rect = this->playerViewRect->rect(); - rect.setWidth(qMax(size.width(), 16)); - rect.setHeight(qMax(size.height(), 16)); - this->playerViewRect->setRect(rect); +void Editor::setPlayerViewRect(const QRectF &rect) { + delete this->playerViewRect; + this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, rect, qRgb(255, 255, 255)); if (ui->graphicsView_Map->scene()) ui->graphicsView_Map->scene()->update(); } @@ -1794,11 +1788,10 @@ void Editor::clearMapBorder() { void Editor::displayMapBorder() { clearMapBorder(); - int borderHorzDist = this->layout->getBorderDrawWidth(); - int borderVertDist = this->layout->getBorderDrawHeight(); QPixmap pixmap = this->layout->renderBorder(); - for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += this->layout->getBorderHeight()) - for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += this->layout->getBorderWidth()) { + const QMargins borderMargins = layout->getBorderMargins(); + for (int y = -borderMargins.top(); y < this->layout->getHeight() + borderMargins.bottom(); y += this->layout->getBorderHeight()) + for (int x = -borderMargins.left(); x < this->layout->getWidth() + borderMargins.right(); x += this->layout->getBorderWidth()) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 076f8f8f..24495350 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1171,7 +1171,7 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled); - this->editor->setPlayerViewSize(projectConfig.playerViewSize); + this->editor->setPlayerViewRect(QRectF(0, 0, 16, 16).marginsAdded(projectConfig.playerViewDistance)); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); diff --git a/src/project.cpp b/src/project.cpp index d91d63da..11d7b93c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3235,18 +3235,14 @@ QString Project::getEmptySpeciesName() { return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty); } -// Get the distance in pixels that the player is able to see from the space they're standing on. -// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 112x72. -QSize Project::getViewDistance() { - return ((projectConfig.playerViewSize) - QSize(16,16)) / 2; -} - -// Get the distance in metatiles that the player is able to see from the space they're standing on, rounded up. -// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 7x5 metatiles. -QSize Project::getMetatileViewDistance() { - QSize viewDistance = getViewDistance(); - viewDistance.setWidth(qCeil(viewDistance.width() / 16.0)); - viewDistance.setHeight(qCeil(viewDistance.height() / 16.0)); +// Get the distance in metatiles (rounded up) that the player is able to see in each direction in-game. +// For the default view distance (i.e. assuming the player is centered in a 240x160 pixel GBA screen) this is 7x5 metatiles. +QMargins Project::getMetatileViewDistance() { + QMargins viewDistance = projectConfig.playerViewDistance; + viewDistance.setTop(qCeil(viewDistance.top() / 16.0)); + viewDistance.setBottom(qCeil(viewDistance.bottom() / 16.0)); + viewDistance.setLeft(qCeil(viewDistance.left() / 16.0)); + viewDistance.setRight(qCeil(viewDistance.right() / 16.0)); return viewDistance; } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 9a65af8f..acb1f538 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -606,11 +606,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap() { QMargins MapImageExporter::getMargins(const Map *map) { QMargins margins; if (m_settings.showBorder) { - // When we render map borders we render them in full increments of the border dimensions. - // This means for large border dimensions the painted area of the border may extend well beyond the area the player can see. - // When we call paintBorder we will clip the painting to this visible area, so we only need to consider the visible area here. - QSize viewDistance = m_project->getMetatileViewDistance() * 16; - margins = QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); + margins = m_project->getMetatileViewDistance() * 16; } else if (map && connectionsEnabled()) { for (const auto &connection : map->getConnections()) { const QString dir = connection->direction(); @@ -654,10 +650,9 @@ void MapImageExporter::paintBorder(QPainter *painter, Layout *layout) { painter->save(); painter->setClipRect(layout->getVisibleRect()); - int borderHorzDist = layout->getBorderDrawWidth(); - int borderVertDist = layout->getBorderDrawHeight(); - for (int y = -borderVertDist; y < layout->getHeight() + borderVertDist; y += layout->getBorderHeight()) - for (int x = -borderHorzDist; x < layout->getWidth() + borderHorzDist; x += layout->getBorderWidth()) { + const QMargins borderMargins = layout->getBorderMargins(); + for (int y = -borderMargins.top(); y < layout->getHeight() + borderMargins.bottom(); y += layout->getBorderHeight()) + for (int x = -borderMargins.left(); x < layout->getWidth() + borderMargins.right(); x += layout->getBorderWidth()) { // Skip border painting if it would be fully covered by the rest of the map if (layout->isWithinBounds(QRect(x, y, layout->getBorderWidth(), layout->getBorderHeight()))) continue; diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index fde7f820..4290d1a7 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -5,17 +5,21 @@ #include "movablerect.h" #include "utility.h" -MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color) - : QGraphicsRectItem(0, 0, width, height) +MovableRect::MovableRect(bool *enabled, const QRectF &rect, const QRgb &color) + : QGraphicsRectItem(rect), + enabled(enabled), + baseRect(rect), + color(color) { - this->enabled = enabled; - this->color = color; this->setVisible(*enabled); } /// Center rect on grid position (x, y) void MovableRect::updateLocation(int x, int y) { - this->setRect((x * 16) - this->rect().width() / 2 + 8, (y * 16) - this->rect().height() / 2 + 8, this->rect().width(), this->rect().height()); + this->setRect(this->baseRect.x() + (x * 16), + this->baseRect.y() + (y * 16), + this->baseRect.width(), + this->baseRect.height()); this->setVisible(*this->enabled); } @@ -25,7 +29,7 @@ void MovableRect::updateLocation(int x, int y) { ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) : QObject(parent), - MovableRect(enabled, width * 16, height * 16, color) + MovableRect(enabled, QRect(0, 0, width * 16, height * 16), color) { setZValue(0xFFFFFFFF); // ensure on top of view setAcceptHoverEvents(true); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 8b47913a..5bcf6399 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -138,8 +138,10 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_MaxEvents->setMaximum(INT_MAX); ui->spinBox_MapWidth->setMaximum(INT_MAX); ui->spinBox_MapHeight->setMaximum(INT_MAX); - ui->spinBox_PlayerViewWidth->setMaximum(INT_MAX); - ui->spinBox_PlayerViewHeight->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_West->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_North->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_East->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_South->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -475,8 +477,10 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup); - ui->spinBox_PlayerViewWidth->setValue(projectConfig.playerViewSize.width()); - ui->spinBox_PlayerViewHeight->setValue(projectConfig.playerViewSize.height()); + ui->spinBox_PlayerViewDistance_West->setValue(projectConfig.playerViewDistance.left()); + ui->spinBox_PlayerViewDistance_North->setValue(projectConfig.playerViewDistance.top()); + ui->spinBox_PlayerViewDistance_East->setValue(projectConfig.playerViewDistance.right()); + ui->spinBox_PlayerViewDistance_South->setValue(projectConfig.playerViewDistance.bottom()); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -553,7 +557,10 @@ void ProjectSettingsEditor::save() { projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value(); - projectConfig.playerViewSize = QSize(ui->spinBox_PlayerViewWidth->value(), ui->spinBox_PlayerViewHeight->value()); + projectConfig.playerViewDistance = QMargins(ui->spinBox_PlayerViewDistance_West->value(), + ui->spinBox_PlayerViewDistance_North->value(), + ui->spinBox_PlayerViewDistance_East->value(), + ui->spinBox_PlayerViewDistance_South->value()); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); From 80024d9ce3a6d769490da0492ba6e91c36109a2b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 14:11:34 -0400 Subject: [PATCH 311/364] Add missing tooltip, menu separators --- forms/mainwindow.ui | 3 +++ forms/mapheaderform.ui | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 86f50047..7d391d3b 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2863,6 +2863,7 @@ + @@ -2887,8 +2888,10 @@ + + diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui index 8faba290..08552e2c 100644 --- a/forms/mapheaderform.ui +++ b/forms/mapheaderform.ui @@ -7,7 +7,7 @@ 0 0 407 - 349 + 380 @@ -224,7 +224,11 @@ - + + + <html><head/><body><p>The name that will be displayed in-game for this Location. This name will be shared with any other map that has the same Location.</p></body></html> + + From b1d85d32c12fac6bb02c8603a55f81747ca3741d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 15 Apr 2025 12:22:51 -0400 Subject: [PATCH 312/364] Prevent weird diving map behavior --- include/core/map.h | 1 + include/editor.h | 3 ++- include/ui/newmapconnectiondialog.h | 5 +++- src/core/map.cpp | 9 +++++++ src/editor.cpp | 23 ++++++++++++---- src/mainwindow.cpp | 3 ++- src/ui/connectionpixmapitem.cpp | 2 ++ src/ui/connectionslistitem.cpp | 12 ++++++++- src/ui/newmapconnectiondialog.cpp | 42 ++++++++++++++++++++++++++--- 9 files changed, 87 insertions(+), 13 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index aaf5b8b7..8b757b8e 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -87,6 +87,7 @@ public: void deleteConnections(); QList getConnections() const { return m_connections; } + MapConnection* getConnection(const QString &direction) const; void removeConnection(MapConnection *); void addConnection(MapConnection *); void loadConnection(MapConnection *); diff --git a/include/editor.h b/include/editor.h index 8b8a18b0..82bc5a08 100644 --- a/include/editor.h +++ b/include/editor.h @@ -92,7 +92,8 @@ public: void setConnectionsVisibility(bool visible); void updateDivingMapsVisibility(); void renderDivingConnections(); - void addConnection(MapConnection* connection); + void addNewConnection(const QString &mapName, const QString &direction); + void replaceConnection(const QString &mapName, const QString &direction); void removeConnection(MapConnection* connection); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); diff --git a/include/ui/newmapconnectiondialog.h b/include/ui/newmapconnectiondialog.h index 4781c971..db9eee49 100644 --- a/include/ui/newmapconnectiondialog.h +++ b/include/ui/newmapconnectiondialog.h @@ -20,13 +20,16 @@ public: virtual void accept() override; signals: - void accepted(MapConnection *result); + void newConnectionedAdded(const QString &mapName, const QString &direction); + void connectionReplaced(const QString &mapName, const QString &direction); private: Ui::NewMapConnectionDialog *ui; + Map *m_map; bool mapNameIsValid(); void setWarningVisible(bool visible); + bool askReplaceConnection(MapConnection *connection, const QString &newMapName); }; #endif // NEWMAPCONNECTIONDIALOG_H diff --git a/src/core/map.cpp b/src/core/map.cpp index b9fe4c90..1c958d87 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -288,6 +288,15 @@ void Map::removeConnection(MapConnection *connection) { emit connectionRemoved(connection); } +// Return the first map connection that has the given direction. +MapConnection* Map::getConnection(const QString &direction) const { + for (const auto &connection : m_connections) { + if (connection->direction() == direction) + return connection; + } + return nullptr; +} + void Map::commit(QUndoCommand *cmd) { m_editHistory->push(cmd); } diff --git a/src/editor.cpp b/src/editor.cpp index 78cab8bb..ea74c5bb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -818,19 +818,32 @@ void Editor::displayConnection(MapConnection *connection) { } } -void Editor::addConnection(MapConnection *connection) { - if (!connection) +void Editor::addNewConnection(const QString &mapName, const QString &direction) { + if (!this->map) return; + MapConnection *connection = new MapConnection(mapName, direction); + // Mark this connection to be selected once its display elements have been created. // It's possible this is a Dive/Emerge connection, but that's ok (no selection will occur). - connection_to_select = connection; + this->connection_to_select = connection; this->map->commit(new MapConnectionAdd(this->map, connection)); } +void Editor::replaceConnection(const QString &mapName, const QString &direction) { + if (!this->map) + return; + + MapConnection *connection = this->map->getConnection(direction); + if (!connection || connection->targetMapName() == mapName) + return; + + this->map->commit(new MapConnectionChangeMap(connection, mapName)); +} + void Editor::removeConnection(MapConnection *connection) { - if (!connection) + if (!this->map || !connection) return; this->map->commit(new MapConnectionRemove(this->map, connection)); } @@ -948,7 +961,7 @@ bool Editor::setDivingMapName(const QString &mapName, const QString &direction) } } else if (!mapName.isEmpty()) { // Create new connection - addConnection(new MapConnection(mapName, direction)); + addNewConnection(mapName, direction); } return true; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c539b654..feadc6e8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2588,7 +2588,8 @@ void MainWindow::on_pushButton_AddConnection_clicked() { return; auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames); - connect(dialog, &NewMapConnectionDialog::accepted, this->editor, &Editor::addConnection); + connect(dialog, &NewMapConnectionDialog::newConnectionedAdded, this->editor, &Editor::addNewConnection); + connect(dialog, &NewMapConnectionDialog::connectionReplaced, this->editor, &Editor::replaceConnection); dialog->open(); } diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index f1bceac5..ac755ff8 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -143,6 +143,8 @@ void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { emit connectionItemDoubleClicked(this->connection); } +// TODO: Rather than listening for this here and on the list item, listen for it on the connections graphics view, +// and delete whichever map connections are currently selected. This should fix our weird focus requirements in here. void ConnectionPixmapItem::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { emit deleteRequested(this->connection); diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index b0fdf581..b6c533be 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -100,7 +100,17 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { void ConnectionsListItem::commitDirection() { const QString direction = ui->comboBox_Direction->currentText(); - if (this->map && this->connection && this->connection->direction() != direction) { + if (!this->connection || this->connection->direction() == direction) + return; + + if (MapConnection::isDiving(direction)) { + // Diving maps are displayed separately, no support right now for replacing a list item with a diving map. + // For now just restore the original direction. + ui->comboBox_Direction->setCurrentText(this->connection->direction()); + return; + } + + if (this->map) { this->map->commit(new MapConnectionChangeDirection(this->connection, direction)); } } diff --git a/src/ui/newmapconnectiondialog.cpp b/src/ui/newmapconnectiondialog.cpp index a4f08496..b9938f3e 100644 --- a/src/ui/newmapconnectiondialog.cpp +++ b/src/ui/newmapconnectiondialog.cpp @@ -1,9 +1,11 @@ #include "newmapconnectiondialog.h" #include "ui_newmapconnectiondialog.h" +#include "message.h" NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const QStringList &mapNames) : QDialog(parent), - ui(new Ui::NewMapConnectionDialog) + ui(new Ui::NewMapConnectionDialog), + m_map(map) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); @@ -15,7 +17,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const // Choose default direction QMap directionCounts; - for (auto connection : map->getConnections()) { + for (auto connection : m_map->getConnections()) { directionCounts[connection->direction()]++; } QString defaultDirection; @@ -32,7 +34,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const QString defaultMapName; if (mapNames.isEmpty()) { defaultMapName = QString(); - } else if (mapNames.first() == map->name() && mapNames.length() > 1) { + } else if (mapNames.first() == m_map->name() && mapNames.length() > 1) { // Prefer not to connect the map to itself defaultMapName = mapNames.at(1); } else { @@ -61,11 +63,43 @@ void NewMapConnectionDialog::setWarningVisible(bool visible) { adjustSize(); } +bool NewMapConnectionDialog::askReplaceConnection(MapConnection *connection, const QString &newMapName) { + QString message = QString("%1 already has a %2 connection to '%3'. Replace it with a %2 connection to '%4'?") + .arg(m_map->name()) + .arg(connection->direction()) + .arg(connection->targetMapName()) + .arg(newMapName); + return QuestionMessage::show(message, this) == QMessageBox::Yes; +} + void NewMapConnectionDialog::accept() { if (!mapNameIsValid()) { setWarningVisible(true); return; } - emit accepted(new MapConnection(ui->comboBox_Map->currentText(), ui->comboBox_Direction->currentText())); + + const QString direction = ui->comboBox_Direction->currentText(); + const QString targetMapName = ui->comboBox_Map->currentText(); + + // This is a very niche use case. Normally the user should add Dive/Emerge map connections using the line edits at the top of + // the Connections tab, but because we allow custom direction names in this dialog's Direction drop-down, a user could type + // in "dive" or "emerge" and we have to decide what to do. If there's no existing Dive/Emerge map we can just add it normally + // as if they had typed in the regular line edits. If there's already an existing connection we need to replace it. + if (MapConnection::isDiving(direction)) { + MapConnection *connection = m_map->getConnection(direction); + if (connection) { + if (connection->targetMapName() != targetMapName) { + if (!askReplaceConnection(connection, targetMapName)) + return; // Canceled + emit connectionReplaced(targetMapName, direction); + } + // Replaced the diving connection (or no-op, if adding a diving connection with the same map name) + QDialog::accept(); + return; + } + // Adding a new diving connection that doesn't exist yet, proceed normally. + } + + emit newConnectionedAdded(targetMapName, direction); QDialog::accept(); } From 8b85057ca5d7479b7bfec3886fda851620729ddd Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 15:55:26 -0400 Subject: [PATCH 313/364] Fix connection pixmaps being sensitive to focus --- forms/mainwindow.ui | 11 ++++++++--- include/editor.h | 1 + include/ui/connectionpixmapitem.h | 2 -- include/ui/connectionslistitem.h | 1 - include/ui/graphicsview.h | 13 +++++++++++++ src/editor.cpp | 5 +++++ src/mainwindow.cpp | 1 + src/ui/connectionpixmapitem.cpp | 24 +----------------------- src/ui/connectionslistitem.cpp | 9 --------- src/ui/graphicsview.cpp | 9 +++++++++ 10 files changed, 38 insertions(+), 38 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 8a8757a0..cbb611fe 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2589,7 +2589,7 @@ Qt::Orientation::Horizontal - + 0 @@ -3310,9 +3310,14 @@ MapView - QWidget + QGraphicsView
mapview.h
+ + ConnectionsView + QGraphicsView +
graphicsview.h
+
MapTree QTreeView @@ -3321,7 +3326,7 @@ NoScrollGraphicsView QGraphicsView -
mapview.h
+
graphicsview.h
MapListToolBar diff --git a/include/editor.h b/include/editor.h index 82bc5a08..b908b335 100644 --- a/include/editor.h +++ b/include/editor.h @@ -95,6 +95,7 @@ public: void addNewConnection(const QString &mapName, const QString &direction); void replaceConnection(const QString &mapName, const QString &direction); void removeConnection(MapConnection* connection); + void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); void configureEncounterJSON(QWidget *); diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 26b83aa6..32e309f9 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -43,8 +43,6 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; - virtual void keyPressEvent(QKeyEvent*) override; - virtual void focusInEvent(QFocusEvent*) override; signals: void connectionItemDoubleClicked(MapConnection*); diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index bce05345..1b9713cf 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -36,7 +36,6 @@ private: protected: virtual void mousePressEvent(QMouseEvent*) override; - virtual void keyPressEvent(QKeyEvent*) override; virtual bool eventFilter(QObject*, QEvent *event) override; signals: diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index 92771cf7..0ca36239 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -32,6 +32,19 @@ signals: void clicked(QMouseEvent *event); }; +class ConnectionsView : public QGraphicsView +{ + Q_OBJECT +public: + ConnectionsView(QWidget *parent = nullptr) : QGraphicsView(parent) {} + +signals: + void pressedDelete(); + +protected: + virtual void keyPressEvent(QKeyEvent *event) override; +}; + class Editor; // TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView. diff --git a/src/editor.cpp b/src/editor.cpp index ea74c5bb..39ad3647 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -848,6 +848,11 @@ void Editor::removeConnection(MapConnection *connection) { this->map->commit(new MapConnectionRemove(this->map, connection)); } +void Editor::removeSelectedConnection() { + if (selected_connection_item) + removeConnection(selected_connection_item->connection); +} + void Editor::removeConnectionPixmap(MapConnection *connection) { if (!connection) return; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index feadc6e8..60bb3e80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -352,6 +352,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); + connect(ui->graphicsView_Connections, &ConnectionsView::pressedDelete, this->editor, &Editor::removeSelectedConnection); this->loadUserSettings(); diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index ac755ff8..7ea5f820 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -9,7 +9,6 @@ ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection) connection(connection) { this->setEditable(true); - setFlag(ItemIsFocusable, true); this->basePixmap = pixmap(); updateOrigin(); render(false); @@ -118,10 +117,6 @@ bool ConnectionPixmapItem::getEditable() { } void ConnectionPixmapItem::setSelected(bool selected) { - if (selected && !hasFocus()) { - setFocus(Qt::OtherFocusReason); - } - if (this->selected == selected) return; this->selected = selected; @@ -131,7 +126,7 @@ void ConnectionPixmapItem::setSelected(bool selected) { } void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { - setFocus(Qt::MouseFocusReason); + this->setSelected(true); } void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { @@ -142,20 +137,3 @@ void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { emit connectionItemDoubleClicked(this->connection); } - -// TODO: Rather than listening for this here and on the list item, listen for it on the connections graphics view, -// and delete whichever map connections are currently selected. This should fix our weird focus requirements in here. -void ConnectionPixmapItem::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { - emit deleteRequested(this->connection); - } else { - QGraphicsPixmapItem::keyPressEvent(event); - } -} - -void ConnectionPixmapItem::focusInEvent(QFocusEvent* event) { - if (!this->getEditable()) - return; - this->setSelected(true); - QGraphicsPixmapItem::focusInEvent(event); -} diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index b6c533be..0ba27223 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -129,12 +129,3 @@ void ConnectionsListItem::commitRemove() { if (this->map) this->map->commit(new MapConnectionRemove(this->map, this->connection)); } - -void ConnectionsListItem::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { - commitRemove(); - event->accept(); - } else { - QFrame::keyPressEvent(event); - } -} diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index 68479e98..1297102e 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -79,3 +79,12 @@ Overlay * MapView::getOverlay(int layer) { } return overlay; } + +void ConnectionsView::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + emit pressedDelete(); + event->accept(); + } else { + QGraphicsView::keyPressEvent(event); + } +} From b660ef5d3003f259e58a313daa60a606d16c3b3b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 16:16:40 -0400 Subject: [PATCH 314/364] Fix Qt5 build --- src/ui/connectionslistitem.cpp | 2 +- src/ui/noscrollcombobox.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index 0ba27223..86df9525 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -46,7 +46,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->spinBox_Offset->installEventFilter(this); connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); // Distinguish between move actions for the edit history - connect(ui->spinBox_Offset, &QSpinBox::valueChanged, this, &ConnectionsListItem::commitMove); + connect(ui->spinBox_Offset, QOverload::of(&QSpinBox::valueChanged), this, &ConnectionsListItem::commitMove); // If the connection changes externally we want to update to reflect the change. connect(connection, &MapConnection::offsetChanged, this, &ConnectionsListItem::updateUI); diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index bd6b438b..191c5c45 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -26,7 +26,7 @@ NoScrollComboBox::NoScrollComboBox(QWidget *parent) // QComboBox (as of writing) has no 'editing finished' signal to capture // changes made either through the text edit or the drop-down. - connect(this, &QComboBox::activated, this, &NoScrollComboBox::editingFinished); + connect(this, QOverload::of(&QComboBox::activated), this, &NoScrollComboBox::editingFinished); connect(this->lineEdit(), &QLineEdit::editingFinished, this, &NoScrollComboBox::editingFinished); } From d992a29e3646df0d52885351770658a5d192bd42 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 18:00:38 -0400 Subject: [PATCH 315/364] Add input fields for LOCALID --- include/core/events.h | 8 ++- include/core/map.h | 1 + include/ui/eventframes.h | 4 ++ src/core/map.cpp | 25 +++++++++ src/mainwindow.cpp | 9 +-- src/project.cpp | 5 ++ src/ui/eventframes.cpp | 116 +++++++++++++++++++++++++++++++++------ 7 files changed, 141 insertions(+), 27 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 3963e022..4a00c501 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -79,9 +79,13 @@ public: None, }; - // all event groups except warps have IDs that start at 1 + // Normally we refer to events using their index in the list of that group's events. + // Object events often get referred to with a special "local ID", which is really just the index + 1. + // We use this local ID number in the index spinner for object events instead of the actual index. + // This distinction is only really important for object and warp events, because these are normally + // the only two groups of events that need to be explicitly referred to. static int getIndexOffset(Event::Group group) { - return (group == Event::Group::Warp) ? 0 : 1; + return (group == Event::Group::Object) ? 1 : 0; } static Event::Group typeToGroup(Event::Type type) { diff --git a/include/core/map.h b/include/core/map.h index 07fce0e5..1f8b5da5 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -77,6 +77,7 @@ public: QList getEvents(Event::Group group = Event::Group::None) const; Event* getEvent(Event::Group group, int index) const; Event* getEvent(Event::Group group, const QString &idName) const; + QStringList getEventIdNames(Event::Group group) const; int getNumEvents(Event::Group group = Event::Group::None) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilePath() const; diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index a5ae6765..0cbce712 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -58,6 +58,7 @@ protected: bool connected = false; void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); private: Event *event; @@ -78,6 +79,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_local_id; NoScrollComboBox *combo_sprite; NoScrollComboBox *combo_movement; NoScrollSpinBox *spinner_radius_x; @@ -108,6 +110,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_local_id; NoScrollComboBox *combo_sprite; NoScrollComboBox *combo_target_id; NoScrollComboBox *combo_target_map; @@ -131,6 +134,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_id; NoScrollComboBox *combo_dest_map; NoScrollComboBox *combo_dest_warp; QPushButton *warning; diff --git a/src/core/map.cpp b/src/core/map.cpp index c61c1e8b..caff07db 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -212,6 +212,31 @@ Event* Map::getEvent(Event::Group group, const QString &idName) const { return nullptr; } +// Returns a list of ID names for the given event group (or all events, if no group is given). +// For events with no explicit ID name, their index string is given instead. +QStringList Map::getEventIdNames(Event::Group group) const { + QList groups; + if (group == Event::Group::None) { + groups = Event::groups(); + } else { + groups.append(group); + } + + QStringList idNames; + for (const auto &group : groups) { + const auto events = m_events[group]; + int indexOffset = Event::getIndexOffset(group); + for (int i = 0; i < events.length(); i++) { + QString idName = events.at(i)->getIdName(); + if (idName.isEmpty()) { + idName = QString::number(i + indexOffset); + } + idNames.append(idName); + } + } + return idNames; +} + int Map::getNumEvents(Event::Group group) const { if (group == Event::Group::None) { // Total number of events diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b8de0b63..f72c2c89 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1112,13 +1112,8 @@ void MainWindow::openEventMap(Event *sourceEvent) { return; // Map opened successfully, now try to select the targeted event on that map. - Event* targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName); - if (targetEvent) { - this->editor->selectMapEvent(targetEvent); - } else { - // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 '%2' doesn't exist on map '%3'").arg(Event::groupToString(targetEventGroup)).arg(targetEventIdName).arg(targetMapName)); - } + Event *targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName); + this->editor->selectMapEvent(targetEvent); } void MainWindow::displayMapProperties() { diff --git a/src/project.cpp b/src/project.cpp index e02db858..300af774 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -161,6 +161,11 @@ void Project::clearTilesetCache() { } Map* Project::loadMap(const QString &mapName) { + if (mapName == getDynamicMapName()) { + // Silently ignored, caller is expected to handle this if they want this to be an error. + return nullptr; + } + Map* map = this->maps.value(mapName); if (!map) { logError(QString("Unknown map name '%1'.").arg(mapName)); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 3b5d71fa..cba5a78d 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -173,6 +173,11 @@ void EventFrame::setActive(bool active) { this->blockSignals(!active); } +// TODO: For populateScriptDropdown and populateIdNameDropdown, it would be nice to connect them to the source of their items +// and update them automatically when the source changes, i.e. for the script dropdown, invalidating the list when the +// the script file changes, and for the ID name dropdown invalidating the list when an event ID name chnages (or perhaps +// more simply invalidating it when the target map is opened). + void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. if (!this->event->getMap()) @@ -201,10 +206,31 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } +void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) { + if (!project->mapNames.contains(mapName)) + return; + + Map *map = project->loadMap(mapName); + if (!map) + return; + + combo->clear(); + combo->addItems(map->getEventIdNames(group)); +} + void ObjectFrame::setup() { EventFrame::setup(); + // local id + QFormLayout *l_form_local_id = new QFormLayout(); + this->line_edit_local_id = new QLineEdit(this); + this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" + "If no game is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setPlaceholderText("LOCALID_MY_NPC"); + l_form_local_id->addRow("Local ID", this->line_edit_local_id); + this->layout_contents->addLayout(l_form_local_id); + // sprite combo QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); @@ -290,6 +316,13 @@ void ObjectFrame::connectSignals(MainWindow *window) { EventFrame::connectSignals(window); + // local id + this->line_edit_local_id->disconnect(); + connect(this->line_edit_local_id, &QLineEdit::textChanged, [this](const QString &text) { + this->object->setIdName(text); + this->object->modify(); + }); + // sprite update this->combo_sprite->disconnect(); connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) { @@ -361,6 +394,9 @@ void ObjectFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); + // local id + this->line_edit_local_id->setText(this->object->getIdName()); + // sprite this->combo_sprite->setTextItem(this->object->getGfx()); @@ -407,9 +443,21 @@ void CloneObjectFrame::setup() { this->spinner_z->setEnabled(false); + // local id + QFormLayout *l_form_local_id = new QFormLayout(); + this->line_edit_local_id = new QLineEdit(this); + this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" + "If no game is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setPlaceholderText("LOCALID_MY_CLONE_NPC"); + l_form_local_id->addRow("Local ID", this->line_edit_local_id); + this->layout_contents->addLayout(l_form_local_id); + // sprite combo (edits disabled) QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); + this->combo_sprite->setToolTip("The sprite graphics to use for this object. This is updated automatically\n" + "to match the target object, and so can't be edited. By default the games\n" + "will get the graphics directly from the target object, so this field is ignored."); l_form_sprite->addRow("Sprite", this->combo_sprite); this->combo_sprite->setEnabled(false); this->layout_contents->addLayout(l_form_sprite); @@ -424,8 +472,7 @@ void CloneObjectFrame::setup() { // clone local id combo QFormLayout *l_form_dest_id = new QFormLayout(); this->combo_target_id = new NoScrollComboBox(this); - // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field - this->combo_target_id->setToolTip("event_object ID of the object being cloned."); + this->combo_target_id->setToolTip("The Local ID name or number of the object being cloned."); l_form_dest_id->addRow("Target Local ID", this->combo_target_id); this->layout_contents->addLayout(l_form_dest_id); @@ -437,18 +484,26 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; + + // local id + this->line_edit_local_id->disconnect(); + connect(this->line_edit_local_id, &QLineEdit::textChanged, [this](const QString &text) { + this->clone->setIdName(text); + this->clone->modify(); + }); // update icon displayed in frame with target connect(this->clone->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); // target map this->combo_target_map->disconnect(); - connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->clone->setTargetMap(text); + connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + this->clone->setTargetMap(mapName); this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); - // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. + populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); }); // target id @@ -467,6 +522,9 @@ void CloneObjectFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); + // local id + this->line_edit_local_id->setText(this->clone->getIdName()); + // sprite this->combo_sprite->setCurrentText(this->clone->getGfx()); @@ -484,12 +542,21 @@ void CloneObjectFrame::populate(Project *project) { EventFrame::populate(project); this->combo_target_map->addItems(project->mapNames); - // TODO: Populate combo_target_id with local IDs from target map. + populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object); } void WarpFrame::setup() { EventFrame::setup(); + // ID + QFormLayout *l_form_id = new QFormLayout(); + this->line_edit_id = new QLineEdit(this); + this->line_edit_id->setToolTip("An optional, unique name to use to refer to this warp from other warps.\n" + "If no game is given you can refer to this warp using its 'warp id' number."); + this->line_edit_id->setPlaceholderText("WARP_ID_MY_WARP"); + l_form_id->addRow("ID", this->line_edit_id); + this->layout_contents->addLayout(l_form_id); + // desination map combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_dest_map = new NoScrollComboBox(this); @@ -524,13 +591,21 @@ void WarpFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; + + // id + this->line_edit_id->disconnect(); + connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { + this->warp->setIdName(text); + this->warp->modify(); + }); // dest map this->combo_dest_map->disconnect(); - connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->warp->setDestinationMap(text); + connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + this->warp->setDestinationMap(mapName); this->warp->modify(); - // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. + populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp); }); // dest id @@ -551,6 +626,9 @@ void WarpFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); + // id + this->line_edit_id->setText(this->warp->getIdName()); + // dest map this->combo_dest_map->setTextItem(this->warp->getDestinationMap()); @@ -565,7 +643,7 @@ void WarpFrame::populate(Project *project) { EventFrame::populate(project); this->combo_dest_map->addItems(project->mapNames); - // TODO: Populate combo_dest_warp with local IDs from target map. + populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp); } @@ -965,9 +1043,8 @@ void HealLocationFrame::setup() { QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc); l_form_respawn_npc->setContentsMargins(0, 0, 0, 0); this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc); - // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field - this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" - "upon respawning after whiteout."); + this->combo_respawn_npc->setToolTip("The Local ID name or number of the NPC the player\n" + "interacts with upon respawning after whiteout."); l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); this->layout_contents->addWidget(hideable_respawn_npc); @@ -979,6 +1056,7 @@ void HealLocationFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; this->line_edit_id->disconnect(); connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { @@ -987,10 +1065,10 @@ void HealLocationFrame::connectSignals(MainWindow *window) { }); this->combo_respawn_map->disconnect(); - connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->healLocation->setRespawnMapName(text); + connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + this->healLocation->setRespawnMapName(mapName); this->healLocation->modify(); - // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. + populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object); }); this->combo_respawn_npc->disconnect(); @@ -1021,6 +1099,8 @@ void HealLocationFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_respawn_map->addItems(project->mapNames); - // TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map + if (projectConfig.healLocationRespawnDataEnabled) { + this->combo_respawn_map->addItems(project->mapNames); + populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object); + } } From 0f4028ab928c3e639b4d76440fbb5084c2eca1dd Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 18 Apr 2025 12:08:30 -0400 Subject: [PATCH 316/364] Add missing event frame invalidation --- include/mainwindow.h | 3 +- include/ui/eventframes.h | 7 +++ src/mainwindow.cpp | 14 ++---- src/ui/eventframes.cpp | 99 +++++++++++++++++++++++++++------------- 4 files changed, 79 insertions(+), 44 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 832d51ee..4d33847d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -197,8 +197,7 @@ private slots: void onMapLoaded(Map *map); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); - void markMapEdited(); - void markSpecificMapEdited(Map*); + void markMapEdited(Map*); void markLayoutEdited(); void on_actionNew_Tileset_triggered(); diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 0cbce712..09eae50b 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -57,6 +57,7 @@ protected: bool initialized = false; bool connected = false; + void populateDropdown(NoScrollComboBox * combo, const QStringList &items); void populateScriptDropdown(NoScrollComboBox * combo, Project * project); void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); @@ -117,6 +118,8 @@ public: private: CloneObjectEvent *clone; + + void tryInvalidateIdDropdown(Map *map); }; @@ -141,6 +144,8 @@ public: private: WarpEvent *warp; + + void tryInvalidateIdDropdown(Map *map); }; @@ -279,6 +284,8 @@ public: private: HealLocationEvent *healLocation; + + void tryInvalidateIdDropdown(Map *map); }; #endif // EVENTRAMES_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f72c2c89..e44b967d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -343,7 +343,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); connect(this->editor, &Editor::openEventMap, this, &MainWindow::openEventMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); - connect(this->editor, &Editor::wildMonTableEdited, this, &MainWindow::markMapEdited); + connect(this->editor, &Editor::wildMonTableEdited, [this] { markMapEdited(this->editor->map); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); @@ -523,11 +523,7 @@ void MainWindow::updateWindowTitle() { } } -void MainWindow::markMapEdited() { - if (editor) markSpecificMapEdited(editor->map); -} - -void MainWindow::markSpecificMapEdited(Map* map) { +void MainWindow::markMapEdited(Map* map) { if (!map) return; map->setHasUnsavedDataChanges(true); @@ -949,8 +945,6 @@ bool MainWindow::setMap(QString map_name) { updateMapList(); resetMapListFilters(); - connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); - // If the map's MAPSEC / layout changes, update the map's position in the map list. // These are doing more work than necessary, rather than rebuilding the entire list they should find and relocate the appropriate row. connect(editor->map, &Map::layoutChanged, this, &MainWindow::rebuildMapList_Layouts, Qt::UniqueConnection); @@ -1153,7 +1147,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te } this->editor->map->setLayout(layout); setMap(this->editor->map->name()); - markMapEdited(); + markMapEdited(this->editor->map); } void MainWindow::onLayoutSelectorEditingFinished() { @@ -2520,7 +2514,7 @@ void MainWindow::onOpenConnectedMap(MapConnection *connection) { } void MainWindow::onMapLoaded(Map *map) { - connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(map); }); + connect(map, &Map::modified, [this, map] { markMapEdited(map); }); } void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index cba5a78d..40b2cdf2 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -173,10 +173,16 @@ void EventFrame::setActive(bool active) { this->blockSignals(!active); } -// TODO: For populateScriptDropdown and populateIdNameDropdown, it would be nice to connect them to the source of their items -// and update them automatically when the source changes, i.e. for the script dropdown, invalidating the list when the -// the script file changes, and for the ID name dropdown invalidating the list when an event ID name chnages (or perhaps -// more simply invalidating it when the target map is opened). +void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &items) { + // Set the items in the combo box. This may be called after the frame is initialized + // if the frame needs to be repopulated, so ensure the text in the combo is preserved + // and that we don't accidentally fire 'currentTextChanged'. + const QSignalBlocker b(combo); + const QString savedText = combo->currentText(); + combo->clear(); + combo->addItems(items); + combo->setCurrentText(savedText); +} void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. @@ -184,13 +190,14 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj return; QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup()); - combo->addItems(scripts); + populateDropdown(combo, scripts); // Depending on the settings, the autocomplete may also contain all global scripts. if (porymapConfig.loadAllEventScripts) { project->insertGlobalScriptLabels(scripts); } + // Note: Because 'combo' is the parent, the old QCompleter will be deleted when a new one is set. auto completer = new QCompleter(scripts, combo); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); @@ -203,6 +210,8 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj combo->setCompleter(completer); // If the project changes the script labels, update the EventFrame. + // TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes). + // This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes. connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } @@ -211,11 +220,7 @@ void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * proj return; Map *map = project->loadMap(mapName); - if (!map) - return; - - combo->clear(); - combo->addItems(map->getEventIdNames(group)); + if (map) populateDropdown(combo, map->getEventIdNames(group)); } @@ -428,12 +433,11 @@ void ObjectFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_sprite->addItems(project->gfxDefines.keys()); - this->combo_movement->addItems(project->movementTypes); - this->combo_flag->addItems(project->flagNames); - this->combo_trainer_type->addItems(project->trainerTypes); - - this->populateScriptDropdown(this->combo_script, project); + populateDropdown(this->combo_sprite, project->gfxDefines.keys()); + populateDropdown(this->combo_movement, project->movementTypes); + populateDropdown(this->combo_flag, project->flagNames); + populateDropdown(this->combo_trainer_type, project->trainerTypes); + populateScriptDropdown(this->combo_script, project); } @@ -505,6 +509,7 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { this->clone->modify(); populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); }); + connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); // target id this->combo_target_id->disconnect(); @@ -514,6 +519,17 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) { + // If the clone's target map is opened then the names in this frame's ID dropdown may be changed. + // Make sure we update the frame next time it's opened. + if (map && this->clone && map->name() == this->clone->getTargetMap()) { + invalidateValues(); + } } void CloneObjectFrame::initialize() { @@ -541,7 +557,7 @@ void CloneObjectFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_target_map->addItems(project->mapNames); + populateDropdown(this->combo_target_map, project->mapNames); populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object); } @@ -607,6 +623,7 @@ void WarpFrame::connectSignals(MainWindow *window) { this->warp->modify(); populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp); }); + connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); // dest id this->combo_dest_warp->disconnect(); @@ -618,6 +635,17 @@ void WarpFrame::connectSignals(MainWindow *window) { // warning this->warning->disconnect(); connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void WarpFrame::tryInvalidateIdDropdown(Map *map) { + // If the warps's target map is opened then the names in this frame's ID dropdown may be changed. + // Make sure we update the frame next time it's opened. + if (map && this->warp && map->name() == this->warp->getDestinationMap()) { + invalidateValues(); + } } void WarpFrame::initialize() { @@ -642,7 +670,7 @@ void WarpFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_dest_map->addItems(project->mapNames); + populateDropdown(this->combo_dest_map, project->mapNames); populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp); } @@ -726,10 +754,8 @@ void TriggerFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - // var combo - this->combo_var->addItems(project->varNames); - - this->populateScriptDropdown(this->combo_script, project); + populateDropdown(this->combo_var, project->varNames); + populateScriptDropdown(this->combo_script, project); } @@ -777,8 +803,7 @@ void WeatherTriggerFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - // weather - this->combo_weather->addItems(project->coordEventWeatherNames); + populateDropdown(this->combo_weather, project->coordEventWeatherNames); } @@ -844,10 +869,8 @@ void SignFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - // facing dir - this->combo_facing_dir->addItems(project->bgEventFacingDirections); - - this->populateScriptDropdown(this->combo_script, project); + populateDropdown(this->combo_facing_dir, project->bgEventFacingDirections); + populateScriptDropdown(this->combo_script, project); } @@ -958,8 +981,8 @@ void HiddenItemFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_item->addItems(project->itemNames); - this->combo_flag->addItems(project->flagNames); + populateDropdown(this->combo_item, project->itemNames); + populateDropdown(this->combo_flag, project->flagNames); } @@ -1010,7 +1033,7 @@ void SecretBaseFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_base_id->addItems(project->secretBaseIds); + populateDropdown(this->combo_base_id, project->secretBaseIds); } @@ -1070,12 +1093,24 @@ void HealLocationFrame::connectSignals(MainWindow *window) { this->healLocation->modify(); populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object); }); + connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); this->combo_respawn_npc->disconnect(); connect(this->combo_respawn_npc, &QComboBox::currentTextChanged, [this](const QString &text) { this->healLocation->setRespawnNPC(text); this->healLocation->modify(); }); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void HealLocationFrame::tryInvalidateIdDropdown(Map *map) { + // If the heal locations's target map is opened then the names in this frame's ID dropdown may be changed. + // Make sure we update the frame next time it's opened. + if (map && this->healLocation && map->name() == this->healLocation->getRespawnMapName()) { + invalidateValues(); + } } void HealLocationFrame::initialize() { @@ -1100,7 +1135,7 @@ void HealLocationFrame::populate(Project *project) { EventFrame::populate(project); if (projectConfig.healLocationRespawnDataEnabled) { - this->combo_respawn_map->addItems(project->mapNames); + populateDropdown(this->combo_respawn_map, project->mapNames); populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object); } } From e26be84d9128a64842fa054ae21b4021e7274142 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 18 Apr 2025 13:00:17 -0400 Subject: [PATCH 317/364] Fix z value for events, separate EventPixmapItem from Editor --- include/core/events.h | 6 +- include/editor.h | 17 +++++ include/ui/eventpixmapitem.h | 43 +++++++------ src/core/events.cpp | 13 ++-- src/editor.cpp | 49 +++++++------- src/ui/connectionpixmapitem.cpp | 5 +- src/ui/eventframes.cpp | 19 +++--- src/ui/eventpixmapitem.cpp | 110 ++++++++++++++++++++------------ src/ui/mapimageexporter.cpp | 3 +- src/ui/movablerect.cpp | 1 - src/ui/resizelayoutpopup.cpp | 1 + 11 files changed, 159 insertions(+), 108 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 4a00c501..9da9b531 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -153,7 +153,7 @@ public: QJsonObject getCustomAttributes() const { return this->customAttributes; } void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } - virtual void loadPixmap(Project *project); + virtual QPixmap loadPixmap(Project *project); void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } QPixmap getPixmap() const { return this->pixmap; } @@ -233,7 +233,7 @@ public: virtual QSet getExpectedFields() override; - virtual void loadPixmap(Project *project) override; + virtual QPixmap loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } QString getGfx() const { return this->gfx; } @@ -300,7 +300,7 @@ public: virtual QSet getExpectedFields() override; - virtual void loadPixmap(Project *project) override; + virtual QPixmap loadPixmap(Project *project) override; void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } QString getTargetMap() const { return this->targetMap; } diff --git a/include/editor.h b/include/editor.h index 07a5dc68..fe01014f 100644 --- a/include/editor.h +++ b/include/editor.h @@ -117,6 +117,7 @@ public: void redrawAllEvents(); void redrawEvents(const QList &events); void redrawEventPixmapItem(EventPixmapItem *item); + void updateEventPixmapItemZValue(EventPixmapItem *item); qreal getEventOpacity(const Event *event) const; void updateCursorRectPos(int x, int y); @@ -182,6 +183,22 @@ public: static void openInTextEditor(const QString &path, int lineNum = 0); void setCollisionGraphics(); + enum ZValue { + MapBorder = -4, + MapConnectionInactive = -3, + MapConnectionActive = -2, + MapConnectionMask = -1, + + // Event pixmaps set their z value to be their y position on the map. + // Their y value is int16_t, so we have enough space to allocate the + // full range + 1 for the selected event (which should always be on top). + EventMinimum = 1, + EventMaximum = EventMinimum + 0x10000, + + Ruler, + ResizeLayoutPopup + }; + public slots: void openMapScripts() const; void openScript(const QString &scriptLabel) const; diff --git a/include/ui/eventpixmapitem.h b/include/ui/eventpixmapitem.h index c44fc6bf..a28232ba 100644 --- a/include/ui/eventpixmapitem.h +++ b/include/ui/eventpixmapitem.h @@ -10,38 +10,39 @@ #include "events.h" -class Editor; +class Project; class EventPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - EventPixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} - - EventPixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { - this->event = event; - event->setPixmapItem(this); - this->editor = editor; - updatePosition(); - } + explicit EventPixmapItem(Event *event); - Event *event = nullptr; + void render(Project *project); + + bool isSelected() const { return m_selected; } + void setSelected(bool selected) { m_selected = selected; } + + Event * getEvent() const { return m_event; } - void updatePosition(); void move(int dx, int dy); + void moveTo(int x, int y); void moveTo(const QPoint &pos); - void emitPositionChanged(); - void updatePixmap(); private: - Editor *editor = nullptr; - QPoint lastPos; - bool active = false; - bool releaseSelectionQueued = false; + QPixmap m_basePixmap; + Event *const m_event = nullptr; + QPoint m_lastPos; + bool m_active = false; + bool m_selected = false; + bool m_releaseSelectionQueued = false; + + void updatePixelPosition(); signals: - void xChanged(int); - void yChanged(int); - void spriteChanged(const QPixmap &pixmap); + void xChanged(int x); + void yChanged(int y); + void posChanged(int x, int y); + void rendered(const QPixmap &pixmap); void selected(Event *event, bool toggle); void dragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); void released(Event *event, const QPoint &position); @@ -51,7 +52,7 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(this->event); } + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(m_event); } }; #endif // EVENTPIXMAPITEM_H diff --git a/src/core/events.cpp b/src/core/events.cpp index 694919c0..ad50a60e 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -114,9 +114,10 @@ QString Event::typeToString(Event::Type type) { return typeToStringMap.value(type); } -void Event::loadPixmap(Project *project) { +QPixmap Event::loadPixmap(Project *project) { this->pixmap = project->getEventPixmap(this->getEventGroup()); this->usesDefaultPixmap = true; + return this->pixmap; } @@ -225,13 +226,13 @@ QSet ObjectEvent::getExpectedFields() { return expectedFields; } -void ObjectEvent::loadPixmap(Project *project) { +QPixmap ObjectEvent::loadPixmap(Project *project) { this->pixmap = project->getEventPixmap(this->gfx, this->movement); if (!this->pixmap.isNull()) { this->usesDefaultPixmap = false; - } else { - Event::loadPixmap(project); + return this->pixmap; } + return Event::loadPixmap(project); } @@ -314,7 +315,7 @@ QSet CloneObjectEvent::getExpectedFields() { return expectedFields; } -void CloneObjectEvent::loadPixmap(Project *project) { +QPixmap CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone Map *clonedMap = project->loadMap(this->targetMap); Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; @@ -329,7 +330,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); } - ObjectEvent::loadPixmap(project); + return ObjectEvent::loadPixmap(project); } diff --git a/src/editor.cpp b/src/editor.cpp index 6230771a..ec87c6f3 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1484,7 +1484,7 @@ bool Editor::displayLayout() { scene->installEventFilter(filter); connect(filter, &MapSceneEventFilter::wheelZoom, this, &Editor::onWheelZoom); scene->installEventFilter(this->map_ruler); - this->map_ruler->setZValue(1000); + this->map_ruler->setZValue(ZValue::Ruler); scene->addItem(this->map_ruler); } @@ -1696,11 +1696,13 @@ void Editor::displayMapEvents() { EventPixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); - auto item = new EventPixmapItem(event, this); + auto item = new EventPixmapItem(event); connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap); connect(item, &EventPixmapItem::dragged, this, &Editor::onEventDragged); connect(item, &EventPixmapItem::released, this, &Editor::onEventReleased); connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent); + connect(item, &EventPixmapItem::posChanged, [this, event] { updateWarpEventWarning(event); }); + connect(item, &EventPixmapItem::yChanged, [this, item] { updateEventPixmapItemZValue(item); }); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; @@ -1781,6 +1783,7 @@ void Editor::maskNonVisibleConnectionTiles() { QBrush brush(ui->graphicsView_Map->palette().color(QPalette::Active, QPalette::Base)); connection_mask = scene->addPath(mask, pen, brush); + connection_mask->setZValue(ZValue::MapConnectionMask); } void Editor::clearMapBorder() { @@ -1806,7 +1809,7 @@ void Editor::displayMapBorder() { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); - item->setZValue(-3); + item->setZValue(ZValue::MapBorder); scene->addItem(item); borderItems.append(item); } @@ -1977,36 +1980,36 @@ qreal Editor::getEventOpacity(const Event *event) const { } void Editor::redrawEventPixmapItem(EventPixmapItem *item) { - if (!item || !item->event) - return; + if (!item) return; + Event *event = item->getEvent(); + if (!event) return; - project->loadEventPixmap(item->event, true); - - QPixmap pixmap = item->event->getPixmap(); - if (pixmap.isNull()) - return; - - qreal zValue = item->event->getY(); if (this->editMode == EditMode::Events) { - if (this->selectedEvents.contains(item->event)) { - // Draw the selection rectangle - QPainter painter(&pixmap); - painter.setPen(Qt::magenta); - painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); - zValue++; - } item->setAcceptedMouseButtons(Qt::AllButtons); + item->setSelected(this->selectedEvents.contains(event)); } else { // Can't interact with event pixmaps outside of event editing mode. // We could do setEnabled(false), but rather than ignoring the mouse events this // would reject them, which would prevent painting on the map behind the events. item->setAcceptedMouseButtons(Qt::NoButton); + item->setSelected(false); } - item->setPixmap(pixmap); - item->setZValue(zValue); - item->setOpacity(getEventOpacity(item->event)); + updateEventPixmapItemZValue(item); + item->setOpacity(getEventOpacity(event)); item->setShapeMode(porymapConfig.eventSelectionShapeMode); - item->updatePosition(); + item->render(project); +} + +void Editor::updateEventPixmapItemZValue(EventPixmapItem *item) { + if (!item) return; + Event *event = item->getEvent(); + if (!event) return; + + if (item->isSelected()) { + item->setZValue(ZValue::EventMaximum); + } else { + item->setZValue(event->getY() + ((ZValue::EventMaximum - ZValue::EventMinimum) / 2)); + } } void Editor::onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition) { diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index f1bceac5..f023aa22 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -1,6 +1,7 @@ #include "connectionpixmapitem.h" #include "editcommands.h" #include "map.h" +#include "editor.h" #include @@ -31,7 +32,7 @@ void ConnectionPixmapItem::render(bool ignoreCache) { this->basePixmap = this->connection->render(); QPixmap pixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height()); - this->setZValue(-1); + this->setZValue(Editor::ZValue::MapConnectionActive); // When editing is inactive the current selection is ignored, all connections should appear normal. if (this->getEditable()) { @@ -43,7 +44,7 @@ void ConnectionPixmapItem::render(bool ignoreCache) { painter.end(); } else { // Darken the image - this->setZValue(-2); + this->setZValue(Editor::ZValue::MapConnectionInactive); QPainter painter(&pixmap); int alpha = static_cast(255 * 0.25); painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QColor(0, 0, 0, alpha)); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 40b2cdf2..86461b83 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -320,6 +320,7 @@ void ObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; // local id this->line_edit_local_id->disconnect(); @@ -330,18 +331,18 @@ void ObjectFrame::connectSignals(MainWindow *window) { // sprite update this->combo_sprite->disconnect(); - connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) { + connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->object->setGfx(text); - this->object->getPixmapItem()->updatePixmap(); + this->object->getPixmapItem()->render(project); this->object->modify(); }); - connect(this->object->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap); // movement this->combo_movement->disconnect(); - connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) { + connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->object->setMovement(text); - this->object->getPixmapItem()->updatePixmap(); + this->object->getPixmapItem()->render(project); this->object->modify(); }); @@ -498,13 +499,13 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { }); // update icon displayed in frame with target - connect(this->clone->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->clone->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap); // target map this->combo_target_map->disconnect(); connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { this->clone->setTargetMap(mapName); - this->clone->getPixmapItem()->updatePixmap(); + this->clone->getPixmapItem()->render(project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); @@ -513,9 +514,9 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { // target id this->combo_target_id->disconnect(); - connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) { + connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->clone->setTargetID(text); - this->clone->getPixmapItem()->updatePixmap(); + this->clone->getPixmapItem()->render(project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); diff --git a/src/ui/eventpixmapitem.cpp b/src/ui/eventpixmapitem.cpp index 1face67c..7c0b43d3 100644 --- a/src/ui/eventpixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -1,52 +1,80 @@ #include "eventpixmapitem.h" -#include "editor.h" +#include "project.h" #include "editcommands.h" #include "mapruler.h" #include "metatile.h" +EventPixmapItem::EventPixmapItem(Event *event) + : QGraphicsPixmapItem(event->getPixmap()), + m_basePixmap(pixmap()), + m_event(event) +{ + m_event->setPixmapItem(this); + updatePixelPosition(); +} + +void EventPixmapItem::render(Project *project) { + if (!m_event) + return; + + m_basePixmap = m_event->loadPixmap(project); + + // If the base pixmap changes, the event's pixel position may change. + updatePixelPosition(); + + QPixmap pixmap = m_basePixmap; + if (m_selected) { + // Draw the selection rectangle + QPainter painter(&pixmap); + painter.setPen(Qt::magenta); + painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); + } + setPixmap(pixmap); + emit rendered(m_basePixmap); +} + void EventPixmapItem::move(int dx, int dy) { - event->setX(event->getX() + dx); - event->setY(event->getY() + dy); - updatePosition(); - emitPositionChanged(); + moveTo(m_event->getX() + dx, + m_event->getY() + dy); } void EventPixmapItem::moveTo(const QPoint &pos) { - event->setX(pos.x()); - event->setY(pos.y()); - updatePosition(); - emitPositionChanged(); + moveTo(pos.x(), pos.y()); } -void EventPixmapItem::updatePosition() { - int x = this->event->getPixelX(); - int y = this->event->getPixelY(); - setX(x); - setY(y); - editor->updateWarpEventWarning(event); +void EventPixmapItem::moveTo(int x, int y) { + bool changed = false; + if (m_event->getX() != x) { + m_event->setX(x); + emit xChanged(x); + changed = true; + } + if (m_event->getY() != y) { + m_event->setY(y); + emit yChanged(y); + changed = true; + } + if (changed) { + updatePixelPosition(); + emit posChanged(x, y); + } } -void EventPixmapItem::emitPositionChanged() { - emit xChanged(event->getX()); - emit yChanged(event->getY()); -} - -void EventPixmapItem::updatePixmap() { - editor->redrawEventPixmapItem(this); - emit spriteChanged(event->getPixmap()); +void EventPixmapItem::updatePixelPosition() { + setPos(m_event->getPixelX(), m_event->getPixelY()); } void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (this->active) + if (m_active) return; - this->active = true; - this->lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); + m_active = true; + m_lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); bool selectionToggle = mouseEvent->modifiers() & Qt::ControlModifier; - if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) { + if (selectionToggle || !m_selected) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. - emit selected(this->event, selectionToggle); + emit selected(m_event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: // 1. This is the only selected event, and the selection is pointless. @@ -55,32 +83,32 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { // 4. There's a group selection, and they want to drag the group around. // 'selectMapEvent' will immediately clear the rest of the selection, which supports #1-3 but prevents #4. // To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred. - this->releaseSelectionQueued = true; + m_releaseSelectionQueued = true; } mouseEvent->accept(); } void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (!this->active) + if (!m_active) return; QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); - if (pos == this->lastPos) + if (pos == m_lastPos) return; - this->releaseSelectionQueued = false; - emit dragged(this->event, this->lastPos, pos); - this->lastPos = pos; + m_releaseSelectionQueued = false; + emit dragged(m_event, m_lastPos, pos); + m_lastPos = pos; } void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (!this->active) + if (!m_active) return; - this->active = false; - if (this->releaseSelectionQueued) { - this->releaseSelectionQueued = false; - if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == this->lastPos) - emit selected(this->event, false); + m_active = false; + if (m_releaseSelectionQueued) { + m_releaseSelectionQueued = false; + if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == m_lastPos) + emit selected(m_event, false); } - emit released(this->event, this->lastPos); + emit released(m_event, m_lastPos); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 3272acfe..f036d215 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -113,8 +113,7 @@ void MapImageExporter::setModeSpecificUi() { } if (m_mode == ImageExporterMode::Timelapse) { - // TODO: At the moment edit history for events (and the EventPixmapItem class) - // explicitly depend on the editor and assume their map is currently open. + // TODO: At the moment edit history for events explicitly depend on the editor and assume their map is currently open. // Other edit commands rely on this more subtly, like triggering API callbacks or // spending time rendering their layout (which can make creating timelapses very slow). // Until this is resolved, the selected map/layout must remain the same as in the editor. diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index fde7f820..d867c598 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -27,7 +27,6 @@ ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int heig : QObject(parent), MovableRect(enabled, width * 16, height * 16, color) { - setZValue(0xFFFFFFFF); // ensure on top of view setAcceptHoverEvents(true); setFlags(this->flags() | QGraphicsItem::ItemIsMovable); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 5629d8e9..2b4f6c0b 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -139,6 +139,7 @@ void ResizeLayoutPopup::setupLayoutView() { static bool layoutSizeRectVisible = true; this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); + this->outline->setZValue(Editor::ZValue::ResizeLayoutPopup); // Ensure on top of view this->outline->setLimit(cover->rect().toAlignedRect()); connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ // Note: this extra limit check needs access to the project values, so it is done here and not ResizableRect::mouseMoveEvent From 1d6d0c6dc9c83af91588bd27c3daf5b444f487a5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:38:12 -0400 Subject: [PATCH 318/364] Fix region map tile selector palette differing from selection --- CHANGELOG.md | 1 + src/ui/regionmapeditor.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 984f3879..4f1f5a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix `Add Region Map...` not updating the region map settings file. - Fix some crashes on invalid region map tilesets. - Improve error reporting for invalid region map editor settings. +- Fix the region map editor's palette resetting between region maps. - Fix config files being written before the project is opened successfully. - Fix the map and other project info still displaying if a new project fails to open. - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index b66d442b..493ea78e 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -774,7 +774,10 @@ void RegionMapEditor::displayRegionMapTileSelector() { this->mapsquare_selector_item = new TilemapTileSelector(this->region_map->pngPath(), this->region_map->tilemapFormat(), this->region_map->palPath()); - this->mapsquare_selector_item->draw(); + // Initialize with current settings + this->mapsquare_selector_item->selectHFlip(ui->checkBox_tileHFlip->isChecked()); + this->mapsquare_selector_item->selectVFlip(ui->checkBox_tileVFlip->isChecked()); + this->mapsquare_selector_item->selectPalette(ui->spinBox_tilePalette->value()); // This will also draw the selector this->scene_region_map_tiles->addItem(this->mapsquare_selector_item); From 2df722ab4c03525049d84f3c8faea787727d84a8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:43:30 -0400 Subject: [PATCH 319/364] Fix region map tile selector swapping h/vflip --- CHANGELOG.md | 1 + include/ui/tilemaptileselector.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f1f5a13..a6d6b19d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix some crashes on invalid region map tilesets. - Improve error reporting for invalid region map editor settings. - Fix the region map editor's palette resetting between region maps. +- Fix the region map editor's h-flip and v-flip settings being swapped. - Fix config files being written before the project is opened successfully. - Fix the map and other project info still displaying if a new project fails to open. - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 5c3b8dac..155957a6 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -149,10 +149,10 @@ public: void select(unsigned tileId); unsigned selectedTile = 0; - void selectVFlip(bool hFlip) { this->tile_hFlip = hFlip; } + void selectHFlip(bool hFlip) { this->tile_hFlip = hFlip; } bool tile_hFlip = false; - void selectHFlip(bool vFlip) { this->tile_vFlip = vFlip; } + void selectVFlip(bool vFlip) { this->tile_vFlip = vFlip; } bool tile_vFlip = false; void selectPalette(int palette) { From 84882a5fadb58b0cab2cc5ec4711656a40560da5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:24:32 -0400 Subject: [PATCH 320/364] Prevent dragging events that aren't selected --- src/ui/eventpixmapitem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/eventpixmapitem.cpp b/src/ui/eventpixmapitem.cpp index 7c0b43d3..bc7bb964 100644 --- a/src/ui/eventpixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -74,6 +74,7 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (selectionToggle || !m_selected) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. + m_selected = (selectionToggle) ? !m_selected : true; emit selected(m_event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: @@ -89,7 +90,7 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { } void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (!m_active) + if (!m_active || !m_selected) return; QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); From c0df85e43bb7521e015ed92c53ede606493c7d8c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 20:03:11 -0400 Subject: [PATCH 321/364] Fix dangling references, other warnings --- forms/wildmonchart.ui | 2 +- src/project.cpp | 15 ++++++++++----- src/ui/wildmonsearch.cpp | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index 488e066e..8d6668e4 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -145,7 +145,7 @@ false
- QComboBox::AdjustToMinimumContentsLength + QComboBox::AdjustToMinimumContentsLengthWithIcon 8 diff --git a/src/project.cpp b/src/project.cpp index 6f225ff6..b8f31373 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1688,19 +1688,22 @@ bool Project::readWildMonData() { // Each element describes a type of wild encounter Porymap can expect to find, and we represent this data with an EncounterField. // They should contain a name ("type"), the number of encounter slots and the ratio at which they occur ("encounter_rates"), // and whether the encounters are divided into groups (like fishing rods). - for (const OrderedJson &fieldJson : mainArrayObject.take("fields").array_items()) { + OrderedJson::array fieldsArray = mainArrayObject.take("fields").array_items(); + for (const OrderedJson &fieldJson : fieldsArray) { OrderedJson::object fieldObject = fieldJson.object_items(); EncounterField encounterField; encounterField.name = fieldObject.take("type").string_value(); - for (auto val : fieldObject.take("encounter_rates").array_items()) { + OrderedJson::array encounterRatesArray = fieldObject.take("encounter_rates").array_items(); + for (const auto &val : encounterRatesArray) { encounterField.encounterRates.append(val.int_value()); } // Each element of the "groups" array is an object with the group name as the key (e.g. "old_rod") // and an array of slot numbers indicating which encounter slots in this encounter type belong to that group. - for (auto groupPair : fieldObject.take("groups").object_items()) { + OrderedJson::object groups = fieldObject.take("groups").object_items(); + for (auto groupPair : groups) { const QString groupName = groupPair.first; for (auto slotNum : groupPair.second.array_items()) { encounterField.groups[groupName].append(slotNum.int_value()); @@ -1716,7 +1719,8 @@ bool Project::readWildMonData() { // Each element is an object that will tell us which map it's associated with, // its symbol name (which we will display in the Groups dropdown) and a list of // pokémon associated with any of the encounter types described by the data we parsed above. - for (const auto &encounterJson : mainArrayObject.take("encounters").array_items()) { + OrderedJson::array encountersArray = mainArrayObject.take("encounters").array_items(); + for (const auto &encounterJson : encountersArray) { OrderedJson::object encounterObj = encounterJson.object_items(); WildPokemonHeader header; @@ -1738,7 +1742,8 @@ bool Project::readWildMonData() { encounterRateFrequencyMaps[field][monInfo.encounterRate]++; // Read wild pokémon list - for (const auto &monJson : encounterFieldObj.take("mons").array_items()) { + OrderedJson::array monsArray = encounterFieldObj.take("mons").array_items(); + for (const auto &monJson : monsArray) { OrderedJson::object monObj = monJson.object_items(); WildPokemon newMon; diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index 056e7565..e64fbca4 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -129,6 +129,7 @@ void WildMonSearch::updateResults(const QString &species) { .fieldName = QStringLiteral("--"), .levelRange = QStringLiteral("--"), .chance = QStringLiteral("--"), + .mapName = "", }; addTableEntry(noResults); } else { From 4b3c8abb938850d805955a7ae06f85068f8b35c5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 12:58:15 -0400 Subject: [PATCH 322/364] Remove old heal location map tracking, missing assignment in HealLocationEvent::duplicate --- src/core/events.cpp | 1 + src/project.cpp | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index 7fac9ece..0175556e 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -775,6 +775,7 @@ Event *HealLocationEvent::duplicate() const { copy->setX(this->getX()); copy->setY(this->getY()); copy->setIdName(this->getIdName()); + copy->setHostMapName(this->getHostMapName()); copy->setRespawnMapName(this->getRespawnMapName()); copy->setRespawnNPC(this->getRespawnNPC()); diff --git a/src/project.cpp b/src/project.cpp index b8f31373..c9d13840 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -848,15 +848,9 @@ void Project::saveHealLocations() { // Build the JSON data for output. QMap> idNameToJson; - for (auto i = this->healLocations.constBegin(); i != this->healLocations.constEnd(); i++) { - const QString mapConstant = i.key(); - for (const auto &event : i.value()) { - // Heal location events don't need to track the "map" field, we're already tracking it either with - // the keys in the healLocations map or by virtue of the event being added to a particular Map object. - // The global JSON data needs this field, so we add it back here. - auto eventJson = event->buildEventJson(this); - eventJson["map"] = mapConstant; - idNameToJson[event->getIdName()].append(eventJson); + for (const auto &events : this->healLocations) { + for (const auto &event : events) { + idNameToJson[event->getIdName()].append(event->buildEventJson(this)); } } @@ -871,8 +865,8 @@ void Project::saveHealLocations() { } } // Save any heal locations that weren't covered above (should be any new data). - for (auto i = idNameToJson.constBegin(); i != idNameToJson.constEnd(); i++) { - for (const auto &object : i.value()) { + for (const auto &objects : idNameToJson) { + for (const auto &object : objects) { eventJsonArr.push_back(object); } } From 6e8dc8c0c4a69c6035cf82a4b6660e00f04dd63f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 17:45:18 -0400 Subject: [PATCH 323/364] Update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d6b19d..50007ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add support for defining project values with `enum` where `#define` was expected. - Add a setting to specify the tile values to use for the unused metatile layer. - Add a setting to specify the maximum number of events in a group. A warning will be shown if too many events are added. +- Add a setting to customize the size and position of the player view distance. - Add `onLayoutOpened` to the scripting API. ### Changed @@ -35,7 +36,6 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions. - A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening. - The base game version is now auto-detected if the project name contains only one of "emerald", "firered/leafgreen", or "ruby/sapphire". -- The max encounter rate is now read from the project, rather than assuming the default value from RSE. - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. - `Export Map Stitch Image` and `Export Map Timelapse Image` now show a preview of the full image/gif, not just the current map. @@ -50,6 +50,10 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting. - The options for `Encounter Type` and `Terrain Type` in the Tileset Editor are not hardcoded anymore, they're now read from the project. - The `symbol_wild_encounters` setting was replaced; this value is now read from the project. +- The max encounter rate is now read from the project, rather than assuming the default value from RSE. +- `MAP_OFFSET_W` and `MAP_OFFSET_H` (used to limit the maximum map size) are now read from the project. +- The rendered area of the map border is now limited to the maximum player view distance (prior to this it included two extra rows on the top and bottom). +- An error message will now be shown when Porymap is unable to save changes (e.g. if Porymap doesn't have write permissions for your project). - A project may now be opened even if it has no maps or map groups. A minimum of one map layout is required. - The file extensions that are expected for `.png` and `.pal` data files and the extensions outputted when creating a new tileset can now be customized. - Miscellaneous performance improvements, especially for opening projects. From e8ac63370097f43fe760afe8828858d2ddd08c31 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 18:57:42 -0400 Subject: [PATCH 324/364] Save grid settings in config --- include/config.h | 11 ++++++++--- src/config.cpp | 37 ++++++++++++++++++++++++++++++++----- src/mainwindow.cpp | 3 +++ src/ui/gridsettings.cpp | 2 -- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/include/config.h b/include/config.h index 72891be0..67a8b507 100644 --- a/include/config.h +++ b/include/config.h @@ -14,6 +14,7 @@ #include #include "events.h" +#include "gridsettings.h" extern const QVersionNumber porymapVersion; @@ -36,9 +37,11 @@ protected: virtual QMap getKeyValueMap() = 0; virtual void init() = 0; virtual void setUnreadKeys() = 0; - bool getConfigBool(QString key, QString value); - int getConfigInteger(QString key, QString value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); - uint32_t getConfigUint32(QString key, QString value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); + + static bool getConfigBool(const QString &key, const QString &value); + static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); + static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); + static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = Qt::black); }; class PorymapConfig: public KeyValueConfigBase @@ -92,6 +95,7 @@ public: this->rateLimitTimes.clear(); this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; this->shownInGameReloadMessage = false; + this->gridSettings = GridSettings(); } void addRecentProject(QString project); void setRecentProjects(QStringList projects); @@ -156,6 +160,7 @@ public: QByteArray newMapDialogGeometry; QByteArray newLayoutDialogGeometry; bool shownInGameReloadMessage; + GridSettings gridSettings; protected: virtual QString getConfigFilepath() override; diff --git a/src/config.cpp b/src/config.cpp index 9bc2b9a6..7e7422c7 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -259,7 +259,7 @@ bool KeyValueConfigBase::save() { return true; } -bool KeyValueConfigBase::getConfigBool(QString key, QString value) { +bool KeyValueConfigBase::getConfigBool(const QString &key, const QString &value) { bool ok; int result = value.toInt(&ok, 0); if (!ok || (result != 0 && result != 1)) { @@ -268,26 +268,35 @@ bool KeyValueConfigBase::getConfigBool(QString key, QString value) { return (result != 0); } -int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, int max, int defaultValue) { +int KeyValueConfigBase::getConfigInteger(const QString &key, const QString &value, int min, int max, int defaultValue) { bool ok; int result = value.toInt(&ok, 0); if (!ok) { - logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); result = defaultValue; } return qMin(max, qMax(min, result)); } -uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_t min, uint32_t max, uint32_t defaultValue) { +uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString &value, uint32_t min, uint32_t max, uint32_t defaultValue) { bool ok; uint32_t result = value.toUInt(&ok, 0); if (!ok) { - logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); result = defaultValue; } return qMin(max, qMax(min, result)); } +QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &value, const QColor &defaultValue) { + QColor color = QColor("#" + value); + if (!color.isValid()) { + logWarn(QString("Invalid config value for %1: '%2'. Must be a color in the format 'RRGGBB'. Using default value '%3'.").arg(key).arg(value).arg(defaultValue.name())); + color = defaultValue; + } + return color; +} + PorymapConfig porymapConfig; QString PorymapConfig::getConfigFilepath() { @@ -455,6 +464,18 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "shown_in_game_reload_message") { this->shownInGameReloadMessage = getConfigBool(key, value); + } else if (key == "grid_width") { + this->gridSettings.width = getConfigUint32(key, value); + } else if (key == "grid_height") { + this->gridSettings.height = getConfigUint32(key, value); + } else if (key == "grid_x") { + this->gridSettings.offsetX = getConfigInteger(key, value, 0, 999); + } else if (key == "grid_y") { + this->gridSettings.offsetY = getConfigInteger(key, value, 0, 999); + } else if (key == "grid_style") { + this->gridSettings.style = GridSettings::getStyleFromName(value); + } else if (key == "grid_color") { + this->gridSettings.color = getConfigColor(key, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -532,6 +553,12 @@ QMap PorymapConfig::getKeyValueMap() { } map.insert("event_selection_shape_mode", (this->eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) ? "mask" : "bounding_rect"); map.insert("shown_in_game_reload_message", this->shownInGameReloadMessage ? "1" : "0"); + map.insert("grid_width", QString::number(this->gridSettings.width)); + map.insert("grid_height", QString::number(this->gridSettings.height)); + map.insert("grid_x", QString::number(this->gridSettings.offsetX)); + map.insert("grid_y", QString::number(this->gridSettings.offsetY)); + map.insert("grid_style", GridSettings::getStyleName(this->gridSettings.style)); + map.insert("grid_color", this->gridSettings.color.name().remove("#")); // Our text config treats '#' as the start of a comment. return map; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fdc325fc..d81bd2a0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -587,6 +587,8 @@ void MainWindow::loadUserSettings() { ui->checkBox_ToggleBorder->setChecked(porymapConfig.showBorder); ui->actionShow_Events_In_Map_View->setChecked(porymapConfig.eventOverlayEnabled); + this->editor->gridSettings = porymapConfig.gridSettings; + setTheme(porymapConfig.theme); setDivingMapsVisible(porymapConfig.showDiveEmergeMaps); } @@ -1989,6 +1991,7 @@ void MainWindow::on_actionGrid_Settings_triggered() { if (!this->gridSettingsDialog) { this->gridSettingsDialog = new GridSettingsDialog(&this->editor->gridSettings, this); connect(this->gridSettingsDialog, &GridSettingsDialog::changedGridSettings, this->editor, &Editor::updateMapGrid); + connect(this->gridSettingsDialog, &GridSettingsDialog::accepted, [this] { porymapConfig.gridSettings = this->editor->gridSettings; }); } openSubWindow(this->gridSettingsDialog); } diff --git a/src/ui/gridsettings.cpp b/src/ui/gridsettings.cpp index d3346f11..87e0896a 100644 --- a/src/ui/gridsettings.cpp +++ b/src/ui/gridsettings.cpp @@ -1,8 +1,6 @@ #include "ui_gridsettingsdialog.h" #include "gridsettings.h" -// TODO: Save settings in config - const QMap GridSettings::styleToName = { {Style::Solid, "Solid"}, {Style::LargeDashes, "Large Dashes"}, From d33f0fc6f00d97001cb81354b147ec7d38c1b5d9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 21:22:29 -0400 Subject: [PATCH 325/364] Stop QTextEdit from stealing scroll focus --- forms/projectsettingseditor.ui | 7 ++++++- include/ui/noscrolltextedit.h | 25 +++++++++++++++++++++++++ porymap.pro | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 include/ui/noscrolltextedit.h diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index fe065be9..642c13a8 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1374,7 +1374,7 @@ - + Metatile Behaviors on this list won't trigger warnings for Warp Events @@ -1744,6 +1744,11 @@ QSpinBox
noscrollspinbox.h
+ + NoScrollTextEdit + QTextEdit +
noscrolltextedit.h
+
UIntSpinBox QAbstractSpinBox diff --git a/include/ui/noscrolltextedit.h b/include/ui/noscrolltextedit.h new file mode 100644 index 00000000..dfc66789 --- /dev/null +++ b/include/ui/noscrolltextedit.h @@ -0,0 +1,25 @@ +#ifndef NOSCROLLTEXTEDIT_H +#define NOSCROLLTEXTEDIT_H + +#include +#include + +class NoScrollTextEdit : public QTextEdit +{ + Q_OBJECT +public: + explicit NoScrollTextEdit(const QString &text, QWidget *parent = nullptr) : QTextEdit(text, parent) { + setFocusPolicy(Qt::StrongFocus); + }; + explicit NoScrollTextEdit(QWidget *parent = nullptr) : NoScrollTextEdit(QString(), parent) {}; + + virtual void wheelEvent(QWheelEvent *event) override { + if (hasFocus()) { + QTextEdit::wheelEvent(event); + } else { + event->ignore(); + } + }; +}; + +#endif // NOSCROLLTEXTEDIT_H diff --git a/porymap.pro b/porymap.pro index 35fd6af2..1cdffddf 100644 --- a/porymap.pro +++ b/porymap.pro @@ -223,6 +223,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/newmapgroupdialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ + include/ui/noscrolltextedit.h \ include/ui/montabwidget.h \ include/ui/encountertablemodel.h \ include/ui/encountertabledelegates.h \ From c26c01aaff95c1883a257b07e72f9b477edd73e4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 22 Apr 2025 14:48:15 -0400 Subject: [PATCH 326/364] Add missing tooltip formatting --- forms/connectionslistitem.ui | 14 +-- forms/customattributesdialog.ui | 6 +- forms/customscriptseditor.ui | 6 +- forms/mainwindow.ui | 50 +-------- forms/maplisttoolbar.ui | 8 +- forms/newmapconnectiondialog.ui | 12 +-- forms/preferenceeditor.ui | 16 +-- forms/projectsettingseditor.ui | 160 ++++++++++++++++------------- forms/regionmappropertiesdialog.ui | 36 +++---- include/core/utility.h | 1 + src/core/utility.cpp | 4 + src/ui/customattributestable.cpp | 3 +- src/ui/eventframes.cpp | 103 +++++++++++-------- src/ui/maplisttoolbar.cpp | 2 +- src/ui/projectsettingseditor.cpp | 5 +- 15 files changed, 215 insertions(+), 211 deletions(-) diff --git a/forms/connectionslistitem.ui b/forms/connectionslistitem.ui index bf04e8be..116da983 100644 --- a/forms/connectionslistitem.ui +++ b/forms/connectionslistitem.ui @@ -6,7 +6,7 @@ 0 0 - 178 + 188 157
@@ -20,7 +20,7 @@ .ConnectionsListItem { border-width: 1px; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel @@ -65,7 +65,7 @@ - Remove this connection. + <html><head/><body><p>Remove this connection.</p></body></html> ... @@ -79,28 +79,28 @@ - Where the connected map should be positioned relative to the current map. + <html><head/><body><p>Where the connected map should be positioned relative to the current map.</p></body></html> - The name of the map to connect to the current map. + <html><head/><body><p>The name of the map to connect to the current map.</p></body></html> - The number of spaces to move the connected map perpendicular to its connected direction. + <html><head/><body><p>The number of spaces to move the connected map perpendicular to its connected direction.</p></body></html> - Open the connected map. + <html><head/><body><p>Open the connected map.</p></body></html> ... diff --git a/forms/customattributesdialog.ui b/forms/customattributesdialog.ui index b1f1ee4b..90dfba6e 100644 --- a/forms/customattributesdialog.ui +++ b/forms/customattributesdialog.ui @@ -33,7 +33,7 @@ - The key name for the new JSON field + <html><head/><body><p>The key name for the new JSON field</p></body></html> true @@ -50,7 +50,7 @@ - The data type for the new JSON field + <html><head/><body><p>The data type for the new JSON field</p></body></html> @@ -70,7 +70,7 @@ - The value for the new JSON field + <html><head/><body><p>The value for the new JSON field</p></body></html> diff --git a/forms/customscriptseditor.ui b/forms/customscriptseditor.ui index e2efa2af..7db3b208 100644 --- a/forms/customscriptseditor.ui +++ b/forms/customscriptseditor.ui @@ -60,7 +60,7 @@ - Create a new Porymap script file with a default template + <html><head/><body><p>Create a new Porymap script file with a default template</p></body></html> Create New Script... @@ -74,7 +74,7 @@ - Add an existing script file to the list below + <html><head/><body><p>Add an existing script file to the list below</p></body></html> Load Script... @@ -88,7 +88,7 @@ - Refresh all loaded scripts to account for any recent edits + <html><head/><body><p>Refresh all loaded scripts to account for any recent edits</p></body></html> Refresh Scripts diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 53224385..bf0d2608 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -260,9 +260,6 @@ false - - - 0 @@ -2380,7 +2377,7 @@ - If enabled, connections will automatically be updated on the connected map. + <html><head/><body><p>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</p></body></html> Mirror to Connecting Maps @@ -2433,7 +2430,7 @@ false - Open the selected Dive Map + <html><head/><body><p>Open the selected Dive Map</p></body></html> ... @@ -2447,7 +2444,7 @@ - If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider. + <html><head/><body><p>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</p></body></html> Show Emerge/Dive Maps @@ -2570,7 +2567,7 @@ false - Open the selected Emerge Map + <html><head/><body><p>Open the selected Emerge Map</p></body></html> ... @@ -3079,45 +3076,6 @@ Ctrl+T - - - true - - - - :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - - - Sort by &Location - - - - - true - - - - :/icons/sort_number.ico:/icons/sort_number.ico - - - Sort by &Group - - - Sort by Group - - - - - true - - - - :/icons/sort_map.ico:/icons/sort_map.ico - - - Sort by &Layout - - About Porymap... diff --git a/forms/maplisttoolbar.ui b/forms/maplisttoolbar.ui index 54eb48d0..07878f0a 100644 --- a/forms/maplisttoolbar.ui +++ b/forms/maplisttoolbar.ui @@ -32,7 +32,7 @@ - Add a new folder to the list. + <html><head/><body><p>Add a new folder to the list.</p></body></html> @@ -73,7 +73,7 @@ - Expand all folders in the list. + <html><head/><body><p>Expand all folders in the list.</p></body></html> @@ -93,7 +93,7 @@ - Collapse all folders in the list. + <html><head/><body><p>Collapse all folders in the list.</p></body></html> @@ -113,7 +113,7 @@ - If enabled, folders may be renamed and items in the list may be rearranged. + <html><head/><body><p>If enabled, folders may be renamed and items in the list may be rearranged.</p></body></html> diff --git a/forms/newmapconnectiondialog.ui b/forms/newmapconnectiondialog.ui index 9b3a3b6e..85aeec72 100644 --- a/forms/newmapconnectiondialog.ui +++ b/forms/newmapconnectiondialog.ui @@ -17,10 +17,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -45,7 +45,7 @@ - The name of the map to connect to the current map. + <html><head/><body><p>The name of the map to connect to the current map.</p></body></html> @@ -59,7 +59,7 @@ - Where the connected map should be positioned relative to the current map. + <html><head/><body><p>Where the connected map should be positioned relative to the current map.</p></body></html> @@ -82,10 +82,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index 2ce37cbe..83de6f18 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -40,7 +40,7 @@ - If checked, a prompt to reload your project will appear if relevant project files are edited + <html><head/><body><p>If checked, a prompt to reload your project will appear if relevant project files are edited</p></body></html> Monitor project files @@ -50,7 +50,7 @@ - If checked, Porymap will automatically open your most recently opened project on startup + <html><head/><body><p>If checked, Porymap will automatically open your most recently opened project on startup</p></body></html> Open recent project on launch @@ -60,7 +60,7 @@ - If checked, Porymap will automatically alert you on startup if a new release is available + <html><head/><body><p>If checked, Porymap will automatically alert you on startup if a new release is available</p></body></html> Automatically check for updates @@ -112,7 +112,7 @@ - If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. + <html><head/><body><p>If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted.</p></body></html> Disable warning when deleting events with IDs @@ -138,7 +138,7 @@ - If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. + <html><head/><body><p>If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping.</p></body></html> Select by clicking on sprite @@ -148,7 +148,7 @@ - If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. + <html><head/><body><p>If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites.</p></body></html> Select by clicking within bounding rectangle @@ -231,7 +231,7 @@ - The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + <html><head/><body><p>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</p></body></html> e.g. code %D @@ -264,7 +264,7 @@ - The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + <html><head/><body><p>The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH).</p></body></html> e.g. code --goto %F:%L diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 642c13a8..4cf06515 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -52,7 +52,7 @@ - Whether map script files should prefer using .pory + <html><head/><body><p>Whether map script files should prefer using .pory</p></body></html> Use Poryscript @@ -61,6 +61,9 @@ + + <html><head/><body><p>If enabled, Porymap will display wild encounter data on the Wild Pokémon tab.</p></body></html> + Show Wild Encounter Tables @@ -99,7 +102,7 @@ - Restore the data in the prefabs file to the version defaults. Will create a new file if one doesn't exist. + <html><head/><body><p>Restore the data in the prefabs file to the version defaults. Will create a new file if one doesn't exist.</p></body></html> Import Defaults @@ -109,7 +112,7 @@ - The file that will be used to populate the Prefabs tab + <html><head/><body><p>The file that will be used to populate the Prefabs tab</p></body></html> prefabs.json @@ -148,7 +151,7 @@ - The image sheet that will be used to represent elevation and collision on the Collision tab + <html><head/><body><p>The image sheet that will be used to represent elevation and collision on the Collision tab</p></body></html> true @@ -176,7 +179,7 @@ - The maximum collision value represented with an icon on the image sheet + <html><head/><body><p>The maximum collision value represented with an icon on the image sheet</p></body></html> @@ -197,7 +200,7 @@ - The maximum elevation value represented with an icon on the image sheet + <html><head/><body><p>The maximum elevation value represented with an icon on the image sheet</p></body></html> @@ -270,7 +273,7 @@ - The icon that will be displayed on the Wild Pokémon tab for the above species + <html><head/><body><p>The icon that will be displayed on the Wild Pokémon tab for the above species</p></body></html> true @@ -304,22 +307,22 @@ - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see North of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + 0 + - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see South of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + 0 + @@ -342,22 +345,22 @@ - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see West of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + 0 + - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see East of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + 0 + @@ -463,7 +466,7 @@ 0 0 - 561 + 570 622 @@ -491,14 +494,14 @@ - The default elevation that will be used to fill new maps + <html><head/><body><p>The default elevation that will be used to fill new maps</p></body></html> - Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file + <html><head/><body><p>Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file</p></body></html> Create separate text file @@ -507,6 +510,9 @@ + + <html><head/><body><p>The default layout width for new maps</p></body></html> + 1 @@ -514,6 +520,9 @@ + + <html><head/><body><p>The default layout height for new maps</p></body></html> + 1 @@ -543,14 +552,14 @@ - The default metatile value that will be used to fill new maps + <html><head/><body><p>The default metatile value that will be used to fill new maps</p></body></html> - The default collision that will be used to fill new maps + <html><head/><body><p>The default collision that will be used to fill new maps</p></body></html> @@ -573,7 +582,7 @@ - A comma-separated list of metatile values that will be used to fill new map borders + <html><head/><body><p>A comma-separated list of metatile values that will be used to fill new map borders</p></body></html> @@ -596,28 +605,28 @@ - The default metatile value that will be used for the top-left border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the top-left border metatile on new maps.</p></body></html> - The default metatile value that will be used for the top-right border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the top-right border metatile on new maps.</p></body></html> - The default metatile value that will be used for the bottom-left border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the bottom-left border metatile on new maps.</p></body></html> - The default metatile value that will be used for the bottom-right border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the bottom-right border metatile on new maps.</p></body></html> @@ -697,7 +706,7 @@ - The mask used to read/write metatile IDs in map data. + <html><head/><body><p>The mask used to read/write metatile IDs in map data.</p></body></html> @@ -711,7 +720,7 @@ - The mask used to read/write collision values in map data. + <html><head/><body><p>The mask used to read/write collision values in map data.</p></body></html> @@ -725,7 +734,7 @@ - The mask used to read/write elevation values in map data. + <html><head/><body><p>The mask used to read/write elevation values in map data.</p></body></html> @@ -754,7 +763,7 @@ - Whether "Allow Running", "Allow Biking" and "Allow Dig & Escape Rope" are default options for Map Headers + <html><head/><body><p>Whether &quot;Allow Running&quot;, &quot;Allow Biking&quot; and &quot;Allow Dig &amp; Escape Rope&quot; are default options for Map Headers</p></body></html> Enable 'Allow Running/Biking/Escaping' @@ -764,7 +773,7 @@ - Whether "Floor Number" is a default option for Map Headers + <html><head/><body><p>Whether &quot;Floor Number&quot; is a default option for Map Headers</p></body></html> Enable 'Floor Number' @@ -774,7 +783,7 @@ - Whether the dimensions of the border can be changed. If not set, all borders are 2x2 + <html><head/><body><p>Whether the dimensions of the border can be changed. If not set, all borders are 2x2</p></body></html> Enable Custom Border Size @@ -834,7 +843,7 @@ 0 0 - 561 + 570 798 @@ -853,7 +862,11 @@ - + + + <html><head/><body><p>The default primary tileset to use for new maps/layouts.</p></body></html> + + @@ -863,7 +876,11 @@ - + + + <html><head/><body><p>The default secondary tileset to use for new maps/layouts.</p></body></html> + + @@ -877,7 +894,7 @@ - Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default) + <html><head/><body><p>Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default)</p></body></html> Render as black @@ -887,7 +904,7 @@ - Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA) + <html><head/><body><p>Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA)</p></body></html> Render using first palette color @@ -913,7 +930,7 @@ - This raw tile value will be used to fill the unused bottom layer of Normal metatiles + <html><head/><body><p>This raw tile value will be used to fill the unused bottom layer of Normal metatiles</p></body></html> @@ -927,7 +944,7 @@ - This raw tile value will be used to fill the unused top layer of Covered metatiles + <html><head/><body><p>This raw tile value will be used to fill the unused top layer of Covered metatiles</p></body></html> @@ -941,7 +958,7 @@ - This raw tile value will be used to fill the unused middle layer of Split metatiles + <html><head/><body><p>This raw tile value will be used to fill the unused middle layer of Split metatiles</p></body></html> @@ -985,22 +1002,19 @@ - The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> - The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> - - The number of bytes used per metatile for metatile attributes - Attributes size (in bytes) @@ -1031,6 +1045,9 @@ + + <html><head/><body><p>If checked, metatiles will be interpreted as having 3 layers of 4 tiles each (12 tiles total) as opposed to the default 2 layers of 4 tiles each (8 total).</p></body></html> + Enable Triple Layer Metatiles @@ -1039,7 +1056,7 @@ - The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> @@ -1066,7 +1083,7 @@ - The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> @@ -1079,6 +1096,9 @@ + + <html><head/><body><p>The number of bytes each metatile has for metatile attributes. This is the metadata about each metatile like behvior, layer type, etc.</p></body></html> + false @@ -1119,7 +1139,7 @@ - Whether the C data outputted for new tilesets will include the "callback" field + <html><head/><body><p>Whether the C data outputted for new tilesets will include the &quot;callback&quot; field</p></body></html> Output 'callback' field @@ -1129,7 +1149,7 @@ - Whether the C data outputted for new tilesets will include the "isCompressed" field + <html><head/><body><p>Whether the C data outputted for new tilesets will include the &quot;isCompressed&quot; field</p></body></html> Output 'isCompressed' field @@ -1176,7 +1196,7 @@ 0 0 - 561 + 570 840 @@ -1204,7 +1224,7 @@ - The icon that will be used to represent Warp events + <html><head/><body><p>The icon that will be used to represent Warp events</p></body></html> true @@ -1214,7 +1234,7 @@ - The icon that will be used to represent Heal Location events + <html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html> true @@ -1238,7 +1258,7 @@ - The icon that will be used to represent Object events that don't have their own sprite + <html><head/><body><p>The icon that will be used to represent Object events that don't have their own sprite</p></body></html> true @@ -1255,7 +1275,7 @@ - The icon that will be used to represent Trigger events + <html><head/><body><p>The icon that will be used to represent Trigger events</p></body></html> true @@ -1265,7 +1285,7 @@ - The icon that will be used to represent BG events + <html><head/><body><p>The icon that will be used to represent BG events</p></body></html> true @@ -1339,7 +1359,7 @@ - Remove the current text from the list + <html><head/><body><p>Remove the current text from the list</p></body></html> ... @@ -1363,7 +1383,7 @@ - If checked, Warp Events will not display a warning about incompatible metatile behaviors + <html><head/><body><p>If checked, Warp Events will not display a warning about incompatible metatile behaviors</p></body></html> Disable Warning @@ -1376,7 +1396,7 @@ - Metatile Behaviors on this list won't trigger warnings for Warp Events + <html><head/><body><p>Metatile Behaviors on this list won't trigger warnings for Warp Events</p></body></html> true @@ -1392,7 +1412,7 @@ - Add the current text to the list + <html><head/><body><p>Add the current text to the list</p></body></html> ... @@ -1558,8 +1578,8 @@ 0 0 - 561 - 593 + 570 + 499 @@ -1605,8 +1625,8 @@ 0 0 - 535 - 531 + 544 + 437 @@ -1647,8 +1667,8 @@ 0 0 - 561 - 593 + 570 + 499 @@ -1694,8 +1714,8 @@ 0 0 - 535 - 531 + 544 + 437 diff --git a/forms/regionmappropertiesdialog.ui b/forms/regionmappropertiesdialog.ui index 80e7020a..88b465f4 100644 --- a/forms/regionmappropertiesdialog.ui +++ b/forms/regionmappropertiesdialog.ui @@ -21,7 +21,7 @@ - QFormLayout::AllNonFixedFieldsGrow + QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow @@ -33,7 +33,7 @@ - A nickname for this region map that will differentiate it from others (should be unique). + <html><head/><body><p>A nickname for this region map that will differentiate it from others (should be unique).</p></body></html> @@ -131,7 +131,7 @@ - The height of the tilemap + <html><head/><body><p>The height of the tilemap</p></body></html> 255 @@ -148,10 +148,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -218,10 +218,10 @@ <html><head/><body><p>Path to the tilemap binary relative to the project root.</p></body></html> - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -269,10 +269,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -392,10 +392,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -487,7 +487,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -517,7 +517,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -590,7 +590,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -617,7 +617,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -646,7 +646,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -659,10 +659,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/include/core/utility.h b/include/core/utility.h index 6613ee71..09caebce 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -9,6 +9,7 @@ namespace Util { int roundUp(int numToRound, int multiple); QString toDefineCase(QString input); QString toHexString(uint32_t value, int minLength = 0); + QString toHtmlParagraph(const QString &text); Qt::Orientations getOrientation(bool xflip, bool yflip); } diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 55830b8a..7c7e5435 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -43,6 +43,10 @@ QString Util::toHexString(uint32_t value, int minLength) { return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper(); } +QString Util::toHtmlParagraph(const QString &text) { + return QString("

%1

").arg(text); +} + Qt::Orientations Util::getOrientation(bool xflip, bool yflip) { Qt::Orientations flags; if (xflip) flags |= Qt::Orientation::Horizontal; diff --git a/src/ui/customattributestable.cpp b/src/ui/customattributestable.cpp index 28153b4d..ba9409c8 100644 --- a/src/ui/customattributestable.cpp +++ b/src/ui/customattributestable.cpp @@ -1,6 +1,7 @@ #include "customattributestable.h" #include "parseutil.h" #include "noscrollspinbox.h" +#include "utility.h" #include #include @@ -96,7 +97,7 @@ int CustomAttributesTable::addAttribute(const QString &key, const QJsonValue &va keyItem->setFlags(Qt::ItemIsEnabled); keyItem->setData(DataRole::JsonType, type); // Record the type for writing to the file keyItem->setTextAlignment(Qt::AlignCenter); - keyItem->setToolTip(key); // Display name as tool tip in case it's too long to see in the cell + keyItem->setToolTip(Util::toHtmlParagraph(key)); // Display name as tool tip in case it's too long to see in the cell this->setItem(rowIndex, Column::Key, keyItem); // Add value to table diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index bce55c66..d3cfdc80 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -209,15 +209,16 @@ void ObjectFrame::setup() { // sprite combo QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); - this->combo_sprite->setToolTip("The sprite graphics to use for this object."); + static const QString combo_sprite_toolTip = Util::toHtmlParagraph("The sprite graphics to use for this object."); + this->combo_sprite->setToolTip(combo_sprite_toolTip); l_form_sprite->addRow("Sprite", this->combo_sprite); this->layout_contents->addLayout(l_form_sprite); // movement QFormLayout *l_form_movement = new QFormLayout(); this->combo_movement = new NoScrollComboBox(this); - this->combo_movement->setToolTip("The object's natural movement behavior when\n" - "the player is not interacting with it."); + static const QString combo_movement_toolTip = Util::toHtmlParagraph("The object's natural movement behavior when the player is not interacting with it."); + this->combo_movement->setToolTip(combo_movement_toolTip); l_form_movement->addRow("Movement", this->combo_movement); this->layout_contents->addLayout(l_form_movement); @@ -226,15 +227,15 @@ void ObjectFrame::setup() { this->spinner_radius_x = new NoScrollSpinBox(this); this->spinner_radius_x->setMinimum(0); this->spinner_radius_x->setMaximum(255); - this->spinner_radius_x->setToolTip("The maximum number of metatiles this object\n" - "is allowed to move left or right during its\n" - "normal movement behavior actions."); + static const QString spinner_radius_x_toolTip = Util::toHtmlParagraph("The maximum number of metatiles this object is allowed to move left " + "or right during its normal movement behavior actions."); + this->spinner_radius_x->setToolTip(spinner_radius_x_toolTip); this->spinner_radius_y = new NoScrollSpinBox(this); this->spinner_radius_y->setMinimum(0); this->spinner_radius_y->setMaximum(255); - this->spinner_radius_y->setToolTip("The maximum number of metatiles this object\n" - "is allowed to move up or down during its\n" - "normal movement behavior actions."); + static const QString spinner_radius_y_toolTip = Util::toHtmlParagraph("The maximum number of metatiles this object is allowed to move up " + "or down during its normal movement behavior actions."); + this->spinner_radius_y->setToolTip(spinner_radius_y_toolTip); l_form_radii_xy->addRow("Movement Radius X", this->spinner_radius_x); l_form_radii_xy->addRow("Movement Radius Y", this->spinner_radius_y); this->layout_contents->addLayout(l_form_radii_xy); @@ -242,11 +243,13 @@ void ObjectFrame::setup() { // script QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); - this->combo_script->setToolTip("The script which is executed with this event."); + static const QString combo_script_toolTip = Util::toHtmlParagraph("The script that is executed with this event."); + this->combo_script->setToolTip(combo_script_toolTip); // Add button next to combo which opens combo's current script. this->button_script = new QToolButton(this); - this->button_script->setToolTip("Go to this script definition in text editor."); + static const QString button_script_toolTip = Util::toHtmlParagraph("Go to this script definition in text editor."); + this->button_script->setToolTip(button_script_toolTip); this->button_script->setFixedSize(this->combo_script->height(), this->combo_script->height()); this->button_script->setIcon(QFileIconProvider().icon(QFileIconProvider::File)); @@ -261,24 +264,25 @@ void ObjectFrame::setup() { // event flag QFormLayout *l_form_flag = new QFormLayout(); this->combo_flag = new NoScrollComboBox(this); - this->combo_flag->setToolTip("The flag which hides the object when set."); + static const QString combo_flag_toolTip = Util::toHtmlParagraph("The flag that hides the object when set."); + this->combo_flag->setToolTip(combo_flag_toolTip); l_form_flag->addRow("Event Flag", this->combo_flag); this->layout_contents->addLayout(l_form_flag); // trainer type QFormLayout *l_form_trainer = new QFormLayout(); this->combo_trainer_type = new NoScrollComboBox(this); - this->combo_trainer_type->setToolTip("The trainer type of this object event.\n" - "If it is not a trainer, use NONE. SEE ALL DIRECTIONS\n" - "should only be used with a sight radius of 1."); + static const QString combo_trainer_type_toolTip = Util::toHtmlParagraph("The trainer type of this object event. If it is not a trainer, use NONE. " + "SEE ALL DIRECTIONS should only be used with a sight radius of 1."); + this->combo_trainer_type->setToolTip(combo_trainer_type_toolTip); l_form_trainer->addRow("Trainer Type", this->combo_trainer_type); this->layout_contents->addLayout(l_form_trainer); // sight radius / berry tree id QFormLayout *l_form_radius_treeid = new QFormLayout(); this->combo_radius_treeid = new NoScrollComboBox(this); - this->combo_radius_treeid->setToolTip("The maximum sight range of a trainer,\n" - "OR the unique id of the berry tree."); + static const QString combo_radius_treeid_toolTip = Util::toHtmlParagraph("The maximum sight range of a trainer, OR the unique id of the berry tree."); + this->combo_radius_treeid->setToolTip(combo_radius_treeid_toolTip); l_form_radius_treeid->addRow("Sight Radius / Berry Tree ID", this->combo_radius_treeid); this->layout_contents->addLayout(l_form_radius_treeid); @@ -420,14 +424,16 @@ void CloneObjectFrame::setup() { // clone map id combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_target_map = new NoScrollComboBox(this); - this->combo_target_map->setToolTip("The name of the map that the object being cloned is on."); + static const QString combo_target_map_toolTip = Util::toHtmlParagraph("The name of the map that the object being cloned is on."); + this->combo_target_map->setToolTip(combo_target_map_toolTip); l_form_dest_map->addRow("Target Map", this->combo_target_map); this->layout_contents->addLayout(l_form_dest_map); // clone local id spinbox QFormLayout *l_form_dest_id = new QFormLayout(); this->spinner_target_id = new NoScrollSpinBox(this); - this->spinner_target_id->setToolTip("event_object ID of the object being cloned."); + static const QString spinner_target_id_toolTip = Util::toHtmlParagraph("event_object ID of the object being cloned."); + this->spinner_target_id->setToolTip(spinner_target_id_toolTip); l_form_dest_id->addRow("Target Local ID", this->spinner_target_id); this->layout_contents->addLayout(l_form_dest_id); @@ -497,21 +503,21 @@ void WarpFrame::setup() { // desination map combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_dest_map = new NoScrollComboBox(this); - this->combo_dest_map->setToolTip("The destination map name of the warp."); + static const QString combo_dest_map_toolTip = Util::toHtmlParagraph("The destination map name of the warp."); + this->combo_dest_map->setToolTip(combo_dest_map_toolTip); l_form_dest_map->addRow("Destination Map", this->combo_dest_map); this->layout_contents->addLayout(l_form_dest_map); // desination warp id QFormLayout *l_form_dest_warp = new QFormLayout(); this->combo_dest_warp = new NoScrollComboBox(this); - this->combo_dest_warp->setToolTip("The warp id on the destination map."); + static const QString combo_dest_warp_toolTip = Util::toHtmlParagraph("The warp id on the destination map."); + this->combo_dest_warp->setToolTip(combo_dest_warp_toolTip); l_form_dest_warp->addRow("Destination Warp", this->combo_dest_warp); this->layout_contents->addLayout(l_form_dest_warp); // warning - static const QString warningText = "Warning:\n" - "This warp event is not positioned on a metatile with a warp behavior.\n" - "Click this warning for more details."; + auto warningText = QStringLiteral("Warning:\nThis warp event is not positioned on a metatile with a warp behavior.\nClick this warning for more details."); QVBoxLayout *l_vbox_warning = new QVBoxLayout(); this->warning = new QPushButton(warningText, this); this->warning->setFlat(true); @@ -580,22 +586,25 @@ void TriggerFrame::setup() { // script combo QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); - this->combo_script->setToolTip("The script which is executed with this event."); + static const QString combo_script_toolTip = Util::toHtmlParagraph("The script that is executed with this event."); + this->combo_script->setToolTip(combo_script_toolTip); l_form_script->addRow("Script", this->combo_script); this->layout_contents->addLayout(l_form_script); // var combo QFormLayout *l_form_var = new QFormLayout(); this->combo_var = new NoScrollComboBox(this); - this->combo_var->setToolTip("The variable by which the script is triggered.\n" - "The script is triggered when this variable's value matches 'Var Value'."); + static const QString combo_var_toolTip = Util::toHtmlParagraph("The variable by which the script is triggered. " + "The script is triggered when this variable's value matches 'Var Value'."); + this->combo_var->setToolTip(combo_var_toolTip); l_form_var->addRow("Var", this->combo_var); this->layout_contents->addLayout(l_form_var); // var value combo QFormLayout *l_form_var_val = new QFormLayout(); this->combo_var_value = new NoScrollComboBox(this); - this->combo_var_value->setToolTip("The variable's value which triggers the script."); + static const QString combo_var_value_toolTip = Util::toHtmlParagraph("The variable's value that triggers the script."); + this->combo_var_value->setToolTip(combo_var_value_toolTip); l_form_var_val->addRow("Var Value", this->combo_var_value); this->layout_contents->addLayout(l_form_var_val); @@ -668,7 +677,8 @@ void WeatherTriggerFrame::setup() { // weather combo QFormLayout *l_form_weather = new QFormLayout(); this->combo_weather = new NoScrollComboBox(this); - this->combo_weather->setToolTip("The weather that starts when the player steps on this spot."); + static const QString combo_weather_toolTip = Util::toHtmlParagraph("The weather that starts when the player steps on this spot."); + this->combo_weather->setToolTip(combo_weather_toolTip); l_form_weather->addRow("Weather", this->combo_weather); this->layout_contents->addLayout(l_form_weather); @@ -719,15 +729,16 @@ void SignFrame::setup() { // facing dir combo QFormLayout *l_form_facing_dir = new QFormLayout(); this->combo_facing_dir = new NoScrollComboBox(this); - this->combo_facing_dir->setToolTip("The direction which the player must be facing\n" - "to be able to interact with this event."); + static const QString combo_facing_dir_toolTip = Util::toHtmlParagraph("The direction that the player must be facing to be able to interact with this event."); + this->combo_facing_dir->setToolTip(combo_facing_dir_toolTip); l_form_facing_dir->addRow("Player Facing Direction", this->combo_facing_dir); this->layout_contents->addLayout(l_form_facing_dir); // script combo QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); - this->combo_script->setToolTip("The script which is executed with this event."); + static const QString combo_script_toolTip = Util::toHtmlParagraph("The script that is executed with this event."); + this->combo_script->setToolTip(combo_script_toolTip); l_form_script->addRow("Script", this->combo_script); this->layout_contents->addLayout(l_form_script); @@ -790,14 +801,16 @@ void HiddenItemFrame::setup() { // item combo QFormLayout *l_form_item = new QFormLayout(); this->combo_item = new NoScrollComboBox(this); - this->combo_item->setToolTip("The item to be given."); + static const QString combo_item_toolTip = Util::toHtmlParagraph("The item to be given."); + this->combo_item->setToolTip(combo_item_toolTip); l_form_item->addRow("Item", this->combo_item); this->layout_contents->addLayout(l_form_item); // flag combo QFormLayout *l_form_flag = new QFormLayout(); this->combo_flag = new NoScrollComboBox(this); - this->combo_flag->setToolTip("The flag which is set when the hidden item is picked up."); + static const QString combo_flag_toolTip = Util::toHtmlParagraph("The flag that is set when the hidden item is picked up."); + this->combo_flag->setToolTip(combo_flag_toolTip); l_form_flag->addRow("Flag", this->combo_flag); this->layout_contents->addLayout(l_form_flag); @@ -806,7 +819,8 @@ void HiddenItemFrame::setup() { QFormLayout *l_form_quantity = new QFormLayout(hideable_quantity); l_form_quantity->setContentsMargins(0, 0, 0, 0); this->spinner_quantity = new NoScrollSpinBox(hideable_quantity); - this->spinner_quantity->setToolTip("The number of items received when the hidden item is picked up."); + static const QString spinner_quantity_toolTip = Util::toHtmlParagraph("The number of items received when the hidden item is picked up."); + this->spinner_quantity->setToolTip(spinner_quantity_toolTip); this->spinner_quantity->setMinimum(0x01); this->spinner_quantity->setMaximum(0xFF); l_form_quantity->addRow("Quantity", this->spinner_quantity); @@ -817,7 +831,8 @@ void HiddenItemFrame::setup() { QFormLayout *l_form_itemfinder = new QFormLayout(hideable_itemfinder); l_form_itemfinder->setContentsMargins(0, 0, 0, 0); this->check_itemfinder = new QCheckBox(hideable_itemfinder); - this->check_itemfinder->setToolTip("If checked, hidden item can only be picked up using the Itemfinder"); + static const QString check_itemfinder_toolTip = Util::toHtmlParagraph("If checked, hidden item can only be picked up using the Itemfinder"); + this->check_itemfinder->setToolTip(check_itemfinder_toolTip); l_form_itemfinder->addRow("Requires Itemfinder", this->check_itemfinder); this->layout_contents->addWidget(hideable_itemfinder); @@ -906,9 +921,9 @@ void SecretBaseFrame::setup() { // item combo QFormLayout *l_form_base_id = new QFormLayout(); this->combo_base_id = new NoScrollComboBox(this); - this->combo_base_id->setToolTip("The secret base id which is inside this secret\n" - "base entrance. Secret base ids are meant to be\n" - "unique to each and every secret base entrance."); + static const QString combo_base_id_toolTip = Util::toHtmlParagraph("The secret base id that is inside this secret base entrance. " + "Secret base ids are meant to be unique to each and every secret base entrance."); + this->combo_base_id->setToolTip(combo_base_id_toolTip); l_form_base_id->addRow("Secret Base", this->combo_base_id); this->layout_contents->addLayout(l_form_base_id); @@ -960,7 +975,8 @@ void HealLocationFrame::setup() { // ID QFormLayout *l_form_id = new QFormLayout(); this->line_edit_id = new QLineEdit(this); - this->line_edit_id->setToolTip("The unique identifier for this heal location."); + static const QString line_edit_id_toolTip = Util::toHtmlParagraph("The unique identifier for this heal location."); + this->line_edit_id->setToolTip(line_edit_id_toolTip); this->line_edit_id->setPlaceholderText(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + "MY_MAP"); l_form_id->addRow("ID", this->line_edit_id); this->layout_contents->addLayout(l_form_id); @@ -970,7 +986,8 @@ void HealLocationFrame::setup() { QFormLayout *l_form_respawn_map = new QFormLayout(hideable_respawn_map); l_form_respawn_map->setContentsMargins(0, 0, 0, 0); this->combo_respawn_map = new NoScrollComboBox(hideable_respawn_map); - this->combo_respawn_map->setToolTip("The map where the player will respawn after whiteout."); + static const QString combo_respawn_map_toolTip = Util::toHtmlParagraph("The map where the player will respawn after whiteout."); + this->combo_respawn_map->setToolTip(combo_respawn_map_toolTip); l_form_respawn_map->addRow("Respawn Map", this->combo_respawn_map); this->layout_contents->addWidget(hideable_respawn_map); @@ -979,8 +996,8 @@ void HealLocationFrame::setup() { QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc); l_form_respawn_npc->setContentsMargins(0, 0, 0, 0); this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc); - this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" - "upon respawning after whiteout."); + static const QString combo_respawn_npc_toolTip = Util::toHtmlParagraph("event_object ID of the NPC the player interacts with upon respawning after whiteout."); + this->combo_respawn_npc->setToolTip(combo_respawn_npc_toolTip); l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); this->layout_contents->addWidget(hideable_respawn_npc); diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 304d6490..f56be39b 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -93,7 +93,7 @@ void MapListToolBar::setEmptyFoldersVisible(bool visible) { } // Update tool tip to reflect what will happen if the button is pressed. - const QString toolTip = QString("%1 empty folders in the list.").arg(visible ? "Hide" : "Show"); + const QString toolTip = Util::toHtmlParagraph(QString("%1 empty folders in the list.").arg(visible ? "Hide" : "Show")); ui->button_ToggleEmptyFolders->setToolTip(toolTip); const QSignalBlocker b(ui->button_ToggleEmptyFolders); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 5bcf6399..ccbf1ec3 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -173,7 +173,10 @@ void ProjectSettingsEditor::initUi() { bool ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString &identifier, const QString &filepath) { if (project && project->disabledSettingsNames.contains(identifier)) { widget->setEnabled(false); - widget->setToolTip(QString("This value has been set using '%1' in %2").arg(identifier).arg(filepath)); + QString toolTip = QString("This value has been set using '%1' in %2").arg(identifier).arg(filepath); + if (!widget->toolTip().isEmpty()) + toolTip.prepend(QString("%1\n\n").arg(widget->toolTip())); + widget->setToolTip(Util::toHtmlParagraph(toolTip)); return true; } return false; From 57545eae0a3c3cdd18eff756de76d47c1122a207 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 22 Apr 2025 14:57:27 -0400 Subject: [PATCH 327/364] Fix initializer order warning --- src/ui/wildmonsearch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index e64fbca4..9957f33a 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -125,11 +125,11 @@ void WildMonSearch::updateResults(const QString &species) { const QList results = this->resultsCache.value(species, search(species)); if (results.isEmpty()) { static const RowData noResults = { + .mapName = "", .groupName = QStringLiteral("Species not found."), .fieldName = QStringLiteral("--"), .levelRange = QStringLiteral("--"), .chance = QStringLiteral("--"), - .mapName = "", }; addTableEntry(noResults); } else { From ed273b9ca0e6f00b7a77463dff13302ede424e8a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 20:16:59 -0400 Subject: [PATCH 328/364] Add functionality for global constants --- include/config.h | 2 ++ include/core/parseutil.h | 6 ++++- include/project.h | 3 ++- src/core/parseutil.cpp | 56 ++++++++++++++++++++++++---------------- src/mainwindow.cpp | 2 +- src/project.cpp | 19 +++++++++++--- 6 files changed, 60 insertions(+), 28 deletions(-) diff --git a/include/config.h b/include/config.h index 67a8b507..4f1aeada 100644 --- a/include/config.h +++ b/include/config.h @@ -345,6 +345,7 @@ public: this->unusedTileCovered = 0x0000; this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; + this->globalConstantsFilepaths.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -417,6 +418,7 @@ public: QMargins playerViewDistance; QList warpBehaviors; int maxEventsPerGroup; + QStringList globalConstantsFilepaths; protected: virtual QString getConfigFilepath() override; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index e02d0504..58b5d0ab 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -43,7 +43,7 @@ class ParseUtil { public: ParseUtil(); - void set_root(const QString &dir); + void setRoot(const QString &dir) { this->root = dir; } static QString readTextFile(const QString &path, QString *error = nullptr); bool cacheFile(const QString &path, QString *error = nullptr); void clearFileCache() { this->fileCache.clear(); } @@ -58,6 +58,8 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); + void loadGlobalCDefines(const QString &filename, QString *error = nullptr); + void resetGlobalCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -90,6 +92,8 @@ private: QString curDefine; QHash fileCache; QHash errorMap; + QMap globalDefineValues; + QMap globalDefineExpressions; int evaluateDefine(const QString&, const QString &, QMap*, QMap*); QList tokenizeExpression(QString, QMap*, QMap*); QList generatePostfix(const QList &tokens); diff --git a/include/project.h b/include/project.h index 1031640b..2d2b5fd1 100644 --- a/include/project.h +++ b/include/project.h @@ -76,7 +76,7 @@ public: int maxEncounterRate; bool wildEncountersLoaded; - void set_root(QString); + void setRoot(const QString&); void clearMaps(); void clearTilesetCache(); @@ -203,6 +203,7 @@ public: bool readEventGraphics(); bool readFieldmapProperties(); bool readFieldmapMasks(); + bool readGlobalConstants(); QMap> readObjEventGfxInfo(); QPixmap getEventPixmap(const QString &gfxName, const QString &movementName); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index da2a8f8e..30146284 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -15,26 +15,8 @@ const QRegularExpression ParseUtil::re_poryScriptLabel("\\b(script)(\\((global|l const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((global)\\))?\\s*\\b(?
- - - - - ... + + + + + Qt::Orientation::Horizontal - - - :/icons/help.ico:/icons/help.ico + + + 40 + 20 + - +
- + @@ -1626,7 +1628,7 @@ 0 0 544 - 437 + 338 @@ -1646,6 +1648,34 @@ + + + + <html><head/><body><p>Add additional C files containing #defines or enums. These will be used to resolve unknown symbols during project launch.</p></body></html> + + + Add Global Constants File... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + + + +
@@ -1671,8 +1701,8 @@ 499
- - + + ... @@ -1683,7 +1713,7 @@ - + @@ -1715,7 +1745,7 @@ 0 0 544 - 437 + 421 @@ -1735,6 +1765,36 @@ + + + + <html><head/><body><p>Add an additional #define name and expression. This may be used to evaluate other #defines during project launch.</p></body></html> + + + Add Global Constant... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + +
diff --git a/include/config.h b/include/config.h index 4f1aeada..26d2a353 100644 --- a/include/config.h +++ b/include/config.h @@ -346,6 +346,7 @@ public: this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; this->globalConstantsFilepaths.clear(); + this->globalConstants.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -419,6 +420,7 @@ public: QList warpBehaviors; int maxEventsPerGroup; QStringList globalConstantsFilepaths; + QMap globalConstants; protected: virtual QString getConfigFilepath() override; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 58b5d0ab..59d4ae49 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -58,7 +58,8 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); - void loadGlobalCDefines(const QString &filename, QString *error = nullptr); + void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); + void loadGlobalCDefines(const QMap &defines); void resetGlobalCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); diff --git a/include/ui/newdefinedialog.h b/include/ui/newdefinedialog.h new file mode 100644 index 00000000..2107dcec --- /dev/null +++ b/include/ui/newdefinedialog.h @@ -0,0 +1,32 @@ +#ifndef NEWDEFINEDIALOG_H +#define NEWDEFINEDIALOG_H + +#include +#include + +namespace Ui { +class NewDefineDialog; +} + +class NewDefineDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewDefineDialog(QWidget *parent = nullptr); + ~NewDefineDialog(); + + virtual void accept() override; + +signals: + void createdDefine(const QString &name, const QString &expression); + +private: + Ui::NewDefineDialog *ui; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWDEFINEDIALOG_H diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index e4a6ae94..579aec21 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -67,6 +67,12 @@ private: void setWarpBehaviorsList(QStringList list); void openFilesHelp(); void openIdentifiersHelp(); + void addNewGlobalConstantsFilepath(); + void addGlobalConstantsFilepath(const QString &filepath); + QStringList getGlobalConstantsFilepaths(); + void addNewGlobalConstant(); + void addGlobalConstant(const QString &name, const QString &expression); + QMap getGlobalConstants(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/porymap.pro b/porymap.pro index 1cdffddf..601b2f81 100644 --- a/porymap.pro +++ b/porymap.pro @@ -104,6 +104,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ + src/ui/newdefinedialog.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ @@ -216,6 +217,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ + include/ui/newdefinedialog.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ @@ -269,6 +271,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newdefinedialog.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ forms/newlocationdialog.ui \ diff --git a/src/config.cpp b/src/config.cpp index 7e7422c7..0e22840e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -234,7 +234,7 @@ void KeyValueConfigBase::load() { continue; } - this->parseConfigKeyValue(match.captured("key").trimmed().toLower(), match.captured("value").trimmed()); + this->parseConfigKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed()); } this->setUnreadKeys(); @@ -840,6 +840,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } + } else if (key.startsWith("global_constant/")) { + this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value); + } else if (key == "global_constants_filepaths") { + this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts); } else if (key == "prefabs_filepath") { this->prefabFilepath = value; } else if (key == "prefabs_import_prompted") { @@ -863,7 +867,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "event_icon_path_heal") { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { - this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()).toUpper(), value); + this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { @@ -970,12 +974,16 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("event_icon_path_coord", this->eventIconPaths[Event::Group::Coord]); map.insert("event_icon_path_bg", this->eventIconPaths[Event::Group::Bg]); map.insert("event_icon_path_heal", this->eventIconPaths[Event::Group::Heal]); - for (auto i = this->pokemonIconPaths.cbegin(), end = this->pokemonIconPaths.cend(); i != end; i++){ - const QString path = i.value(); - if (!path.isEmpty()) map.insert("pokemon_icon_path/" + i.key(), path); + for (auto it = this->pokemonIconPaths.constBegin(); it != this->pokemonIconPaths.constEnd(); it++) { + const QString path = it.value(); + if (!path.isEmpty()) map.insert("pokemon_icon_path/" + it.key(), path); } - for (auto i = this->identifiers.cbegin(), end = this->identifiers.cend(); i != end; i++) { - map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); + for (auto it = this->globalConstants.constBegin(); it != this->globalConstants.constEnd(); it++) { + map.insert("global_constant/" + it.key(), it.value()); + } + map.insert("global_constants_filepaths", this->globalConstantsFilepaths.join(",")); + for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { + map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); } map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 30146284..3a8adf93 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -483,7 +483,7 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS const QString expression = defines.expressions.take(name); if (expression == " ") continue; this->curDefine = name; - filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); + filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); // TODO: Unite map with global expressions? Allows users to overwrite project defines logRecordedErrors(); // Only log errors for defines that Porymap is looking for } @@ -509,8 +509,12 @@ QStringList ParseUtil::readCDefineNames(const QString &filename, const QSetglobalDefineExpressions.insert(readCDefines(filename, {}, false, error).expressions); +void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *error) { + loadGlobalCDefines(readCDefines(filename, {}, false, error).expressions); +} + +void ParseUtil::loadGlobalCDefines(const QMap &defines) { + this->globalDefineExpressions.insert(defines); } void ParseUtil::resetGlobalCDefines() { diff --git a/src/project.cpp b/src/project.cpp index 5abc2036..33cac9ce 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2747,11 +2747,12 @@ bool Project::readGlobalConstants() { this->parser.resetGlobalCDefines(); for (const auto &path : projectConfig.globalConstantsFilepaths) { QString error; - this->parser.loadGlobalCDefines(path, &error); + this->parser.loadGlobalCDefinesFromFile(path, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read global constants file '%1': %2").arg(path).arg(error)); } } + this->parser.loadGlobalCDefines(projectConfig.globalConstants); return true; } diff --git a/src/ui/newdefinedialog.cpp b/src/ui/newdefinedialog.cpp new file mode 100644 index 00000000..567e4985 --- /dev/null +++ b/src/ui/newdefinedialog.cpp @@ -0,0 +1,60 @@ +#include "newdefinedialog.h" +#include "ui_newdefinedialog.h" +#include "validator.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewDefineDialog::NewDefineDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewDefineDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + ui->lineEdit_Name->setValidator(new IdentifierValidator(this)); + + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewDefineDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewDefineDialog::dialogButtonClicked); + + adjustSize(); +} + +NewDefineDialog::~NewDefineDialog() +{ + delete ui; +} + +void NewDefineDialog::onNameChanged(const QString &) { + validateName(true); +} + +bool NewDefineDialog::validateName(bool allowEmpty) { + const QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (name.isEmpty() && !allowEmpty) { + errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewDefineDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewDefineDialog::accept() { + if (!validateName()) + return; + emit createdDefine(ui->lineEdit_Name->text(), ui->lineEdit_Value->text()); + QDialog::accept(); +} diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index ccbf1ec3..13b6198f 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -3,6 +3,7 @@ #include "noscrollcombobox.h" #include "prefab.h" #include "filedialog.h" +#include "newdefinedialog.h" #include "utility.h" #include @@ -54,6 +55,9 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_AddWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(true); }); connect(ui->button_RemoveWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(false); }); + connect(ui->button_AddGlobalConstantsFile, &QAbstractButton::clicked, this, &ProjectSettingsEditor::addNewGlobalConstantsFilepath); + connect(ui->button_AddGlobalConstant, &QAbstractButton::clicked, this, &ProjectSettingsEditor::addNewGlobalConstant); + // Connect file selection buttons connect(ui->button_ChoosePrefabs, &QAbstractButton::clicked, [this](bool) { this->choosePrefabsFile(); }); connect(ui->button_CollisionGraphics, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_CollisionGraphics); }); @@ -501,6 +505,12 @@ void ProjectSettingsEditor::refresh() { lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName())); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) lineEdit->setText(projectConfig.getCustomIdentifier(lineEdit->objectName())); + for (const auto &path : projectConfig.globalConstantsFilepaths) { + addGlobalConstantsFilepath(path); + } + for (auto it = projectConfig.globalConstants.constBegin(); it != projectConfig.globalConstants.constEnd(); it++) { + addGlobalConstant(it.key(), it.value()); + } // Set warp behaviors QStringList behaviorNames; @@ -578,6 +588,10 @@ void ProjectSettingsEditor::save() { for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) projectConfig.setIdentifier(lineEdit->objectName(), lineEdit->text()); + // Save global constants + projectConfig.globalConstantsFilepaths = getGlobalConstantsFilepaths(); + projectConfig.globalConstants = getGlobalConstants(); + // Save warp behaviors projectConfig.warpBehaviors.clear(); const QStringList behaviorNames = this->getWarpBehaviorsList(); @@ -624,6 +638,100 @@ void ProjectSettingsEditor::chooseFile(QLineEdit * filepathEdit, const QString & this->hasUnsavedChanges = true; } +void ProjectSettingsEditor::addNewGlobalConstantsFilepath() { + QString filepath = stripProjectDir(FileDialog::getOpenFileName(this, "Choose Global Constants File")); + if (filepath.isEmpty() || getGlobalConstantsFilepaths().contains(filepath)) + return; + + addGlobalConstantsFilepath(filepath); + this->hasUnsavedChanges = true; +} + +void ProjectSettingsEditor::addGlobalConstantsFilepath(const QString &filepath) { + auto filepathLabel = new QLabel(filepath, this); + filepathLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); + filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + + // TODO: Tool tips + + int newRow = ui->gridLayout_GlobalConstantsFiles->rowCount(); + ui->gridLayout_GlobalConstantsFiles->addWidget(filepathLabel, newRow, 0); + + auto deleteButton = new QToolButton(); + deleteButton->setIcon(QIcon(":/icons/delete.ico")); + connect(deleteButton, &QAbstractButton::clicked, [this, filepathLabel, deleteButton](bool) { + ui->gridLayout_GlobalConstantsFiles->removeWidget(filepathLabel); + ui->gridLayout_GlobalConstantsFiles->removeWidget(deleteButton); + delete filepathLabel; + delete deleteButton; + this->hasUnsavedChanges = true; + }); + ui->gridLayout_GlobalConstantsFiles->addWidget(deleteButton, newRow, 1); +} + +QStringList ProjectSettingsEditor::getGlobalConstantsFilepaths() { + QStringList paths; + for (int row = 1; row < ui->gridLayout_GlobalConstantsFiles->rowCount(); row++) { + auto item = ui->gridLayout_GlobalConstantsFiles->itemAtPosition(row, 0); + if (!item) continue; + auto pathLabel = dynamic_cast(item->widget()); + if (!pathLabel) continue; + paths.append(pathLabel->text()); + } + return paths; +} + +void ProjectSettingsEditor::addNewGlobalConstant() { + auto dialog = new NewDefineDialog(this); + connect(dialog, &NewDefineDialog::createdDefine, [this](const QString &name, const QString &expression) { + if (!getGlobalConstants().contains(name)) { + addGlobalConstant(name, expression); + this->hasUnsavedChanges = true; + } + }); + dialog->open(); +} + +void ProjectSettingsEditor::addGlobalConstant(const QString &name, const QString &expression) { + // TODO: Tool tips + auto nameLabel = new QLabel(name, this); + nameLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); + nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + + auto expressionLineEdit = new QLineEdit(expression, this); + + int newRow = ui->gridLayout_GlobalConstants->rowCount(); + ui->gridLayout_GlobalConstants->addWidget(nameLabel, newRow, 0); + ui->gridLayout_GlobalConstants->addWidget(expressionLineEdit, newRow, 1); + + auto deleteButton = new QToolButton(); + deleteButton->setIcon(QIcon(":/icons/delete.ico")); + connect(deleteButton, &QAbstractButton::clicked, [this, nameLabel, expressionLineEdit, deleteButton](bool) { + ui->gridLayout_GlobalConstants->removeWidget(nameLabel); + ui->gridLayout_GlobalConstants->removeWidget(expressionLineEdit); + ui->gridLayout_GlobalConstants->removeWidget(deleteButton); + delete nameLabel; + delete expressionLineEdit; + delete deleteButton; + this->hasUnsavedChanges = true; + }); + ui->gridLayout_GlobalConstants->addWidget(deleteButton, newRow, 2); +} + +QMap ProjectSettingsEditor::getGlobalConstants() { + QMap constants; + for (int row = 1; row < ui->gridLayout_GlobalConstants->rowCount(); row++) { + auto nameItem = ui->gridLayout_GlobalConstants->itemAtPosition(row, 0); + auto expressionItem = ui->gridLayout_GlobalConstants->itemAtPosition(row, 1); + if (!nameItem || !expressionItem) continue; + auto nameLabel = dynamic_cast(nameItem->widget()); + auto expressionLineEdit = dynamic_cast(expressionItem->widget()); + if (!nameLabel || !expressionLineEdit) continue; + constants.insert(nameLabel->text(), expressionLineEdit->text()); + } + return constants; +} + // Display relative path if this file is in the project folder QString ProjectSettingsEditor::stripProjectDir(QString s) { if (s.startsWith(this->baseDir)) From 10aa9a623f20ddd1c13120b4ea6f941729772366 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 02:33:55 -0400 Subject: [PATCH 331/364] Update new tool tips --- src/ui/eventframes.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 07b77c0b..95a31557 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -230,8 +230,9 @@ void ObjectFrame::setup() { // local id QFormLayout *l_form_local_id = new QFormLayout(); this->line_edit_local_id = new QLineEdit(this); - this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" - "If no game is given you can refer to this object using its 'object id' number."); + static const QString line_edit_local_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this object in scripts. " + "If no name is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setToolTip(line_edit_local_id_toolTip); this->line_edit_local_id->setPlaceholderText("LOCALID_MY_NPC"); l_form_local_id->addRow("Local ID", this->line_edit_local_id); this->layout_contents->addLayout(l_form_local_id); @@ -455,8 +456,9 @@ void CloneObjectFrame::setup() { // local id QFormLayout *l_form_local_id = new QFormLayout(); this->line_edit_local_id = new QLineEdit(this); - this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" - "If no game is given you can refer to this object using its 'object id' number."); + static const QString line_edit_local_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this object in scripts. " + "If no name is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setToolTip(line_edit_local_id_toolTip); this->line_edit_local_id->setPlaceholderText("LOCALID_MY_CLONE_NPC"); l_form_local_id->addRow("Local ID", this->line_edit_local_id); this->layout_contents->addLayout(l_form_local_id); @@ -464,9 +466,10 @@ void CloneObjectFrame::setup() { // sprite combo (edits disabled) QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); - this->combo_sprite->setToolTip("The sprite graphics to use for this object. This is updated automatically\n" - "to match the target object, and so can't be edited. By default the games\n" - "will get the graphics directly from the target object, so this field is ignored."); + static const QString combo_sprite_toolTip = Util::toHtmlParagraph("The sprite graphics to use for this object. This is updated automatically " + "to match the target object, and so can't be edited. By default the games " + "will get the graphics directly from the target object, so this field is ignored."); + this->combo_sprite->setToolTip(combo_sprite_toolTip); l_form_sprite->addRow("Sprite", this->combo_sprite); this->combo_sprite->setEnabled(false); this->layout_contents->addLayout(l_form_sprite); @@ -574,8 +577,9 @@ void WarpFrame::setup() { // ID QFormLayout *l_form_id = new QFormLayout(); this->line_edit_id = new QLineEdit(this); - this->line_edit_id->setToolTip("An optional, unique name to use to refer to this warp from other warps.\n" - "If no game is given you can refer to this warp using its 'warp id' number."); + static const QString line_edit_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this warp from other warps. " + "If no name is given you can refer to this warp using its 'warp id' number."); + this->line_edit_id->setToolTip(line_edit_id_toolTip); this->line_edit_id->setPlaceholderText("WARP_ID_MY_WARP"); l_form_id->addRow("ID", this->line_edit_id); this->layout_contents->addLayout(l_form_id); From dedf0d3e5748dc356f3965ce74d78557fed8f317 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 02:50:50 -0400 Subject: [PATCH 332/364] Fix crash on project switch --- src/core/events.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index c902db32..22315211 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -20,8 +20,7 @@ Event* Event::create(Event::Type type) { } Event::~Event() { - if (this->eventFrame) - this->eventFrame->deleteLater(); + delete this->eventFrame; } EventFrame *Event::getEventFrame() { From 046f942f4192344953939332c7ee74e5e9c06c68 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 11:41:08 -0400 Subject: [PATCH 333/364] Fix some issues with player view rectangle visibility --- include/ui/movablerect.h | 10 ++++++++-- src/editor.cpp | 2 ++ src/mainwindow.cpp | 2 +- src/ui/movablerect.cpp | 21 +++++++++++++++------ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 21edd21d..92dd43f7 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -20,7 +20,7 @@ public: } void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { - if (!(*enabled)) return; + if (!isVisible()) return; painter->setPen(this->color); painter->drawRect(this->rect() + QMargins(1,1,1,1)); // Fill painter->setPen(Qt::black); @@ -28,11 +28,17 @@ public: painter->drawRect(this->rect()); // Inner border } void updateLocation(int x, int y); - bool *enabled; + + void setActive(bool active); + bool getActive() const { return this->active; } protected: + bool *enabled = nullptr; + bool active = true; QRectF baseRect; QRgb color; + + void updateVisibility(); }; diff --git a/src/editor.cpp b/src/editor.cpp index 0f6991ba..2d789bb8 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -170,6 +170,7 @@ void Editor::setEditMode(EditMode editMode) { } this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(editingLayout); + this->playerViewRect->setActive(editingLayout); this->editGroup.setActiveStack(editStack); setMapEditingButtonsEnabled(editingLayout); @@ -1111,6 +1112,7 @@ void Editor::scaleMapView(int s) { void Editor::setPlayerViewRect(const QRectF &rect) { delete this->playerViewRect; this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, rect, qRgb(255, 255, 255)); + this->playerViewRect->setActive(getEditingLayout()); if (ui->graphicsView_Map->scene()) ui->graphicsView_Map->scene()->update(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 365c0d1c..3860c456 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1961,7 +1961,7 @@ void MainWindow::on_actionPlayer_View_Rectangle_triggered() this->editor->settings->playerViewRectEnabled = enabled; if ((this->editor->map_item && this->editor->map_item->has_mouse) || (this->editor->collision_item && this->editor->collision_item->has_mouse)) { - this->editor->playerViewRect->setVisible(enabled); + this->editor->playerViewRect->setVisible(enabled && this->editor->playerViewRect->getActive()); ui->graphicsView_Map->scene()->update(); } } diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 4290d1a7..ade48afd 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -11,16 +11,25 @@ MovableRect::MovableRect(bool *enabled, const QRectF &rect, const QRgb &color) baseRect(rect), color(color) { - this->setVisible(*enabled); + updateVisibility(); } /// Center rect on grid position (x, y) void MovableRect::updateLocation(int x, int y) { - this->setRect(this->baseRect.x() + (x * 16), - this->baseRect.y() + (y * 16), - this->baseRect.width(), - this->baseRect.height()); - this->setVisible(*this->enabled); + setRect(this->baseRect.x() + (x * 16), + this->baseRect.y() + (y * 16), + this->baseRect.width(), + this->baseRect.height()); + updateVisibility(); +} + +void MovableRect::setActive(bool active) { + this->active = active; + updateVisibility(); +} + +void MovableRect::updateVisibility() { + setVisible(*this->enabled && this->active); } /****************************************************************************** From fc0b1b1b586d289bbe445942df484dad12c29157 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 14:11:13 -0400 Subject: [PATCH 334/364] Fix invalid selections being marginally visible on the collision selector --- src/ui/selectablepixmapitem.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index 7cf17ec1..e15824ad 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -92,19 +92,22 @@ QPoint SelectablePixmapItem::getCellPos(QPointF pos) void SelectablePixmapItem::drawSelection() { - QPixmap pixmap = this->pixmap(); - QPainter painter(&pixmap); QPoint origin = this->getSelectionStart(); QPoint dimensions = this->getSelectionDimensions(); + QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.x() * this->cellWidth, dimensions.y() * this->cellHeight); - int rectWidth = dimensions.x() * this->cellWidth; - int rectHeight = dimensions.y() * this->cellHeight; + // If a selection is fully outside the bounds of the selectable area, don't draw anything. + // This prevents the border of the selection rectangle potentially being visible on an otherwise invisible selection. + QPixmap pixmap = this->pixmap(); + if (!selectionRect.intersects(pixmap.rect())) + return; + QPainter painter(&pixmap); painter.setPen(QColor(0xff, 0xff, 0xff)); - painter.drawRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, rectWidth - 1, rectHeight -1); + painter.drawRect(selectionRect.x(), selectionRect.y(), selectionRect.width() - 1, selectionRect.height() - 1); painter.setPen(QColor(0, 0, 0)); - painter.drawRect(origin.x() * this->cellWidth - 1, origin.y() * this->cellHeight - 1, rectWidth + 1, rectHeight + 1); - painter.drawRect(origin.x() * this->cellWidth + 1, origin.y() * this->cellHeight + 1, rectWidth - 3, rectHeight - 3); + painter.drawRect(selectionRect.x() - 1, selectionRect.y() - 1, selectionRect.width() + 1, selectionRect.height() + 1); + painter.drawRect(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 3, selectionRect.height() - 3); this->setPixmap(pixmap); } From 781f965d6b7586d024961f00a8568def07471bea Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 16:51:57 -0400 Subject: [PATCH 335/364] Allow parser to remember defines, globals take precedence --- include/core/parseutil.h | 16 ++++++-- src/core/parseutil.cpp | 85 +++++++++++++++++++++++----------------- src/project.cpp | 2 +- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 59d4ae49..a8a7939d 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -60,7 +60,7 @@ public: QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); void loadGlobalCDefines(const QMap &defines); - void resetGlobalCDefines(); + void resetCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -93,10 +93,20 @@ private: QString curDefine; QHash fileCache; QHash errorMap; + + // The maps of define names to values/expressions that are available while parsing C defines. + // As the parser reads and evaluates more defines it will update these maps accordingly. + QMap knownDefineValues; + QMap knownDefineExpressions; + + // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. + // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. QMap globalDefineValues; QMap globalDefineExpressions; - int evaluateDefine(const QString&, const QString &, QMap*, QMap*); - QList tokenizeExpression(QString, QMap*, QMap*); + + int evaluateDefine(const QString &identifier, bool *ok = nullptr); + int evaluateExpression(const QString &expression); + QList tokenizeExpression(QString expression); QList generatePostfix(const QList &tokens); int evaluatePostfix(const QList &postfix); void recordError(const QString &message); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 3a8adf93..e9c6f1c0 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -16,7 +16,7 @@ const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((gl const QRegularExpression ParseUtil::re_poryRawSection("\\b(raw)\\s*`(?[^`]*)"); ParseUtil::ParseUtil() { - resetGlobalCDefines(); + resetCDefines(); } QString ParseUtil::pathWithRoot(const QString &path) { @@ -125,28 +125,48 @@ QList ParseUtil::parseAsm(const QString &filename) { return parsed; } -// 'identifier' is the name of the #define to evaluate, e.g. 'FOO' in '#define FOO (BAR+1)' -// 'expression' is the text of the #define to evaluate, e.g. '(BAR+1)' in '#define FOO (BAR+1)' -// 'knownValues' is a pointer to a map of identifier->values for defines that have already been evaluated. -// 'unevaluatedExpressions' is a pointer to a map of identifier->expressions for defines that have not been evaluated. If this map contains any -// identifiers found in 'expression' then this function will be called recursively to evaluate that define first. -// This function will maintain the passed maps appropriately as new #defines are evaluated. -int ParseUtil::evaluateDefine(const QString &identifier, const QString &expression, QMap *knownValues, QMap *unevaluatedExpressions) { - if (unevaluatedExpressions->contains(identifier)) - unevaluatedExpressions->remove(identifier); +// Try to evaluate the given #define/enum 'identifier' name using the information the parser has. +// If it recognizes the name as an identifier it's aware of (either from having parsed it or having been told about +// it using 'loadGlobalCDefines') it will evaluate it if necessary then return the resulting value and set 'ok' to true. +// Evaluated identifiers are cached, and will only be re-evaluated if the parser encounters a new expression for that identifier. +// If it doesn't recognize it, 'ok' will be set to false and it will return 0. +int ParseUtil::evaluateDefine(const QString &identifier, bool *ok) { + if (ok) *ok = true; - if (knownValues->contains(identifier)) - return knownValues->value(identifier); + // Global defines take precedence + if (this->globalDefineExpressions.contains(identifier)) { + int value = evaluateExpression(this->globalDefineExpressions.take(identifier)); + this->globalDefineValues.insert(identifier, value); + return value; + } + auto it = this->globalDefineValues.constFind(identifier); + if (it != this->globalDefineValues.constEnd()) { + return it.value(); + } - QList tokens = tokenizeExpression(expression, knownValues, unevaluatedExpressions); - QList postfixExpression = generatePostfix(tokens); - int value = evaluatePostfix(postfixExpression); + // Check known expressions before checking known values. + // If an identifier is redefined then we'll receive a new expression for it, and we want to make sure we re-evaluate it. + if (this->knownDefineExpressions.contains(identifier)) { + int value = evaluateExpression(this->knownDefineExpressions.take(identifier)); + this->knownDefineValues.insert(identifier, value); + return value; + } + it = this->knownDefineValues.constFind(identifier); + if (it != this->knownDefineValues.constEnd()) { + return it.value(); + } - knownValues->insert(identifier, value); - return value; + if (ok) *ok = false; + return 0; } -QList ParseUtil::tokenizeExpression(QString expression, QMap *knownValues, QMap *unevaluatedExpressions) { +int ParseUtil::evaluateExpression(const QString &expression) { + QList tokens = tokenizeExpression(expression); + QList postfixExpression = generatePostfix(tokens); + return evaluatePostfix(postfixExpression); +} + +QList ParseUtil::tokenizeExpression(QString expression) { QList tokens; static const QStringList tokenTypes = {"hex", "decimal", "identifier", "operator", "leftparen", "rightparen"}; @@ -163,18 +183,14 @@ QList ParseUtil::tokenizeExpression(QString expression, QMapcontains(token)) { - evaluateDefine(token, unevaluatedExpressions->value(token), knownValues, unevaluatedExpressions); - } else if (this->globalDefineExpressions.contains(token)) { - int value = evaluateDefine(token, this->globalDefineExpressions.value(token), &this->globalDefineValues, &this->globalDefineExpressions); - knownValues->insert(token, value); - } - - if (knownValues->contains(token)) { + bool ok; + int tokenValue = evaluateDefine(token, &ok); + if (ok) { // Any errors encountered when this identifier was evaluated should be recorded for this expression as well. recordErrors(this->errorMap.value(token)); - QString actualToken = QString("%1").arg(knownValues->value(token)); + + // Replace token with evaluated expression + QString actualToken = QString::number(tokenValue); expression = expression.replace(0, token.length(), actualToken); token = actualToken; tokenType = "decimal"; @@ -467,6 +483,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const result.filteredNames.append(name); } } + this->knownDefineExpressions.insert(result.expressions); return result; } @@ -476,14 +493,10 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS // Evaluate defines QMap filteredValues; - QMap allValues = this->globalDefineValues; this->errorMap.clear(); while (!defines.filteredNames.isEmpty()) { - const QString name = defines.filteredNames.takeFirst(); - const QString expression = defines.expressions.take(name); - if (expression == " ") continue; - this->curDefine = name; - filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); // TODO: Unite map with global expressions? Allows users to overwrite project defines + this->curDefine = defines.filteredNames.takeFirst(); + filteredValues.insert(this->curDefine, evaluateDefine(this->curDefine)); logRecordedErrors(); // Only log errors for defines that Porymap is looking for } @@ -517,7 +530,7 @@ void ParseUtil::loadGlobalCDefines(const QMap &defines) { this->globalDefineExpressions.insert(defines); } -void ParseUtil::resetGlobalCDefines() { +void ParseUtil::resetCDefines() { static const QMap defaultDefineValues = { {"FALSE", 0}, {"TRUE", 1}, @@ -535,6 +548,8 @@ void ParseUtil::resetGlobalCDefines() { }; this->globalDefineValues = defaultDefineValues; this->globalDefineExpressions.clear(); + this->knownDefineValues.clear(); + this->knownDefineExpressions.clear(); } QStringList ParseUtil::readCArray(const QString &filename, const QString &label) { diff --git a/src/project.cpp b/src/project.cpp index 33cac9ce..b798a203 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2744,7 +2744,7 @@ bool Project::readMiscellaneousConstants() { } bool Project::readGlobalConstants() { - this->parser.resetGlobalCDefines(); + this->parser.resetCDefines(); for (const auto &path : projectConfig.globalConstantsFilepaths) { QString error; this->parser.loadGlobalCDefinesFromFile(path, &error); From 715f53731d0cb43a4d0aa1bf8e1190de5ed5e3b0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 17:08:47 -0400 Subject: [PATCH 336/364] Parser define maps to hashes --- include/core/parseutil.h | 17 ++++++------ src/core/parseutil.cpp | 17 ++++++++---- src/project.cpp | 59 ++++++++++++++++++++-------------------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index a8a7939d..aae86a88 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -55,11 +55,12 @@ public: QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); - QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); + QHash readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); + QHash readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); void loadGlobalCDefines(const QMap &defines); + void loadGlobalCDefines(const QHash &defines); void resetCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); @@ -96,13 +97,13 @@ private: // The maps of define names to values/expressions that are available while parsing C defines. // As the parser reads and evaluates more defines it will update these maps accordingly. - QMap knownDefineValues; - QMap knownDefineExpressions; + QHash knownDefineValues; + QHash knownDefineExpressions; // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. - QMap globalDefineValues; - QMap globalDefineExpressions; + QHash globalDefineValues; + QHash globalDefineExpressions; int evaluateDefine(const QString &identifier, bool *ok = nullptr); int evaluateExpression(const QString &expression); @@ -115,11 +116,11 @@ private: QString createErrorMessage(const QString &message, const QString &expression); struct ParsedDefines { - QMap expressions; // Map of all define names encountered to their expressions + QHash expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; ParsedDefines readCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); - QMap evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + QHash evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; QString loadTextFile(const QString &path, QString *error = nullptr); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index e9c6f1c0..9130aea0 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -488,11 +488,11 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const } // Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on). -QMap ParseUtil::evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error) { +QHash ParseUtil::evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error) { ParsedDefines defines = readCDefines(filename, filterList, useRegex, error); // Evaluate defines - QMap filteredValues; + QHash filteredValues; this->errorMap.clear(); while (!defines.filteredNames.isEmpty()) { this->curDefine = defines.filteredNames.takeFirst(); @@ -504,12 +504,12 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS } // Find and evaluate a specific set of defines with known names. -QMap ParseUtil::readCDefinesByName(const QString &filename, const QSet &names, QString *error) { +QHash ParseUtil::readCDefinesByName(const QString &filename, const QSet &names, QString *error) { return evaluateCDefines(filename, names, false, error); } // Find and evaluate an unknown list of defines with a known name pattern. -QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error) { +QHash ParseUtil::readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error) { return evaluateCDefines(filename, regexList, true, error); } @@ -526,12 +526,17 @@ void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *err loadGlobalCDefines(readCDefines(filename, {}, false, error).expressions); } -void ParseUtil::loadGlobalCDefines(const QMap &defines) { +void ParseUtil::loadGlobalCDefines(const QHash &defines) { this->globalDefineExpressions.insert(defines); } +void ParseUtil::loadGlobalCDefines(const QMap &defines) { + for (auto it = defines.constBegin(); it != defines.constEnd(); it++) + this->globalDefineExpressions.insert(it.key(), it.value()); +} + void ParseUtil::resetCDefines() { - static const QMap defaultDefineValues = { + static const QHash defaultDefineValues = { {"FALSE", 0}, {"TRUE", 1}, {"SCHAR_MIN", SCHAR_MIN}, diff --git a/src/project.cpp b/src/project.cpp index b798a203..4b038618 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1481,7 +1481,7 @@ bool Project::readTilesetMetatileLabels() { fileWatcher.addPath(root + "/" + metatileLabelsFilename); const QSet regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; - const QMap defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); + const auto defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); for (auto i = defines.constBegin(); i != defines.constEnd(); i++) { QString label = i.key(); uint32_t metatileId = i.value(); @@ -2116,16 +2116,15 @@ bool Project::readFieldmapProperties() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); fileWatcher.addPath(root + "/" + filename); - const QMap defines = parser.readCDefinesByName(filename, { - numTilesPrimaryName, - numTilesTotalName, - numMetatilesPrimaryName, - numPalsPrimaryName, - numPalsTotalName, - maxMapSizeName, - numTilesPerMetatileName, - mapOffsetWidthName, - mapOffsetHeightName, + const auto defines = parser.readCDefinesByName(filename, { numTilesPrimaryName, + numTilesTotalName, + numMetatilesPrimaryName, + numPalsPrimaryName, + numPalsTotalName, + maxMapSizeName, + numTilesPerMetatileName, + mapOffsetWidthName, + mapOffsetHeightName, }); auto loadDefine = [defines](const QString name, int * dest, int min, int max) { @@ -2219,16 +2218,15 @@ bool Project::readFieldmapMasks() { const QString elevationMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_elevation); const QString behaviorMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior); const QString layerTypeMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer); - const QSet searchNames = { - metatileIdMaskName, - collisionMaskName, - elevationMaskName, - behaviorMaskName, - layerTypeMaskName, - }; + const QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); fileWatcher.addPath(root + "/" + globalFieldmap); - QMap defines = parser.readCDefinesByName(globalFieldmap, searchNames); + const auto defines = parser.readCDefinesByName(globalFieldmap, { metatileIdMaskName, + collisionMaskName, + elevationMaskName, + behaviorMaskName, + layerTypeMaskName, + }); // These mask values are accessible via the settings editor for users who don't have these defines. // If users do have the defines we disable them in the settings editor and direct them to their project files. @@ -2239,8 +2237,8 @@ bool Project::readFieldmapMasks() { // Read Block masks auto readBlockMask = [defines](const QString name, uint16_t *value) { - auto it = defines.find(name); - if (it == defines.end()) + auto it = defines.constFind(name); + if (it == defines.constEnd()) return false; *value = static_cast(it.value()); if (*value != it.value()){ @@ -2314,7 +2312,7 @@ bool Project::readFieldmapMasks() { // Read #defines for encounter and terrain types to populate in the Tileset Editor dropdowns (if necessary) QString error; if (projectConfig.metatileEncounterTypeMask) { - QMap defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_encounter_types)}, &error); + const auto defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_encounter_types)}, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read encounter type constants from '%1': %2").arg(globalFieldmap).arg(error)); error = QString(); @@ -2325,7 +2323,7 @@ bool Project::readFieldmapMasks() { } } if (projectConfig.metatileTerrainTypeMask) { - QMap defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_terrain_types)}, &error); + const auto defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_terrain_types)}, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read terrain type constants from '%1': %2").arg(globalFieldmap).arg(error)); error = QString(); @@ -2673,7 +2671,7 @@ bool Project::readMetatileBehaviors() { QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors); fileWatcher.addPath(root + "/" + filename); QString error; - QMap defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); + const auto defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); if (defines.isEmpty() && projectConfig.metatileBehaviorMask) { // Not having any metatile behavior names is ok (their values will be displayed instead) // but if the user's metatiles can have nonzero values then warn them, as they likely want names. @@ -2710,9 +2708,14 @@ bool Project::readObjEventGfxConstants() { QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events); fileWatcher.addPath(root + "/" + filename); QString error; - this->gfxDefines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); + const auto defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error)); + + this->gfxDefines.clear(); + for (auto it = defines.constBegin(); it != defines.constEnd(); it++) + this->gfxDefines.insert(it.key(), it.value()); + return true; } @@ -2720,7 +2723,7 @@ bool Project::readMiscellaneousConstants() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global); const QString maxObjectEventsName = projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count); fileWatcher.addPath(root + "/" + filename); - QMap defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); + const auto defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); this->maxObjectEvents = 64; // Default value auto it = defines.find(maxObjectEventsName); @@ -2953,9 +2956,7 @@ QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { // Invalid gfx constant. If this is a number, try to use that instead. bool ok; int gfxNum = ParseUtil::gameStringToInt(gfxName, &ok); - if (ok && gfxNum < this->gfxDefines.count()) { - gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr); - } + if (ok) gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr); } if (gfx && !gfx->loaded) { // This is the first request for this event's sprite. We'll attempt to load it now. From 4259e652448832362ce74452bb673278948c1e6c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 17:34:15 -0400 Subject: [PATCH 337/364] Clean up settings editor changes --- forms/projectsettingseditor.ui | 2 +- src/mainwindow.cpp | 7 +++++++ src/ui/projectsettingseditor.cpp | 7 ++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index fbcaa312..05598b8f 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -21,7 +21,7 @@ - 4 + 0 diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0a88c34b..ee127f36 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1665,6 +1665,13 @@ void MainWindow::duplicate() { void MainWindow::copy() { auto focused = QApplication::focusWidget(); if (focused) { + // Allow copying text from selectable QLabels. + auto label = dynamic_cast(focused); + if (label && !label->selectedText().isEmpty()) { + setClipboardData(label->selectedText()); + return; + } + QString objectName = focused->objectName(); if (objectName == "graphicsView_currentMetatileSelection") { // copy the current metatile selection as json data diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 13b6198f..d67bcc82 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -650,9 +650,7 @@ void ProjectSettingsEditor::addNewGlobalConstantsFilepath() { void ProjectSettingsEditor::addGlobalConstantsFilepath(const QString &filepath) { auto filepathLabel = new QLabel(filepath, this); filepathLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); - filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work - - // TODO: Tool tips + filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); int newRow = ui->gridLayout_GlobalConstantsFiles->rowCount(); ui->gridLayout_GlobalConstantsFiles->addWidget(filepathLabel, newRow, 0); @@ -693,10 +691,9 @@ void ProjectSettingsEditor::addNewGlobalConstant() { } void ProjectSettingsEditor::addGlobalConstant(const QString &name, const QString &expression) { - // TODO: Tool tips auto nameLabel = new QLabel(name, this); nameLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); - nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); auto expressionLineEdit = new QLineEdit(expression, this); From 845f93c9e4b7a24fca5fed2b7cccddb83301c3ba Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 17:53:33 -0400 Subject: [PATCH 338/364] Fix Qt 5.14 build --- src/core/parseutil.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9130aea0..00e53e52 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -483,7 +483,14 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const result.filteredNames.append(name); } } + // QHash::insert(const QHash &other) was introduced in 5.15. +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) this->knownDefineExpressions.insert(result.expressions); +#else + for (auto it = result.expressions.constBegin(); it != result.expressions.constEnd(); it++) { + this->knownDefineExpressions.insert(it.key(), it.value()); + } +#endif return result; } @@ -527,7 +534,14 @@ void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *err } void ParseUtil::loadGlobalCDefines(const QHash &defines) { + // QHash::insert(const QHash &other) was introduced in 5.15. +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) this->globalDefineExpressions.insert(defines); +#else + for (auto it = defines.constBegin(); it != defines.constEnd(); it++) { + this->globalDefineExpressions.insert(it.key(), it.value()); + } +#endif } void ParseUtil::loadGlobalCDefines(const QMap &defines) { From d97fd5b2a6a96bf581668cf201736562ec067657 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 21:54:42 -0400 Subject: [PATCH 339/364] Update onMapResized --- docsrc/manual/scripting-capabilities.rst | 8 +++--- include/core/maplayout.h | 4 +-- include/scripting.h | 5 +++- resources/text/script_template.txt | 2 +- src/core/maplayout.cpp | 34 +++++++++--------------- src/scriptapi/scripting.cpp | 14 +++++++--- 6 files changed, 34 insertions(+), 33 deletions(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index ac1cf521..0651f7d9 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -204,7 +204,7 @@ Callbacks Called when the mouse exits the map. -.. js:function:: onMapResized(oldWidth, oldHeight, newWidth, newHeight) +.. js:function:: onMapResized(oldWidth, oldHeight, delta) Called when the dimensions of the map are changed. @@ -212,10 +212,8 @@ Callbacks :type oldWidth: number :param oldHeight: the height of the map before the change :type oldHeight: number - :param newWidth: the width of the map after the change - :type newWidth: number - :param newHeight: the height of the map after the change - :type newHeight: number + :param delta: the amount the map size changed in each direction. The object's shape is ``{left, right, top, bottom}`` + :type prevBlock: delta .. js:function:: onBorderResized(oldWidth, oldHeight, newWidth, newHeight) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 6e82604a..3e67af18 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -107,8 +107,8 @@ public: void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); - void adjustDimensions(QMargins margins, bool setNewBlockdata = true); - void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); + void adjustDimensions(const QMargins &margins, bool setNewBlockdata = true); + void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void cacheBlockdata(); diff --git a/include/scripting.h b/include/scripting.h index 6870f159..b85e9e26 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -39,6 +39,7 @@ public: static void populateGlobalObject(MainWindow *mainWindow); static QJSEngine *getEngine(); static void invokeAction(int actionIndex); + static void cb_ProjectOpened(QString projectPath); static void cb_ProjectClosed(QString projectPath); static void cb_MetatileChanged(int x, int y, Block prevBlock, Block newBlock); @@ -47,18 +48,20 @@ public: static void cb_BlockHoverCleared(); static void cb_MapOpened(QString mapName); static void cb_LayoutOpened(QString layoutName); - static void cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight); + static void cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta); static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_MapShifted(int xDelta, int yDelta); static void cb_TilesetUpdated(QString tilesetName); static void cb_MainTabChanged(int oldTab, int newTab); static void cb_MapViewTabChanged(int oldTab, int newTab); static void cb_BorderVisibilityToggled(bool visible); + static bool tryErrorJS(QJSValue js); static QJSValue fromBlock(Block block); static QJSValue fromTile(Tile tile); static Tile toTile(QJSValue obj); static QJSValue dimensions(int width, int height); + static QJSValue margins(const QMargins &margins); static QJSValue position(int x, int y); static const QImage * getImage(const QString &filepath, bool useCache); static QJSValue dialogInput(QJSValue input, bool selectedOk); diff --git a/resources/text/script_template.txt b/resources/text/script_template.txt index 4b5134d1..bee6e56e 100644 --- a/resources/text/script_template.txt +++ b/resources/text/script_template.txt @@ -39,7 +39,7 @@ export function onBlockHoverCleared() { } // Called when the dimensions of the map are changed. -export function onMapResized(oldWidth, oldHeight, newWidth, newHeight) { +export function onMapResized(oldWidth, oldHeight, delta) { } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index fb0f714c..c3e91ba3 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -189,46 +189,38 @@ void Layout::setBorderBlockData(Blockdata newBlockdata, bool enableScriptCallbac } } -void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { +void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata) { if (setNewBlockdata) { setNewDimensionsBlockdata(newWidth, newHeight); } - - int oldWidth = this->width; - int oldHeight = this->height; this->width = newWidth; this->height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); - } - - emit dimensionsChanged(QSize(getWidth(), getHeight())); + emit dimensionsChanged(QSize(this->width, this->height)); } -void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { - int newWidth = this->width + margins.left() + margins.right(); - int newHeight = this->height + margins.top() + margins.bottom(); +void Layout::adjustDimensions(const QMargins &margins, bool setNewBlockdata) { + int oldWidth = this->width; + int oldHeight = this->height; + this->width = oldWidth + margins.left() + margins.right(); + this->height = oldHeight + margins.top() + margins.bottom(); if (setNewBlockdata) { // Fill new blockdata Blockdata newBlockdata; - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if ((x < margins.left()) || (x >= newWidth - margins.right()) || (y < margins.top()) || (y >= newHeight - margins.bottom())) { + for (int y = 0; y < this->height; y++) + for (int x = 0; x < this->width; x++) { + if ((x < margins.left()) || (x >= this->width - margins.right()) || (y < margins.top()) || (y >= this->height - margins.bottom())) { newBlockdata.append(0); } else { - int index = (y - margins.top()) * this->width + (x - margins.left()); + int index = (y - margins.top()) * oldWidth + (x - margins.left()); newBlockdata.append(this->blockdata.value(index)); } } this->blockdata = newBlockdata; } - this->width = newWidth; - this->height = newHeight; - - emit dimensionsChanged(QSize(getWidth(), getHeight())); + Scripting::cb_MapResized(oldWidth, oldHeight, margins); + emit dimensionsChanged(QSize(this->width, this->height)); } void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 05fd31d9..93823de2 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -268,14 +268,13 @@ void Scripting::cb_LayoutOpened(QString layoutName) { instance->invokeCallback(OnLayoutOpened, args); } -void Scripting::cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight) { +void Scripting::cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta) { if (!instance) return; QJSValueList args { oldWidth, oldHeight, - newWidth, - newHeight, + Scripting::margins(delta), }; instance->invokeCallback(OnMapResized, args); } @@ -356,6 +355,15 @@ QJSValue Scripting::dimensions(int width, int height) { return obj; } +QJSValue Scripting::margins(const QMargins &margins) { + QJSValue obj = instance->engine->newObject(); + obj.setProperty("left", margins.left()); + obj.setProperty("right", margins.right()); + obj.setProperty("top", margins.top()); + obj.setProperty("bottom", margins.bottom()); + return obj; +} + QJSValue Scripting::position(int x, int y) { QJSValue obj = instance->engine->newObject(); obj.setProperty("x", x); From c52bc46c0fdc24b0b03271c9624dde9ee62336f9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 24 Apr 2025 16:29:33 -0400 Subject: [PATCH 340/364] Update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50007ef5..eaf4c225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add an input field to the Tileset Editor for editing the full metatile attributes value directly, including unused bits. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. +- Add support for referring to object events and warps with named IDs, rather than referring to them with their index number. - Add a setting to specify the tile values to use for the unused metatile layer. - Add a setting to specify the maximum number of events in a group. A warning will be shown if too many events are added. - Add a setting to customize the size and position of the player view distance. @@ -67,9 +68,13 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix config files being written before the project is opened successfully. - Fix the map and other project info still displaying if a new project fails to open. - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). -- Fix selections with multiple Events not always clearing when making a new selection. +- Fix selections with multiple events not always clearing when making a new selection. - Fix the new event button not updating correctly when selecting object events. - Fix duplicated `Hidden Item` events not copying the `Requires Itemfinder` field. +- Fix event sprites disappearing in certain areas outside the map boundaries. +- Fix deselecting an event still allowing you to drag the event around. +- Fix events rendering on top of the ruler at very high y values. +- Fix new map names not appearing in event dropdowns that have already been populated. - Fix `About porymap` opening a new window each time it's activated. - Fix the `Edit History` window not raising to the front when reactivated. - New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded. From 79ffe668a3596ced52027cfc12bb4ae59eec8ae7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 24 Apr 2025 16:19:08 -0400 Subject: [PATCH 341/364] Update script dropdowns when a map's scripts file is edited --- include/core/events.h | 42 ++++----------------- include/core/map.h | 18 ++++++--- include/ui/eventframes.h | 2 + src/core/events.cpp | 11 ++++-- src/core/map.cpp | 45 +++++++++++++--------- src/ui/eventframes.cpp | 81 +++++++++++++++++++++------------------- 6 files changed, 99 insertions(+), 100 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 9da9b531..76a58dcf 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -34,15 +34,6 @@ class HiddenItemEvent; class SecretBaseEvent; class HealLocationEvent; -class EventVisitor { -public: - virtual void nothing() { } - virtual void visitObject(ObjectEvent *) = 0; - virtual void visitTrigger(TriggerEvent *) = 0; - virtual void visitSign(SignEvent *) = 0; -}; - - /// /// Event base class -- purely virtual /// @@ -121,8 +112,6 @@ public: void modify(); - virtual void accept(EventVisitor *) { } - void setX(int newX) { this->x = newX; } void setY(int newY) { this->y = newY; } void setZ(int newZ) { this->elevation = newZ; } @@ -150,6 +139,8 @@ public: virtual QSet getExpectedFields() = 0; + virtual QStringList getScripts() const { return QStringList(); } + QJsonObject getCustomAttributes() const { return this->customAttributes; } void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } @@ -222,8 +213,6 @@ public: virtual Event *duplicate() const override; - virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); } - virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; @@ -233,6 +222,8 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScript()}; } + virtual QPixmap loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } @@ -392,8 +383,6 @@ public: virtual Event *duplicate() const override; - virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); } - virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; @@ -403,6 +392,8 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScriptLabel()}; } + void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; } QString getScriptVar() const { return this->scriptVar; } @@ -490,8 +481,6 @@ public: virtual Event *duplicate() const override; - virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); } - virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; @@ -501,6 +490,8 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScriptLabel()}; } + void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; } QString getFacingDirection() const { return this->facingDirection; } @@ -633,21 +624,4 @@ inline uint qHash(const Event::Group &key, uint seed = 0) { return qHash(static_cast(key), seed); } - -/// -/// Keeps track of scripts -/// -class ScriptTracker : public EventVisitor { -public: - virtual void visitObject(ObjectEvent *object) override { this->scripts << object->getScript(); }; - virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); }; - virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); }; - - QStringList getScripts() const { return this->scripts; } - -private: - QStringList scripts; -}; - - #endif // EVENTS_H diff --git a/include/core/map.h b/include/core/map.h index 84791fa3..2ba172ce 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #define DEFAULT_BORDER_WIDTH 2 @@ -56,7 +57,7 @@ public: MapHeader* header() const { return m_header; } void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } - void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; } + void setSharedScriptsMap(const QString &sharedScriptsMap); QString sharedEventsMap() const { return m_sharedEventsMap; } QString sharedScriptsMap() const { return m_sharedScriptsMap; } @@ -75,14 +76,15 @@ public: Event* getEvent(Event::Group group, const QString &idName) const; QStringList getEventIdNames(Event::Group group) const; int getNumEvents(Event::Group group = Event::Group::None) const; - QStringList getScriptLabels(Event::Group group = Event::Group::None); - QString getScriptsFilePath() const; - void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); int getIndexOfEvent(Event *) const; bool hasEvent(Event *) const; + QStringList getScriptLabels(Event::Group group = Event::Group::None); + QString getScriptsFilePath() const; + void openScript(const QString &label); + void deleteConnections(); QList getConnections() const { return m_connections; } MapConnection* getConnection(const QString &direction) const; @@ -108,7 +110,7 @@ private: QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; - QStringList m_scriptsFileLabels; + QStringList m_scriptLabels; QJsonObject m_customAttributes; MapHeader *m_header = nullptr; @@ -131,10 +133,14 @@ private: QList m_connections; QSet m_ownedConnections; - QUndoStack *m_editHistory = nullptr; + QPointer m_editHistory; + QPointer m_scriptFileWatcher; + + void invalidateScripts(); signals: void modified(); + void scriptsModified(); void mapDimensionsChanged(const QSize &size); void openScriptRequested(QString label); void connectionAdded(MapConnection*); diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 09eae50b..3858e54e 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -56,9 +56,11 @@ protected: bool populated = false; bool initialized = false; bool connected = false; + QPointer project; void populateDropdown(NoScrollComboBox * combo, const QStringList &items); void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + void populateMapNameDropdown(NoScrollComboBox * combo, Project * project); void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); private: diff --git a/src/core/events.cpp b/src/core/events.cpp index 22315211..ce376c44 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -114,7 +114,7 @@ QString Event::typeToString(Event::Type type) { } QPixmap Event::loadPixmap(Project *project) { - this->pixmap = project->getEventPixmap(this->getEventGroup()); + this->pixmap = project ? project->getEventPixmap(this->getEventGroup()) : QPixmap(); this->usesDefaultPixmap = true; return this->pixmap; } @@ -226,7 +226,7 @@ QSet ObjectEvent::getExpectedFields() { } QPixmap ObjectEvent::loadPixmap(Project *project) { - this->pixmap = project->getEventPixmap(this->gfx, this->movement); + this->pixmap = project ? project->getEventPixmap(this->gfx, this->movement) : QPixmap(); if (!this->pixmap.isNull()) { this->usesDefaultPixmap = false; return this->pixmap; @@ -316,7 +316,7 @@ QSet CloneObjectEvent::getExpectedFields() { QPixmap CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone - Map *clonedMap = project->loadMap(this->targetMap); + Map *clonedMap = project ? project->loadMap(this->targetMap) : nullptr; Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { @@ -324,10 +324,13 @@ QPixmap CloneObjectEvent::loadPixmap(Project *project) { ObjectEvent *clonedObject = dynamic_cast(clonedEvent); this->gfx = clonedObject->getGfx(); this->movement = clonedObject->getMovement(); - } else { + } else if (project) { // Invalid object specified, use default graphics data (as would be shown in-game) this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); + } else { + this->gfx = "0"; + this->movement = "0"; } return ObjectEvent::loadPixmap(project); } diff --git a/src/core/map.cpp b/src/core/map.cpp index 3cd087e1..c1905f0a 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -14,6 +14,10 @@ Map::Map(QObject *parent) : QObject(parent) { m_editHistory = new QUndoStack(this); + + m_scriptFileWatcher = new QFileSystemWatcher(this); + connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); + resetEvents(); m_header = new MapHeader(this); @@ -120,35 +124,40 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); } -void Map::openScript(QString label) { +void Map::openScript(const QString &label) { emit openScriptRequested(label); } +void Map::setSharedScriptsMap(const QString &sharedScriptsMap) { + if (m_sharedScriptsMap == sharedScriptsMap) + return; + m_sharedScriptsMap = sharedScriptsMap; + invalidateScripts(); +} + +void Map::invalidateScripts() { + m_scriptsLoaded = false; + emit scriptsModified(); +} + QStringList Map::getScriptLabels(Event::Group group) { if (!m_scriptsLoaded) { - m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath()); + const QString scriptsFilePath = getScriptsFilePath(); + m_scriptLabels = ParseUtil::getGlobalScriptLabels(scriptsFilePath); m_scriptsLoaded = true; + + // Track the scripts file for changes. Path may have changed, so stop tracking old files. + m_scriptFileWatcher->removePaths(m_scriptFileWatcher->files()); + m_scriptFileWatcher->addPath(scriptsFilePath); } - QStringList scriptLabels; + QStringList scriptLabels = m_scriptLabels; - // Get script labels currently in-use by the map's events - if (group == Event::Group::None) { - ScriptTracker scriptTracker; - for (const auto &event : getEvents()) { - event->accept(&scriptTracker); - } - scriptLabels = scriptTracker.getScripts(); - } else { - ScriptTracker scriptTracker; - for (const auto &event : m_events.value(group)) { - event->accept(&scriptTracker); - } - scriptLabels = scriptTracker.getScripts(); + // Add script labels currently in-use by the map's events + for (const auto &event : getEvents(group)) { + scriptLabels.append(event->getScripts()); } - // Add labels from the map's scripts file - scriptLabels.append(m_scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); scriptLabels.removeAll(""); scriptLabels.removeAll("0"); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 95a31557..b9cdab22 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -152,8 +152,12 @@ void EventFrame::initialize() { this->label_icon->setPixmap(this->event->getPixmap()); } -void EventFrame::populate(Project *) { +void EventFrame::populate(Project *project) { this->populated = true; + if (this->project && this->project != project) { + this->project->disconnect(this); + } + this->project = project; } void EventFrame::invalidateConnections() { @@ -166,6 +170,10 @@ void EventFrame::invalidateUi() { void EventFrame::invalidateValues() { this->populated = false; + if (this->isVisible()) { + // Repopulate immediately + this->populate(this->project); + } } void EventFrame::setActive(bool active) { @@ -186,14 +194,15 @@ void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &i void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. - if (!this->event->getMap()) + Map *map = this->event ? this->event->getMap() : nullptr; + if (!map) return; - QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup()); + QStringList scripts = map->getScriptLabels(this->event->getEventGroup()); populateDropdown(combo, scripts); // Depending on the settings, the autocomplete may also contain all global scripts. - if (porymapConfig.loadAllEventScripts) { + if (project && porymapConfig.loadAllEventScripts) { project->insertGlobalScriptLabels(scripts); } @@ -209,14 +218,23 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj combo->setCompleter(completer); - // If the project changes the script labels, update the EventFrame. - // TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes). - // This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes. - connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); + // If the script labels change then we need to update the EventFrame. + if (project) connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); + connect(map, &Map::scriptsModified, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void EventFrame::populateMapNameDropdown(NoScrollComboBox * combo, Project * project) { + if (!project) + return; + + populateDropdown(combo, project->mapNames); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) { - if (!project->mapNames.contains(mapName)) + if (!project || !project->mapNames.contains(mapName)) return; Map *map = project->loadMap(mapName); @@ -325,7 +343,6 @@ void ObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; // local id this->line_edit_local_id->disconnect(); @@ -336,18 +353,18 @@ void ObjectFrame::connectSignals(MainWindow *window) { // sprite update this->combo_sprite->disconnect(); - connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) { + connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) { this->object->setGfx(text); - this->object->getPixmapItem()->render(project); + this->object->getPixmapItem()->render(this->project); this->object->modify(); }); connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap); // movement this->combo_movement->disconnect(); - connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) { + connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) { this->object->setMovement(text); - this->object->getPixmapItem()->render(project); + this->object->getPixmapItem()->render(this->project); this->object->modify(); }); @@ -498,7 +515,6 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; // local id this->line_edit_local_id->disconnect(); @@ -512,26 +528,23 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { // target map this->combo_target_map->disconnect(); - connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &mapName) { this->clone->setTargetMap(mapName); - this->clone->getPixmapItem()->render(project); + this->clone->getPixmapItem()->render(this->project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); - populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); + populateIdNameDropdown(this->combo_target_id, this->project, mapName, Event::Group::Object); }); connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); // target id this->combo_target_id->disconnect(); - connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) { + connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) { this->clone->setTargetID(text); - this->clone->getPixmapItem()->render(project); + this->clone->getPixmapItem()->render(this->project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); - - // This frame type displays map names, so when a new map is created we need to repopulate it. - connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) { @@ -567,7 +580,7 @@ void CloneObjectFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - populateDropdown(this->combo_target_map, project->mapNames); + populateMapNameDropdown(this->combo_target_map, project); populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object); } @@ -618,7 +631,6 @@ void WarpFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; // id this->line_edit_id->disconnect(); @@ -629,10 +641,10 @@ void WarpFrame::connectSignals(MainWindow *window) { // dest map this->combo_dest_map->disconnect(); - connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &mapName) { this->warp->setDestinationMap(mapName); this->warp->modify(); - populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp); + populateIdNameDropdown(this->combo_dest_warp, this->project, mapName, Event::Group::Warp); }); connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); @@ -646,9 +658,6 @@ void WarpFrame::connectSignals(MainWindow *window) { // warning this->warning->disconnect(); connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked); - - // This frame type displays map names, so when a new map is created we need to repopulate it. - connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void WarpFrame::tryInvalidateIdDropdown(Map *map) { @@ -681,7 +690,7 @@ void WarpFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - populateDropdown(this->combo_dest_map, project->mapNames); + populateMapNameDropdown(this->combo_dest_map, project); populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp); } @@ -1102,7 +1111,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; this->line_edit_id->disconnect(); connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { @@ -1111,10 +1119,10 @@ void HealLocationFrame::connectSignals(MainWindow *window) { }); this->combo_respawn_map->disconnect(); - connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &mapName) { this->healLocation->setRespawnMapName(mapName); this->healLocation->modify(); - populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object); + populateIdNameDropdown(this->combo_respawn_npc, this->project, mapName, Event::Group::Object); }); connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); @@ -1123,9 +1131,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) { this->healLocation->setRespawnNPC(text); this->healLocation->modify(); }); - - // This frame type displays map names, so when a new map is created we need to repopulate it. - connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void HealLocationFrame::tryInvalidateIdDropdown(Map *map) { @@ -1158,7 +1163,7 @@ void HealLocationFrame::populate(Project *project) { EventFrame::populate(project); if (projectConfig.healLocationRespawnDataEnabled) { - populateDropdown(this->combo_respawn_map, project->mapNames); + populateMapNameDropdown(this->combo_respawn_map, project); populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object); } } From 134a933d11143ed84a21f947bc07e37631b7c6b5 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 16 Apr 2025 12:42:15 -0400 Subject: [PATCH 342/364] create splash screen for loading --- forms/loadingscreen.ui | 164 ++++++++++++++++++++++++++++++++ include/mainwindow.h | 2 + include/ui/loadingscreen.h | 46 +++++++++ porymap.pro | 3 + resources/images.qrc | 1 + resources/images/porysplash.gif | Bin 0 -> 2884 bytes src/core/parseutil.cpp | 3 + src/main.cpp | 10 +- src/mainwindow.cpp | 11 ++- src/ui/loadingscreen.cpp | 51 ++++++++++ 10 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 forms/loadingscreen.ui create mode 100644 include/ui/loadingscreen.h create mode 100644 resources/images/porysplash.gif create mode 100644 src/ui/loadingscreen.cpp diff --git a/forms/loadingscreen.ui b/forms/loadingscreen.ui new file mode 100644 index 00000000..799f0a9d --- /dev/null +++ b/forms/loadingscreen.ui @@ -0,0 +1,164 @@ + + + LoadingScreen + + + Qt::ApplicationModal + + + + 0 + 0 + 366 + 255 + + + + BusyCursor + + + Qt::NoContextMenu + + + Form + + + + + + + 20 + true + + + + porymap + + + Qt::AlignCenter + + + + + + + + 12 + + + + Version 6.0.0 + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 64 + 64 + + + + IMAGE + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + Loading..... + + + + + + + TextLabel + + + + + + + + + + + diff --git a/include/mainwindow.h b/include/mainwindow.h index 683df61f..c65cac79 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -52,6 +52,8 @@ public: MainWindow(const MainWindow &) = delete; MainWindow & operator = (const MainWindow &) = delete; + void initialize(); + // Scripting API Q_INVOKABLE QJSValue getBlock(int x, int y); void tryRedrawMapArea(bool forceRedraw); diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h new file mode 100644 index 00000000..10ea4629 --- /dev/null +++ b/include/ui/loadingscreen.h @@ -0,0 +1,46 @@ +#include "qgifimage.h" + +#include +#include +#include + +namespace Ui { +class LoadingScreen; +} + +// Loading class +// LOAD() // or Offload() OFFLOAD() WorkerThread() +// function that wraps around QFuture and QtConcurrent, does not block gui thread +// can call any other function that is not directly painting the gui on another thread + +// Execute function while playing loading screen and display text +// void executeLoadingScreen(QString text, void (*function)(void)); + +class PorymapLoadingScreen : public QWidget { + + Q_OBJECT + +public: + explicit PorymapLoadingScreen(QWidget *parent = nullptr); + ~PorymapLoadingScreen(); + + void setPixmap(QPixmap pixmap); + void showMessage(QString text); + + void start() { this->timer.start(120); } + +private: + void setupUi(); + +public slots: + void updateFrame(); + +private: + Ui::LoadingScreen *ui; + + QGifImage splashImage; + int frame = 0; + QTimer timer; +}; + +extern PorymapLoadingScreen *porysplash; diff --git a/porymap.pro b/porymap.pro index d8505e11..263bc61b 100644 --- a/porymap.pro +++ b/porymap.pro @@ -133,6 +133,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/preferenceeditor.cpp \ src/ui/regionmappropertiesdialog.cpp \ src/ui/colorpicker.cpp \ + src/ui/loadingscreen.cpp \ src/config.cpp \ src/editor.cpp \ src/main.cpp \ @@ -248,6 +249,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/preferenceeditor.h \ include/ui/regionmappropertiesdialog.h \ include/ui/colorpicker.h \ + include/ui/loadingscreen.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ @@ -267,6 +269,7 @@ FORMS += forms/mainwindow.ui \ forms/connectionslistitem.ui \ forms/customattributesframe.ui \ forms/gridsettingsdialog.ui \ + forms/loadingscreen.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ forms/newlayoutdialog.ui \ diff --git a/resources/images.qrc b/resources/images.qrc index e1253139..41789a6e 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -94,6 +94,7 @@ images/collisions_unknown.png images/Entities_16x16.png images/pokemon_icon_placeholder.png + images/porysplash.gif icons/clipboard.ico icons/map_go.ico diff --git a/resources/images/porysplash.gif b/resources/images/porysplash.gif new file mode 100644 index 0000000000000000000000000000000000000000..900874d43389e40ca91fefcce500c075d0f3145c GIT binary patch literal 2884 zcmcK5cTiK=9>DQ?Ne@Lx2*dyyPy~bkA_%A`gceFD7DS~a1SC4W(Nd8=9J#nwbHbzw0?VIS_*aS?r)dVhoGTBr@6Th{)|m zMnUnxtRseu@KB?mBS(T5>`1_1cuCsp7UElv0S9{h#-*HUW?H43?%Gib(d(`Na84^r zNt$@Hrge_uaX~d=TZLA!`D}UX;cluT$&8c+gq{g2 zqY1$yJ1XoHN@6z`)#)n;PMhQF?CB>2Deg^}G@HYXrE9kg2(7oHVK68poRFGuOW}5x z$yND7=N!$hKTxoG6#NYUIRIFC(RwQTkDF;j?{v5szQ@Y|TD>CB^ZG;q(`mO*#8pkwc$JaS_bvX3wI?P+zo@`dcPGJAb!%#A zjuGG_$AozF3&-(u{I&OH?baLnSZ~M?BFE}HYMi4AzEbDYhNmrW{HijSZ4i=2+#IiZ+@$tA6>;V=& zLyen|s>aLY=Q7oKE_m=Hz$jj;Uw7op2mlGBpJ6m2@%SbV2)9dI}snAKjUkk%JW;dXy^Ly#X4fZC9j7B6Z_7NOdI&fwxiS z*YoWNHGaR{-5{Q9Qa#!wN5nOj)g8IlV5SCsT~&YW7C9;XBHh=xW&exYwG8&3Zq8qD z+mrGCbAyapuE3#CD{v4b0wIH(r4Cab6c6CwvvZ^=tKs`Ms}`_}&*l(N7-E}^pYkaD z?y27Aeijf=4$t)LRqqXhgixTKW0mA|)lhEz0%?w;2u62JT@f48PZ~g$GTuZJwYm!o zlnQRW&&;+dGT0E$9Xzp#%+i%_zWLoN2Eq+t()_KxM{QrQM^th|ExKf^aF^1wSl9Gf zuYwQhjt~v08#}uZg6{_RKG`k^%=6x;8CcqPc6B~~(beJDa*u2O>``O6$J~+6d%TwQc8ldokBrS$D?QGy z_DC~c;2s3(8E&Az3_9bt*F`(mM6$67QE{krTv8H_i-BZ@s^zHV0m#fObr!&jA`};t zF$iGc$&$j=FhxLY314`gSqp~SUT}>Quamep(UM$%rXCckP2&=k)u&=PT&gW{s>vO` zwLS!5SxX@Uvhwn>Fc3LOJUJ}WLw$&Sx&Zq-6j`rY83&>=<7ww(F3?gEKa$__vS_|_ zMA@Y&#$J!99`w1y`MVEVs$}3Oo}5aAInnxZ`jqL%MR7C3Hf?QZ6YABP8Rufs4i)v5 zfPhPV3F;oBM{S_S7JP@3h(6!b^{$q&(C8_alsFWvRbtC;K=W&X1IA zb?5$Xuvh2rze_I<;lIMxTgK+`0w_OI*#csa#nJ~qVI^acwmHX~8Dvv#QocFVev5o; zsW){6TL*Tr#>VKO?2HSMKj=S=Id032ib+;ZKq0em9E58OKb8PVM24#6Bas4rE(TG= zQ>S1NB1C!g8KA1DgiwnSArTz{u(7bZ5p%JvqZ0;|MBij#vvZ0y+2+U{IMQ-^91EEhJ$`%x+V;Kh>C?8Ck3rGG`Vi?mJBiu=Cq-uGxrP1W zn?^HMP%relF54Ln8QuJTDe{tQ%1t+187Xs+8TLpoK~d)sok7{i^#w`Mwv`mj4JOh^ zhQl%{84BamULVE=tfxlKCZqLx73Rly4(8g(U>CjikP)Wu9{lrz<}B;DT@&&3{+T}Q z4VXTaMm@)H>B9x#CjC;={2cLRAI%P({pA`Twd7TAXL)9^lFFz`zv!L`%gCcFI3H^W z7?Q0aLvpLn_w|)19ao@UZes!eY3R$h?rPrBKT z1D%*?dcrX(+AtZHfPiPKa8eRs?lCd>SeZn4m|6iJ28S0G5elk`l}pc5!D>iiphBeH zkWvMQNeaQ1_H%9gPRZqMT_p3PSYB?c`p9*OOc&Th5)NWUZnY>++!*h=aUCMHC56#9?(#RknSzNKV3Zp2DX@ zU;Fjd$FZ09!IR6H}AwRE?t~eJ^`20qLqlTeIn?e|bis>5=y6SH2fRsx4X@C9OY>s%ini Gjz0j%eWB0* literal 0 HcmV?d00001 diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index da2a8f8e..4e048140 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -1,5 +1,6 @@ #include "log.h" #include "parseutil.h" +#include "loadingscreen.h" #include #include @@ -74,6 +75,8 @@ QString ParseUtil::createErrorMessage(const QString &message, const QString &exp } QString ParseUtil::readTextFile(const QString &path, QString *error) { + // splash screen message + porysplash->showMessage(path); QFile file(path); if (!file.open(QIODevice::ReadOnly)) { if (error) *error = file.errorString(); diff --git a/src/main.cpp b/src/main.cpp index 0e8fa7d3..ebd591df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,6 @@ #include "mainwindow.h" +#include "loadingscreen.h" + #include int main(int argc, char *argv[]) @@ -6,8 +8,14 @@ int main(int argc, char *argv[]) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QApplication a(argc, argv); a.setStyle("fusion"); + + porysplash = new PorymapLoadingScreen; + porysplash->show(); + + QObject::connect(&a, &QCoreApplication::aboutToQuit, [=]() { delete porysplash; }); + MainWindow w(nullptr); - w.show(); + w.initialize(); return a.exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5a0169a8..aa296779 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -29,6 +29,7 @@ #include "newmapgroupdialog.h" #include "newlocationdialog.h" #include "message.h" +#include "loadingscreen.h" #include #include @@ -75,10 +76,14 @@ MainWindow::MainWindow(QWidget *parent) : cleanupLargeLog(); logInfo(QString("Launching Porymap v%1").arg(QCoreApplication::applicationVersion())); +} +void MainWindow::initialize() { + porysplash->start(); this->initWindow(); - if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) + if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) { on_toolButton_Paint_clicked(); + } // there is a bug affecting macOS users, where the trackpad deilveres a bad touch-release gesture // the warning is a bit annoying, so it is disabled here @@ -86,6 +91,9 @@ MainWindow::MainWindow(QWidget *parent) : if (porymapConfig.checkForUpdates) this->checkForUpdates(false); + + porysplash->close(); + this->show(); } MainWindow::~MainWindow() @@ -153,7 +161,6 @@ void MainWindow::initWindow() { #endif setWindowDisabled(true); - show(); } void MainWindow::initShortcuts() { diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp new file mode 100644 index 00000000..1e8a7f9d --- /dev/null +++ b/src/ui/loadingscreen.cpp @@ -0,0 +1,51 @@ + +#include "loadingscreen.h" +#include "ui_loadingscreen.h" +#include "qgifimage.h" + +#include + +PorymapLoadingScreen *porysplash = nullptr; + + + +PorymapLoadingScreen::~PorymapLoadingScreen() { + delete ui; +} + +PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), ui(new Ui::LoadingScreen) { + ui->setupUi(this); + this->setWindowFlags(Qt::FramelessWindowHint); + + this->splashImage.load(":/images/porysplash.gif"); + + this->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); + + connect(&this->timer, &QTimer::timeout, this, &PorymapLoadingScreen::updateFrame); +} + +void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { + if (!this->isVisible()) return; + this->ui->labelPixmap->setPixmap(pixmap); +} + +void PorymapLoadingScreen::showMessage(QString text) { + if (!this->isVisible()) return; + this->ui->labelText->setText(text.mid(text.lastIndexOf("/") + 1)); + //this->updateFrame(); + + QApplication::processEvents(); +} + +void PorymapLoadingScreen::updateFrame() { + // + this->frame = (this->frame + 1) % this->splashImage.frameCount(); + + this->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); + + QApplication::processEvents(); + + //this->showMessage("Frame Number: " + QString::number(this->frame)); + + //this->repaint(); +} From c2b1f5ab85f37f9bb2b499a3cf0ee8cf3c775c58 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 09:49:53 -0400 Subject: [PATCH 343/364] fix geometry setting before window exists --- src/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index aa296779..5fc5dfd6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -93,6 +93,8 @@ void MainWindow::initialize() { this->checkForUpdates(false); porysplash->close(); + + this->restoreWindowState(); this->show(); } @@ -150,7 +152,6 @@ void MainWindow::initWindow() { this->initMiscHeapObjects(); this->initMapList(); this->initShortcuts(); - this->restoreWindowState(); #ifndef RELEASE_PLATFORM ui->actionCheck_for_Updates->setVisible(false); From 9a0a7865fbeaa90b03b3ee8db8b13dbf56002f06 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 15:29:13 -0400 Subject: [PATCH 344/364] show splash screen whenever project is being loaded ... including when switching projects and reloading projects --- include/ui/loadingscreen.h | 3 ++- src/main.cpp | 1 - src/mainwindow.cpp | 8 +++++--- src/ui/loadingscreen.cpp | 17 ++++++++++------- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h index 10ea4629..af3e4f6f 100644 --- a/include/ui/loadingscreen.h +++ b/include/ui/loadingscreen.h @@ -27,7 +27,8 @@ public: void setPixmap(QPixmap pixmap); void showMessage(QString text); - void start() { this->timer.start(120); } + void start(); + void stop (); private: void setupUi(); diff --git a/src/main.cpp b/src/main.cpp index ebd591df..d85c1b38 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,6 @@ int main(int argc, char *argv[]) a.setStyle("fusion"); porysplash = new PorymapLoadingScreen; - porysplash->show(); QObject::connect(&a, &QCoreApplication::aboutToQuit, [=]() { delete porysplash; }); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5fc5dfd6..7527e8c3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -79,7 +79,6 @@ MainWindow::MainWindow(QWidget *parent) : } void MainWindow::initialize() { - porysplash->start(); this->initWindow(); if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) { on_toolButton_Paint_clicked(); @@ -92,8 +91,6 @@ void MainWindow::initialize() { if (porymapConfig.checkForUpdates) this->checkForUpdates(false); - porysplash->close(); - this->restoreWindowState(); this->show(); } @@ -656,6 +653,8 @@ bool MainWindow::openProject(QString dir, bool initial) { return false; } + porysplash->start(); + const QString openMessage = QString("Opening %1").arg(projectString); this->statusBar()->showMessage(openMessage); logInfo(openMessage); @@ -684,6 +683,7 @@ bool MainWindow::openProject(QString dir, bool initial) { // Make sure project looks reasonable before attempting to load it if (!checkProjectSanity()) { delete this->editor->project; + porysplash->stop(); return false; } @@ -693,6 +693,7 @@ bool MainWindow::openProject(QString dir, bool initial) { showProjectOpenFailure(); delete this->editor->project; // TODO: Allow changing project settings at this point + porysplash->stop(); return false; } @@ -713,6 +714,7 @@ bool MainWindow::openProject(QString dir, bool initial) { editor->layout); Scripting::cb_ProjectOpened(dir); setWindowDisabled(false); + porysplash->stop(); return true; } diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 1e8a7f9d..d66ccf63 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -24,6 +24,16 @@ PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), u connect(&this->timer, &QTimer::timeout, this, &PorymapLoadingScreen::updateFrame); } +void PorymapLoadingScreen::start() { + this->timer.start(120); + this->show(); +} + +void PorymapLoadingScreen::stop () { + this->timer.stop(); + this->hide(); +} + void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { if (!this->isVisible()) return; this->ui->labelPixmap->setPixmap(pixmap); @@ -32,20 +42,13 @@ void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { void PorymapLoadingScreen::showMessage(QString text) { if (!this->isVisible()) return; this->ui->labelText->setText(text.mid(text.lastIndexOf("/") + 1)); - //this->updateFrame(); QApplication::processEvents(); } void PorymapLoadingScreen::updateFrame() { - // this->frame = (this->frame + 1) % this->splashImage.frameCount(); - this->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); QApplication::processEvents(); - - //this->showMessage("Frame Number: " + QString::number(this->frame)); - - //this->repaint(); } From 7fb657376d81ed26074219db956d20fd98f7fb1a Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 15:50:55 -0400 Subject: [PATCH 345/364] use common version function for splash and about screen --- forms/loadingscreen.ui | 2 +- include/ui/aboutporymap.h | 2 ++ src/ui/aboutporymap.cpp | 14 +++++++++----- src/ui/loadingscreen.cpp | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/forms/loadingscreen.ui b/forms/loadingscreen.ui index 799f0a9d..d6f52430 100644 --- a/forms/loadingscreen.ui +++ b/forms/loadingscreen.ui @@ -47,7 +47,7 @@
- Version 6.0.0 + Version X.x.x Qt::AlignCenter diff --git a/include/ui/aboutporymap.h b/include/ui/aboutporymap.h index 6bd0ed32..3a760f44 100644 --- a/include/ui/aboutporymap.h +++ b/include/ui/aboutporymap.h @@ -13,6 +13,8 @@ class AboutPorymap : public QDialog public: explicit AboutPorymap(QWidget *parent = nullptr); ~AboutPorymap(); + + static QString getVersionString(); private: Ui::AboutPorymap *ui; }; diff --git a/src/ui/aboutporymap.cpp b/src/ui/aboutporymap.cpp index 07654e14..5aa5f0c8 100644 --- a/src/ui/aboutporymap.cpp +++ b/src/ui/aboutporymap.cpp @@ -9,15 +9,19 @@ AboutPorymap::AboutPorymap(QWidget *parent) : setAttribute(Qt::WA_DeleteOnClose); static const QString commitHash = PORYMAP_LATEST_COMMIT; - this->ui->label_Version->setText(QString("Version %1%2\nQt %3 (%4)\n%5") + this->ui->label_Version->setText(getVersionString()); + + layout()->setSizeConstraint(QLayout::SetFixedSize); +} + +QString AboutPorymap::getVersionString() { + static const QString commitHash = PORYMAP_LATEST_COMMIT; + return QString("Version %1%2\nQt %3 (%4)\n%5") .arg(QCoreApplication::applicationVersion()) .arg(commitHash.isEmpty() ? "" : QString(" (%1)").arg(commitHash)) .arg(QStringLiteral(QT_VERSION_STR)) .arg(QSysInfo::buildCpuArchitecture()) - .arg(QStringLiteral(__DATE__)) - ); - - layout()->setSizeConstraint(QLayout::SetFixedSize); + .arg(QStringLiteral(__DATE__)); } AboutPorymap::~AboutPorymap() diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index d66ccf63..9cb08be1 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -1,5 +1,6 @@ #include "loadingscreen.h" +#include "aboutporymap.h" #include "ui_loadingscreen.h" #include "qgifimage.h" @@ -25,6 +26,11 @@ PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), u } void PorymapLoadingScreen::start() { + static bool shownVersion = false; + if (!shownVersion) { + this->ui->labelVersion->setText(AboutPorymap::getVersionString()); + shownVersion = true; + } this->timer.start(120); this->show(); } From 7426f474711d903c8a559232e241589a92f5e4e1 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 15:59:53 -0400 Subject: [PATCH 346/364] clean up --- include/ui/loadingscreen.h | 10 ++-------- src/ui/loadingscreen.cpp | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h index af3e4f6f..df55b01c 100644 --- a/include/ui/loadingscreen.h +++ b/include/ui/loadingscreen.h @@ -4,18 +4,12 @@ #include #include + + namespace Ui { class LoadingScreen; } -// Loading class -// LOAD() // or Offload() OFFLOAD() WorkerThread() -// function that wraps around QFuture and QtConcurrent, does not block gui thread -// can call any other function that is not directly painting the gui on another thread - -// Execute function while playing loading screen and display text -// void executeLoadingScreen(QString text, void (*function)(void)); - class PorymapLoadingScreen : public QWidget { Q_OBJECT diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 9cb08be1..f21d0c86 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -1,4 +1,3 @@ - #include "loadingscreen.h" #include "aboutporymap.h" #include "ui_loadingscreen.h" From 54461991f7ab037acce935079d2e86ab6caaa092 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 29 Apr 2025 16:18:15 -0400 Subject: [PATCH 347/364] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf4c225..5e78fe5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add a setting to specify the maximum number of events in a group. A warning will be shown if too many events are added. - Add a setting to customize the size and position of the player view distance. - Add `onLayoutOpened` to the scripting API. +- Add a splash loading screen for project openings. ### Changed - `Change Dimensions` now has an interactive resizing rectangle. From 7b7eb221e594d5b4fbdc021f804654466e2eb749 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Apr 2025 16:43:04 -0400 Subject: [PATCH 348/364] Fix missing first frame of loading screen --- src/ui/loadingscreen.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index f21d0c86..04545ff7 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -19,8 +19,6 @@ PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), u this->splashImage.load(":/images/porysplash.gif"); - this->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); - connect(&this->timer, &QTimer::timeout, this, &PorymapLoadingScreen::updateFrame); } @@ -30,6 +28,7 @@ void PorymapLoadingScreen::start() { this->ui->labelVersion->setText(AboutPorymap::getVersionString()); shownVersion = true; } + this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); this->timer.start(120); this->show(); } From 184025aace9785dbcc3cf0e57afd8f2141fd10a0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Apr 2025 16:45:09 -0400 Subject: [PATCH 349/364] Reset frames between loading screens --- src/ui/loadingscreen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 04545ff7..37fa6357 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -28,7 +28,8 @@ void PorymapLoadingScreen::start() { this->ui->labelVersion->setText(AboutPorymap::getVersionString()); shownVersion = true; } - this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); + this->frame = 0; + this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); this->timer.start(120); this->show(); } From 15e2d3cf05d388e4e9ca8420635e1930c87f6ef7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Apr 2025 23:40:03 -0400 Subject: [PATCH 350/364] Start fixing some of the Message UI deadlocks --- include/ui/message.h | 8 +++--- src/mainwindow.cpp | 61 +++++++++++++++++++++------------------- src/ui/loadingscreen.cpp | 4 +++ src/ui/message.cpp | 28 ++++++++++-------- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/include/ui/message.h b/include/ui/message.h index 8e36372d..1756c48d 100644 --- a/include/ui/message.h +++ b/include/ui/message.h @@ -24,21 +24,21 @@ public: class ErrorMessage : public Message { public: ErrorMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; // Basic warning message with an 'Ok' button. class WarningMessage : public Message { public: WarningMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; // Basic informational message with a 'Close' button. class InfoMessage : public Message { public: InfoMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; // Basic question message with a 'Yes' and 'No' button. @@ -53,7 +53,7 @@ public: class RecentErrorMessage : public ErrorMessage { public: RecentErrorMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7527e8c3..f9c9c5c5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -653,8 +653,6 @@ bool MainWindow::openProject(QString dir, bool initial) { return false; } - porysplash->start(); - const QString openMessage = QString("Opening %1").arg(projectString); this->statusBar()->showMessage(openMessage); logInfo(openMessage); @@ -664,6 +662,8 @@ bool MainWindow::openProject(QString dir, bool initial) { projectConfig.projectDir = dir; projectConfig.load(); + porysplash->start(); + Scripting::init(this); // Create the project @@ -730,7 +730,7 @@ bool MainWindow::checkProjectSanity() { logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root)); - ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), this); + ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), porysplash); msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" "Make sure you selected the correct project directory " "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(editor->project->root)); @@ -750,14 +750,15 @@ void MainWindow::showProjectOpenFailure() { // Alert the user that one or more maps have been excluded while loading the project. void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { - RecentErrorMessage msgBox("", this); + auto msgBox = new RecentErrorMessage("", this); + msgBox->setAttribute(Qt::WA_DeleteOnClose); if (excludedMapNames.length() == 1) { - msgBox.setText(QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first())); + msgBox->setText(QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first())); } else { - msgBox.setText(QStringLiteral("Failed to load the maps listed below. Saving will exclude these maps from your project.")); - msgBox.setDetailedText(excludedMapNames.join("\n")); // Overwrites error details text, user will need to check the log. + msgBox->setText(QStringLiteral("Failed to load the maps listed below. Saving will exclude these maps from your project.")); + msgBox->setDetailedText(excludedMapNames.join("\n")); // Overwrites error details text, user will need to check the log. } - msgBox.exec(); + msgBox->open(); } bool MainWindow::isProjectOpen() { @@ -867,28 +868,28 @@ void MainWindow::showFileWatcherWarning() { path.remove(root); } - QuestionMessage msgBox("", this); + QPointer msgBox = new QuestionMessage("", this); if (modifiedFiles.count() == 1) { - msgBox.setText(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(modifiedFiles.first())); + msgBox->setText(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(modifiedFiles.first())); } else { - msgBox.setText(QStringLiteral("Some project files have changed on disk. Would you like to reload the project?")); - msgBox.setDetailedText(QStringLiteral("The following files have changed:\n") + modifiedFiles.join("\n")); + msgBox->setText(QStringLiteral("Some project files have changed on disk. Would you like to reload the project?")); + msgBox->setDetailedText(QStringLiteral("The following files have changed:\n") + modifiedFiles.join("\n")); } + msgBox->setCheckBox(new QCheckBox("Do not ask again.")); - QCheckBox showAgainCheck("Do not ask again."); - msgBox.setCheckBox(&showAgainCheck); - - auto reply = msgBox.exec(); - if (reply == QMessageBox::Yes) { - on_action_Reload_Project_triggered(); - } else if (reply == QMessageBox::No) { - if (showAgainCheck.isChecked()) { - porymapConfig.monitorFiles = false; - if (this->preferenceEditor) - this->preferenceEditor->updateFields(); + connect(msgBox, &QuestionMessage::accepted, this, &MainWindow::on_action_Reload_Project_triggered); + connect(msgBox, &QuestionMessage::finished, [this, msgBox] { + if (msgBox) { + if (msgBox->checkBox() && msgBox->checkBox()->isChecked()) { + porymapConfig.monitorFiles = false; + if (this->preferenceEditor) + this->preferenceEditor->updateFields(); + } + msgBox->deleteLater(); } - } - showing = false; + showing = false; + }); + msgBox->open(); } QString MainWindow::getExistingDirectory(QString dir) { @@ -903,7 +904,8 @@ void MainWindow::on_action_Open_Project_triggered() } void MainWindow::on_action_Reload_Project_triggered() { - openProject(editor->project->root); + if (this->editor && this->editor->project) + openProject(this->editor->project->root); } void MainWindow::on_action_Close_Project_triggered() { @@ -928,9 +930,10 @@ bool MainWindow::userSetMap(QString map_name) { } if (map_name == editor->project->getDynamicMapName()) { - WarningMessage msgBox(QString("Cannot open map '%1'.").arg(map_name), this); - msgBox.setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); - msgBox.exec(); + auto msgBox = new WarningMessage(QString("Cannot open map '%1'.").arg(map_name), this); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); + msgBox->open(); return false; } diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 37fa6357..9b60cf92 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -28,8 +28,12 @@ void PorymapLoadingScreen::start() { this->ui->labelVersion->setText(AboutPorymap::getVersionString()); shownVersion = true; } + this->frame = 0; this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); + + this->ui->labelText->setText(""); + this->timer.start(120); this->show(); } diff --git a/src/ui/message.cpp b/src/ui/message.cpp index d2d91f75..ce51f7c7 100644 --- a/src/ui/message.cpp +++ b/src/ui/message.cpp @@ -52,19 +52,22 @@ RecentErrorMessage::RecentErrorMessage(const QString &message, QWidget *parent) setDetailedText(getMostRecentError()); } -int RecentErrorMessage::show(const QString &message, QWidget *parent) { - RecentErrorMessage msgBox(message, parent); - return msgBox.exec(); +void RecentErrorMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new RecentErrorMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; -int ErrorMessage::show(const QString &message, QWidget *parent) { - ErrorMessage msgBox(message, parent); - return msgBox.exec(); +void ErrorMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new ErrorMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; -int WarningMessage::show(const QString &message, QWidget *parent) { - WarningMessage msgBox(message, parent); - return msgBox.exec(); +void WarningMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new WarningMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; int QuestionMessage::show(const QString &message, QWidget *parent) { @@ -72,7 +75,8 @@ int QuestionMessage::show(const QString &message, QWidget *parent) { return msgBox.exec(); }; -int InfoMessage::show(const QString &message, QWidget *parent) { - InfoMessage msgBox(message, parent); - return msgBox.exec(); +void InfoMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new InfoMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; From 8b5c2ec792537269bcf6078f2d1535394ddce91f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 30 Apr 2025 11:48:39 -0400 Subject: [PATCH 351/364] Fix missing warning for initially incorrect warps --- src/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor.cpp b/src/editor.cpp index 0ebc2cb2..174d0b07 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1753,6 +1753,7 @@ EventPixmapItem *Editor::addEventPixmapItem(Event *event) { connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent); connect(item, &EventPixmapItem::posChanged, [this, event] { updateWarpEventWarning(event); }); connect(item, &EventPixmapItem::yChanged, [this, item] { updateEventPixmapItemZValue(item); }); + updateWarpEventWarning(event); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; From 31cf00de8b5b2e29bc4e1e9e36228374d5fdb483 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 30 Apr 2025 13:03:30 -0400 Subject: [PATCH 352/364] Update git search error messages --- include/mainwindow.h | 2 ++ src/mainwindow.cpp | 49 +++++++++++++++++++++++++++----------------- src/project.cpp | 27 +++++++++++++----------- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index b2bba6ca..5de2a50a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -352,6 +352,8 @@ private: void setLayoutOnlyMode(bool layoutOnly); bool isInvalidProject(Project *project); + bool checkProjectSanity(Project *project); + bool checkProjectVersion(Project *project); bool loadProjectData(); bool setProjectUI(); void clearProjectUI(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e15828ae..4e82dce4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -725,39 +725,50 @@ bool MainWindow::loadProjectData() { } bool MainWindow::isInvalidProject(Project *project) { - if (!project->sanityCheck()) { - logWarn(QString("The directory '%1' failed the project sanity check.").arg(project->root)); + return !(checkProjectSanity(project) && checkProjectVersion(project)); +} - ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), porysplash); - msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" - "Make sure you selected the correct project directory " - "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(project->root)); - auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); - msgBox.exec(); +bool MainWindow::checkProjectSanity(Project *project) { + if (project->sanityCheck()) + return true; - // The user may choose to try to load this project anyway. + logWarn(QString("The directory '%1' failed the project sanity check.").arg(project->root)); + + ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), porysplash); + msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" + "Make sure you selected the correct project directory " + "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(project->root)); + auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); + msgBox.exec(); + if (msgBox.clickedButton() == tryAnyway) { + // The user has chosen to try to load this project anyway. // This will almost certainly fail, but they'll get a more specific error message. - if (msgBox.clickedButton() != tryAnyway){ - return true; - } + return true; } + return false; +} + +bool MainWindow::checkProjectVersion(Project *project) { QString error; int projectVersion = project->getSupportedMajorVersion(&error); - if (projectVersion <= 0) { + if (projectVersion < 0) { // Failed to identify a supported major version. // We can't draw any conclusions from this, so we don't consider the project to be invalid. - logWarn(error.isEmpty() ? QStringLiteral("Unable to identify project's Porymap version.") : error); + QString msg = QStringLiteral("Failed to check project version"); + logWarn(error.isEmpty() ? msg : QString("%1: '%2'").arg(msg).arg(error)); } else { - logInfo(QString("Successful project version check. Supports at least Porymap v%1.").arg(projectVersion)); + QString msg = QStringLiteral("Successfully checked project version. "); + logInfo(msg + ((projectVersion != 0) ? QString("Supports at least Porymap v%1").arg(projectVersion) + : QStringLiteral("Too old for any Porymap version"))); if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) { // We were unable to find the necessary changes for Porymap's current major version. - // Warn the user that this might mean their project is missing breaking changes. + // Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes. // Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting. // It is very plausible that the user may have reproduced the required changes in an // unknown commit, rather than merging the required changes directly from the base repo. // In this case the 'projectVersion' may actually be too old to use for their repo. - ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), this); + ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), porysplash); msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2." "") // TODO: Once we have a wiki or manual page describing breaking changes, link that here. .arg(project->getProjectTitle()) @@ -765,13 +776,13 @@ bool MainWindow::isInvalidProject(Project *project) { auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); msgBox.exec(); if (msgBox.clickedButton() != tryAnyway){ - return true; + return false; } // User opted to try with this version anyway. Don't warn them about this version again. projectConfig.forcedMajorVersion = porymapVersion.majorVersion(); } } - return false; + return true; } void MainWindow::showProjectOpenFailure() { diff --git a/src/project.cpp b/src/project.cpp index b8183553..b340ed27 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -79,13 +79,15 @@ bool Project::sanityCheck() { // We can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess. // We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project // should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1. -// This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check -// is not important enough to significantly slow down project launch, we'd rather just timeout. int Project::getSupportedMajorVersion(QString *errorOut) { + // This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check + // is not important enough to significantly slow down project launch, we'd rather just timeout. + const int timeoutLimit = 500; const int failureVersion = -1; - QString gitPath = QStandardPaths::findExecutable("git"); + QString gitName = "git"; + QString gitPath = QStandardPaths::findExecutable(gitName); if (gitPath.isEmpty()) { - if (errorOut) *errorOut = QStringLiteral("Failed to identify project history: Unable to locate git."); + if (errorOut) *errorOut = QString("Unable to locate %1.").arg(gitName); return failureVersion; } @@ -95,14 +97,14 @@ int Project::getSupportedMajorVersion(QString *errorOut) { process.setReadChannel(QProcess::StandardOutput); process.setStandardInputFile(QProcess::nullDevice()); // We won't have any writing to do. - // First we need to know which (if any) known git history this project belongs to. + // First we need to know which (if any) known history this project belongs to. // We'll get the root commit, then compare it to the known root commits for the base project repos. static const QStringList args_getRootCommit = { "rev-list", "--max-parents=0", "HEAD" }; process.setArguments(args_getRootCommit); process.start(); - if (!process.waitForFinished(500) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) { + if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) { if (errorOut) { - *errorOut = QStringLiteral("Failed to identify project history"); + *errorOut = QStringLiteral("Failed to identify commit history"); if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) { errorOut->append(QString(": %1").arg(process.errorString())); } else { @@ -145,8 +147,8 @@ int Project::getSupportedMajorVersion(QString *errorOut) { }}, }; if (!historyMap.contains(rootCommit)) { - // Either this repo does not share history with one of the base repos, - // (that's ok, don't report an error) or we got some unexpected result. + // Either this repo does not share history with one of the base repos, or we got some unexpected result. + if (errorOut) *errorOut = QStringLiteral("Unrecognized commit history"); return failureVersion; } @@ -162,9 +164,9 @@ int Project::getSupportedMajorVersion(QString *errorOut) { } process.setArguments({ "merge-base", "--is-ancestor", commitHash, "HEAD" }); process.start(); - if (!process.waitForFinished(500) || process.exitStatus() != QProcess::ExitStatus::NormalExit) { + if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) { if (errorOut) { - *errorOut = QStringLiteral("Failed to identify project's supported Porymap version"); + *errorOut = QStringLiteral("Failed to search commit history"); if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) { errorOut->append(QString(": %1").arg(process.errorString())); } else { @@ -180,7 +182,8 @@ int Project::getSupportedMajorVersion(QString *errorOut) { return versionNum; } } - return failureVersion; + // We recognized the commit history, but it's too old for any version of Porymap to support. + return 0; } bool Project::load() { From 311b6c8638be5614704280abc4caace304ac2792 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 30 Apr 2025 15:23:48 -0400 Subject: [PATCH 353/364] Add more player icons --- include/config.h | 5 +++ resources/icons/player/brendan_em.ico | Bin 0 -> 3638 bytes resources/icons/player/brendan_rs.ico | Bin 0 -> 3638 bytes resources/icons/player/green.ico | Bin 0 -> 3638 bytes resources/icons/player/may_em.ico | Bin 0 -> 3638 bytes resources/icons/player/may_rs.ico | Bin 0 -> 3638 bytes .../icons/{viewsprites.ico => player/red.ico} | Bin resources/images.qrc | 7 ++++- src/config.cpp | 29 ++++++++++++++++++ src/mainwindow.cpp | 17 +++++++++- 10 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 resources/icons/player/brendan_em.ico create mode 100644 resources/icons/player/brendan_rs.ico create mode 100644 resources/icons/player/green.ico create mode 100644 resources/icons/player/may_em.ico create mode 100644 resources/icons/player/may_rs.ico rename resources/icons/{viewsprites.ico => player/red.ico} (100%) diff --git a/include/config.h b/include/config.h index 26d2a353..285a86e1 100644 --- a/include/config.h +++ b/include/config.h @@ -335,6 +335,7 @@ public: this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); + this->eventTabIconPath = QString(); this->collisionSheetPath = QString(); this->collisionSheetSize = QSize(2, 16); this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER); @@ -355,6 +356,9 @@ public: static const QStringList versionStrings; static BaseGameVersion stringToBaseGameVersion(const QString &string); + static QString getPlayerIconPath(BaseGameVersion baseGameVersion, int character); + static QIcon getPlayerIcon(BaseGameVersion baseGameVersion, int character); + void reset(BaseGameVersion baseGameVersion); void setFilePath(ProjectFilePath pathId, const QString &path); void setFilePath(const QString &pathId, const QString &path); @@ -414,6 +418,7 @@ public: uint16_t unusedTileCovered; uint16_t unusedTileSplit; bool mapAllowFlagsEnabled; + QString eventTabIconPath; QString collisionSheetPath; QSize collisionSheetSize; QMargins playerViewDistance; diff --git a/resources/icons/player/brendan_em.ico b/resources/icons/player/brendan_em.ico new file mode 100644 index 0000000000000000000000000000000000000000..2c896c4494b7b0a951758d8ea1aae283294b6743 GIT binary patch literal 3638 zcmeHKF>V_%5S$~$GkFwXJwa9AFSv@`x=fieReF_f@`9w%Bl3VeK#fb6X`NIq4Fhgc z1h~wQM}w!tcWD#cKzrQXSuVvDv?~D(3^p5Z^?2`qR{*x#Y=8E^NBYj_V+iO@aa>-+ z`F4Zrt8-jlUQ=%HW`BjZ?|SU^ci7$Tu)pc?z_uM|(1otGx=f%-G)>SV*b^dg#R}osLozghuN-27?YbttsB&*WHvqB$JWR4`hcWb-!slJMaagL*HfcMTF4XGL4 z5s~9h^H4)icB#A8IUe8~P17_@RBH8Z>C?P1Iz5B)RwH*bRp)qBK)dta{5;Mu%_~EO z;-nqlV4%}Ef8tC0?>`X=lh-+3?_|8=*sA#hzc{r#0;iIn!0TFyzmAn&{AI28Naw$@ R(ajS_ZhX1P!`SmQF7sm7r6H;KoGYqUC+t*~m`M~&ENXPyH$PMD8Es zsw>L9&Dl9CeWPd|x1pc2ZQbd)z5}1R1CH~Rv({;}8j8YvHt4$flF)e6c(y9wjOLUM zW7KhSxAE$2%(6aPrL^Z=ykcFL{$T^uDcLg zuz7E|6HV+$>mp5prNo^fAIafx1yX=vg|jnoZSZjoyaBM;xcsdFzEF2g9Zf)Wvg2}) zE{6g4+by=+OANyf?>~*$eZ9bNeT$2$2i)J>;A%JG!^4R2@$q1s1z0MuRN$YhK+{;& z)s;1D9irG+XST8?CNaQjZB~Y$H3Wo)#KmQBA0u^+6OD-c(!%Y7B?`%&;o3e?>jAB8 zPZG6Muvzu(2|wx4Q$d;b>vgX+8BFd}NR-7yE|&2V4nN)|*me1N$0Ws{iO#X?zOxqM zr&C}Yz0YnK>>XMC_!upH_E>cmY^lKiUIFz@v#RR4x~?i?%gs&KZ$5{|DF2S_nGJd+ ze>U2Jj^paTW_9Z3a#DoU&!-6UxGX~I{>s!PJ)PL#XK-*<$S;yS&hzt+;eNJmEPp*c zhkX7VuKdC@jC>}zCi}aTr-jxM*WHZ6Do-1&99h3AIETA;9(H-;c?8cu(GTOsIVaS4 zzw3LSY_G#T|24_;mt6WE#$VokUx9u8(v?)+PZ3H?dp|qBfqDE-_0QffGyMH@*_Cfs l`@OI4=J4nCua%#G(~|G_WBnVPWBvf%mdw8<`g<~F`U&vfakT&d literal 0 HcmV?d00001 diff --git a/resources/icons/player/may_em.ico b/resources/icons/player/may_em.ico new file mode 100644 index 0000000000000000000000000000000000000000..ffa56bab11cba96ad61cf197a37bf13a688dbff8 GIT binary patch literal 3638 zcmeHKu}&L75Pcr6*SFrY%qO^tNExM~BG;y*Oi2~dQl^QNJ{2E8K|zs^po!AplGcip zAMght9dCBL+?^8NB_+{~ygNJdcIIG=-aCK^1V=}pFYs{&yaKRZTYOspUzs~*j#t2R zx<_}=s*~3^dGj7s^&Mv)KH>8G9A_6dSbwW<`DKHvn=Pt`3v4$VY_>J(x<1%z1jY%B z6L?V*h!Im7=#wp%m`tY=(3_PV$G{adw?|VLLQaUdB1lAe%jkGBv`;*u6+|3sND77M1LcH;x)GGk&fk`FYY^?K}DW>j%h##4)J(HlxQWiH;8^VEdPv%zBjWpNIbR(;AiT#rTG!kG?+WT=s1MSZp z4&&r+%0f|uKtZ92B+;R~JoEbiPEnz0rPb?6d*=FnoXSN>vRb`qJt$VsQy9X%4?#QR zUBohZctbegY5HtO(_Q0St$qmCs+iPN|59Sq9_{)eT&Sw5wbWnJWOiuR_v4BRrP(@A zv1gXa9sUNVs;3@SYf-zt4^Qs^{KlL6`u*ofy~a(Z8@2EK`*{bf+UpTG)x}o`eT}H^ cfw#KuNDqCh-9yj0&u{tF?(~t1|Enc?0&RwMrvLx| literal 0 HcmV?d00001 diff --git a/resources/icons/player/may_rs.ico b/resources/icons/player/may_rs.ico new file mode 100644 index 0000000000000000000000000000000000000000..7450d620ac4609a00441b9eb863875f1f0d5aefa GIT binary patch literal 3638 zcmeHKv2NQi5Pdq55@ij}CwL0lG3b;j^_nqb#!f-kj9m(3@6-?IkRhLurN|Qhf>EG9 z;139(zT<@vgT_)irqF{(-rc)9T9kb!0~G`Z2jCiU5r9JgtCi_D5%@;iJKE?1nv)-w zy;vW=$MMlA*6W`*JO6^S&(}Er)Z_AMgYVZ{Y&RQhwmtg3-s;k;0NL54bfpyt&QQi>3R=p__V z$`HJGUcpp&aW&!OaYw1^w(Huu)@!H=z74v=M(0V+Exz3KA&dkjx@V}oxOK2sk2rWmZ4|hGqcLeJU z%QFi!tCZA`@E@+^SZZ)#aSM+xKU=~doJ(!N literal 0 HcmV?d00001 diff --git a/resources/icons/viewsprites.ico b/resources/icons/player/red.ico similarity index 100% rename from resources/icons/viewsprites.ico rename to resources/icons/player/red.ico diff --git a/resources/images.qrc b/resources/images.qrc index 41789a6e..8d6af6fd 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -46,9 +46,14 @@ icons/sort_number.ico icons/tall_grass.ico icons/minimap.ico - icons/viewsprites.ico icons/application_form_edit.ico icons/connections.ico + icons/player/brendan_em.ico + icons/player/brendan_rs.ico + icons/player/green.ico + icons/player/may_em.ico + icons/player/may_rs.ico + icons/player/red.ico icons/ui/dark/checkbox_checked_disabled.png icons/ui/dark/checkbox_checked_disabled@2x.png icons/ui/dark/checkbox_checked.png diff --git a/src/config.cpp b/src/config.cpp index 0e22840e..6ecce6d4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -731,6 +731,32 @@ BaseGameVersion ProjectConfig::stringToBaseGameVersion(const QString &string) { return version; } +QString ProjectConfig::getPlayerIconPath(BaseGameVersion baseGameVersion, int character) { + switch (baseGameVersion) { + case BaseGameVersion::pokeemerald: { + static const QStringList paths = { QStringLiteral(":/icons/player/brendan_em.ico"), + QStringLiteral(":/icons/player/may_em.ico"), }; + return paths.value(character); + } + case BaseGameVersion::pokefirered: { + static const QStringList paths = { QStringLiteral(":/icons/player/red.ico"), + QStringLiteral(":/icons/player/green.ico"), }; + return paths.value(character); + } + case BaseGameVersion::pokeruby: { + static const QStringList paths = { QStringLiteral(":/icons/player/brendan_rs.ico"), + QStringLiteral(":/icons/player/may_rs.ico"), }; + return paths.value(character); + } + default: break; + } + return QString(); +} + +QIcon ProjectConfig::getPlayerIcon(BaseGameVersion baseGameVersion, int character) { + return QIcon(getPlayerIconPath(baseGameVersion, character)); +} + ProjectConfig projectConfig; QString ProjectConfig::getConfigFilepath() { @@ -868,6 +894,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); + } else if (key == "event_tab_icon_path") { + this->eventTabIconPath = value; } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { @@ -985,6 +1013,7 @@ QMap ProjectConfig::getKeyValueMap() { for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); } + map.insert("event_tab_icon_path", this->eventTabIconPath); map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac41f582..18fbe198 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -249,7 +249,7 @@ void MainWindow::initCustomUI() { static const QMap mainTabIcons = { {MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))}, - {MainTab::Events, QIcon(QStringLiteral(":/icons/viewsprites.ico"))}, + {MainTab::Events, ProjectConfig::getPlayerIcon(BaseGameVersion::pokefirered, 0)}, // Arbitrary default {MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))}, {MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))}, {MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))}, @@ -1255,6 +1255,21 @@ bool MainWindow::setProjectUI() { ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->getTopLevelMapFields()); + // Set a version dependent player icon (or user-chosen icon) for the Events tab. + QIcon eventTabIcon; + if (!projectConfig.eventTabIconPath.isEmpty()) { + eventTabIcon = QIcon(projectConfig.eventTabIconPath); + if (eventTabIcon.isNull()) { + logWarn(QString("Failed to load custom Events tab icon '%1'.").arg(projectConfig.eventTabIconPath)); + } + } + if (eventTabIcon.isNull()) { + // We randomly choose between the available characters for ~flavor~. + // For now, this correctly assumes all versions have 2 icons. + eventTabIcon = ProjectConfig::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2)); + } + ui->mainTabBar->setTabIcon(MainTab::Events, eventTabIcon); + return true; } From 6d3fd5bc7e7f8c70d8e5d7474d7f8ebfea49b8c8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 30 Apr 2025 16:58:50 -0400 Subject: [PATCH 354/364] Add settings for events tab icon --- forms/projectsettingseditor.ui | 244 ++++++++++++++++++------------- include/config.h | 4 +- src/config.cpp | 6 +- src/mainwindow.cpp | 6 +- src/ui/projectsettingseditor.cpp | 34 +++++ 5 files changed, 186 insertions(+), 108 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 05598b8f..c840d331 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -39,7 +39,7 @@ 0 0 570 - 692 + 680 @@ -94,7 +94,7 @@ ...
- + :/icons/folder.ico:/icons/folder.ico
@@ -164,7 +164,7 @@ ...
- + :/icons/folder.ico:/icons/folder.ico
@@ -248,7 +248,7 @@ ...
- + :/icons/folder.ico:/icons/folder.ico
@@ -1197,23 +1197,62 @@ 0 0 570 - 840 + 927
+ + + + Tab Icon + + + + + + false + + + ... + + + + :/icons/folder.ico:/icons/folder.ico + + + + + + + false + + + <html><head/><body><p>The image file path to use for the icon of the Events tab.</p></body></html> + + + true + + + + + + + <html><head/><body><p>The icon that will be displayed for the Events tab in the editor. If 'Automatic' is chosen, the icon will be a random player character from the project's base game version. If 'Custom' is chosen an image file path may be specified.</p></body></html> + + + false + + + + + + Default Icons - - - - Triggers - - - @@ -1221,23 +1260,31 @@ - - + + - <html><head/><body><p>The icon that will be used to represent Warp events</p></body></html> + <html><head/><body><p>The icon that will be used to represent BG events</p></body></html> true - - - - <html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html> + + + + ... - - true + + + :/icons/folder.ico:/icons/folder.ico + + + + + + + Objects @@ -1248,6 +1295,24 @@ + + + + Triggers + + + + + + + ... + + + + :/icons/folder.ico:/icons/folder.ico + + + @@ -1265,10 +1330,56 @@ - - + + + + <html><head/><body><p>The icon that will be used to represent Warp events</p></body></html> + + + true + + + + + - Objects + ... + + + + :/icons/folder.ico:/icons/folder.ico + + + + + + + <html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html> + + + true + + + + + + + ... + + + + :/icons/folder.ico:/icons/folder.ico + + + + + + + ... + + + + :/icons/folder.ico:/icons/folder.ico @@ -1282,71 +1393,6 @@
- - - - <html><head/><body><p>The icon that will be used to represent BG events</p></body></html> - - - true - - - - - - - ... - - - - :/icons/folder.ico:/icons/folder.ico - - - - - - - ... - - - - :/icons/folder.ico:/icons/folder.ico - - - - - - - ... - - - - :/icons/folder.ico:/icons/folder.ico - - - - - - - ... - - - - :/icons/folder.ico:/icons/folder.ico - - - - - - - ... - - - - :/icons/folder.ico:/icons/folder.ico - - -
@@ -1365,7 +1411,7 @@ ... - + :/icons/delete.ico:/icons/delete.ico
@@ -1418,7 +1464,7 @@ ... - + :/icons/add.ico:/icons/add.ico @@ -1628,7 +1674,7 @@ 0 0 544 - 338 + 341 @@ -1657,7 +1703,7 @@ Add Global Constants File... - + :/icons/add.ico:/icons/add.ico @@ -1668,7 +1714,7 @@ ... - + :/icons/help.ico:/icons/help.ico @@ -1708,7 +1754,7 @@ ... - + :/icons/help.ico:/icons/help.ico @@ -1745,7 +1791,7 @@ 0 0 544 - 421 + 425 @@ -1774,7 +1820,7 @@ Add Global Constant... - + :/icons/add.ico:/icons/add.ico @@ -1840,8 +1886,6 @@
uintspinbox.h
- - - + diff --git a/include/config.h b/include/config.h index 285a86e1..f2353ca4 100644 --- a/include/config.h +++ b/include/config.h @@ -335,7 +335,7 @@ public: this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); - this->eventTabIconPath = QString(); + this->eventsTabIconPath = QString(); this->collisionSheetPath = QString(); this->collisionSheetSize = QSize(2, 16); this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER); @@ -418,7 +418,7 @@ public: uint16_t unusedTileCovered; uint16_t unusedTileSplit; bool mapAllowFlagsEnabled; - QString eventTabIconPath; + QString eventsTabIconPath; QString collisionSheetPath; QSize collisionSheetSize; QMargins playerViewDistance; diff --git a/src/config.cpp b/src/config.cpp index 6ecce6d4..08e8ba8c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -894,8 +894,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); - } else if (key == "event_tab_icon_path") { - this->eventTabIconPath = value; + } else if (key == "events_tab_icon_path") { + this->eventsTabIconPath = value; } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { @@ -1013,7 +1013,7 @@ QMap ProjectConfig::getKeyValueMap() { for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); } - map.insert("event_tab_icon_path", this->eventTabIconPath); + map.insert("events_tab_icon_path", this->eventsTabIconPath); map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 18fbe198..58ce61fe 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1257,10 +1257,10 @@ bool MainWindow::setProjectUI() { // Set a version dependent player icon (or user-chosen icon) for the Events tab. QIcon eventTabIcon; - if (!projectConfig.eventTabIconPath.isEmpty()) { - eventTabIcon = QIcon(projectConfig.eventTabIconPath); + if (!projectConfig.eventsTabIconPath.isEmpty()) { + eventTabIcon = QIcon(project->getExistingFilepath(projectConfig.eventsTabIconPath)); if (eventTabIcon.isNull()) { - logWarn(QString("Failed to load custom Events tab icon '%1'.").arg(projectConfig.eventTabIconPath)); + logWarn(QString("Failed to load custom Events tab icon '%1'.").arg(projectConfig.eventsTabIconPath)); } } if (eventTabIcon.isNull()) { diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 166874ee..de39c09b 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -67,6 +67,7 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_BGsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_BGsIcon); }); connect(ui->button_HealLocationsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealLocationsIcon); }); connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); }); + connect(ui->button_EventsTabIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_EventsTabIcon); }); // Display a warning if a mask value overlaps with another mask in its group. @@ -113,6 +114,20 @@ void ProjectSettingsEditor::initUi() { ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings); ui->comboBox_AttributesSize->addItems({"1", "2", "4"}); + ui->comboBox_EventsTabIcon->addItem("Automatic", ""); + ui->comboBox_EventsTabIcon->addItem("Brendan (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 0)); + ui->comboBox_EventsTabIcon->addItem("Brendan (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 0)); + ui->comboBox_EventsTabIcon->addItem("May (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 1)); + ui->comboBox_EventsTabIcon->addItem("May (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 1)); + ui->comboBox_EventsTabIcon->addItem("Red", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 0)); + ui->comboBox_EventsTabIcon->addItem("Green", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 1)); + ui->comboBox_EventsTabIcon->addItem("Custom", "Custom"); + connect(ui->comboBox_EventsTabIcon, &NoScrollComboBox::currentIndexChanged, [this](int index) { + bool usingCustom = (index == ui->comboBox_EventsTabIcon->findText("Custom")); + ui->lineEdit_EventsTabIcon->setVisible(usingCustom); + ui->button_EventsTabIcon->setVisible(usingCustom); + }); + // Validate that the border metatiles text is a comma-separated list of metatile values static const QString regex_Hex = "(0[xX])?[A-Fa-f0-9]+"; static const QRegularExpression expression_HexList(QString("^(%1,)*%1$").arg(regex_Hex)); // Comma-separated list of hex values @@ -520,6 +535,15 @@ void ProjectSettingsEditor::refresh() { } this->setWarpBehaviorsList(behaviorNames); + int index = ui->comboBox_EventsTabIcon->findData(projectConfig.eventsTabIconPath); + if (index < 0) { + index = ui->comboBox_EventsTabIcon->findData("Custom"); + ui->lineEdit_EventsTabIcon->setText(projectConfig.eventsTabIconPath); + } else { + ui->lineEdit_EventsTabIcon->setText(""); + } + ui->comboBox_EventsTabIcon->setCurrentIndex(index); + this->refreshing = false; // Allow signals } @@ -608,6 +632,16 @@ void ProjectSettingsEditor::save() { for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++) projectConfig.setPokemonIconPath(i.key(), i.value()); + QString eventsTabIconPath; + QVariant data = ui->comboBox_EventsTabIcon->currentData(); + if (data.isValid() && data.canConvert()) { + eventsTabIconPath = data.toString(); + if (eventsTabIconPath == "Custom") { + eventsTabIconPath = ui->lineEdit_EventsTabIcon->text(); + } + } + projectConfig.eventsTabIconPath = eventsTabIconPath; + projectConfig.save(); userConfig.save(); porymapConfig.save(); From 91d89a742242a953aeba3d2a36312820ce75203a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 1 May 2025 00:29:08 -0400 Subject: [PATCH 355/364] Fix Qt5 build --- src/ui/projectsettingseditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index de39c09b..fa2d3ee2 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -122,7 +122,7 @@ void ProjectSettingsEditor::initUi() { ui->comboBox_EventsTabIcon->addItem("Red", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 0)); ui->comboBox_EventsTabIcon->addItem("Green", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 1)); ui->comboBox_EventsTabIcon->addItem("Custom", "Custom"); - connect(ui->comboBox_EventsTabIcon, &NoScrollComboBox::currentIndexChanged, [this](int index) { + connect(ui->comboBox_EventsTabIcon, QOverload::of(&NoScrollComboBox::currentIndexChanged), [this](int index) { bool usingCustom = (index == ui->comboBox_EventsTabIcon->findText("Custom")); ui->lineEdit_EventsTabIcon->setVisible(usingCustom); ui->button_EventsTabIcon->setVisible(usingCustom); From 8245f60e2bba2a574d1401efacdaffea00bc1c58 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 1 May 2025 10:17:51 -0400 Subject: [PATCH 356/364] Prefer NoScrollComboBox::setTextItem over setCurrentText --- CHANGELOG.md | 1 + forms/mainwindow.ui | 2 +- forms/mapheaderform.ui | 3 +++ forms/wildmonchart.ui | 7 ++++++- include/ui/divingmappixmapitem.h | 5 +++-- include/ui/mapheaderform.h | 3 ++- src/editor.cpp | 12 ++++++------ src/mainwindow.cpp | 10 +++++----- src/ui/connectionslistitem.cpp | 2 +- src/ui/divingmappixmapitem.cpp | 4 ++-- src/ui/encountertabledelegates.cpp | 2 +- src/ui/eventframes.cpp | 22 +++++++++++----------- src/ui/gridsettings.cpp | 2 +- src/ui/mapheaderform.cpp | 9 ++++----- src/ui/mapimageexporter.cpp | 8 ++++---- src/ui/newmapdialog.cpp | 2 +- src/ui/preferenceeditor.cpp | 2 +- src/ui/regionmapeditor.cpp | 8 ++++---- src/ui/wildmonchart.cpp | 2 +- 19 files changed, 58 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e78fe5e..5d489b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix exporting a timelapse sometimes altering the state of the current map's edit history. - Stop sliders in the Palette Editor from creating a bunch of edit history when used. - Fix scrolling on some containers locking up when the mouse stops over a spin box or combo box. +- Fix the selection index for some combo boxes differing from their displayed text. - Fix some file dialogs returning to an incorrect window when closed. - Fix bug where reloading a layout would overwrite all unsaved changes. - Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index bf0d2608..177f639f 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2733,7 +2733,7 @@ - + QComboBox::SizeAdjustPolicy::AdjustToContents diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui index 08552e2c..d4fc36f3 100644 --- a/forms/mapheaderform.ui +++ b/forms/mapheaderform.ui @@ -10,6 +10,9 @@ 380 + + Qt::FocusPolicy::ClickFocus + Form diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index 8d6668e4..05c0ba44 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -197,7 +197,7 @@ - + true @@ -237,6 +237,11 @@ QGraphicsView
QtCharts
+ + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
diff --git a/include/ui/divingmappixmapitem.h b/include/ui/divingmappixmapitem.h index 7eceba07..0a4a32c1 100644 --- a/include/ui/divingmappixmapitem.h +++ b/include/ui/divingmappixmapitem.h @@ -2,6 +2,7 @@ #define DIVINGMAPPIXMAPITEM_H #include "mapconnection.h" +#include "noscrollcombobox.h" #include #include @@ -10,7 +11,7 @@ class DivingMapPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - DivingMapPixmapItem(MapConnection *connection, QComboBox *combo); + DivingMapPixmapItem(MapConnection *connection, NoScrollComboBox *combo); ~DivingMapPixmapItem(); MapConnection* connection() const { return m_connection; } @@ -18,7 +19,7 @@ public: private: QPointer m_connection; - QPointer m_combo; + QPointer m_combo; void setComboText(const QString &text); static QPixmap getBasePixmap(MapConnection* connection); diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 4f3dc775..fb4f7aa9 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -12,6 +12,7 @@ #include "mapheader.h" #include "project.h" +#include "noscrollcombobox.h" namespace Ui { class MapHeaderForm; @@ -64,7 +65,7 @@ private: QPointer m_project = nullptr; bool m_allowProjectChanges = true; - void setText(QComboBox *combo, const QString &text) const; + void setText(NoScrollComboBox *combo, const QString &text) const; void setText(QLineEdit *lineEdit, const QString &text) const; void setLocations(const QStringList &locations); void updateLocationName(); diff --git a/src/editor.cpp b/src/editor.cpp index 174d0b07..fb870dff 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -238,7 +238,7 @@ void Editor::displayWildMonTables() { labelComboStrings.sort(); labelCombo->addItems(labelComboStrings); - labelCombo->setCurrentText(labelCombo->itemText(0)); + labelCombo->setCurrentIndex(0); QStackedWidget *stack = ui->stackedWidget_WildMons; int labelIndex = 0; @@ -495,7 +495,7 @@ void Editor::configureEncounterJSON(QWidget *window) { QFrame *slotChoiceFrame = new QFrame; QVBoxLayout *slotChoiceLayout = new QVBoxLayout; if (useGroups) { - QComboBox *groupCombo = new QComboBox; + auto groupCombo = new NoScrollComboBox; connect(groupCombo, QOverload::of(&QComboBox::textActivated), [&tempFields, ¤tField, &updateTotal, index](QString newGroupName) { for (EncounterField &field : tempFields) { if (field.name == currentField.name) { @@ -526,7 +526,7 @@ void Editor::configureEncounterJSON(QWidget *window) { break; } } - groupCombo->setCurrentText(currentGroupName); + groupCombo->setTextItem(currentGroupName); slotChoiceLayout->addWidget(groupCombo); } slotChoiceLayout->addWidget(chanceSpinner); @@ -982,7 +982,7 @@ QString Editor::getDivingMapName(const QString &direction) const { void Editor::onDivingMapEditingFinished(NoScrollComboBox *combo, const QString &direction) { if (!setDivingMapName(combo->currentText(), direction)) { // If user input was invalid, restore the combo to the previously-valid text. - combo->setCurrentText(getDivingMapName(direction)); + combo->setTextItem(getDivingMapName(direction)); } } @@ -1281,8 +1281,8 @@ bool Editor::setLayout(QString layoutId) { ui->comboBox_PrimaryTileset->blockSignals(true); ui->comboBox_SecondaryTileset->blockSignals(true); - ui->comboBox_PrimaryTileset->setCurrentText(this->layout->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(this->layout->tileset_secondary_label); + ui->comboBox_PrimaryTileset->setTextItem(this->layout->tileset_primary_label); + ui->comboBox_SecondaryTileset->setTextItem(this->layout->tileset_secondary_label); ui->comboBox_PrimaryTileset->blockSignals(false); ui->comboBox_SecondaryTileset->blockSignals(false); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac41f582..3fc7d4bd 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1141,8 +1141,8 @@ void MainWindow::displayMapProperties() { const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - ui->comboBox_PrimaryTileset->setCurrentText(editor->map->layout()->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(editor->map->layout()->tileset_secondary_label); + ui->comboBox_PrimaryTileset->setTextItem(editor->map->layout()->tileset_primary_label); + ui->comboBox_SecondaryTileset->setTextItem(editor->map->layout()->tileset_secondary_label); ui->mapCustomAttributesFrame->table()->setAttributes(editor->map->customAttributes()); } @@ -1162,7 +1162,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te // New layout failed to load, restore previous layout const QSignalBlocker b(ui->comboBox_LayoutSelector); - ui->comboBox_LayoutSelector->setCurrentText(this->editor->map->layout()->id); + ui->comboBox_LayoutSelector->setTextItem(this->editor->map->layout()->id); return; } this->editor->map->setLayout(layout); @@ -1178,7 +1178,7 @@ void MainWindow::onLayoutSelectorEditingFinished() { const QString text = ui->comboBox_LayoutSelector->currentText(); if (!this->editor->project->mapLayouts.contains(text)) { const QSignalBlocker b(ui->comboBox_LayoutSelector); - ui->comboBox_LayoutSelector->setCurrentText(this->editor->layout->id); + ui->comboBox_LayoutSelector->setTextItem(this->editor->layout->id); } } @@ -2681,7 +2681,7 @@ void MainWindow::openWildMonTable(const QString &mapName, const QString &groupNa if (userSetMap(mapName)) { // Switch to the correct main tab, wild encounter group, and wild encounter type tab. on_mainTabBar_tabBarClicked(MainTab::WildPokemon); - ui->comboBox_EncounterGroupLabel->setCurrentText(groupName); + ui->comboBox_EncounterGroupLabel->setTextItem(groupName); QWidget *w = ui->stackedWidget_WildMons->currentWidget(); if (w) static_cast(w)->setCurrentField(fieldName); } diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index 86df9525..b773f752 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -106,7 +106,7 @@ void ConnectionsListItem::commitDirection() { if (MapConnection::isDiving(direction)) { // Diving maps are displayed separately, no support right now for replacing a list item with a diving map. // For now just restore the original direction. - ui->comboBox_Direction->setCurrentText(this->connection->direction()); + ui->comboBox_Direction->setTextItem(this->connection->direction()); return; } diff --git a/src/ui/divingmappixmapitem.cpp b/src/ui/divingmappixmapitem.cpp index b774b1e9..550e0037 100644 --- a/src/ui/divingmappixmapitem.cpp +++ b/src/ui/divingmappixmapitem.cpp @@ -1,7 +1,7 @@ #include "divingmappixmapitem.h" #include "config.h" -DivingMapPixmapItem::DivingMapPixmapItem(MapConnection *connection, QComboBox *combo) +DivingMapPixmapItem::DivingMapPixmapItem(MapConnection *connection, NoScrollComboBox *combo) : QGraphicsPixmapItem(getBasePixmap(connection)) { m_connection = connection; @@ -38,5 +38,5 @@ void DivingMapPixmapItem::onTargetMapChanged() { } void DivingMapPixmapItem::setComboText(const QString &text) { - if (m_combo) m_combo->setCurrentText(text); + if (m_combo) m_combo->setTextItem(text); } diff --git a/src/ui/encountertabledelegates.cpp b/src/ui/encountertabledelegates.cpp index 2d46f54f..12e105bb 100644 --- a/src/ui/encountertabledelegates.cpp +++ b/src/ui/encountertabledelegates.cpp @@ -28,7 +28,7 @@ QWidget *SpeciesComboDelegate::createEditor(QWidget *parent, const QStyleOptionV void SpeciesComboDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QString species = index.data(Qt::EditRole).toString(); NoScrollComboBox *combo = static_cast(editor); - combo->setCurrentText(species); + combo->setTextItem(species); } void SpeciesComboDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 95a31557..51293dd5 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -181,7 +181,7 @@ void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &i const QString savedText = combo->currentText(); combo->clear(); combo->addItems(items); - combo->setCurrentText(savedText); + combo->setTextItem(savedText); } void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { @@ -419,7 +419,7 @@ void ObjectFrame::initialize() { this->spinner_radius_y->setValue(this->object->getRadiusY()); // script - this->combo_script->setCurrentText(this->object->getScript()); + this->combo_script->setTextItem(this->object->getScript()); if (porymapConfig.textEditorGotoLine.isEmpty()) this->button_script->hide(); @@ -430,7 +430,7 @@ void ObjectFrame::initialize() { this->combo_trainer_type->setTextItem(this->object->getTrainerType()); // sight berry - this->combo_radius_treeid->setCurrentText(this->object->getSightRadiusBerryTreeID()); + this->combo_radius_treeid->setTextItem(this->object->getSightRadiusBerryTreeID()); } void ObjectFrame::populate(Project *project) { @@ -515,7 +515,7 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { this->clone->setTargetMap(mapName); this->clone->getPixmapItem()->render(project); - this->combo_sprite->setCurrentText(this->clone->getGfx()); + this->combo_sprite->setTextItem(this->clone->getGfx()); this->clone->modify(); populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); }); @@ -526,7 +526,7 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->clone->setTargetID(text); this->clone->getPixmapItem()->render(project); - this->combo_sprite->setCurrentText(this->clone->getGfx()); + this->combo_sprite->setTextItem(this->clone->getGfx()); this->clone->modify(); }); @@ -552,10 +552,10 @@ void CloneObjectFrame::initialize() { this->line_edit_local_id->setText(this->clone->getIdName()); // sprite - this->combo_sprite->setCurrentText(this->clone->getGfx()); + this->combo_sprite->setTextItem(this->clone->getGfx()); // target id - this->combo_target_id->setCurrentText(this->clone->getTargetID()); + this->combo_target_id->setTextItem(this->clone->getTargetID()); // target map this->combo_target_map->setTextItem(this->clone->getTargetMap()); @@ -672,7 +672,7 @@ void WarpFrame::initialize() { this->combo_dest_map->setTextItem(this->warp->getDestinationMap()); // dest id - this->combo_dest_warp->setCurrentText(this->warp->getDestinationWarpID()); + this->combo_dest_warp->setTextItem(this->warp->getDestinationWarpID()); } void WarpFrame::populate(Project *project) { @@ -753,13 +753,13 @@ void TriggerFrame::initialize() { EventFrame::initialize(); // script - this->combo_script->setCurrentText(this->trigger->getScriptLabel()); + this->combo_script->setTextItem(this->trigger->getScriptLabel()); // var this->combo_var->setTextItem(this->trigger->getScriptVar()); // var value - this->combo_var_value->setCurrentText(this->trigger->getScriptVarValue()); + this->combo_var_value->setTextItem(this->trigger->getScriptVarValue()); } void TriggerFrame::populate(Project *project) { @@ -876,7 +876,7 @@ void SignFrame::initialize() { this->combo_facing_dir->setTextItem(this->sign->getFacingDirection()); // script - this->combo_script->setCurrentText(this->sign->getScriptLabel()); + this->combo_script->setTextItem(this->sign->getScriptLabel()); } void SignFrame::populate(Project *project) { diff --git a/src/ui/gridsettings.cpp b/src/ui/gridsettings.cpp index 87e0896a..4e36b60d 100644 --- a/src/ui/gridsettings.cpp +++ b/src/ui/gridsettings.cpp @@ -144,7 +144,7 @@ void GridSettingsDialog::updateInput() { ui->colorInput->setColor(m_settings->color.rgb()); const QSignalBlocker b_Style(ui->comboBox_Style); - ui->comboBox_Style->setCurrentText(GridSettings::getStyleName(m_settings->style)); + ui->comboBox_Style->setTextItem(GridSettings::getStyleName(m_settings->style)); } void GridSettingsDialog::setWidth(int value) { diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index a2a0b8b5..991fe1c5 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -1,6 +1,5 @@ #include "mapheaderform.h" #include "ui_mapheaderform.h" -#include "project.h" MapHeaderForm::MapHeaderForm(QWidget *parent) : QWidget(parent) @@ -94,7 +93,7 @@ void MapHeaderForm::setLocations(const QStringList &locations) { const QString before = ui->comboBox_Location->currentText(); ui->comboBox_Location->clear(); ui->comboBox_Location->addItems(locations); - ui->comboBox_Location->setCurrentText(before); + ui->comboBox_Location->setTextItem(before); } // Assign a MapHeader that the form will keep in sync with the UI. @@ -187,10 +186,10 @@ void MapHeaderForm::setAllowsBiking(bool allowsBiking) { ui->checkBox_ void MapHeaderForm::setAllowsEscaping(bool allowsEscaping) { ui->checkBox_AllowEscaping->setChecked(allowsEscaping); } void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_FloorNumber->setValue(floorNumber); } -// If we always call setText / setCurrentText the user's cursor may move to the end of the text while they're typing. -void MapHeaderForm::setText(QComboBox *combo, const QString &text) const { +// If we always call setText / setTextItem the user's cursor may move to the end of the text while they're typing. +void MapHeaderForm::setText(NoScrollComboBox *combo, const QString &text) const { if (combo->currentText() != text) - combo->setCurrentText(text); + combo->setTextItem(text); } void MapHeaderForm::setText(QLineEdit *lineEdit, const QString &text) const { if (lineEdit->text() != text) diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 732d3e83..05664aef 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -101,11 +101,11 @@ void MapImageExporter::setModeSpecificUi() { ui->comboBox_MapSelection->clear(); if (m_map) { ui->comboBox_MapSelection->addItems(m_project->mapNames); - ui->comboBox_MapSelection->setCurrentText(m_map->name()); + ui->comboBox_MapSelection->setTextItem(m_map->name()); ui->label_MapSelection->setText(m_mode == ImageExporterMode::Stitch ? QStringLiteral("Starting Map") : QStringLiteral("Map")); } else if (m_layout) { ui->comboBox_MapSelection->addItems(m_project->layoutIds); - ui->comboBox_MapSelection->setCurrentText(m_layout->id); + ui->comboBox_MapSelection->setTextItem(m_layout->id); ui->label_MapSelection->setText(QStringLiteral("Layout")); } @@ -146,7 +146,7 @@ void MapImageExporter::setLayout(Layout *layout) { void MapImageExporter::setSelectionText(const QString &text) { const QSignalBlocker b(ui->comboBox_MapSelection); - ui->comboBox_MapSelection->setCurrentText(text); + ui->comboBox_MapSelection->setTextItem(text); updateMapSelection(); } @@ -171,7 +171,7 @@ void MapImageExporter::updateMapSelection() { // Ensure text in the combo box remains valid const QSignalBlocker b(ui->comboBox_MapSelection); - ui->comboBox_MapSelection->setCurrentText(m_map ? m_map->name() : m_layout->id); + ui->comboBox_MapSelection->setTextItem(m_map ? m_map->name() : m_layout->id); if (m_map != oldMap && (!m_map || !oldMap)) { // Switching to or from layout-only mode diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index caa1e839..36819404 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -167,7 +167,7 @@ void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { // Changing the map name updates the layout ID field to match. if (ui->comboBox_LayoutID->isEnabled()) { - ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); + ui->comboBox_LayoutID->setTextItem(Layout::layoutConstantFromName(text)); } } diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index f9071ec2..1289fe79 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -44,7 +44,7 @@ void PreferenceEditor::initFields() { } void PreferenceEditor::updateFields() { - themeSelector->setCurrentText(porymapConfig.theme); + themeSelector->setTextItem(porymapConfig.theme); if (porymapConfig.eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) { ui->radioButton_OnSprite->setChecked(true); } else if (porymapConfig.eventSelectionShapeMode == QGraphicsPixmapItem::BoundingRectShape) { diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 493ea78e..0415e0f0 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -657,7 +657,7 @@ void RegionMapEditor::displayRegionMapLayoutOptions() { void RegionMapEditor::updateRegionMapLayoutOptions(int index) { const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap); - this->ui->comboBox_RM_ConnectedMap->setCurrentText(this->region_map->squareMapSection(index)); + this->ui->comboBox_RM_ConnectedMap->setTextItem(this->region_map->squareMapSection(index)); this->ui->pushButton_RM_Options_delete->setEnabled(this->region_map->squareHasMap(index)); @@ -736,7 +736,7 @@ void RegionMapEditor::updateRegionMapEntryOptions(QString section) { this->ui->pushButton_entryActivate->setEnabled(section != this->region_map->default_map_section); this->ui->pushButton_entryActivate->setText(enabled ? "Remove" : "Add"); - this->ui->comboBox_RM_Entry_MapSection->setCurrentText(section); + this->ui->comboBox_RM_Entry_MapSection->setTextItem(section); this->activeEntry = section; this->region_map_entries_item->currentSection = section; MapSectionEntry entry = enabled ? this->region_map_entries[section] : MapSectionEntry(); @@ -1296,11 +1296,11 @@ void RegionMapEditor::setLocations(const QStringList &locations) { auto before = ui->comboBox_RM_ConnectedMap->currentText(); ui->comboBox_RM_ConnectedMap->clear(); ui->comboBox_RM_ConnectedMap->addItems(locations); - ui->comboBox_RM_ConnectedMap->setCurrentText(before); + ui->comboBox_RM_ConnectedMap->setTextItem(before); const QSignalBlocker b_MapSection(ui->comboBox_RM_Entry_MapSection); before = ui->comboBox_RM_Entry_MapSection->currentText(); ui->comboBox_RM_Entry_MapSection->clear(); ui->comboBox_RM_Entry_MapSection->addItems(locations); - ui->comboBox_RM_Entry_MapSection->setCurrentText(before); + ui->comboBox_RM_Entry_MapSection->setTextItem(before); } diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 521706d6..c6e18284 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -312,7 +312,7 @@ QBarSet* WildMonChart::createLevelDistributionBarSet(const QString &species, con const QSignalBlocker blocker1(ui->groupBox_Species); const QSignalBlocker blocker2(ui->comboBox_Species); ui->groupBox_Species->setChecked(true); - ui->comboBox_Species->setCurrentText(species); + ui->comboBox_Species->setTextItem(species); refreshLevelDistributionChart(); }); } From 6d6b0066208f41babf583caf3828415d1a057a9f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 1 May 2025 15:52:36 -0400 Subject: [PATCH 357/364] Minor combo box fixes --- forms/mainwindow.ui | 3 +++ src/editor.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 177f639f..c75c8328 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2734,6 +2734,9 @@
+ + false + QComboBox::SizeAdjustPolicy::AdjustToContents diff --git a/src/editor.cpp b/src/editor.cpp index fb870dff..d02006cd 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -496,6 +496,8 @@ void Editor::configureEncounterJSON(QWidget *window) { QVBoxLayout *slotChoiceLayout = new QVBoxLayout; if (useGroups) { auto groupCombo = new NoScrollComboBox; + groupCombo->setEditable(false); + groupCombo->setMinimumContentsLength(10); connect(groupCombo, QOverload::of(&QComboBox::textActivated), [&tempFields, ¤tField, &updateTotal, index](QString newGroupName) { for (EncounterField &field : tempFields) { if (field.name == currentField.name) { From 3876b63836e6127cf1b8a7dec4b5df7070b291a7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 1 May 2025 13:42:13 -0400 Subject: [PATCH 358/364] More loading screen messages, include map.json files --- forms/loadingscreen.ui | 33 +++++++++++++-------------------- include/core/parseutil.h | 4 ++++ include/ui/loadingscreen.h | 6 ++++-- src/core/parseutil.cpp | 18 ++++++++++++++++-- src/mainwindow.cpp | 18 ++++++++++++++++-- src/project.cpp | 2 ++ src/ui/loadingscreen.cpp | 25 ++++++++++++++++++++++--- 7 files changed, 77 insertions(+), 29 deletions(-) diff --git a/forms/loadingscreen.ui b/forms/loadingscreen.ui index d6f52430..4ed06b9f 100644 --- a/forms/loadingscreen.ui +++ b/forms/loadingscreen.ui @@ -3,7 +3,7 @@ LoadingScreen - Qt::ApplicationModal + Qt::WindowModality::ApplicationModal @@ -17,7 +17,7 @@ BusyCursor - Qt::NoContextMenu + Qt::ContextMenuPolicy::NoContextMenu Form @@ -35,7 +35,7 @@ porymap - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -50,14 +50,14 @@ Version X.x.x - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter - Qt::Vertical + Qt::Orientation::Vertical @@ -70,16 +70,16 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised - Qt::Horizontal + Qt::Orientation::Horizontal @@ -105,7 +105,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -121,7 +121,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -134,23 +134,16 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain - - - - Loading..... - - - - TextLabel + Loading.... diff --git a/include/core/parseutil.h b/include/core/parseutil.h index aae86a88..09cc1ecb 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -44,6 +44,7 @@ class ParseUtil public: ParseUtil(); void setRoot(const QString &dir) { this->root = dir; } + void setUpdatesSplashScreen(bool updates) { this->updatesSplashScreen = updates; } static QString readTextFile(const QString &path, QString *error = nullptr); bool cacheFile(const QString &path, QString *error = nullptr); void clearFileCache() { this->fileCache.clear(); } @@ -105,6 +106,8 @@ private: QHash globalDefineValues; QHash globalDefineExpressions; + bool updatesSplashScreen = false; + int evaluateDefine(const QString &identifier, bool *ok = nullptr); int evaluateExpression(const QString &expression); QList tokenizeExpression(QString expression); @@ -114,6 +117,7 @@ private: void recordErrors(const QStringList &errors); void logRecordedErrors(); QString createErrorMessage(const QString &message, const QString &expression); + void updateSplashScreen(QString path); struct ParsedDefines { QHash expressions; // Map of all define names encountered to their expressions diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h index df55b01c..ff88e87c 100644 --- a/include/ui/loadingscreen.h +++ b/include/ui/loadingscreen.h @@ -18,8 +18,10 @@ public: explicit PorymapLoadingScreen(QWidget *parent = nullptr); ~PorymapLoadingScreen(); - void setPixmap(QPixmap pixmap); - void showMessage(QString text); + void setPixmap(const QPixmap &pixmap); + void showMessage(const QString &text); + void showMessage(const QString &prefix, const QString &text); + void showLoadingMessage(const QString &text); void start(); void stop (); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index cba8c223..80252c13 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -56,9 +56,17 @@ QString ParseUtil::createErrorMessage(const QString &message, const QString &exp return QString("%1:%2:%3: %4").arg(this->file).arg(lineNum).arg(colNum).arg(message); } +void ParseUtil::updateSplashScreen(QString path) { + if (!this->updatesSplashScreen) + return; + + if (path.startsWith(this->root)) { + path.remove(0, this->root.length()); + } + porysplash->showLoadingMessage(path); +} + QString ParseUtil::readTextFile(const QString &path, QString *error) { - // splash screen message - porysplash->showMessage(path); QFile file(path); if (!file.open(QIODevice::ReadOnly)) { if (error) *error = file.errorString(); @@ -80,6 +88,8 @@ QString ParseUtil::readTextFile(const QString &path, QString *error) { // Note that this doesn't insert any parsed files into the file cache, and we don't // want it to (we read a lot of files only once, storing them all is a waste of memory). QString ParseUtil::loadTextFile(const QString &path, QString *error) { + updateSplashScreen(path); + auto it = this->fileCache.constFind(path); if (it != this->fileCache.constEnd()) { // Load text file from cache @@ -89,6 +99,8 @@ QString ParseUtil::loadTextFile(const QString &path, QString *error) { } bool ParseUtil::cacheFile(const QString &path, QString *error) { + updateSplashScreen(path); + this->fileCache.insert(path, readTextFile(pathWithRoot(path), error)); return !error || error->isEmpty(); } @@ -732,6 +744,8 @@ QStringList ParseUtil::getLabelValues(const QList &list, const QStr } bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error) { + updateSplashScreen(filepath); + QFile file(pathWithRoot(filepath)); if (!file.open(QIODevice::ReadOnly)) { if (error) *error = file.errorString(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3fc7d4bd..e422cfc8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -657,13 +657,15 @@ bool MainWindow::openProject(QString dir, bool initial) { this->statusBar()->showMessage(openMessage); logInfo(openMessage); + porysplash->start(); + + porysplash->showLoadingMessage("config"); userConfig.projectDir = dir; userConfig.load(); projectConfig.projectDir = dir; projectConfig.load(); - porysplash->start(); - + porysplash->showLoadingMessage("custom scripts"); Scripting::init(this); // Create the project @@ -681,6 +683,7 @@ bool MainWindow::openProject(QString dir, bool initial) { this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it + porysplash->showMessage("Verifying project"); if (!checkProjectSanity()) { delete this->editor->project; porysplash->stop(); @@ -719,6 +722,7 @@ bool MainWindow::openProject(QString dir, bool initial) { } bool MainWindow::loadProjectData() { + porysplash->showLoadingMessage("project"); bool success = editor->project->load(); Scripting::populateGlobalObject(this); return success; @@ -745,6 +749,12 @@ bool MainWindow::checkProjectSanity() { } void MainWindow::showProjectOpenFailure() { + if (!this->isVisible()){ + // The main window is not visible during the initial project open; the splash screen is busy providing visual feedback. + // If project opening fails we can immediately display the empty main window (which we need anyway to parent messages to). + restoreWindowState(); + show(); + } RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this); } @@ -766,6 +776,8 @@ bool MainWindow::isProjectOpen() { } bool MainWindow::setInitialMap() { + porysplash->showMessage("Opening initial map"); + const QString recent = userConfig.recentMapOrLayout; if (editor->project->mapNames.contains(recent)) { // User recently had a map open that still exists. @@ -1184,6 +1196,8 @@ void MainWindow::onLayoutSelectorEditingFinished() { // Update the UI using information we've read from the user's project files. bool MainWindow::setProjectUI() { + porysplash->showLoadingMessage("project UI"); + Project *project = editor->project; this->mapHeaderForm->setProject(project); diff --git a/src/project.cpp b/src/project.cpp index 978f94ff..0e845048 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -76,6 +76,7 @@ bool Project::sanityCheck() { } bool Project::load() { + this->parser.setUpdatesSplashScreen(true); resetFileCache(); this->disabledSettingsNames.clear(); bool success = readGlobalConstants() @@ -116,6 +117,7 @@ bool Project::load() { initNewMapSettings(); applyParsedLimits(); } + this->parser.setUpdatesSplashScreen(false); return success; } diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 9b60cf92..18642afb 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -43,18 +43,37 @@ void PorymapLoadingScreen::stop () { this->hide(); } -void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { +void PorymapLoadingScreen::setPixmap(const QPixmap &pixmap) { if (!this->isVisible()) return; this->ui->labelPixmap->setPixmap(pixmap); } -void PorymapLoadingScreen::showMessage(QString text) { +// Displays the message 'prefixtext...'. The 'text' portion may be elided if it's too long. +void PorymapLoadingScreen::showMessage(const QString &prefix, const QString &text) { if (!this->isVisible()) return; - this->ui->labelText->setText(text.mid(text.lastIndexOf("/") + 1)); + + // Limit text (excluding prefix) to avoid increasing the splash screen's width. + static const QFontMetrics fontMetrics = this->ui->labelText->fontMetrics(); + static const int maxWidth = this->ui->labelText->width() + 1; + int prefixWidth = fontMetrics.horizontalAdvance(prefix); + QString message = fontMetrics.elidedText(text + QStringLiteral("..."), Qt::ElideLeft, qMax(maxWidth - prefixWidth, 0)); + message.prepend(prefix); + + this->ui->labelText->setText(message); QApplication::processEvents(); } +// Displays the message 'text...' +void PorymapLoadingScreen::showMessage(const QString &text) { + showMessage("", text); +} + +// Displays the message 'Loading text...' +void PorymapLoadingScreen::showLoadingMessage(const QString &text) { + showMessage(QStringLiteral("Loading "), text); +} + void PorymapLoadingScreen::updateFrame() { this->frame = (this->frame + 1) % this->splashImage.frameCount(); this->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); From f8f6fe827d17ef828e0cfee9608a1d9589b56727 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 1 May 2025 15:41:37 -0400 Subject: [PATCH 359/364] Fix missing filewatcher paths --- include/core/parseutil.h | 5 +- include/core/tileset.h | 6 +-- include/project.h | 5 +- src/core/tileset.cpp | 24 ++++----- src/project.cpp | 113 +++++++++++++++++++++++++-------------- 5 files changed, 92 insertions(+), 61 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 09cc1ecb..2edb9f78 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -43,9 +43,13 @@ class ParseUtil { public: ParseUtil(); + void setRoot(const QString &dir) { this->root = dir; } void setUpdatesSplashScreen(bool updates) { this->updatesSplashScreen = updates; } + static QString readTextFile(const QString &path, QString *error = nullptr); + QString loadTextFile(const QString &path, QString *error = nullptr); + bool cacheFile(const QString &path, QString *error = nullptr); void clearFileCache() { this->fileCache.clear(); } static int textFileLineCount(const QString &path); @@ -127,7 +131,6 @@ private: QHash evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; - QString loadTextFile(const QString &path, QString *error = nullptr); QString pathWithRoot(const QString &path); static const QRegularExpression re_incScriptLabel; diff --git a/include/core/tileset.h b/include/core/tileset.h index a05afdc3..abb31ab1 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -67,9 +67,9 @@ public: bool saveTilesImage(); bool savePalettes(); - bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); - bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); - bool appendToMetatiles(QString root, QString friendlyName, bool usingAsm); + bool appendToHeaders(const QString &filepath, const QString &friendlyName, bool usingAsm); + bool appendToGraphics(const QString &filepath, const QString &friendlyName, bool usingAsm); + bool appendToMetatiles(const QString &filepath, const QString &friendlyName, bool usingAsm); void setTilesImage(const QImage &image); diff --git a/include/project.h b/include/project.h index 2d2b5fd1..3c11009f 100644 --- a/include/project.h +++ b/include/project.h @@ -308,7 +308,10 @@ private: void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); - void ignoreWatchedFileTemporarily(QString filepath); + void watchFile(const QString &filename); + void watchFiles(const QStringList &filenames); + void ignoreWatchedFileTemporarily(const QString &filepath); + void ignoreWatchedFilesTemporarily(const QStringList &filepaths); void recordFileChange(const QString &filepath); void resetFileCache(); diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 0ad43d1a..dc3bbd65 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -249,12 +249,10 @@ QList Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset return paletteTable; } -bool Tileset::appendToHeaders(QString root, QString friendlyName, bool usingAsm) { - QString headersFile = root + "/" + (usingAsm ? projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm) - : projectConfig.getFilePath(ProjectFilePath::tilesets_headers)); - QFile file(headersFile); +bool Tileset::appendToHeaders(const QString &filepath, const QString &friendlyName, bool usingAsm) { + QFile file(filepath); if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) { - logError(QString("Could not write to file \"%1\"").arg(headersFile)); + logError(QString("Could not write to file \"%1\"").arg(filepath)); return false; } QString isSecondaryStr = this->is_secondary ? "TRUE" : "FALSE"; @@ -294,12 +292,10 @@ bool Tileset::appendToHeaders(QString root, QString friendlyName, bool usingAsm) return true; } -bool Tileset::appendToGraphics(QString root, QString friendlyName, bool usingAsm) { - QString graphicsFile = root + "/" + (usingAsm ? projectConfig.getFilePath(ProjectFilePath::tilesets_graphics_asm) - : projectConfig.getFilePath(ProjectFilePath::tilesets_graphics)); - QFile file(graphicsFile); +bool Tileset::appendToGraphics(const QString &filepath, const QString &friendlyName, bool usingAsm) { + QFile file(filepath); if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) { - logError(QString("Could not write to file \"%1\"").arg(graphicsFile)); + logError(QString("Could not write to file \"%1\"").arg(filepath)); return false; } @@ -332,12 +328,10 @@ bool Tileset::appendToGraphics(QString root, QString friendlyName, bool usingAsm return true; } -bool Tileset::appendToMetatiles(QString root, QString friendlyName, bool usingAsm) { - QString metatileFile = root + "/" + (usingAsm ? projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles_asm) - : projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles)); - QFile file(metatileFile); +bool Tileset::appendToMetatiles(const QString &filepath, const QString &friendlyName, bool usingAsm) { + QFile file(filepath); if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) { - logError(QString("Could not write to file \"%1\"").arg(metatileFile)); + logError(QString("Could not write to file \"%1\"").arg(filepath)); return false; } diff --git a/src/project.cpp b/src/project.cpp index 0e845048..bf43143e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -138,7 +138,9 @@ void Project::resetFileCache() { projectConfig.getFilePath(ProjectFilePath::global_fieldmap), }; for (const auto &path : filepaths) { - this->parser.cacheFile(path); + if (this->parser.cacheFile(path)) { + watchFile(path); + } } } @@ -218,6 +220,7 @@ QSet Project::getTopLevelMapFields() const { bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName); + watchFile(mapFilepath); QString error; if (!parser.tryParseJsonFile(out, mapFilepath, &error)) { logError(QString("Failed to read map data from '%1': %2").arg(mapFilepath).arg(error)); @@ -488,7 +491,7 @@ bool Project::readMapLayouts() { clearMapLayouts(); const QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); - fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(layoutsFilepath)); + watchFile(layoutsFilepath); QJsonDocument layoutsDoc; QString error; if (!parser.tryParseJsonFile(&layoutsDoc, layoutsFilepath, &error)) { @@ -642,11 +645,26 @@ bool Project::saveMapLayouts() { return true; } -void Project::ignoreWatchedFileTemporarily(QString filepath) { +void Project::watchFile(const QString &filename) { + this->fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filename)); +} + +void Project::watchFiles(const QStringList &filenames) { + for (const auto &filename : filenames) + watchFile(filename); +} + +void Project::ignoreWatchedFileTemporarily(const QString &filepath) { // Ignore any file-change events for this filepath for the next 5 seconds. this->modifiedFileTimestamps.insert(filepath, QDateTime::currentMSecsSinceEpoch() + 5000); } +void Project::ignoreWatchedFilesTemporarily(const QStringList &filepaths) { + for (const auto &filepath : filepaths) { + ignoreWatchedFileTemporarily(filepath); + } +} + void Project::recordFileChange(const QString &filepath) { if (this->modifiedFiles.contains(filepath)) { // We already recorded a change to this file @@ -1257,6 +1275,8 @@ bool Project::saveMap(Map *map, bool skipLayout) { // Custom header fields. OrderedJson::append(&mapObj, map->customAttributes()); + ignoreWatchedFileTemporarily(mapFilepath); + OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); jsonDoc.dump(&mapFile); @@ -1456,12 +1476,25 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa labelList->insert(i, tileset->name); this->tilesetLabelsOrdered.append(tileset->name); + // Append to tileset specific files. // TODO: Ideally we wouldn't save new Tilesets immediately - // Append to tileset specific files. Strip prefix from name to get base name for use in other symbols. - name.remove(0, prefix.length()); - tileset->appendToHeaders(this->root, name, this->usingAsmTilesets); - tileset->appendToGraphics(this->root, name, this->usingAsmTilesets); - tileset->appendToMetatiles(this->root, name, this->usingAsmTilesets); + QString headersFilepath = this->root + "/"; + QString graphicsFilepath = this->root + "/"; + QString metatilesFilepath = this->root + "/"; + if (this->usingAsmTilesets) { + headersFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm)); + graphicsFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_graphics_asm)); + metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles_asm)); + } else { + headersFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_headers)); + graphicsFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_graphics)); + metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles)); + } + ignoreWatchedFilesTemporarily({headersFilepath, graphicsFilepath, metatilesFilepath}); + name.remove(0, prefix.length()); // Strip prefix from name to get base name for use in other symbols. + tileset->appendToHeaders(headersFilepath, name, this->usingAsmTilesets); + tileset->appendToGraphics(graphicsFilepath, name, this->usingAsmTilesets); + tileset->appendToMetatiles(metatilesFilepath, name, this->usingAsmTilesets); tileset->save(); @@ -1485,7 +1518,7 @@ bool Project::readTilesetMetatileLabels() { unusedMetatileLabels.clear(); QString metatileLabelsFilename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels); - fileWatcher.addPath(root + "/" + metatileLabelsFilename); + watchFile(metatileLabelsFilename); const QSet regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; const auto defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); @@ -1593,7 +1626,7 @@ bool Project::readWildMonData() { const QString encounterRateFile = projectConfig.getFilePath(ProjectFilePath::wild_encounter); const QString maxEncounterRateName = projectConfig.getIdentifier(ProjectIdentifier::define_max_encounter_rate); - fileWatcher.addPath(QString("%1/%2").arg(root).arg(encounterRateFile)); + watchFile(encounterRateFile); auto defines = parser.readCDefinesByName(encounterRateFile, {maxEncounterRateName}); if (defines.contains(maxEncounterRateName)) this->maxEncounterRate = defines.value(maxEncounterRateName)/16; @@ -1602,8 +1635,7 @@ bool Project::readWildMonData() { const QString levelRangeFile = projectConfig.getFilePath(ProjectFilePath::constants_pokemon); const QString minLevelName = projectConfig.getIdentifier(ProjectIdentifier::define_min_level); const QString maxLevelName = projectConfig.getIdentifier(ProjectIdentifier::define_max_level); - - fileWatcher.addPath(QString("%1/%2").arg(root).arg(levelRangeFile)); + watchFile(levelRangeFile); defines = parser.readCDefinesByName(levelRangeFile, {minLevelName, maxLevelName}); if (defines.contains(minLevelName)) this->pokemonMinLevel = defines.value(minLevelName); @@ -1615,7 +1647,7 @@ bool Project::readWildMonData() { // Read encounter data const QString wildMonJsonFilepath = projectConfig.getFilePath(ProjectFilePath::json_wild_encounters); - fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(wildMonJsonFilepath)); + watchFile(wildMonJsonFilepath); OrderedJson::object wildMonObj; QString error; @@ -1772,7 +1804,7 @@ bool Project::readMapGroups() { this->customMapGroupsData = QJsonObject(); const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_map_groups); - fileWatcher.addPath(root + "/" + filepath); + watchFile(filepath); QJsonDocument mapGroupsDoc; QString error; if (!parser.tryParseJsonFile(&mapGroupsDoc, filepath, &error)) { @@ -2075,7 +2107,7 @@ bool Project::readTilesetLabels() { // If the tileset headers file is missing, the user may still have the old assembly format. this->usingAsmTilesets = true; QString asm_filename = projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm); - QString text = parser.readTextFile(this->root + "/" + asm_filename); + QString text = parser.loadTextFile(asm_filename); if (text.isEmpty()) { logError(QString("Failed to read tileset labels from '%1' or '%2'.").arg(filename).arg(asm_filename)); return false; @@ -2122,7 +2154,7 @@ bool Project::readFieldmapProperties() { const QString mapOffsetHeightName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_height); const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); const auto defines = parser.readCDefinesByName(filename, { numTilesPrimaryName, numTilesTotalName, numMetatilesPrimaryName, @@ -2226,8 +2258,7 @@ bool Project::readFieldmapMasks() { const QString behaviorMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior); const QString layerTypeMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer); - const QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); - fileWatcher.addPath(root + "/" + globalFieldmap); + const QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); // File already being watched const auto defines = parser.readCDefinesByName(globalFieldmap, { metatileIdMaskName, collisionMaskName, elevationMaskName, @@ -2282,7 +2313,7 @@ bool Project::readFieldmapMasks() { const QString layerTypeTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_layer); const QString encounterTypeTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_encounter); const QString terrainTypeTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_terrain); - fileWatcher.addPath(root + "/" + srcFieldmap); + watchFile(srcFieldmap); bool ok; // Read terrain type mask @@ -2360,7 +2391,7 @@ bool Project::readRegionMapSections() { logError(QString("Failed to read region map sections from '%1': %2").arg(filepath).arg(error)); return false; } - fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); + watchFile(filepath); QJsonObject mapSectionsGlobalObj = doc.object(); QJsonArray mapSections = mapSectionsGlobalObj.take("map_sections").toArray(); @@ -2524,7 +2555,7 @@ bool Project::readHealLocations() { logError(QString("Failed to read heal locations from '%1': %2").arg(filepath).arg(error)); return false; } - fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); + watchFile(filepath); QJsonObject healLocationsObj = doc.object(); QJsonArray healLocations = healLocationsObj.take("heal_locations").toArray(); @@ -2547,7 +2578,7 @@ bool Project::readHealLocations() { bool Project::readItemNames() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->itemNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}, &error); if (!error.isEmpty()) @@ -2557,7 +2588,7 @@ bool Project::readItemNames() { bool Project::readFlagNames() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->flagNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}, &error); if (!error.isEmpty()) @@ -2567,7 +2598,7 @@ bool Project::readFlagNames() { bool Project::readVarNames() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->varNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}, &error); if (!error.isEmpty()) @@ -2577,7 +2608,7 @@ bool Project::readVarNames() { bool Project::readMovementTypes() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->movementTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}, &error); if (!error.isEmpty()) @@ -2587,7 +2618,7 @@ bool Project::readMovementTypes() { bool Project::readInitialFacingDirections() { QString filename = projectConfig.getFilePath(ProjectFilePath::initial_facing_table); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->facingDirections = parser.readNamedIndexCArray(filename, projectConfig.getIdentifier(ProjectIdentifier::symbol_facing_directions), &error); if (!error.isEmpty()) @@ -2597,7 +2628,7 @@ bool Project::readInitialFacingDirections() { bool Project::readMapTypes() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); - fileWatcher.addPath(root + "/" + filename); + // File already being watched QString error; this->mapTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}, &error); if (!error.isEmpty()) @@ -2607,7 +2638,7 @@ bool Project::readMapTypes() { bool Project::readMapBattleScenes() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); - fileWatcher.addPath(root + "/" + filename); + // File already being watched QString error; this->mapBattleScenes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}, &error); if (!error.isEmpty()) @@ -2617,7 +2648,7 @@ bool Project::readMapBattleScenes() { bool Project::readWeatherNames() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->weatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}, &error); if (!error.isEmpty()) @@ -2630,7 +2661,7 @@ bool Project::readCoordEventWeatherNames() { return true; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->coordEventWeatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}, &error); if (!error.isEmpty()) @@ -2643,7 +2674,7 @@ bool Project::readSecretBaseIds() { return true; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->secretBaseIds = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}, &error); if (!error.isEmpty()) @@ -2653,7 +2684,7 @@ bool Project::readSecretBaseIds() { bool Project::readBgEventFacingDirections() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->bgEventFacingDirections = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}, &error); if (!error.isEmpty()) @@ -2663,7 +2694,7 @@ bool Project::readBgEventFacingDirections() { bool Project::readTrainerTypes() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->trainerTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}, &error); if (!error.isEmpty()) @@ -2676,7 +2707,7 @@ bool Project::readMetatileBehaviors() { this->metatileBehaviorMapInverse.clear(); QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; const auto defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); if (defines.isEmpty() && projectConfig.metatileBehaviorMask) { @@ -2698,7 +2729,7 @@ bool Project::readMetatileBehaviors() { bool Project::readSongNames() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; this->songNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}, &error); if (!error.isEmpty()) @@ -2713,7 +2744,7 @@ bool Project::readSongNames() { bool Project::readObjEventGfxConstants() { QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); QString error; const auto defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); if (!error.isEmpty()) @@ -2729,7 +2760,7 @@ bool Project::readObjEventGfxConstants() { bool Project::readMiscellaneousConstants() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global); const QString maxObjectEventsName = projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count); - fileWatcher.addPath(root + "/" + filename); + watchFile(filename); const auto defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); this->maxObjectEvents = 64; // Default value @@ -2856,7 +2887,7 @@ bool Project::readEventGraphics() { const QString gfxInfoFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); const QString picTablesFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables); const QString gfxFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx); - fileWatcher.addPaths({pointersFilepath, gfxInfoFilepath, picTablesFilepath, gfxFilepath}); + watchFiles({pointersFilepath, gfxInfoFilepath, picTablesFilepath, gfxFilepath}); // Read the table mapping OBJ_EVENT_GFX constants to the names of pointers to data about their graphics. const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers); @@ -3057,14 +3088,14 @@ bool Project::readSpeciesIconPaths() { // Read map of species constants to icon names const QString srcfilename = projectConfig.getFilePath(ProjectFilePath::pokemon_icon_table); - fileWatcher.addPath(this->root + "/" + srcfilename); + watchFile(srcfilename); const QString tableName = projectConfig.getIdentifier(ProjectIdentifier::symbol_pokemon_icon_table); const QMap monIconNames = parser.readNamedIndexCArray(srcfilename, tableName); // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); - fileWatcher.addPath(this->root + "/" + constantsFilename); + watchFile(constantsFilename); this->speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); if (this->speciesNames.isEmpty()) { this->speciesNames = monIconNames.keys(); @@ -3077,7 +3108,7 @@ bool Project::readSpeciesIconPaths() { // do this on request in Project::getDefaultSpeciesIconPath. if (!monIconNames.isEmpty()) { const QString iconGraphicsFile = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx); - fileWatcher.addPath(this->root + "/" + iconGraphicsFile); + watchFile(iconGraphicsFile); QMap iconNameToFilepath = parser.readCIncbinMulti(iconGraphicsFile); for (auto i = monIconNames.constBegin(); i != monIconNames.constEnd(); i++) { From 7ba4af1a505e0b29c3598917dbd835ebbf4a1583 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 2 May 2025 11:15:50 -0400 Subject: [PATCH 360/364] Remove old event frames UI --- forms/eventpropertiesframe.ui | 319 ---------------------------------- 1 file changed, 319 deletions(-) delete mode 100644 forms/eventpropertiesframe.ui diff --git a/forms/eventpropertiesframe.ui b/forms/eventpropertiesframe.ui deleted file mode 100644 index 4629438f..00000000 --- a/forms/eventpropertiesframe.ui +++ /dev/null @@ -1,319 +0,0 @@ - - - EventPropertiesFrame - - - - 0 - 0 - 284 - 146 - - - - - 0 - 0 - - - - - 284 - 90 - - - - Frame - - - Qt::LeftToRight - - - QFrame::Box - - - QFrame::Raised - - - 1 - - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - QFrame::Box - - - QFrame::Sunken - - - - - - false - - - Qt::AlignCenter - - - -1 - - - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - The index of the event currently being inspected. - - - 255 - - - - - - - - 0 - 0 - - - - Id - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - X - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>The X coordinate of this object.</p></body></html> - - - -32768 - - - 32767 - - - - - - - - - - - Y - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>The Y coordinate of this object.</p></body></html> - - - -32768 - - - 32767 - - - - - - - - - - - Z - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>The elevation of this object.</p></body></html> - - - 15 - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - QFormLayout::WrapLongRows - - - 9 - - - 0 - - - 0 - - - - - Sprite - - - comboBox_sprite - - - - - - - true - - - Qt::StrongFocus - - - <html><head/><body><p>The sprite graphics to use for this object.</p></body></html> - - - true - - - - - - 25 - - - QComboBox::AdjustToContentsOnFirstShow - - - - - - - - - - - NoScrollComboBox - QComboBox -
noscrollcombobox.h
-
- - NoScrollSpinBox - QSpinBox -
noscrollspinbox.h
-
-
- - spinBox_x - spinBox_y - spinBox_z - comboBox_sprite - - - -
From c8a9e33d405ef51f4c792198eff12d9ad7994984 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 2 May 2025 11:22:38 -0400 Subject: [PATCH 361/364] Allow copy-pasting version info --- forms/aboutporymap.ui | 5 ++++- src/ui/aboutporymap.cpp | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/forms/aboutporymap.ui b/forms/aboutporymap.ui index 7a211e0a..fc777020 100644 --- a/forms/aboutporymap.ui +++ b/forms/aboutporymap.ui @@ -6,7 +6,7 @@ 0 0 - 383 + 388 121
@@ -53,6 +53,9 @@ Qt::AlignmentFlag::AlignCenter + + Qt::TextInteractionFlag::TextSelectableByMouse +
diff --git a/src/ui/aboutporymap.cpp b/src/ui/aboutporymap.cpp index 5aa5f0c8..2f7246c0 100644 --- a/src/ui/aboutporymap.cpp +++ b/src/ui/aboutporymap.cpp @@ -8,20 +8,20 @@ AboutPorymap::AboutPorymap(QWidget *parent) : ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - static const QString commitHash = PORYMAP_LATEST_COMMIT; this->ui->label_Version->setText(getVersionString()); layout()->setSizeConstraint(QLayout::SetFixedSize); } QString AboutPorymap::getVersionString() { - static const QString commitHash = PORYMAP_LATEST_COMMIT; - return QString("Version %1%2\nQt %3 (%4)\n%5") + static const QString commitHash = QStringLiteral(PORYMAP_LATEST_COMMIT); + static const QString versionString = QString("Version %1%2\nQt %3 (%4)\n%5") .arg(QCoreApplication::applicationVersion()) .arg(commitHash.isEmpty() ? "" : QString(" (%1)").arg(commitHash)) .arg(QStringLiteral(QT_VERSION_STR)) .arg(QSysInfo::buildCpuArchitecture()) .arg(QStringLiteral(__DATE__)); + return versionString; } AboutPorymap::~AboutPorymap() From a1d264cd47d5d59d9e304947b0c478968c31a4b2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 May 2025 00:08:06 -0400 Subject: [PATCH 362/364] Fix regression to status bar message clearing --- src/editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor.cpp b/src/editor.cpp index d02006cd..058173da 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1165,7 +1165,7 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { void Editor::onHoveredMapMetatileCleared() { this->setCursorRectVisible(false); - if (!map_item->getEditsEnabled()) { + if (map_item->getEditsEnabled()) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); From e2371eb1e61282400ad79f9031ee728c4dfd6ef3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 May 2025 16:34:43 -0400 Subject: [PATCH 363/364] Remove redundant text change for tileset combo boxes --- src/mainwindow.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 322d6e83..16b24f1e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1192,11 +1192,6 @@ void MainWindow::displayMapProperties() { ui->frame_HeaderData->setEnabled(true); this->mapHeaderForm->setHeader(editor->map->header()); - const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - ui->comboBox_PrimaryTileset->setTextItem(editor->map->layout()->tileset_primary_label); - ui->comboBox_SecondaryTileset->setTextItem(editor->map->layout()->tileset_secondary_label); - ui->mapCustomAttributesFrame->table()->setAttributes(editor->map->customAttributes()); } From 3a02df50af7a7c36795639864600beccf357343f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 May 2025 16:36:34 -0400 Subject: [PATCH 364/364] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d489b17..abdb4d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8. - The prompt to reload the project when a file has changed will now only appear when Porymap is the active application. - `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting. +- `Script` dropdowns now update automatically if the current map's scripts file is edited. - The options for `Encounter Type` and `Terrain Type` in the Tileset Editor are not hardcoded anymore, they're now read from the project. - The `symbol_wild_encounters` setting was replaced; this value is now read from the project. - The max encounter rate is now read from the project, rather than assuming the default value from RSE.