diff --git a/forms/regionmapeditor.ui b/forms/regionmapeditor.ui index 517e6e0c..ec845dc3 100644 --- a/forms/regionmapeditor.ui +++ b/forms/regionmapeditor.ui @@ -1124,8 +1124,6 @@ Edit - - @@ -1150,22 +1148,6 @@ Ctrl+S - - - Undo - - - Ctrl+Z - - - - - Redo - - - Ctrl+Y - - Resize diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 3c85d847..58dc6e35 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -19,32 +19,6 @@ using std::shared_ptr; class Project; -enum RegionMapEditorBox { - BackgroundImage = 1, - CityMapImage = 2, -}; - -class RegionMapHistoryItem { -public: - int which; - int mapWidth = 0; - int mapHeight = 0; - QVector tiles; - QString cityMap; - RegionMapHistoryItem(int which, QVector tiles, QString cityMap) { - this->which = which; - this->tiles = tiles; - this->cityMap = cityMap; - } - RegionMapHistoryItem(int which, QVector tiles, int width, int height) { - this->which = which; - this->tiles = tiles; - this->mapWidth = width; - this->mapHeight = height; - } - ~RegionMapHistoryItem() {} -}; - struct LayoutSquare { QString map_section; @@ -69,9 +43,9 @@ public: RegionMap() = delete; RegionMap(Project *); - Project *project = nullptr; + ~RegionMap() {} - History history; // TODO + Project *project = nullptr; bool loadMapData(poryjson::Json); bool loadTilemap(poryjson::Json); @@ -121,6 +95,9 @@ public: QByteArray getTilemap(); void setTilemap(QByteArray newTilemap); + QList getLayout(QString layer); + void setLayout(QString layer, QList layout); + QStringList getLayers() { return this->layout_layers; } void setLayer(QString layer) { this->current_layer = layer; } QString getLayer() { return this->current_layer; } @@ -146,6 +123,12 @@ public: QString fullPath(QString local); + void commit(QUndoCommand *command); + QUndoStack editHistory; + + void undo(); + void redo(); + private: // TODO: defaults needed? tsl::ordered_map *region_map_entries = nullptr; diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 32c74adc..e3e53a75 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -45,9 +45,6 @@ public: void onRegionMapEntriesSelectedTileChanged(QString) {}; void onRegionMapEntryDragged(int, int); - void undo(); - void redo(); - void resize(int width, int height); QObjectList shortcutableObjects() const; @@ -61,7 +58,7 @@ private: poryjson::Json rmConfigJson; - History history; + QUndoGroup history; int currIndex = 0; unsigned selectedCityTile; @@ -124,8 +121,6 @@ private: private slots: void on_action_RegionMap_Save_triggered(); - void on_action_RegionMap_Undo_triggered(); - void on_action_RegionMap_Redo_triggered(); void on_action_RegionMap_Resize_triggered(); void on_action_RegionMap_ClearImage_triggered(); void on_action_RegionMap_ClearLayout_triggered(); diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index c7e45b47..9c2ba1af 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -123,7 +123,9 @@ public: this->tileset = QImage(tilesetFilepath); this->format = format; bool err; - this->palette = PaletteUtil::parse(palFilepath, &err); + if (!palFilepath.isEmpty()) { + this->palette = PaletteUtil::parse(palFilepath, &err); + } this->setPixmap(QPixmap::fromImage(this->tileset)); this->numTilesWide = this->tileset.width() / 8; this->selectedTile = 0x00; diff --git a/porymap.pro b/porymap.pro index 2a86653e..89808d2c 100644 --- a/porymap.pro +++ b/porymap.pro @@ -12,7 +12,7 @@ TARGET = porymap TEMPLATE = app RC_ICONS = resources/icons/porymap-icon-2.ico ICON = resources/icons/porymap.icns -QMAKE_CXXFLAGS += -std=c++11 -Wall +QMAKE_CXXFLAGS += -std=c++17 -Wall QMAKE_TARGET_BUNDLE_PREFIX = com.pret SOURCES += src/core/block.cpp \ @@ -33,6 +33,7 @@ SOURCES += src/core/block.cpp \ src/core/wildmoninfo.cpp \ src/core/editcommands.cpp \ src/lib/orderedjson.cpp \ + src/core/regionmapeditcommands.cpp \ src/mainwindow_scriptapi.cpp \ src/ui/aboutporymap.cpp \ src/ui/draggablepixmapitem.cpp \ @@ -106,6 +107,7 @@ HEADERS += include/core/block.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ include/core/editcommands.h \ + include/core/regionmapeditcommands.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index f4b43933..b0d17c65 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -4,6 +4,7 @@ #include "project.h" #include "log.h" #include "config.h" +#include "regionmapeditcommands.h" #include #include @@ -163,7 +164,7 @@ bool RegionMap::loadLayout(poryjson::Json layoutJson) { layout.append(square); } } - this->layouts["main"] = layout; + setLayout("main", layout); break; } case LayoutFormat::CArray: @@ -226,7 +227,7 @@ bool RegionMap::loadLayout(poryjson::Json layoutJson) { } y++; } - this->layouts[layerName] = layout; + setLayout(layerName, layout); } } else { @@ -241,13 +242,23 @@ bool RegionMap::loadLayout(poryjson::Json layoutJson) { return !errored; } -void RegionMap::save() { - logInfo("Saving region map data."); +void RegionMap::commit(QUndoCommand *command) { + editHistory.push(command); +} +void RegionMap::undo() { + // + editHistory.undo(); +} + +void RegionMap::redo() { + // + editHistory.redo(); +} + +void RegionMap::save() { saveTilemap(); saveLayout(); - - // TODO } void RegionMap::saveTilemap() { @@ -320,18 +331,19 @@ void RegionMap::saveOptions(int id, QString sec, QString name, int x, int y) { } void RegionMap::resetSquare(int index) { - // TODO - + this->layouts[this->current_layer][index].map_section = "MAPSEC_NONE"; + this->layouts[this->current_layer][index].has_map = false; } void RegionMap::clearLayout() { - // TODO - + for (int i = 0; i < this->layout_width * this->layout_height; i++) { + resetSquare(i); + } } void RegionMap::clearImage() { - // TODO - + QByteArray zeros(this->tilemapSize(), 0); + this->setTilemap(zeros); } void RegionMap::replaceSectionId(unsigned oldId, unsigned newId) { @@ -404,8 +416,17 @@ void RegionMap::setTilemap(QByteArray newTilemap) { } } +QList RegionMap::getLayout(QString layer) { + return this->layouts[layer]; +} + +void RegionMap::setLayout(QString layer, QList layout) { + this->layouts[layer] = layout; +} + QVector RegionMap::getTiles() { QVector tileIds; + // unused? remove when redo history is fully transitioned // TODO: change this to use TilemapTile instead of uint8_t return tileIds; @@ -423,7 +444,7 @@ int RegionMap::get_tilemap_index(int x, int y) { // Layout coords to layout index. int RegionMap::get_layout_index(int x, int y) { - return x + y * this->tilemap_width; + return x + y * this->layout_width; } unsigned RegionMap::getTileId(int index) { @@ -526,7 +547,7 @@ MapSectionEntry RegionMap::getEntry(QString section) { } QString RegionMap::palPath() { - return this->project->root + "/" + this->palette_path; + return this->palette_path.isEmpty() ? QString() : this->project->root + "/" + this->palette_path; } QString RegionMap::pngPath() { diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index e7eb46d2..bcde453e 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -1,6 +1,7 @@ #include "regionmapeditor.h" #include "ui_regionmapeditor.h" #include "regionmappropertiesdialog.h" +#include "regionmapeditcommands.h" #include "imageexport.h" #include "shortcut.h" #include "config.h" @@ -36,6 +37,11 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project_) : RegionMapEditor::~RegionMapEditor() { delete ui; + // deletion must be done in this order else crashes + auto stacks = this->history.stacks(); + for (auto *stack : stacks) { + this->history.removeStack(stack); + } for (auto p : this->region_maps) { delete p.second; } @@ -64,6 +70,21 @@ void RegionMapEditor::initShortcuts() { shortcut_RM_Options_delete->setObjectName("shortcut_RM_Options_delete"); shortcut_RM_Options_delete->setWhatsThis("Map Layout: Delete Square"); + QAction *undoAction = this->history.createUndoAction(this, tr("&Undo")); + undoAction->setObjectName("action_RegionMap_Undo"); + undoAction->setShortcut(QKeySequence("Ctrl+Z")); + + QAction *redoAction = this->history.createRedoAction(this, tr("&Redo")); + redoAction->setObjectName("action_RegionMap_Redo"); + redoAction->setShortcuts({QKeySequence("Ctrl+Y"), QKeySequence("Ctrl+Shift+Z")}); + + ui->menuEdit->addAction(undoAction); + ui->menuEdit->addAction(redoAction); + + connect(&(this->history), &QUndoGroup::indexChanged, [this](int) { + on_tabWidget_Region_Map_currentChanged(this->ui->tabWidget_Region_Map->currentIndex()); + }); + shortcutsConfig.load(); shortcutsConfig.setDefaultShortcuts(shortcutableObjects()); applyUserShortcuts(); @@ -409,6 +430,8 @@ bool RegionMapEditor::load() { newMap->loadMapData(o); region_maps[alias] = newMap; + + this->history.addStack(&(newMap->editHistory)); } // add to ui @@ -420,6 +443,7 @@ bool RegionMapEditor::load() { if (!region_maps.empty()) { this->region_map = region_maps.begin()->second; this->currIndex = this->region_map->firstLayoutIndex(); + this->region_map->editHistory.setActive(); displayRegionMap(); } @@ -477,11 +501,14 @@ void RegionMapEditor::on_comboBox_regionSelector_textActivated(const QString &re // if (this->region_maps.contains(region)) { this->region_map = region_maps.at(region); + this->region_map->editHistory.setActive(); this->currIndex = this->region_map->firstLayoutIndex(); // TODO: make the above into a function that takes an alias string? in case there is more to it // TODO: anything else needed here? displayRegionMap(); + + //this->editGroup.setActiveStack(&(this->region_map->editHistory)); } } @@ -510,15 +537,6 @@ void RegionMapEditor::displayRegionMapImage() { this->ui->graphicsView_Region_Map_BkgImg->setScene(this->scene_region_map_image); on_verticalSlider_Zoom_Map_Image_valueChanged(this->ui->verticalSlider_Zoom_Map_Image->value()); - - // if (regionMapFirstDraw) { - // on_verticalSlider_Zoom_Map_Image_valueChanged(this->ui->verticalSlider_Zoom_Map_Image->value()); - // RegionMapHistoryItem *commit = new RegionMapHistoryItem( - // RegionMapEditorBox::BackgroundImage, this->region_map->getTiles(), this->region_map->tilemapWidth(), this->region_map->height() - // ); - // history.push(commit); - // regionMapFirstDraw = false; - // } } void RegionMapEditor::displayRegionMapLayout() { @@ -670,6 +688,8 @@ void RegionMapEditor::displayRegionMapTileSelector() { this->ui->graphicsView_RegionMap_Tiles->setScene(this->scene_region_map_tiles); on_verticalSlider_Zoom_Image_Tiles_valueChanged(this->ui->verticalSlider_Zoom_Image_Tiles->value()); + this->ui->frame_tileOptions->setEnabled(this->region_map->tilemapFormat() != TilemapFormat::Plain); + this->mapsquare_selector_item->select(this->selectedImageTile); } @@ -842,21 +862,9 @@ void RegionMapEditor::mouseEvent_region_map(QGraphicsSceneMouseEvent *event, Reg item->select(event); //} else if (event->buttons() & Qt::MiddleButton) {// TODO } else { - if (event->type() == QEvent::GraphicsSceneMouseRelease) { - //< TODO: history - // RegionMapHistoryItem *current = history.current(); - // bool addToHistory = !(current && current->tiles == this->region_map->getTiles()); - // if (addToHistory) { - // RegionMapHistoryItem *commit = new RegionMapHistoryItem( - // RegionMapEditorBox::BackgroundImage, this->region_map->getTiles(), this->region_map->width(), this->region_map->height() - // ); - // history.push(commit); - // } - } else { - item->paint(event); - this->region_map_layout_item->draw(); - this->hasUnsavedChanges = true; - } + item->paint(event); + this->region_map_layout_item->draw(); + this->hasUnsavedChanges = true; } } @@ -894,15 +902,20 @@ void RegionMapEditor::mouseEvent_city_map(QGraphicsSceneMouseEvent *event, CityM void RegionMapEditor::on_tabWidget_Region_Map_currentChanged(int index) { this->ui->stackedWidget_RM_Options->setCurrentIndex(index); + if (!region_map) return; switch (index) { case 0: this->ui->verticalSlider_Zoom_Image_Tiles->setVisible(true); - this->region_map_item->draw(); + if (this->region_map_item) + this->region_map_item->draw(); break; case 1: this->ui->verticalSlider_Zoom_Image_Tiles->setVisible(false); - this->region_map_layout_item->draw(); + if (this->region_map_layout_item) { + this->region_map_layout_item->draw(); + updateRegionMapLayoutOptions(this->currIndex); + } break; case 2: this->ui->verticalSlider_Zoom_Image_Tiles->setVisible(false); @@ -912,9 +925,17 @@ void RegionMapEditor::on_tabWidget_Region_Map_currentChanged(int index) { } void RegionMapEditor::on_comboBox_RM_ConnectedMap_textActivated(const QString &mapsec) { + QString layer = this->region_map->getLayer(); + QList oldLayout = this->region_map->getLayout(layer); this->region_map->setSquareMapSection(this->currIndex, mapsec); - onRegionMapLayoutSelectedTileChanged(this->currIndex);// re-draw layout image + QList newLayout = this->region_map->getLayout(layer); + + EditLayout *command = new EditLayout(this->region_map, layer, this->currIndex, oldLayout, newLayout); + this->region_map->commit(command); + this->hasUnsavedChanges = true;// TODO: sometimes this is called for unknown reasons + + onRegionMapLayoutSelectedTileChanged(this->currIndex);// re-draw layout image } void RegionMapEditor::on_comboBox_RM_Entry_MapSection_textActivated(const QString &text) { @@ -988,10 +1009,21 @@ void RegionMapEditor::on_lineEdit_RM_MapName_textEdited(const QString &text) { } void RegionMapEditor::on_pushButton_RM_Options_delete_clicked() { - this->region_map->resetSquare(this->region_map_layout_item->selectedTile); - updateRegionMapLayoutOptions(this->region_map_layout_item->selectedTile); + // TODO: crashing + int index = this->region_map->tilemapToLayoutIndex(this->currIndex); + QList oldLayout = this->region_map->getLayout(this->region_map->getLayer()); + //this->region_map->resetSquare(this->region_map_layout_item->selectedTile); + this->region_map->resetSquare(index); + QList newLayout = this->region_map->getLayout(this->region_map->getLayer()); + EditLayout *commit = new EditLayout(this->region_map, this->region_map->getLayer(), this->currIndex, oldLayout, newLayout); + commit->setText("Reset Layout Square"); + this->region_map->editHistory.push(commit); + //updateRegionMapLayoutOptions(this->region_map_layout_item->selectedTile); + updateRegionMapLayoutOptions(this->currIndex); this->region_map_layout_item->draw(); - this->region_map_layout_item->select(this->region_map_layout_item->selectedTile); + //this->region_map_layout_item->select(this->region_map_layout_item->selectedTile); + this->region_map_layout_item->select(this->currIndex); + // ^ this line necessary? this->hasUnsavedChanges = true; } @@ -1082,63 +1114,6 @@ void RegionMapEditor::on_action_RegionMap_Resize_triggered() { return; } -void RegionMapEditor::on_action_RegionMap_Undo_triggered() { - //< TODO: edit history - undo(); - this->hasUnsavedChanges = true; -} - -void RegionMapEditor::undo() { - //< TODO: edit history - RegionMapHistoryItem *commit = history.back(); - if (!commit) return; - - switch (commit->which) - { - case RegionMapEditorBox::BackgroundImage: - //if (commit->mapWidth != this->region_map->width() || commit->mapHeight != this->region_map->height()) - // this->resize(commit->mapWidth, commit->mapHeight); - this->region_map->setTiles(commit->tiles); - this->region_map_item->draw(); - this->region_map_layout_item->draw(); - this->region_map_entries_item->draw(); - break; - case RegionMapEditorBox::CityMapImage: - //< if (commit->cityMap == this->city_map_item->file) - //< this->city_map_item->setTiles(commit->tiles); - //< this->city_map_item->draw(); - break; - } -} - -void RegionMapEditor::on_action_RegionMap_Redo_triggered() { - //< TODO: edit history - redo(); - this->hasUnsavedChanges = true; -} - -void RegionMapEditor::redo() { - //< TODO: edit history - RegionMapHistoryItem *commit = history.next(); - if (!commit) return; - - switch (commit->which) - { - case RegionMapEditorBox::BackgroundImage: - //if (commit->mapWidth != this->region_map->width() || commit->mapHeight != this->region_map->height()) - // this->resize(commit->mapWidth, commit->mapHeight); - this->region_map->setTiles(commit->tiles); - this->region_map_item->draw(); - this->region_map_layout_item->draw(); - this->region_map_entries_item->draw(); - break; - case RegionMapEditorBox::CityMapImage: - //< this->city_map_item->setTiles(commit->tiles); - //< this->city_map_item->draw(); - break; - } -} - void RegionMapEditor::resize(int w, int h) { this->region_map->resize(w, h); this->currIndex = this->region_map->padLeft() * w + this->region_map->padTop(); @@ -1189,11 +1164,13 @@ void RegionMapEditor::on_action_Swap_triggered() { } void RegionMapEditor::on_action_RegionMap_ClearImage_triggered() { + QByteArray oldTilemap = this->region_map->getTilemap(); this->region_map->clearImage(); - RegionMapHistoryItem *commit = new RegionMapHistoryItem( - RegionMapEditorBox::BackgroundImage, this->region_map->getTiles(), this->region_map->tilemapWidth(), this->region_map->tilemapHeight() - ); - history.push(commit); + QByteArray newTilemap = this->region_map->getTilemap(); + + EditTilemap *commit = new EditTilemap(this->region_map, oldTilemap, newTilemap, -1); + commit->setText("Clear Tilemap"); + this->region_map->editHistory.push(commit); displayRegionMapImage(); displayRegionMapLayout(); @@ -1209,7 +1186,12 @@ void RegionMapEditor::on_action_RegionMap_ClearLayout_triggered() { ); if (result == QMessageBox::Yes) { + QList oldLayout = this->region_map->getLayout(this->region_map->getLayer()); this->region_map->clearLayout(); + QList newLayout = this->region_map->getLayout(this->region_map->getLayer()); + EditLayout *commit = new EditLayout(this->region_map, this->region_map->getLayer(), -1, oldLayout, newLayout); + commit->setText("Clear Layout"); + this->region_map->editHistory.push(commit); displayRegionMapLayout(); } else { return; diff --git a/src/ui/regionmappixmapitem.cpp b/src/ui/regionmappixmapitem.cpp index 75a9eb3d..ba1cdcc9 100644 --- a/src/ui/regionmappixmapitem.cpp +++ b/src/ui/regionmappixmapitem.cpp @@ -1,4 +1,7 @@ #include "regionmappixmapitem.h" +#include "regionmapeditcommands.h" + +static unsigned actionId_ = 0; void RegionMapPixmapItem::draw() { if (!region_map) return; @@ -20,17 +23,25 @@ void RegionMapPixmapItem::draw() { void RegionMapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { if (region_map) { - QPointF pos = event->pos(); - int x = static_cast(pos.x()) / 8; - int y = static_cast(pos.y()) / 8; - int index = x + y * region_map->tilemapWidth(); - this->region_map->setTileData(index, - this->tile_selector->selectedTile, - this->tile_selector->tile_hFlip, - this->tile_selector->tile_vFlip, - this->tile_selector->tile_palette - ); - draw(); + if (event->type() == QEvent::GraphicsSceneMouseRelease) { + actionId_++; + } else { + QPointF pos = event->pos(); + int x = static_cast(pos.x()) / 8; + int y = static_cast(pos.y()) / 8; + int index = x + y * region_map->tilemapWidth(); + QByteArray oldTilemap = this->region_map->getTilemap(); + this->region_map->setTileData(index, + this->tile_selector->selectedTile, + this->tile_selector->tile_hFlip, + this->tile_selector->tile_vFlip, + this->tile_selector->tile_palette + ); + QByteArray newTilemap = this->region_map->getTilemap(); + EditTilemap *command = new EditTilemap(this->region_map, oldTilemap, newTilemap, actionId_); + this->region_map->commit(command); + draw(); + } } } diff --git a/src/ui/tilemaptileselector.cpp b/src/ui/tilemaptileselector.cpp index b7759c4c..48587893 100644 --- a/src/ui/tilemaptileselector.cpp +++ b/src/ui/tilemaptileselector.cpp @@ -20,6 +20,7 @@ void TilemapTileSelector::select(unsigned tileId) { QPoint coords = this->getTileIdCoords(tileId); SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); this->selectedTile = tileId; + this->drawSelection(); emit selectedTileChanged(tileId); } @@ -50,11 +51,7 @@ QImage TilemapTileSelector::tileImg(shared_ptr tile) { switch(this->format) { case TilemapFormat::Plain: - { - // TODO: even allow palettes for Plain tiles? - // 1 x palette x any colors break; - } case TilemapFormat::BPP_4: { // before Qt 6, the color table is a QVector which is deprecated now, and this method does not exits @@ -67,10 +64,11 @@ QImage TilemapTileSelector::tileImg(shared_ptr tile) { } case TilemapFormat::BPP_8: { + // TODO: #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - tilesetImage.setColorTable(this->palette.toVector()); + //tilesetImage.setColorTable(this->palette.toVector()); #else - tilesetImage.setColorTable(this->palette); + //tilesetImage.setColorTable(this->palette); #endif break; }