From 9c07533d1bdbb0622284b577c0f924811ee6b40b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 5 Nov 2024 14:40:41 -0500 Subject: [PATCH 01/11] Display main window maximized when first shown --- src/mainwindow.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fadf3d60..49b723fd 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -506,13 +506,20 @@ void MainWindow::loadUserSettings() { } void MainWindow::restoreWindowState() { - logInfo("Restoring main window geometry from previous session."); QMap geometry = porymapConfig.getMainGeometry(); - this->restoreGeometry(geometry.value("main_window_geometry")); - this->restoreState(geometry.value("main_window_state")); - this->ui->splitter_map->restoreState(geometry.value("map_splitter_state")); - this->ui->splitter_main->restoreState(geometry.value("main_splitter_state")); - this->ui->splitter_Metatiles->restoreState(geometry.value("metatiles_splitter_state")); + const QByteArray mainWindowGeometry = geometry.value("main_window_geometry"); + if (mainWindowGeometry.isEmpty()) { + // If there's no saved geometry for the main window (i.e., first show) then we show it maximized. + // This simplifies the problem of picking a good window size depending on the screen. + setWindowState(Qt::WindowMaximized | Qt::WindowActive); + } else { + logInfo("Restoring main window geometry from previous session."); + restoreGeometry(mainWindowGeometry); + restoreState(geometry.value("main_window_state")); + ui->splitter_map->restoreState(geometry.value("map_splitter_state")); + ui->splitter_main->restoreState(geometry.value("main_splitter_state")); + ui->splitter_Metatiles->restoreState(geometry.value("metatiles_splitter_state")); + } } void MainWindow::setTheme(QString theme) { From 69482904ccb4a4f67d07b697226d00424ee12b2f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 3 May 2025 17:43:13 -0400 Subject: [PATCH 02/11] Add log display to status bar, keep log file open --- include/log.h | 13 +-- resources/icons/error.ico | Bin 0 -> 5430 bytes resources/icons/information.ico | Bin 0 -> 5430 bytes resources/icons/warning.ico | Bin 0 -> 5430 bytes resources/images.qrc | 3 + src/log.cpp | 144 +++++++++++++++++++++++++------- src/mainwindow.cpp | 7 +- 7 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 resources/icons/error.ico create mode 100644 resources/icons/information.ico create mode 100644 resources/icons/warning.ico diff --git a/include/log.h b/include/log.h index 37571dae..abe85d35 100644 --- a/include/log.h +++ b/include/log.h @@ -8,6 +8,7 @@ #include #include #include +#include enum LogType { LOG_ERROR, @@ -15,12 +16,14 @@ enum LogType { LOG_INFO, }; -void logInfo(QString message); -void logWarn(QString message); -void logError(QString message); -void log(QString message, LogType type); +void logInit(); +void logInfo(const QString &message); +void logWarn(const QString &message); +void logError(const QString &message); +void log(const QString &message, LogType type); QString getLogPath(); QString getMostRecentError(); -bool cleanupLargeLog(); +void addLogStatusBar(QStatusBar *statusBar, const QSet &types = {}); +bool removeLogStatusBar(QStatusBar *statusBar); #endif // LOG_H diff --git a/resources/icons/error.ico b/resources/icons/error.ico new file mode 100644 index 0000000000000000000000000000000000000000..8b3611acf27757ddee52e85a31fce845b37d7fd3 GIT binary patch literal 5430 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 literal 0 HcmV?d00001 diff --git a/resources/icons/information.ico b/resources/icons/information.ico new file mode 100644 index 0000000000000000000000000000000000000000..e93d00a923f24ab3ce1c59458468cb46e7b3b6ce GIT binary patch literal 5430 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 S%2f^3vNcs(8q$&`wf_fbmmg{X literal 0 HcmV?d00001 diff --git a/resources/images.qrc b/resources/images.qrc index 41789a6e..1136caf1 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -5,6 +5,7 @@ icons/collapse_all.ico icons/cursor.ico icons/delete.ico + icons/error.ico icons/expand_all.ico icons/file_add.ico icons/file_edit.ico @@ -20,6 +21,7 @@ icons/folder_map_opened.ico icons/folder_map.ico icons/folder.ico + icons/information.ico icons/lock_edit.ico icons/unlock_edit.ico icons/help.ico @@ -45,6 +47,7 @@ icons/sort_map.ico icons/sort_number.ico icons/tall_grass.ico + icons/warning.ico icons/minimap.ico icons/viewsprites.ico icons/application_form_edit.ico diff --git a/src/log.cpp b/src/log.cpp index 64f6753a..6d7d61d1 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -3,6 +3,23 @@ #include #include #include +#include +#include + +namespace Log { + static QString mostRecentError; + static QString path; + static QFile file; + static QTextStream textStream; + + struct Display { + QPointer statusBar; + QPointer message; + QPointer icon; + QSet types; + }; + static QList displays; +}; // Enabling this does not seem to be simple to color console output // on Windows for all CLIs without external libraries or extreme bloat. @@ -18,22 +35,20 @@ #define CLEAR_COLOR "\033[0m" #endif -void logInfo(QString message) { +void logInfo(const QString &message) { log(message, LogType::LOG_INFO); } -void logWarn(QString message) { +void logWarn(const QString &message) { log(message, LogType::LOG_WARN); } -static QString mostRecentError; - -void logError(QString message) { - mostRecentError = message; +void logError(const QString &message) { + Log::mostRecentError = message; log(message, LogType::LOG_ERROR); } -QString colorizeMessage(QString message, LogType type) { +QString colorizeMessage(const QString &message, LogType type) { QString colorized = message; switch (type) { @@ -50,50 +65,115 @@ QString colorizeMessage(QString message, LogType type) { return colorized; } -void log(QString message, LogType type) { +void addLogStatusBar(QStatusBar *statusBar, const QSet &types) { + if (!statusBar) return; + + static const QSet allTypes = {LOG_ERROR, LOG_WARN, LOG_INFO}; + + Log::Display display = { + .statusBar = statusBar, + .message = new QLabel(statusBar), + .icon = new QLabel(statusBar), + .types = types.isEmpty() ? allTypes : types, + }; + statusBar->addWidget(display.icon); + statusBar->addWidget(display.message); + Log::displays.append(display); +} + +void removeLogStatusBar(int index) { + Log::Display display = Log::displays.takeAt(index); + display.statusBar->removeWidget(display.icon); + display.statusBar->removeWidget(display.message); + delete display.icon; + delete display.message; +} + +bool removeLogStatusBar(QStatusBar *statusBar) { + if (!statusBar) return false; + + for (int i = 0; i < Log::displays.length(); i++) { + if (Log::displays.at(i).statusBar == statusBar) { + removeLogStatusBar(i); + return true; + } + } + return false; +} + +void updateLogDisplays(const QString &message, LogType type) { + static const QMap icons = { + {LogType::LOG_INFO, QPixmap(QStringLiteral(":/icons/information.ico"))}, + {LogType::LOG_WARN, QPixmap(QStringLiteral(":/icons/warning.ico"))}, + {LogType::LOG_ERROR, QPixmap(QStringLiteral(":/icons/error.ico"))}, + }; + + auto it = QMutableListIterator(Log::displays); + while (it.hasNext()) { + auto display = it.next(); + if (!display.statusBar) { + // Status bar was deleted externally, remove entry from the list. + it.remove(); + continue; + } + // Update the display, but only if it accepts this message type. + if (display.types.contains(type)) { + display.icon->setPixmap(icons.value(type)); + display.statusBar->clearMessage(); + display.message->setText(message); + } + } +} + +void log(const QString &message, LogType type) { QString now = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); QString typeString = ""; switch (type) { case LogType::LOG_INFO: - typeString = " [INFO]"; + typeString = QStringLiteral(" [INFO]"); break; case LogType::LOG_WARN: - typeString = " [WARN]"; + typeString = QStringLiteral(" [WARN]"); break; case LogType::LOG_ERROR: - typeString = "[ERROR]"; + typeString = QStringLiteral("[ERROR]"); break; } - message = QString("%1 %2 %3").arg(now).arg(typeString).arg(message); + updateLogDisplays(message, type); - qDebug().noquote() << colorizeMessage(message, type); - QFile outFile(getLogPath()); - outFile.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFile); - ts << message << Qt::endl; + QString fullMessage = QString("%1 %2 %3").arg(now).arg(typeString).arg(message); + + qDebug().noquote() << colorizeMessage(fullMessage, type); + + Log::textStream << fullMessage << Qt::endl; + Log::file.flush(); } QString getLogPath() { + return Log::path; +} + +QString getMostRecentError() { + return Log::mostRecentError; +} + +bool cleanupLargeLog() { + return Log::file.size() >= 20000000 && Log::file.resize(0); +} + +void logInit() { QString settingsPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir dir(settingsPath); if (!dir.exists()) dir.mkpath(settingsPath); - return dir.absoluteFilePath("porymap.log"); -} + Log::path = dir.absoluteFilePath(QStringLiteral("porymap.log")); + Log::file.setFileName(Log::path); + Log::file.open(QIODevice::WriteOnly | QIODevice::Append); + Log::textStream.setDevice(&Log::file); -QString getMostRecentError() { - return mostRecentError; -} - -bool cleanupLargeLog() { - QFile logFile(getLogPath()); - if (logFile.size() < 20000000) - return false; - - bool removed = logFile.remove(); - if (removed) - logWarn(QString("Previous log file %1 was cleared due to being over 20MB in size.").arg(getLogPath())); - return removed; + if (cleanupLargeLog()) { + logWarn(QString("Previous log file %1 was cleared due to being over 20MB in size.").arg(Log::path)); + } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e422cfc8..39432d19 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -74,7 +74,8 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); - cleanupLargeLog(); + logInit(); + addLogStatusBar(this->statusBar(), {LOG_ERROR, LOG_WARN}); logInfo(QString("Launching Porymap v%1").arg(QCoreApplication::applicationVersion())); } @@ -635,7 +636,6 @@ bool MainWindow::openProject(QString dir, bool initial) { if (!QDir(dir).exists()) { const QString errorMsg = QString("Failed to open %1: No such directory").arg(projectString); - this->statusBar()->showMessage(errorMsg); if (initial) { // Graceful startup if recent project directory is missing logWarn(errorMsg); @@ -654,7 +654,6 @@ bool MainWindow::openProject(QString dir, bool initial) { } const QString openMessage = QString("Opening %1").arg(projectString); - this->statusBar()->showMessage(openMessage); logInfo(openMessage); porysplash->start(); @@ -692,7 +691,6 @@ bool MainWindow::openProject(QString dir, bool initial) { // Load the project if (!(loadProjectData() && setProjectUI() && setInitialMap())) { - this->statusBar()->showMessage(QString("Failed to open %1").arg(projectString)); showProjectOpenFailure(); delete this->editor->project; // TODO: Allow changing project settings at this point @@ -704,7 +702,6 @@ bool MainWindow::openProject(QString dir, bool initial) { this->editor->project->saveConfig(); updateWindowTitle(); - this->statusBar()->showMessage(QString("Opened %1").arg(projectString)); porymapConfig.projectManuallyClosed = false; porymapConfig.addRecentProject(dir); From 8ab1359d436ec6fbb3998afcff344a94e1de3956 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 May 2025 03:04:54 -0400 Subject: [PATCH 03/11] Auto-hide warnings/errors after 5 seconds --- src/log.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/log.cpp b/src/log.cpp index 6d7d61d1..a27f46f1 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace Log { static QString mostRecentError; @@ -16,9 +17,10 @@ namespace Log { QPointer statusBar; QPointer message; QPointer icon; - QSet types; + QSet acceptedTypes; }; static QList displays; + static QTimer displayClearTimer; }; // Enabling this does not seem to be simple to color console output @@ -65,7 +67,7 @@ QString colorizeMessage(const QString &message, LogType type) { return colorized; } -void addLogStatusBar(QStatusBar *statusBar, const QSet &types) { +void addLogStatusBar(QStatusBar *statusBar, const QSet &acceptedTypes) { if (!statusBar) return; static const QSet allTypes = {LOG_ERROR, LOG_WARN, LOG_INFO}; @@ -74,7 +76,7 @@ void addLogStatusBar(QStatusBar *statusBar, const QSet &types) { .statusBar = statusBar, .message = new QLabel(statusBar), .icon = new QLabel(statusBar), - .types = types.isEmpty() ? allTypes : types, + .acceptedTypes = acceptedTypes.isEmpty() ? allTypes : acceptedTypes, }; statusBar->addWidget(display.icon); statusBar->addWidget(display.message); @@ -117,12 +119,22 @@ void updateLogDisplays(const QString &message, LogType type) { continue; } // Update the display, but only if it accepts this message type. - if (display.types.contains(type)) { + if (display.acceptedTypes.contains(type)) { display.icon->setPixmap(icons.value(type)); display.statusBar->clearMessage(); display.message->setText(message); } } + + // Auto-hide status bar messages after a set period of time + Log::displayClearTimer.start(5000); +} + +void clearLogDisplays() { + for (const auto &display : Log::displays) { + display.icon->setPixmap(QPixmap()); + display.message->setText(QString()); + } } void log(const QString &message, LogType type) { @@ -173,6 +185,10 @@ void logInit() { Log::file.open(QIODevice::WriteOnly | QIODevice::Append); Log::textStream.setDevice(&Log::file); + QObject::connect(&Log::displayClearTimer, &QTimer::timeout, [=] { + clearLogDisplays(); + }); + if (cleanupLargeLog()) { logWarn(QString("Previous log file %1 was cleared due to being over 20MB in size.").arg(Log::path)); } From eaceb4559271667284a49fb8a2d8bb4fee4d17c7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 May 2025 22:14:30 -0400 Subject: [PATCH 04/11] Add settings to enable/disable status bar logging --- forms/preferenceeditor.ui | 37 +++++++++++++++++++++++++++++++++++++ include/config.h | 4 ++++ include/mainwindow.h | 1 + src/config.cpp | 13 +++++++++++++ src/log.cpp | 4 +++- src/mainwindow.cpp | 11 ++++++++++- src/ui/preferenceeditor.cpp | 11 +++++++++++ 7 files changed, 79 insertions(+), 2 deletions(-) diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index 83de6f18..0fcaf17e 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -80,6 +80,43 @@ + + + + Logging + + + + + + Status bar message types + + + + + + + Errors + + + + + + + Warnings + + + + + + + Information + + + + + + diff --git a/include/config.h b/include/config.h index 2e99dc3e..d261beb6 100644 --- a/include/config.h +++ b/include/config.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "events.h" #include "gridsettings.h" @@ -96,6 +97,7 @@ public: this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; this->shownInGameReloadMessage = false; this->gridSettings = GridSettings(); + this->statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN }; } void addRecentProject(QString project); void setRecentProjects(QStringList projects); @@ -161,6 +163,8 @@ public: QByteArray newLayoutDialogGeometry; bool shownInGameReloadMessage; GridSettings gridSettings; + // Prefer over QSet to prevent shuffling elements when writing the config file. + std::set statusBarLogTypes; protected: virtual QString getConfigFilepath() override; diff --git a/include/mainwindow.h b/include/mainwindow.h index 5de2a50a..016aefc9 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -398,6 +398,7 @@ private: void updateWindowTitle(); void initWindow(); + void initLogStatusBar(); void initCustomUI(); void initExtraSignals(); void initEditor(); diff --git a/src/config.cpp b/src/config.cpp index 7914cdce..669136c0 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -476,6 +476,13 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->gridSettings.style = GridSettings::getStyleFromName(value); } else if (key == "grid_color") { this->gridSettings.color = getConfigColor(key, value); + } else if (key == "status_bar_log_types") { + this->statusBarLogTypes.clear(); + auto typeStrings = value.split(",", Qt::SkipEmptyParts); + for (const auto &typeString : typeStrings) { + LogType type = static_cast(getConfigInteger(key, typeString, 0, 2)); + this->statusBarLogTypes.insert(type); + } } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -559,6 +566,12 @@ QMap PorymapConfig::getKeyValueMap() { 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. + + QStringList logTypesStrings; + for (const auto &type : this->statusBarLogTypes) { + logTypesStrings.append(QString::number(type)); + } + map.insert("status_bar_log_types", logTypesStrings.join(",")); return map; } diff --git a/src/log.cpp b/src/log.cpp index a27f46f1..75847372 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -110,6 +110,7 @@ void updateLogDisplays(const QString &message, LogType type) { {LogType::LOG_ERROR, QPixmap(QStringLiteral(":/icons/error.ico"))}, }; + bool startTimer = false; auto it = QMutableListIterator(Log::displays); while (it.hasNext()) { auto display = it.next(); @@ -123,11 +124,12 @@ void updateLogDisplays(const QString &message, LogType type) { display.icon->setPixmap(icons.value(type)); display.statusBar->clearMessage(); display.message->setText(message); + startTimer = true; } } // Auto-hide status bar messages after a set period of time - Log::displayClearTimer.start(5000); + if (startTimer) Log::displayClearTimer.start(5000); } void clearLogDisplays() { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7dc75114..0f05a966 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -75,7 +75,6 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); logInit(); - addLogStatusBar(this->statusBar(), {LOG_ERROR, LOG_WARN}); logInfo(QString("Launching Porymap v%1").arg(QCoreApplication::applicationVersion())); } @@ -144,6 +143,7 @@ void MainWindow::setWindowDisabled(bool disabled) { void MainWindow::initWindow() { porymapConfig.load(); + this->initLogStatusBar(); this->initCustomUI(); this->initExtraSignals(); this->initEditor(); @@ -239,6 +239,14 @@ void MainWindow::applyUserShortcuts() { shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut)); } +void MainWindow::initLogStatusBar() { + removeLogStatusBar(this->statusBar()); + QSet logTypes = QSet(porymapConfig.statusBarLogTypes.begin(), porymapConfig.statusBarLogTypes.end()); + if (!logTypes.isEmpty()) { + addLogStatusBar(this->statusBar(), logTypes); + } +} + void MainWindow::initCustomUI() { static const QMap mainTabNames = { {MainTab::Map, "Map"}, @@ -2921,6 +2929,7 @@ 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); + connect(preferenceEditor, &PreferenceEditor::preferencesSaved, this, &MainWindow::initLogStatusBar); // 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); diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index 1289fe79..291c14ab 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -57,6 +57,11 @@ void PreferenceEditor::updateFields() { ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates); ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled); ui->checkBox_AutocompleteAllScripts->setChecked(porymapConfig.loadAllEventScripts); + + auto logTypeEnd = porymapConfig.statusBarLogTypes.end(); + ui->checkBox_StatusErrors->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_ERROR) != logTypeEnd); + ui->checkBox_StatusWarnings->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_WARN) != logTypeEnd); + ui->checkBox_StatusInformation->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_INFO) != logTypeEnd); } void PreferenceEditor::saveFields() { @@ -77,6 +82,12 @@ void PreferenceEditor::saveFields() { porymapConfig.reopenOnLaunch = ui->checkBox_OpenRecentProject->isChecked(); porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked(); porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked(); + + porymapConfig.statusBarLogTypes.clear(); + if (ui->checkBox_StatusErrors->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_ERROR); + if (ui->checkBox_StatusWarnings->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_WARN); + if (ui->checkBox_StatusInformation->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_INFO); + porymapConfig.save(); emit preferencesSaved(); From ac8a79a7cc412c6509ce189e8bcbf255b2b4358d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 May 2025 23:25:50 -0400 Subject: [PATCH 05/11] Take screen boundaries into account --- src/mainwindow.cpp | 22 +++++++++++++++++----- src/ui/colorpicker.cpp | 6 +++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eb7efde9..f19fda3d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -602,11 +602,7 @@ void MainWindow::loadUserSettings() { void MainWindow::restoreWindowState() { QMap geometry = porymapConfig.getMainGeometry(); const QByteArray mainWindowGeometry = geometry.value("main_window_geometry"); - if (mainWindowGeometry.isEmpty()) { - // If there's no saved geometry for the main window (i.e., first show) then we show it maximized. - // This simplifies the problem of picking a good window size depending on the screen. - setWindowState(Qt::WindowMaximized | Qt::WindowActive); - } else { + if (!mainWindowGeometry.isEmpty()) { logInfo("Restoring main window geometry from previous session."); restoreGeometry(mainWindowGeometry); restoreState(geometry.value("main_window_state")); @@ -614,6 +610,22 @@ void MainWindow::restoreWindowState() { ui->splitter_main->restoreState(geometry.value("main_splitter_state")); ui->splitter_Metatiles->restoreState(geometry.value("metatiles_splitter_state")); } + + // Resize the window if it exceeds the available screen size. + auto screen = windowHandle() ? windowHandle()->screen() : QGuiApplication::primaryScreen(); + if (!screen) return; + const QRect screenGeometry = screen->availableGeometry(); + if (this->width() > screenGeometry.width() || this->height() > screenGeometry.height()) { + auto pixelRatio = screen->devicePixelRatio(); + logInfo(QString("Resizing main window. Dimensions of %1x%2 exceed available screen size of %3x%4") + .arg(qRound(this->width() * pixelRatio)) + .arg(qRound(this->height() * pixelRatio)) + .arg(qRound(screenGeometry.width() * pixelRatio)) + .arg(qRound(screenGeometry.height() * pixelRatio))); + resize(qMin(this->width(), screenGeometry.width()), + qMin(this->height(), screenGeometry.height())); + move(screenGeometry.center() - this->rect().center()); + } } void MainWindow::setTheme(QString theme) { diff --git a/src/ui/colorpicker.cpp b/src/ui/colorpicker.cpp index 5d2d3992..6967c59a 100644 --- a/src/ui/colorpicker.cpp +++ b/src/ui/colorpicker.cpp @@ -43,11 +43,11 @@ ColorPicker::~ColorPicker() void ColorPicker::hover(const QPoint &pos) { QScreen *screen = QGuiApplication::screenAt(pos); if (!screen) { + // Try the screen the color picker is on, or the primary screen. const QWindow *window = windowHandle(); - if (window) screen = window->screen(); + screen = (window && window->screen()) ? window->screen() : QGuiApplication::primaryScreen(); + if (!screen) return; } - if (!screen) - return; // 15 X 15 box with 8x magnification = 120px square) QPixmap grab = screen->grabWindow(0, pos.x() - zoom_box_dimensions / 2, pos.y() - zoom_box_dimensions / 2, zoom_box_dimensions, zoom_box_dimensions); From c0fb6022f5c8e87241630ac2ef14bc961d2704f0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 8 May 2025 19:41:34 -0400 Subject: [PATCH 06/11] Add tool tip for disabled tabs --- src/mainwindow.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cd01f485..626ff912 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1038,12 +1038,20 @@ bool MainWindow::setMap(QString map_name) { // 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 && editor->project->wildEncountersLoaded); + ui->mainTabBar->setTabEnabled(MainTab::Events, mapEditingEnabled); + ui->mainTabBar->setTabEnabled(MainTab::Header, mapEditingEnabled); + ui->mainTabBar->setTabEnabled(MainTab::Connections, mapEditingEnabled); + ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled && this->editor->project->wildEncountersLoaded); - this->ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled); + // Set a tool tip to explain why the tabs are disabled. + static const QString disabledToolTip = Util::toHtmlParagraph("You are in layout-only mode. This tab is only enabled when a map is open."); + QString toolTip = mapEditingEnabled ? QString() : disabledToolTip; + ui->mainTabBar->setTabToolTip(MainTab::Events, toolTip); + ui->mainTabBar->setTabToolTip(MainTab::Header, toolTip); + ui->mainTabBar->setTabToolTip(MainTab::Connections, toolTip); + ui->mainTabBar->setTabToolTip(MainTab::WildPokemon, this->editor->project->wildEncountersLoaded ? toolTip : QString()); + + ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled); } // setLayout, but with a visible error message in case of failure. From 56722f7359926617f19ab6fe54707a8eced8f673 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 8 May 2025 19:50:41 -0400 Subject: [PATCH 07/11] Don't auto-expand Layout folders when searching map list --- include/ui/maplisttoolbar.h | 4 ++++ src/mainwindow.cpp | 4 ++++ src/ui/maplisttoolbar.cpp | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h index 655ce414..77737db6 100644 --- a/include/ui/maplisttoolbar.h +++ b/include/ui/maplisttoolbar.h @@ -37,6 +37,9 @@ public: void setFilterLocked(bool locked) { m_filterLocked = locked; } bool isFilterLocked() const { return m_filterLocked; } + void setExpandListForSearch(bool expand) { m_expandListForSearch = expand; } + bool getExpandListForSearch() const { return m_expandListForSearch; } + void setSearchFocus(); signals: @@ -51,6 +54,7 @@ private: bool m_filterLocked = false; bool m_editsAllowed = false; bool m_emptyFoldersVisible = true; + bool m_expandListForSearch = true; }; #endif // MAPLISTTOOLBAR_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 626ff912..8ad5081c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -470,6 +470,10 @@ void MainWindow::initMapList() { ui->mapListToolBar_Locations->setEditsAllowedButtonVisible(false); ui->mapListToolBar_Layouts->setEditsAllowedButtonVisible(false); + // When searching the Layouts list, don't expand to show all the maps. If the user is searching + // this list then they're probably more interested in the layouts than the maps. + ui->mapListToolBar_Layouts->setExpandListForSearch(false); + // Initialize settings from config ui->mapListToolBar_Groups->setEditsAllowed(porymapConfig.mapListEditGroupsEnabled); for (auto i = porymapConfig.mapListHideEmptyEnabled.constBegin(); i != porymapConfig.mapListHideEmptyEnabled.constEnd(); i++) { diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index f56be39b..7ef3213c 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -138,7 +138,7 @@ void MapListToolBar::applyFilter(const QString &filterText) { if (filterText.isEmpty()) { m_list->collapseAll(); emit filterCleared(m_list); - } else { + } else if (m_expandListForSearch) { m_list->expandToDepth(0); } } From 96b90dc5e106717d9871e0e3ac88d76ddcd5aa34 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 8 May 2025 20:43:52 -0400 Subject: [PATCH 08/11] Add 'Open in JSON' actions --- include/core/map.h | 5 ++++- include/core/parseutil.h | 2 ++ include/editor.h | 2 ++ include/project.h | 2 +- src/core/map.cpp | 15 +++++++++++---- src/core/parseutil.cpp | 22 +++++++++++++++++----- src/editor.cpp | 16 +++++++++++++--- src/mainwindow.cpp | 6 ++++++ src/project.cpp | 14 +++++++++----- 9 files changed, 65 insertions(+), 19 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 2ba172ce..6fd34a32 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -82,9 +82,12 @@ public: bool hasEvent(Event *) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); - QString getScriptsFilePath() const; + QString getScriptsFilepath() const; void openScript(const QString &label); + static QString getJsonFilepath(const QString &mapName); + QString getJsonFilepath() const { return getJsonFilepath(m_name); } + void deleteConnections(); QList getConnections() const { return m_connections; } MapConnection* getConnection(const QString &direction) const; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 2edb9f78..c8ed3eb6 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -73,6 +73,8 @@ public: bool tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error = nullptr); bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error = nullptr); + static int getJsonLineNumber(const QString &filepath, const QString &searchText); + // 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. static int getScriptLineNumber(const QString &filePath, const QString &scriptLabel); diff --git a/include/editor.h b/include/editor.h index 432084e8..40e0d1c5 100644 --- a/include/editor.h +++ b/include/editor.h @@ -184,6 +184,8 @@ public: void shouldReselectEvents(); void scaleMapView(int); static void openInTextEditor(const QString &path, int lineNum = 0); + void openMapJson(const QString &mapName) const; + void openLayoutJson(const QString &layoutId) const; void setCollisionGraphics(); enum ZValue { diff --git a/include/project.h b/include/project.h index 8b739858..33f29a11 100644 --- a/include/project.h +++ b/include/project.h @@ -218,7 +218,7 @@ public: static QString getScriptFileExtension(bool usePoryScript); QString getScriptDefaultString(bool usePoryScript, QString mapName) const; - QStringList getEventScriptsFilePaths() const; + QStringList getEventScriptsFilepaths() const; void insertGlobalScriptLabels(QStringList &scriptLabels) const; QString getDefaultPrimaryTilesetLabel() const; diff --git a/src/core/map.cpp b/src/core/map.cpp index c1905f0a..0292c7f5 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -142,13 +142,13 @@ void Map::invalidateScripts() { QStringList Map::getScriptLabels(Event::Group group) { if (!m_scriptsLoaded) { - const QString scriptsFilePath = getScriptsFilePath(); - m_scriptLabels = ParseUtil::getGlobalScriptLabels(scriptsFilePath); + 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); + m_scriptFileWatcher->addPath(scriptsFilepath); } QStringList scriptLabels = m_scriptLabels; @@ -167,7 +167,7 @@ QStringList Map::getScriptLabels(Event::Group group) { return scriptLabels; } -QString Map::getScriptsFilePath() const { +QString Map::getScriptsFilepath() const { const bool usePoryscript = projectConfig.usePoryScript; auto path = QDir::cleanPath(QString("%1/%2/%3/scripts") .arg(projectConfig.projectDir) @@ -180,6 +180,13 @@ QString Map::getScriptsFilePath() const { return path; } +QString Map::getJsonFilepath(const QString &mapName) { + return QDir::cleanPath(QString("%1/%2/%3/map.json") + .arg(projectConfig.projectDir) + .arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)) + .arg(mapName)); +} + void Map::resetEvents() { m_events[Event::Group::Object].clear(); m_events[Event::Group::Warp].clear(); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 80252c13..27dc8c0d 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -819,14 +819,26 @@ bool ParseUtil::jsonToBool(const QJsonValue &value, bool * ok) { return false; } -int ParseUtil::getScriptLineNumber(const QString &filePath, const QString &scriptLabel) { +int ParseUtil::getJsonLineNumber(const QString &filepath, const QString &searchText) { + if (searchText.isEmpty()) + return 0; + + const QString text = readTextFile(filepath); + int index = text.indexOf(searchText); + if (index < 0) + return 0; + + return text.left(index).count('\n') + 1; +} + +int ParseUtil::getScriptLineNumber(const QString &filepath, const QString &scriptLabel) { if (scriptLabel.isEmpty()) return 0; - if (filePath.endsWith(".inc") || filePath.endsWith(".s")) - return getRawScriptLineNumber(readTextFile(filePath), scriptLabel); - else if (filePath.endsWith(".pory")) - return getPoryScriptLineNumber(readTextFile(filePath), scriptLabel); + if (filepath.endsWith(".inc") || filepath.endsWith(".s")) + return getRawScriptLineNumber(readTextFile(filepath), scriptLabel); + else if (filepath.endsWith(".pory")) + return getPoryScriptLineNumber(readTextFile(filepath), scriptLabel); return 0; } diff --git a/src/editor.cpp b/src/editor.cpp index f5759e35..eb465ff1 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2272,13 +2272,13 @@ void Editor::deleteSelectedEvents() { } void Editor::openMapScripts() const { - openInTextEditor(map->getScriptsFilePath()); + openInTextEditor(map->getScriptsFilepath()); } void Editor::openScript(const QString &scriptLabel) const { // Find the location of scriptLabel. - QStringList scriptPaths(map->getScriptsFilePath()); - scriptPaths << project->getEventScriptsFilePaths(); + QStringList scriptPaths(map->getScriptsFilepath()); + scriptPaths << project->getEventScriptsFilepaths(); int lineNum = 0; QString scriptPath = scriptPaths.first(); for (const auto &path : scriptPaths) { @@ -2292,6 +2292,16 @@ void Editor::openScript(const QString &scriptLabel) const { openInTextEditor(scriptPath, lineNum); } +void Editor::openMapJson(const QString &mapName) const { + openInTextEditor(Map::getJsonFilepath(mapName)); +} + +void Editor::openLayoutJson(const QString &layoutId) const { + QString path = QDir::cleanPath(QString("%1/%2").arg(projectConfig.projectDir).arg(projectConfig.getFilePath(ProjectFilePath::json_layouts))); + QString idField = QString("\"id\": \"%1\",").arg(layoutId); + openInTextEditor(path, ParseUtil::getJsonLineNumber(path, idField)); +} + void Editor::openInTextEditor(const QString &path, int lineNum) { QString command = porymapConfig.textEditorGotoLine; if (command.isEmpty()) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ad5081c..55a42b64 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1415,6 +1415,9 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); + connect(menu.addAction("Open JSON file"), &QAction::triggered, [this, itemName] { + this->editor->openMapJson(itemName); + }); menu.addSeparator(); copyListNameAction = menu.addAction("Copy Map Name"); copyToolTipAction = menu.addAction("Copy Map ID"); @@ -1442,6 +1445,9 @@ 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("Open JSON file"), &QAction::triggered, [this, itemName] { + this->editor->openLayoutJson(itemName); + }); menu.addSeparator(); copyListNameAction = menu.addAction("Copy Layout Name"); copyToolTipAction = menu.addAction("Copy Layout ID"); diff --git a/src/project.cpp b/src/project.cpp index ec2a8a6f..f2eb3281 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -330,7 +330,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); + const QString mapFilepath = Map::getJsonFilepath(mapName); watchFile(mapFilepath); QString error; if (!parser.tryParseJsonFile(out, mapFilepath, &error)) { @@ -757,7 +757,11 @@ bool Project::saveMapLayouts() { } void Project::watchFile(const QString &filename) { - this->fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filename)); + if (!filename.startsWith(this->root)) { + this->fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filename)); + } else { + this->fileWatcher.addPath(filename); + } } void Project::watchFiles(const QStringList &filenames) { @@ -1284,7 +1288,7 @@ bool Project::saveMap(Map *map, bool skipLayout) { } // Create map.json for map data. - QString mapFilepath = fullPath + "/map.json"; + QString mapFilepath = map->getJsonFilepath(); QFile mapFile(mapFilepath); if (!mapFile.open(QIODevice::WriteOnly)) { logError(QString("Could not open '%1' for writing: %2").arg(mapFilepath).arg(mapFile.errorString())); @@ -2930,7 +2934,7 @@ bool Project::readEventScriptLabels() { this->globalScriptLabels.clear(); if (porymapConfig.loadAllEventScripts) { - for (const auto &filePath : getEventScriptsFilePaths()) + for (const auto &filePath : getEventScriptsFilepaths()) this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); this->globalScriptLabels.sort(Qt::CaseInsensitive); @@ -2973,7 +2977,7 @@ QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) con return QString("%1_MapScripts::\n\t.byte 0\n").arg(mapName); } -QStringList Project::getEventScriptsFilePaths() const { +QStringList Project::getEventScriptsFilepaths() const { QStringList filePaths(QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts))); const QString scriptsDir = QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_scripts_folders)); const QString mapsDir = QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_map_folders)); From 55a2af314f331f4fdc1c7b974a8d9128ac1f415e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 8 May 2025 22:34:59 -0400 Subject: [PATCH 09/11] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abdb4d9b..81d3a192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - `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). +- Error and warning logs are now displayed in the status bar. This can be changed with a new setting. - 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. @@ -123,6 +124,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - 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. +- Fix the main window sometimes exceeding the screen size on first launch. ## [5.4.1] - 2024-03-21 ### Fixed From 6d844346454862c8cdc2e70f1ee499129a9a10f6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 8 May 2025 23:35:38 -0400 Subject: [PATCH 10/11] Move some file operations from Tools to File menus --- forms/mainwindow.ui | 14 +++++++------- forms/paletteeditor.ui | 14 +++++++------- forms/tileseteditor.ui | 22 +++++++++++++--------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index c75c8328..dd10e7c0 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2867,6 +2867,13 @@ + + + + + + + @@ -2906,16 +2913,9 @@ - - - - - - - diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui index c1a4ee7a..c98d49b6 100644 --- a/forms/paletteeditor.ui +++ b/forms/paletteeditor.ui @@ -120,6 +120,12 @@ 37 + + + File + + + Edit @@ -127,14 +133,8 @@ - - - Tools - - - + - diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 1ff9d0fb..d1817859 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -633,32 +633,36 @@ - - - - Tools - + - - + + + Tools + + + + Edit + + + - - + + From 74ed019ca4e0cac2a99eeda59499b4a133a0e3e8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 11 May 2025 13:08:19 -0400 Subject: [PATCH 11/11] Condense panel for new map project settings --- forms/projectsettingseditor.ui | 214 +++++++++++++++++---------------- 1 file changed, 112 insertions(+), 102 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index c840d331..f732f335 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -39,7 +39,7 @@ 0 0 570 - 680 + 692 @@ -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 @@ -467,37 +467,16 @@ 0 0 570 - 622 + 552 - + - Map Data Defaults + New Map Defaults - - - - Elevation - - - - - - - Width - - - - - - - <html><head/><body><p>The default elevation that will be used to fill new maps</p></body></html> - - - @@ -508,61 +487,6 @@ - - - - <html><head/><body><p>The default layout width for new maps</p></body></html> - - - 1 - - - - - - - <html><head/><body><p>The default layout height for new maps</p></body></html> - - - 1 - - - - - - - Metatile ID - - - - - - - Collision - - - - - - - Border Metatiles - - - - - - - <html><head/><body><p>The default metatile value that will be used to fill new maps</p></body></html> - - - - - - - <html><head/><body><p>The default collision that will be used to fill new maps</p></body></html> - - - @@ -634,6 +558,51 @@ + + + + + + Width + + + + + + + <html><head/><body><p>The default layout width for new maps</p></body></html> + + + 1 + + + + + + + Height + + + + + + + <html><head/><body><p>The default layout height for new maps</p></body></html> + + + 1 + + + + + + + + + Border Metatiles + + + @@ -649,13 +618,52 @@ 0 + + + + <html><head/><body><p>The default metatile value that will be used to fill new maps</p></body></html> + + + - - + + + + + + Elevation + + + + + + + Collision + + + + + + + <html><head/><body><p>The default collision that will be used to fill new maps</p></body></html> + + + + + + + <html><head/><body><p>The default elevation that will be used to fill new maps</p></body></html> + + + + + + + - Height + Metatile ID @@ -1197,7 +1205,7 @@ 0 0 570 - 927 + 915 @@ -1216,7 +1224,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -1276,7 +1284,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -1308,7 +1316,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -1346,7 +1354,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -1367,7 +1375,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -1378,7 +1386,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -1411,7 +1419,7 @@ ... - + :/icons/delete.ico:/icons/delete.ico @@ -1464,7 +1472,7 @@ ... - + :/icons/add.ico:/icons/add.ico @@ -1674,7 +1682,7 @@ 0 0 544 - 341 + 338 @@ -1703,7 +1711,7 @@ Add Global Constants File... - + :/icons/add.ico:/icons/add.ico @@ -1714,7 +1722,7 @@ ... - + :/icons/help.ico:/icons/help.ico @@ -1754,7 +1762,7 @@ ... - + :/icons/help.ico:/icons/help.ico @@ -1791,7 +1799,7 @@ 0 0 544 - 425 + 421 @@ -1820,7 +1828,7 @@ Add Global Constant... - + :/icons/add.ico:/icons/add.ico @@ -1886,6 +1894,8 @@
uintspinbox.h
- + + +