mirror of
https://github.com/huderlem/porymap.git
synced 2026-03-21 17:45:44 -05:00
Merge pull request #726 from GriffinRichards/status-bar-log
Add warning/error messages to status bar
This commit is contained in:
commit
9d2e3145bb
|
|
@ -80,6 +80,43 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Logging">
|
||||
<property name="title">
|
||||
<string>Logging</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_StatusMessages">
|
||||
<property name="text">
|
||||
<string>Status bar message types</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_StatusErrors">
|
||||
<property name="text">
|
||||
<string>Errors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_StatusWarnings">
|
||||
<property name="text">
|
||||
<string>Warnings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_StatusInformation">
|
||||
<property name="text">
|
||||
<string>Information</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <QUrl>
|
||||
#include <QVersionNumber>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <set>
|
||||
|
||||
#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<LogType> statusBarLogTypes;
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@ private:
|
|||
void updateWindowTitle();
|
||||
|
||||
void initWindow();
|
||||
void initLogStatusBar();
|
||||
void initCustomUI();
|
||||
void initExtraSignals();
|
||||
void initEditor();
|
||||
|
|
|
|||
BIN
resources/icons/error.ico
Normal file
BIN
resources/icons/error.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
resources/icons/information.ico
Normal file
BIN
resources/icons/information.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
resources/icons/warning.ico
Normal file
BIN
resources/icons/warning.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -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/application_form_edit.ico</file>
|
||||
<file>icons/connections.ico</file>
|
||||
|
|
|
|||
|
|
@ -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<LogType>(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<QString, QString> 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;
|
||||
}
|
||||
|
|
|
|||
160
src/log.cpp
160
src/log.cpp
|
|
@ -3,6 +3,25 @@
|
|||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QSysInfo>
|
||||
#include <QLabel>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
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> acceptedTypes;
|
||||
};
|
||||
static QList<Display> displays;
|
||||
static QTimer displayClearTimer;
|
||||
};
|
||||
|
||||
// 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 +37,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 +67,131 @@ QString colorizeMessage(QString message, LogType type) {
|
|||
return colorized;
|
||||
}
|
||||
|
||||
void log(QString message, LogType type) {
|
||||
void addLogStatusBar(QStatusBar *statusBar, const QSet<LogType> &acceptedTypes) {
|
||||
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),
|
||||
.acceptedTypes = acceptedTypes.isEmpty() ? allTypes : acceptedTypes,
|
||||
};
|
||||
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"))},
|
||||
};
|
||||
|
||||
bool startTimer = false;
|
||||
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.acceptedTypes.contains(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
|
||||
if (startTimer) 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) {
|
||||
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;
|
||||
}
|
||||
QObject::connect(&Log::displayClearTimer, &QTimer::timeout, [=] {
|
||||
clearLogDisplays();
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
ui->setupUi(this);
|
||||
|
||||
cleanupLargeLog();
|
||||
logInit();
|
||||
logInfo(QString("Launching Porymap v%1").arg(QCoreApplication::applicationVersion()));
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +142,7 @@ void MainWindow::setWindowDisabled(bool disabled) {
|
|||
|
||||
void MainWindow::initWindow() {
|
||||
porymapConfig.load();
|
||||
this->initLogStatusBar();
|
||||
this->initCustomUI();
|
||||
this->initExtraSignals();
|
||||
this->initEditor();
|
||||
|
|
@ -237,6 +238,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<int, QString> mainTabNames = {
|
||||
{MainTab::Map, "Map"},
|
||||
|
|
@ -634,7 +643,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);
|
||||
|
|
@ -653,7 +661,6 @@ bool MainWindow::openProject(QString dir, bool initial) {
|
|||
}
|
||||
|
||||
const QString openMessage = QString("Opening %1").arg(projectString);
|
||||
this->statusBar()->showMessage(openMessage);
|
||||
logInfo(openMessage);
|
||||
|
||||
porysplash->start();
|
||||
|
|
@ -691,7 +698,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
|
||||
|
|
@ -703,7 +709,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);
|
||||
|
|
@ -2922,6 +2927,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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user