From 69482904ccb4a4f67d07b697226d00424ee12b2f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 3 May 2025 17:43:13 -0400 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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();