| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- #include "editor_window.h"
- #include "json_edit_dialog.h"
- #include "resize_dialog.h"
- #include <QAction>
- #include <QCloseEvent>
- #include <QFileDialog>
- #include <QFileInfo>
- #include <QHBoxLayout>
- #include <QJsonArray>
- #include <QJsonDocument>
- #include <QJsonObject>
- #include <QMenuBar>
- #include <QMessageBox>
- #include <QSplitter>
- #include <QStatusBar>
- #include <QToolBar>
- #include <QVBoxLayout>
- namespace MapEditor {
- EditorWindow::EditorWindow(QWidget *parent) : QMainWindow(parent) {
- m_mapData = new MapData(this);
- setupUI();
- setupMenus();
- connect(m_mapData, &MapData::modifiedChanged, this,
- &EditorWindow::onModifiedChanged);
- connect(m_mapData, &MapData::undoRedoChanged, this,
- &EditorWindow::onUndoRedoChanged);
- connect(m_mapData, &MapData::dataChanged, this,
- &EditorWindow::updateDimensionsLabel);
- setWindowTitle("Standard of Iron - Map Editor");
- resize(1400, 900);
- newMap();
- }
- EditorWindow::~EditorWindow() = default;
- void EditorWindow::setupUI() {
- auto *centralWidget = new QWidget(this);
- setCentralWidget(centralWidget);
- auto *mainLayout = new QHBoxLayout(centralWidget);
- mainLayout->setContentsMargins(0, 0, 0, 0);
- mainLayout->setSpacing(0);
- m_toolPanel = new ToolPanel(this);
- connect(m_toolPanel, &ToolPanel::toolSelected, this,
- &EditorWindow::onToolSelected);
- m_canvas = new MapCanvas(this);
- m_canvas->setMapData(m_mapData);
- connect(m_canvas, &MapCanvas::elementDoubleClicked, this,
- &EditorWindow::onElementDoubleClicked);
- connect(m_canvas, &MapCanvas::gridDoubleClicked, this,
- &EditorWindow::onGridDoubleClicked);
- connect(m_canvas, &MapCanvas::toolCleared, this,
- &EditorWindow::onToolCleared);
- auto *splitter = new QSplitter(Qt::Horizontal, this);
- splitter->addWidget(m_toolPanel);
- splitter->addWidget(m_canvas);
- splitter->setStretchFactor(0, 0);
- splitter->setStretchFactor(1, 1);
- mainLayout->addWidget(splitter);
- m_statusLabel = new QLabel("Ready", this);
- m_dimensionsLabel = new QLabel("", this);
- m_dimensionsLabel->setToolTip(
- "Double-click on empty canvas area to edit dimensions");
- statusBar()->addWidget(m_statusLabel);
- statusBar()->addPermanentWidget(m_dimensionsLabel);
- }
- void EditorWindow::setupMenus() {
- auto *fileMenu = menuBar()->addMenu("&File");
- auto *newAction = new QAction("&New", this);
- newAction->setShortcut(QKeySequence::New);
- connect(newAction, &QAction::triggered, this, &EditorWindow::newMap);
- fileMenu->addAction(newAction);
- auto *openAction = new QAction("&Open...", this);
- openAction->setShortcut(QKeySequence::Open);
- connect(openAction, &QAction::triggered, this, &EditorWindow::openMap);
- fileMenu->addAction(openAction);
- fileMenu->addSeparator();
- auto *saveAction = new QAction("&Save", this);
- saveAction->setShortcut(QKeySequence::Save);
- connect(saveAction, &QAction::triggered, this, &EditorWindow::saveMap);
- fileMenu->addAction(saveAction);
- auto *saveAsAction = new QAction("Save &As...", this);
- saveAsAction->setShortcut(QKeySequence::SaveAs);
- connect(saveAsAction, &QAction::triggered, this, &EditorWindow::saveMapAs);
- fileMenu->addAction(saveAsAction);
- fileMenu->addSeparator();
- auto *exitAction = new QAction("E&xit", this);
- exitAction->setShortcut(QKeySequence::Quit);
- connect(exitAction, &QAction::triggered, this, &QWidget::close);
- fileMenu->addAction(exitAction);
- auto *editMenu = menuBar()->addMenu("&Edit");
- m_undoAction = new QAction("&Undo", this);
- m_undoAction->setShortcut(QKeySequence::Undo);
- m_undoAction->setEnabled(false);
- connect(m_undoAction, &QAction::triggered, this, &EditorWindow::undo);
- editMenu->addAction(m_undoAction);
- m_redoAction = new QAction("&Redo", this);
- m_redoAction->setShortcut(QKeySequence::Redo);
- m_redoAction->setEnabled(false);
- connect(m_redoAction, &QAction::triggered, this, &EditorWindow::redo);
- editMenu->addAction(m_redoAction);
- editMenu->addSeparator();
- auto *resizeAction = new QAction("&Resize Map...", this);
- connect(resizeAction, &QAction::triggered, this, &EditorWindow::resizeMap);
- editMenu->addAction(resizeAction);
- auto *toolbar = addToolBar("Main");
- toolbar->addAction(newAction);
- toolbar->addAction(openAction);
- toolbar->addAction(saveAction);
- toolbar->addSeparator();
- toolbar->addAction(m_undoAction);
- toolbar->addAction(m_redoAction);
- toolbar->addSeparator();
- toolbar->addAction(resizeAction);
- }
- void EditorWindow::newMap() {
- if (!maybeSave()) {
- return;
- }
- m_mapData->clear();
- m_currentFilePath.clear();
- updateWindowTitle();
- m_statusLabel->setText("New map created");
- }
- void EditorWindow::openMap() {
- if (!maybeSave()) {
- return;
- }
- QString filePath = QFileDialog::getOpenFileName(
- this, "Open Map", QString(), "JSON Files (*.json);;All Files (*)");
- if (filePath.isEmpty()) {
- return;
- }
- if (m_mapData->loadFromJson(filePath)) {
- m_currentFilePath = filePath;
- updateWindowTitle();
- m_statusLabel->setText("Loaded: " + filePath);
- } else {
- QMessageBox::critical(this, "Error",
- "Failed to load map file: " + filePath);
- }
- }
- bool EditorWindow::loadFile(const QString &filePath) {
- if (m_mapData->loadFromJson(filePath)) {
- m_currentFilePath = filePath;
- updateWindowTitle();
- m_statusLabel->setText("Loaded: " + filePath);
- return true;
- }
- QMessageBox::critical(this, "Error", "Failed to load map file: " + filePath);
- return false;
- }
- void EditorWindow::saveMap() {
- if (m_currentFilePath.isEmpty()) {
- saveMapAs();
- } else {
- if (m_mapData->saveToJson(m_currentFilePath)) {
- m_mapData->setModified(false);
- m_statusLabel->setText("Saved: " + m_currentFilePath);
- } else {
- QMessageBox::critical(this, "Error",
- "Failed to save map file: " + m_currentFilePath);
- }
- }
- }
- void EditorWindow::saveMapAs() {
- QString filePath = QFileDialog::getSaveFileName(
- this, "Save Map As", QString(), "JSON Files (*.json);;All Files (*)");
- if (filePath.isEmpty()) {
- return;
- }
- if (!filePath.endsWith(".json", Qt::CaseInsensitive)) {
- filePath += ".json";
- }
- if (m_mapData->saveToJson(filePath)) {
- m_currentFilePath = filePath;
- m_mapData->setModified(false);
- updateWindowTitle();
- m_statusLabel->setText("Saved: " + filePath);
- } else {
- QMessageBox::critical(this, "Error",
- "Failed to save map file: " + filePath);
- }
- }
- void EditorWindow::resizeMap() {
- const GridSettings &grid = m_mapData->grid();
- ResizeDialog dialog(grid.width, grid.height, this);
- if (dialog.exec() == QDialog::Accepted) {
- GridSettings newGrid = grid;
- newGrid.width = dialog.newWidth();
- newGrid.height = dialog.newHeight();
- m_mapData->setGrid(newGrid);
- m_canvas->update();
- m_statusLabel->setText(
- QString("Map resized to %1x%2").arg(newGrid.width).arg(newGrid.height));
- }
- }
- void EditorWindow::undo() {
- m_mapData->undo();
- m_statusLabel->setText("Undo");
- }
- void EditorWindow::redo() {
- m_mapData->redo();
- m_statusLabel->setText("Redo");
- }
- void EditorWindow::onToolSelected(ToolType tool) {
- m_canvas->setCurrentTool(tool);
- QString toolName;
- switch (tool) {
- case ToolType::Select:
- toolName = "Select";
- break;
- case ToolType::Hill:
- toolName = "Hill";
- break;
- case ToolType::Mountain:
- toolName = "Mountain";
- break;
- case ToolType::River:
- toolName = "River (click start, then end)";
- break;
- case ToolType::Road:
- toolName = "Road (click start, then end)";
- break;
- case ToolType::Bridge:
- toolName = "Bridge (click start, then end)";
- break;
- case ToolType::Firecamp:
- toolName = "Firecamp";
- break;
- case ToolType::Barracks:
- toolName = "Barracks (assign to team)";
- break;
- case ToolType::Village:
- toolName = "Village (assign to team)";
- break;
- case ToolType::Eraser:
- toolName = "Eraser";
- break;
- }
- m_statusLabel->setText("Tool: " + toolName);
- }
- void EditorWindow::onToolCleared() {
- m_toolPanel->clearSelection();
- m_statusLabel->setText("Tool: Select");
- }
- void EditorWindow::onGridDoubleClicked() { resizeMap(); }
- void EditorWindow::onUndoRedoChanged() {
- m_undoAction->setEnabled(m_mapData->canUndo());
- m_redoAction->setEnabled(m_mapData->canRedo());
- }
- void EditorWindow::updateDimensionsLabel() {
- const GridSettings &grid = m_mapData->grid();
- m_dimensionsLabel->setText(
- QString("Map: %1 x %2").arg(grid.width).arg(grid.height));
- }
- void EditorWindow::onElementDoubleClicked(int elementType, int index) {
- QJsonObject json;
- QString title;
- if (elementType == 0) {
- const auto &terrain = m_mapData->terrainElements();
- if (index < 0 || index >= terrain.size()) {
- return;
- }
- const auto &elem = terrain[index];
- json["type"] = elem.type;
- json["x"] = static_cast<double>(elem.x);
- json["z"] = static_cast<double>(elem.z);
- json["radius"] = static_cast<double>(elem.radius);
- json["width"] = static_cast<double>(elem.width);
- json["depth"] = static_cast<double>(elem.depth);
- json["height"] = static_cast<double>(elem.height);
- json["rotation"] = static_cast<double>(elem.rotation);
- if (!elem.entrances.isEmpty()) {
- json["entrances"] = elem.entrances;
- }
- for (const QString &key : elem.extraFields.keys()) {
- json[key] = elem.extraFields[key];
- }
- title = "Edit Terrain: " + elem.type;
- } else if (elementType == 1) {
- const auto &firecamps = m_mapData->firecamps();
- if (index < 0 || index >= firecamps.size()) {
- return;
- }
- const auto &elem = firecamps[index];
- json["x"] = static_cast<double>(elem.x);
- json["z"] = static_cast<double>(elem.z);
- json["intensity"] = static_cast<double>(elem.intensity);
- json["radius"] = static_cast<double>(elem.radius);
- for (const QString &key : elem.extraFields.keys()) {
- json[key] = elem.extraFields[key];
- }
- title = "Edit Firecamp";
- } else if (elementType == 2) {
- const auto &linear = m_mapData->linearElements();
- if (index < 0 || index >= linear.size()) {
- return;
- }
- const auto &elem = linear[index];
- json["type"] = elem.type;
- json["start"] = QJsonArray{static_cast<double>(elem.start.x()),
- static_cast<double>(elem.start.y())};
- json["end"] = QJsonArray{static_cast<double>(elem.end.x()),
- static_cast<double>(elem.end.y())};
- json["width"] = static_cast<double>(elem.width);
- if (elem.type == "bridge") {
- json["height"] = static_cast<double>(elem.height);
- }
- if (elem.type == "road" && !elem.style.isEmpty()) {
- json["style"] = elem.style;
- }
- for (const QString &key : elem.extraFields.keys()) {
- json[key] = elem.extraFields[key];
- }
- title = "Edit " + elem.type;
- } else if (elementType == 3) {
- const auto &structures = m_mapData->structures();
- if (index < 0 || index >= structures.size()) {
- return;
- }
- const auto &elem = structures[index];
- json["type"] = elem.type;
- json["x"] = static_cast<double>(elem.x);
- json["z"] = static_cast<double>(elem.z);
- json["playerId"] = elem.playerId;
- json["maxPopulation"] = elem.maxPopulation;
- if (!elem.nation.isEmpty()) {
- json["nation"] = elem.nation;
- }
- for (const QString &key : elem.extraFields.keys()) {
- json[key] = elem.extraFields[key];
- }
- title = "Edit " + elem.type;
- } else {
- return;
- }
- JsonEditDialog dialog(title, json, this);
- if (dialog.exec() == QDialog::Accepted && dialog.isValid()) {
- QJsonObject newJson = dialog.getJson();
- if (elementType == 0) {
- TerrainElement elem;
- elem.type = newJson["type"].toString();
- elem.x = static_cast<float>(newJson["x"].toDouble());
- elem.z = static_cast<float>(newJson["z"].toDouble());
- elem.radius = static_cast<float>(newJson["radius"].toDouble(10.0));
- elem.width = static_cast<float>(newJson["width"].toDouble(0.0));
- elem.depth = static_cast<float>(newJson["depth"].toDouble(0.0));
- elem.height = static_cast<float>(newJson["height"].toDouble(3.0));
- elem.rotation = static_cast<float>(newJson["rotation"].toDouble(0.0));
- elem.entrances = newJson["entrances"].toArray();
- QStringList knownKeys = {"type", "x", "z",
- "radius", "width", "depth",
- "height", "rotation", "entrances"};
- for (const QString &key : newJson.keys()) {
- if (!knownKeys.contains(key)) {
- elem.extraFields[key] = newJson[key];
- }
- }
- m_mapData->updateTerrainElement(index, elem);
- } else if (elementType == 1) {
- FirecampElement elem;
- elem.x = static_cast<float>(newJson["x"].toDouble());
- elem.z = static_cast<float>(newJson["z"].toDouble());
- elem.intensity = static_cast<float>(newJson["intensity"].toDouble(1.0));
- elem.radius = static_cast<float>(newJson["radius"].toDouble(3.0));
- QStringList knownKeys = {"x", "z", "intensity", "radius"};
- for (const QString &key : newJson.keys()) {
- if (!knownKeys.contains(key)) {
- elem.extraFields[key] = newJson[key];
- }
- }
- m_mapData->updateFirecamp(index, elem);
- } else if (elementType == 2) {
- LinearElement elem;
- elem.type = newJson["type"].toString();
- QJsonArray startArr = newJson["start"].toArray();
- QJsonArray endArr = newJson["end"].toArray();
- if (startArr.size() >= 2 && endArr.size() >= 2) {
- elem.start = QVector2D(static_cast<float>(startArr[0].toDouble()),
- static_cast<float>(startArr[1].toDouble()));
- elem.end = QVector2D(static_cast<float>(endArr[0].toDouble()),
- static_cast<float>(endArr[1].toDouble()));
- }
- elem.width = static_cast<float>(newJson["width"].toDouble(3.0));
- elem.height = static_cast<float>(newJson["height"].toDouble(0.5));
- elem.style = newJson["style"].toString("default");
- QStringList knownKeys = {"type", "start", "end",
- "width", "height", "style"};
- for (const QString &key : newJson.keys()) {
- if (!knownKeys.contains(key)) {
- elem.extraFields[key] = newJson[key];
- }
- }
- m_mapData->updateLinearElement(index, elem);
- } else if (elementType == 3) {
- StructureElement elem;
- elem.type = newJson["type"].toString();
- elem.x = static_cast<float>(newJson["x"].toDouble());
- elem.z = static_cast<float>(newJson["z"].toDouble());
- elem.playerId = newJson["playerId"].toInt(0);
- elem.maxPopulation = newJson["maxPopulation"].toInt(150);
- elem.nation = newJson["nation"].toString();
- QStringList knownKeys = {"type", "x", "z", "playerId",
- "maxPopulation", "nation"};
- for (const QString &key : newJson.keys()) {
- if (!knownKeys.contains(key)) {
- elem.extraFields[key] = newJson[key];
- }
- }
- m_mapData->updateStructure(index, elem);
- }
- }
- }
- void EditorWindow::onModifiedChanged(bool modified) {
- Q_UNUSED(modified)
- updateWindowTitle();
- }
- void EditorWindow::updateWindowTitle() {
- QString title = "Standard of Iron - Map Editor";
- if (!m_currentFilePath.isEmpty()) {
- title += " - " + QFileInfo(m_currentFilePath).fileName();
- } else {
- title += " - " + m_mapData->name();
- }
- if (m_mapData->isModified()) {
- title += " *";
- }
- setWindowTitle(title);
- }
- bool EditorWindow::maybeSave() {
- if (!m_mapData->isModified()) {
- return true;
- }
- QMessageBox::StandardButton ret = QMessageBox::warning(
- this, "Unsaved Changes",
- "The map has been modified.\nDo you want to save your changes?",
- QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
- if (ret == QMessageBox::Save) {
- saveMap();
- return !m_mapData->isModified();
- }
- if (ret == QMessageBox::Cancel) {
- return false;
- }
- return true;
- }
- void EditorWindow::closeEvent(QCloseEvent *event) {
- if (maybeSave()) {
- event->accept();
- } else {
- event->ignore();
- }
- }
- } // namespace MapEditor
|