Add log display to status bar, keep log file open

This commit is contained in:
GriffinR 2025-05-03 17:43:13 -04:00
parent a1d264cd47
commit 69482904cc
7 changed files with 125 additions and 42 deletions

View File

@ -8,6 +8,7 @@
#include <QTextStream>
#include <QString>
#include <QDebug>
#include <QStatusBar>
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<LogType> &types = {});
bool removeLogStatusBar(QStatusBar *statusBar);
#endif // LOG_H

BIN
resources/icons/error.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
resources/icons/warning.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -5,6 +5,7 @@
<file>icons/collapse_all.ico</file>
<file>icons/cursor.ico</file>
<file>icons/delete.ico</file>
<file>icons/error.ico</file>
<file>icons/expand_all.ico</file>
<file>icons/file_add.ico</file>
<file>icons/file_edit.ico</file>
@ -20,6 +21,7 @@
<file>icons/folder_map_opened.ico</file>
<file>icons/folder_map.ico</file>
<file>icons/folder.ico</file>
<file>icons/information.ico</file>
<file>icons/lock_edit.ico</file>
<file>icons/unlock_edit.ico</file>
<file>icons/help.ico</file>
@ -45,6 +47,7 @@
<file>icons/sort_map.ico</file>
<file>icons/sort_number.ico</file>
<file>icons/tall_grass.ico</file>
<file>icons/warning.ico</file>
<file>icons/minimap.ico</file>
<file>icons/viewsprites.ico</file>
<file>icons/application_form_edit.ico</file>

View File

@ -3,6 +3,23 @@
#include <QDir>
#include <QStandardPaths>
#include <QSysInfo>
#include <QLabel>
#include <QPointer>
namespace Log {
static QString mostRecentError;
static QString path;
static QFile file;
static QTextStream textStream;
struct Display {
QPointer<QStatusBar> statusBar;
QPointer<QLabel> message;
QPointer<QLabel> icon;
QSet<LogType> types;
};
static QList<Display> 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<LogType> &types) {
if (!statusBar) return;
static const QSet<LogType> 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<LogType, QPixmap> 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));
}
}

View File

@ -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);