diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c38a18..f3124e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,20 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp The **"Breaking Changes"** listed below are changes that have been made in the decompilation projects (e.g. pokeemerald), which porymap requires in order to work properly. If porymap is used on a project that is not up-to-date with the breaking changes, then porymap will likely break or behave improperly. ## Unreleased -Nothing, yet. +### Breaking Changes +- If you are using pokeemerald or pokeruby, there were changes made in [pokeemerald/#1010](https://github.com/pret/pokeemerald/pull/1010) and [pokeruby/#776](https://github.com/pret/pokeruby/pull/776) that you will need to integrate in order to use this version of porymap. + +### Added +- Support for [pokefirered](https://github.com/pret/pokefirered). Kanto fans rejoice! At long last porymap supports the FRLG decompilation project. +- Add ability to export map stitches with `File -> Export Map Stitch Image...`. + +### Changed +- Porymap now saves map and encounter json data in an order consistent with the upstream repos. This will provide more comprehensible diffs, among other things. + +### Fixed +- Fix bug where pressing TAB key did not navigate through widgets in the wild encounter tables. +- Fix bug that allowed selecting an invalid metatile in the metatile selector. + ## [3.0.1] - 2020-03-04 ### Fixed diff --git a/README.md b/README.md index 06a3a3bf..f6f7a8b8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # porymap -A map editor for the generation 3 disassembly projects using Qt. +A map editor for the generation 3 decompilation projects using Qt. -Currently supports [pokeruby][pokeruby] and [pokeemerald][pokeemerald]. +Currently supports [pokeruby][pokeruby], [pokeemerald][pokeemerald], and [pokefirered][pokefirered]. Documentation: https://huderlem.github.io/porymap/ @@ -10,5 +10,6 @@ View the [Changelog][changelog] to see what's new, and download the latest versi [pokeruby]: https://github.com/pret/pokeruby [pokeemerald]: https://github.com/pret/pokeemerald +[pokefirered]: https://github.com/pret/pokefirered [changelog]: https://github.com/huderlem/porymap/blob/master/CHANGELOG.md [releases]: https://github.com/huderlem/porymap/releases diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index f08bde20..56fc6e0e 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2892,6 +2892,7 @@ + @@ -3210,6 +3211,10 @@ Use PoryScript + + + Export Map Stitch Image... + diff --git a/include/editor.h b/include/editor.h index 52c3fdc7..31279095 100644 --- a/include/editor.h +++ b/include/editor.h @@ -137,6 +137,8 @@ public: void objectsView_onMouseMove(QMouseEvent *event); void objectsView_onMouseRelease(QMouseEvent *event); + int getBorderDrawDistance(int dimension); + private: void setConnectionItemsVisible(bool); void setBorderItemsVisible(bool, qreal = 1); @@ -153,7 +155,6 @@ private: void updateMirroredConnectionMap(MapConnection*, QString); void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false); void updateEncounterFields(EncounterFields newFields); - int getBorderDrawDistance(int dimension); Event* createNewObjectEvent(); Event* createNewWarpEvent(); Event* createNewHealLocationEvent(); diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 645b4336..54452314 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -62,6 +62,10 @@ #include #include +#include +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 1) + #include "qstringhash.h" +#endif #include "orderedmap.h" #ifdef _MSC_VER diff --git a/include/lib/orderedmap.h b/include/lib/orderedmap.h index df94d861..40fe08b8 100644 --- a/include/lib/orderedmap.h +++ b/include/lib/orderedmap.h @@ -1640,7 +1640,7 @@ private: * the erased element and all the ones after the erased element (including end()). * Otherwise all the iterators are invalidated if an erase occurs. */ -template, class KeyEqual = std::equal_to, @@ -2031,7 +2031,7 @@ public: - T& operator[](const Key& key) { return m_ht[key]; } + T& operator[](const Key& key) { return m_ht[key]; } T& operator[](Key&& key) { return m_ht[std::move(key)]; } @@ -2042,7 +2042,7 @@ public: * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. */ - size_type count(const Key& key, std::size_t precalculated_hash) const { + size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } @@ -2079,7 +2079,7 @@ public: /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ - const_iterator find(const Key& key, std::size_t precalculated_hash) const { + const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } @@ -2124,7 +2124,7 @@ public: * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. */ - std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } @@ -2133,7 +2133,7 @@ public: /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ - std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } diff --git a/include/lib/qstringhash.h b/include/lib/qstringhash.h new file mode 100644 index 00000000..10947d6f --- /dev/null +++ b/include/lib/qstringhash.h @@ -0,0 +1,19 @@ +#ifndef QSTRINGHASH_H +#define QSTRINGHASH_H + +#include +#include +#include + +// This is a custom hash function for QString so it can be used as +// a key in a std::hash structure. Qt 5.14 added this function, so +// this file should only be included in earlier versions. +namespace std { + template<> struct hash { + std::size_t operator()(const QString& s) const noexcept { + return static_cast(qHash(s)); + } + }; +} + +#endif // QSTRINGHASH_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 3cd89daa..15182ec7 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -117,6 +117,7 @@ private slots: void currentMetatilesSelectionChanged(); void on_action_Export_Map_Image_triggered(); + void on_actionExport_Stitched_Map_Image_triggered(); void on_comboBox_ConnectionDirection_currentIndexChanged(const QString &arg1); void on_spinBox_ConnectionOffset_valueChanged(int offset); @@ -228,6 +229,7 @@ private: void closeSupplementaryWindows(); bool isProjectOpen(); + void showExportMapImageWindow(bool stitchMode); }; enum MapListUserRoles { diff --git a/include/project.h b/include/project.h index 5f432b81..e0e0d859 100644 --- a/include/project.h +++ b/include/project.h @@ -106,7 +106,8 @@ public: QString readMapLocation(QString map_name); bool readWildMonData(); - QMap> wildMonData; + tsl::ordered_map> wildMonData; + QVector wildMonFields; QVector encounterGroupLabels; QVector extraEncounterGroups; diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index a6772ed1..0dedb204 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -15,7 +15,7 @@ class MapImageExporter : public QDialog Q_OBJECT public: - explicit MapImageExporter(QWidget *parent, Editor *editor); + explicit MapImageExporter(QWidget *parent, Editor *editor, bool stitchMode); ~MapImageExporter(); private: @@ -39,9 +39,12 @@ private: bool showGrid = false; bool showBorder = false; bool showCollision = false; + bool stitchMode = false; void updatePreview(); void saveImage(); + QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder); + QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder); private slots: void on_checkBox_Objects_stateChanged(int state); diff --git a/porymap.pro b/porymap.pro index 0bfb5550..5e686c78 100644 --- a/porymap.pro +++ b/porymap.pro @@ -10,7 +10,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = porymap TEMPLATE = app -RC_ICONS = resources/icons/porymap-icon-1.ico +RC_ICONS = resources/icons/porymap-icon-2.ico ICON = resources/icons/porymap.icns QMAKE_CXXFLAGS += -std=c++11 -Wall @@ -134,7 +134,8 @@ HEADERS += include/core/block.h \ include/project.h \ include/settings.h \ include/log.h \ - include/ui/newtilesetdialog.h + include/ui/newtilesetdialog.h \ + include/lib/qstringhash.h FORMS += forms/mainwindow.ui \ forms/eventpropertiesframe.ui \ diff --git a/resources/icons/porymap-icon-2.ico b/resources/icons/porymap-icon-2.ico new file mode 100644 index 00000000..72f52834 Binary files /dev/null and b/resources/icons/porymap-icon-2.ico differ diff --git a/resources/icons/porymap.icns b/resources/icons/porymap.icns index ef3b77fb..a8a8bd4a 100644 Binary files a/resources/icons/porymap.icns and b/resources/icons/porymap.icns differ diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index c44339d0..d2025817 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -195,14 +195,7 @@ bool RegionMap::readLayout() { map_squares[i].has_map = true; } map_squares[i].mapsec = secname; - if (!mapSecToMapEntry.contains(secname)) { - continue; - } - QString name = mapSecToMapEntry.value(secname).name; - if (!sMapNamesMap.contains(name)) { - continue; - } - map_squares[i].map_name = sMapNamesMap.value(name); + map_squares[i].map_name = sMapNamesMap.value(mapSecToMapEntry.value(secname).name); map_squares[i].x = x; map_squares[i].y = y; } diff --git a/src/editor.cpp b/src/editor.cpp index 5f84350f..40f2f404 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -189,17 +189,20 @@ void Editor::displayWildMonTables() { return; } - labelCombo->addItems(project->wildMonData[map->constantName].keys()); - labelCombo->setCurrentText(project->wildMonData[map->constantName].firstKey()); + for (auto groupPair : project->wildMonData[map->constantName]) + labelCombo->addItem(groupPair.first); - for (int labelIndex = 0; labelIndex < project->wildMonData[map->constantName].keys().size(); labelIndex++) { + labelCombo->setCurrentText(labelCombo->itemText(0)); - QString label = project->wildMonData.value(map->constantName).keys().at(labelIndex); + int labelIndex = 0; + for (auto labelPair : project->wildMonData[map->constantName]) { - WildPokemonHeader header = project->wildMonData.value(map->constantName).value(label); + QString label = labelPair.first; + + WildPokemonHeader header = project->wildMonData[map->constantName][label]; MonTabWidget *tabWidget = new MonTabWidget(this); - stack->insertWidget(labelIndex, tabWidget); + stack->insertWidget(labelIndex++, tabWidget); int tabIndex = 0; for (EncounterField monField : project->wildMonFields) { @@ -569,7 +572,7 @@ void Editor::saveEncounterTabData() { if (!stack->count()) return; - QMap &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)); @@ -604,8 +607,10 @@ void Editor::updateEncounterFields(EncounterFields newFields) { if (oldFieldName == newFieldName) { fieldDeleted = false; if (oldField.encounterRates.size() != newField.encounterRates.size()) { - for (QString map : project->wildMonData.keys()) { - for (QString groupName : project->wildMonData.value(map).keys()) { + for (auto mapPair : project->wildMonData) { + QString map = mapPair.first; + for (auto groupNamePair : project->wildMonData[map]) { + QString groupName = groupNamePair.first; WildPokemonHeader &monHeader = project->wildMonData[map][groupName]; for (QString fieldName : monHeader.wildMons.keys()) { if (fieldName == oldFieldName) { @@ -618,8 +623,10 @@ void Editor::updateEncounterFields(EncounterFields newFields) { } } if (fieldDeleted) { - for (QString map : project->wildMonData.keys()) { - for (QString groupName : project->wildMonData.value(map).keys()) { + for (auto mapPair : project->wildMonData) { + QString map = mapPair.first; + for (auto groupNamePair : project->wildMonData[map]) { + QString groupName = groupNamePair.first; WildPokemonHeader &monHeader = project->wildMonData[map][groupName]; for (QString fieldName : monHeader.wildMons.keys()) { if (fieldName == oldFieldName) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2643004f..0ffa3381 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -29,6 +29,7 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -2137,13 +2138,21 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); } -void MainWindow::on_action_Export_Map_Image_triggered() -{ - if (!this->mapImageExporter) { - this->mapImageExporter = new MapImageExporter(this, this->editor); - connect(this->mapImageExporter, &QObject::destroyed, [=](QObject *) { this->mapImageExporter = nullptr; }); - this->mapImageExporter->setAttribute(Qt::WA_DeleteOnClose); - } +void MainWindow::on_action_Export_Map_Image_triggered() { + showExportMapImageWindow(false); +} + +void MainWindow::on_actionExport_Stitched_Map_Image_triggered() { + showExportMapImageWindow(true); +} + +void MainWindow::showExportMapImageWindow(bool stitchMode) { + if (this->mapImageExporter) + delete this->mapImageExporter; + + this->mapImageExporter = new MapImageExporter(this, this->editor, stitchMode); + connect(this->mapImageExporter, &QObject::destroyed, [=](QObject *) { this->mapImageExporter = nullptr; }); + this->mapImageExporter->setAttribute(Qt::WA_DeleteOnClose); if (!this->mapImageExporter->isVisible()) { this->mapImageExporter->show(); diff --git a/src/project.cpp b/src/project.cpp index 2a69469e..e19f2f33 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -806,13 +806,15 @@ void Project::saveWildMonData() { monHeadersObject["fields"] = fieldsInfoArray; OrderedJson::array encountersArray; - for (QString key : wildMonData.keys()) { - for (QString groupLabel : wildMonData.value(key).keys()) { + for (auto keyPair : wildMonData) { + QString key = keyPair.first; + for (auto grouplLabelPair : wildMonData[key]) { + QString groupLabel = grouplLabelPair.first; OrderedJson::object encounterObject; encounterObject["map"] = key; encounterObject["base_label"] = groupLabel; - WildPokemonHeader encounterHeader = wildMonData.value(key).value(groupLabel); + WildPokemonHeader encounterHeader = wildMonData[key][groupLabel]; for (QString fieldName : encounterHeader.wildMons.keys()) { OrderedJson::object fieldObject; WildMonInfo monInfo = encounterHeader.wildMons.value(fieldName); @@ -1843,7 +1845,7 @@ bool Project::readWildMonData() { } } } - wildMonData[mapConstant].insert(encounter.toObject().value("base_label").toString(), header); + wildMonData[mapConstant].insert({encounter.toObject().value("base_label").toString(), header}); encounterGroupLabels.append(encounter.toObject().value("base_label").toString()); } } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 2cdd45e7..bb4878bd 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -7,13 +7,19 @@ #include #include -MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_) : +#define STITCH_MODE_BORDER_DISTANCE 2 + +MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, bool stitchMode) : QDialog(parent_), ui(new Ui::MapImageExporter) { ui->setupUi(this); this->map = editor_->map; this->editor = editor_; + this->stitchMode = stitchMode; + + this->setWindowTitle(this->stitchMode ? "Export Map Stitch Image" : "Export Map Image"); + this->ui->groupBox_Connections->setVisible(!this->stitchMode); this->ui->comboBox_MapSelection->addItems(*editor->project->mapNames); this->ui->comboBox_MapSelection->setCurrentText(map->name); @@ -28,32 +34,192 @@ MapImageExporter::~MapImageExporter() { } void MapImageExporter::saveImage() { - QString defaultFilepath = QString("%1/%2.png").arg(editor->project->root).arg(map->name); - QString filepath = QFileDialog::getSaveFileName(this, "Export Map Image", defaultFilepath, + QString title = this->stitchMode ? "Export Map Stitch Image" : "Export Map Image"; + QString defaultFilename = this->stitchMode ? QString("Stitch_From_%1").arg(map->name) : map->name; + QString defaultFilepath = QString("%1/%2.png").arg(editor->project->root).arg(defaultFilename); + QString filepath = QFileDialog::getSaveFileName(this, title, defaultFilepath, "Image Files (*.png *.jpg *.bmp)"); if (!filepath.isEmpty()) { - this->preview.save(filepath); + if (this->stitchMode) { + QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this); + progress.setAutoClose(true); + progress.setWindowModality(Qt::WindowModal); + progress.setModal(true); + QPixmap pixmap = this->getStitchedImage(&progress, this->showBorder); + if (progress.wasCanceled()) { + progress.close(); + return; + } + pixmap.save(filepath); + progress.close(); + } else { + this->preview.save(filepath); + } this->close(); } } +struct StitchedMap { + int x; + int y; + Map* map; +}; + +QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool includeBorder) { + // Do a breadth-first search to gather a collection of + // all reachable maps with their relative offsets. + QSet visited; + QList stitchedMaps; + QList unvisited; + unvisited.append(StitchedMap{0, 0, this->editor->map}); + + progress->setLabelText("Gathering stitched maps..."); + while (!unvisited.isEmpty()) { + if (progress->wasCanceled()) { + return QPixmap(); + } + progress->setMaximum(visited.size() + unvisited.size()); + progress->setValue(visited.size()); + + StitchedMap cur = unvisited.takeFirst(); + if (visited.contains(cur.map->name)) + continue; + visited.insert(cur.map->name); + stitchedMaps.append(cur); + + for (MapConnection *connection : cur.map->connections) { + if (connection->direction == "dive" || connection->direction == "emerge") + continue; + int x = cur.x; + int y = cur.y; + int offset = connection->offset.toInt(nullptr, 0); + Map *connectionMap = this->editor->project->loadMap(connection->map_name); + if (connection->direction == "up") { + x += offset; + y -= connectionMap->getHeight(); + } else if (connection->direction == "down") { + x += offset; + y += cur.map->getHeight(); + } else if (connection->direction == "left") { + x -= connectionMap->getWidth(); + y += offset; + } else if (connection->direction == "right") { + x += cur.map->getWidth(); + y += offset; + } + unvisited.append(StitchedMap{x, y, connectionMap}); + } + } + + // Determine the overall dimensions of the stitched maps. + int maxX = INT_MIN; + int minX = INT_MAX; + int maxY = INT_MIN; + int minY = INT_MAX; + for (StitchedMap map : stitchedMaps) { + int left = map.x; + int right = map.x + map.map->getWidth(); + int top = map.y; + int bottom = map.y + map.map->getHeight(); + if (left < minX) + minX = left; + if (right > maxX) + maxX = right; + if (top < minY) + minY = top; + if (bottom > maxY) + maxY = bottom; + } + + if (includeBorder) { + minX -= STITCH_MODE_BORDER_DISTANCE; + maxX += STITCH_MODE_BORDER_DISTANCE; + minY -= STITCH_MODE_BORDER_DISTANCE; + maxY += STITCH_MODE_BORDER_DISTANCE; + } + + // Draw the maps on the full canvas, while taking + // their respective offsets into account. + progress->setLabelText("Drawing stitched maps..."); + progress->setValue(0); + progress->setMaximum(stitchedMaps.size()); + int numDrawn = 0; + QPixmap stitchedPixmap((maxX - minX) * 16, (maxY - minY) * 16); + QPainter painter(&stitchedPixmap); + for (StitchedMap map : stitchedMaps) { + if (progress->wasCanceled()) { + return QPixmap(); + } + progress->setValue(numDrawn); + numDrawn++; + + int pixelX = (map.x - minX) * 16; + int pixelY = (map.y - minY) * 16; + if (includeBorder) { + pixelX -= STITCH_MODE_BORDER_DISTANCE * 16; + pixelY -= STITCH_MODE_BORDER_DISTANCE * 16; + } + QPixmap pixmap = this->getFormattedMapPixmap(map.map, false); + painter.drawPixmap(pixelX, pixelY, pixmap); + } + + // When including the borders, we simply draw all the maps again + // without their borders, since the first pass results in maps + // being occluded by other map borders. + if (includeBorder) { + progress->setLabelText("Drawing stitched maps without borders..."); + progress->setValue(0); + progress->setMaximum(stitchedMaps.size()); + numDrawn = 0; + for (StitchedMap map : stitchedMaps) { + if (progress->wasCanceled()) { + return QPixmap(); + } + progress->setValue(numDrawn); + numDrawn++; + + int pixelX = (map.x - minX) * 16; + int pixelY = (map.y - minY) * 16; + QPixmap pixmapWithoutBorders = this->getFormattedMapPixmap(map.map, true); + painter.drawPixmap(pixelX, pixelY, pixmapWithoutBorders); + } + } + + return stitchedPixmap; +} + void MapImageExporter::updatePreview() { if (scene) { delete scene; scene = nullptr; } + + preview = getFormattedMapPixmap(this->map, false); scene = new QGraphicsScene; + scene->addPixmap(preview); + this->scene->setSceneRect(this->scene->itemsBoundingRect()); + + this->ui->graphicsView_Preview->setScene(scene); + this->ui->graphicsView_Preview->setFixedSize(scene->itemsBoundingRect().width() + 2, + scene->itemsBoundingRect().height() + 2); +} + +QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { + QPixmap pixmap; // draw background layer / base image + map->render(true); if (showCollision) { - preview = map->collision_pixmap; + map->renderCollision(editor->collisionOpacity, true); + pixmap = map->collision_pixmap; } else { - preview = map->pixmap; + pixmap = map->pixmap; } // draw events - QPainter eventPainter(&preview); + QPainter eventPainter(&pixmap); QList events = map->getAllEvents(); + editor->project->loadEventPixmaps(events); for (Event *event : events) { QString group = event->get("event_group_type"); if ((showObjects && group == "object_event_group") @@ -69,31 +235,40 @@ void MapImageExporter::updatePreview() { // note: this will break when allowing map to be selected from drop down maybe int borderHeight = 0, borderWidth = 0; bool forceDrawBorder = showUpConnections || showDownConnections || showLeftConnections || showRightConnections; - if (showBorder || forceDrawBorder) { - borderHeight = BORDER_DISTANCE * 16, borderWidth = BORDER_DISTANCE * 16; - QPixmap newPreview = QPixmap(map->pixmap.width() + borderWidth * 2, map->pixmap.height() + borderHeight * 2); - QPainter borderPainter(&newPreview); - for (auto borderItem : editor->borderItems) { - borderPainter.drawImage(QPoint(borderItem->x() + borderWidth, borderItem->y() + borderHeight), - borderItem->pixmap().toImage()); + if (!ignoreBorder && (showBorder || forceDrawBorder)) { + int borderDistance = this->stitchMode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; + map->renderBorder(); + int borderHorzDist = editor->getBorderDrawDistance(map->getBorderWidth()); + int borderVertDist = editor->getBorderDrawDistance(map->getBorderHeight()); + borderWidth = borderDistance * 16; + borderHeight = borderDistance * 16; + QPixmap newPixmap = QPixmap(map->pixmap.width() + borderWidth * 2, map->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); + } } - borderPainter.drawImage(QPoint(borderWidth, borderHeight), preview.toImage()); + + borderPainter.drawImage(borderWidth, borderHeight, pixmap.toImage()); borderPainter.end(); - preview = newPreview; + pixmap = newPixmap; } - // if showing connections, draw on outside of image - QPainter connectionPainter(&preview); - for (auto connectionItem : editor->connection_edit_items) { - QString direction = connectionItem->connection->direction; - if ((showUpConnections && direction == "up") - || (showDownConnections && direction == "down") - || (showLeftConnections && direction == "left") - || (showRightConnections && direction == "right")) - connectionPainter.drawImage(connectionItem->initialX + borderWidth, connectionItem->initialY + borderHeight, - connectionItem->basePixmap.toImage()); + if (!this->stitchMode) { + // if showing connections, draw on outside of image + QPainter connectionPainter(&pixmap); + for (auto connectionItem : editor->connection_edit_items) { + QString direction = connectionItem->connection->direction; + if ((showUpConnections && direction == "up") + || (showDownConnections && direction == "down") + || (showLeftConnections && direction == "left") + || (showRightConnections && direction == "right")) + connectionPainter.drawImage(connectionItem->initialX + borderWidth, connectionItem->initialY + borderHeight, + connectionItem->basePixmap.toImage()); + } + connectionPainter.end(); } - connectionPainter.end(); // draw grid directly onto the pixmap // since the last grid lines are outside of the pixmap, add a pixel to the bottom and right @@ -102,24 +277,20 @@ void MapImageExporter::updatePreview() { if (borderHeight) addY = 0; if (borderWidth) addX = 0; - QPixmap newPreview = QPixmap(preview.width() + addX, preview.height() + addY); - QPainter gridPainter(&newPreview); - gridPainter.drawImage(QPoint(0, 0), preview.toImage()); - for (auto lineItem : editor->gridLines) { - QPointF addPos(borderWidth, borderHeight); - gridPainter.drawLine(lineItem->line().p1() + addPos, lineItem->line().p2() + addPos); + QPixmap newPixmap= QPixmap(pixmap.width() + addX, pixmap.height() + addY); + QPainter gridPainter(&newPixmap); + gridPainter.drawImage(QPoint(0, 0), pixmap.toImage()); + for (int x = 0; x < newPixmap.width(); x += 16) { + gridPainter.drawLine(x, 0, x, newPixmap.height()); + } + for (int y = 0; y < newPixmap.height(); y += 16) { + gridPainter.drawLine(0, y, newPixmap.width(), y); } gridPainter.end(); - preview = newPreview; + pixmap = newPixmap; } - scene->addPixmap(preview); - - this->scene->setSceneRect(this->scene->itemsBoundingRect()); - - this->ui->graphicsView_Preview->setScene(scene); - this->ui->graphicsView_Preview->setFixedSize(scene->itemsBoundingRect().width() + 2, - scene->itemsBoundingRect().height() + 2); + return pixmap; } void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) { diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index a2d5f3f4..0639fab3 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -11,7 +11,7 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : this->setFixedSize(this->width(), this->height()); this->project = project; //only allow characters valid for a symbol - QRegExp expression("[-_.A-Za-z0-9]+$"); + QRegExp expression("[_A-Za-z0-9]+$"); QRegExpValidator *validator = new QRegExpValidator(expression); this->ui->nameLineEdit->setValidator(validator);