Skip to content

Commit

Permalink
Warnings and errors viewable in log dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Reid committed Nov 5, 2024
1 parent 6f275b0 commit 0d2c777
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/Skybolt/SkyboltQt/QtUtil/QtDialogUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ QDialog* createDialogNonModal(QWidget* content, const QString& title, QWidget* p
QPushButton* button = new QPushButton("Close");
button->setAutoDefault(false);
dialog->layout()->addWidget(button);
QObject::connect(button, &QPushButton::pressed, dialog, &QDialog::accept);
QObject::connect(button, &QPushButton::clicked, dialog, &QDialog::accept);

return dialog;
}
77 changes: 77 additions & 0 deletions src/Skybolt/SkyboltQt/Widgets/ErrorLogModel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "ErrorLogModel.h"

#include <assert.h>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/trivial.hpp>

namespace bl = boost::log;

class LabelLogSink : public bl::sinks::basic_formatted_sink_backend<char, bl::sinks::synchronized_feeding>
{
public:
LabelLogSink(std::function<void(bl::trivial::severity_level level, const QString&)> fn) :
mFn(fn)
{
}

void consume(const bl::record_view& rec, const std::string& str) {
if (auto severityAttribute = rec["Severity"]; severityAttribute)
{
if (auto level = severityAttribute.extract<boost::log::trivial::severity_level>(); level)
{
mFn(*level, QString::fromStdString(str));
}
}
}

private:
std::function<void(bl::trivial::severity_level level, const QString&)> mFn;
};

ErrorLogModel::ErrorLogModel(QObject* parent) :
QObject(parent)
{
}

void ErrorLogModel::append(const Item& item)
{
mItems.push_back(item);
Q_EMIT itemAppended(item);
}

void ErrorLogModel::clear()
{
mItems.clear();
Q_EMIT cleared();
}

static ErrorLogModel::Severity toErrorLogModelSeverity(bl::trivial::severity_level level)
{
switch (level)
{
case bl::trivial::severity_level::warning:
return ErrorLogModel::Severity::Warning;
}
return ErrorLogModel::Severity::Error;
}

void connectToBoostLogger(QPointer<ErrorLogModel> model)
{
auto sink = boost::make_shared<LabelLogSink>([model = std::move(model)] (bl::trivial::severity_level level, const QString& message) {
if (model)
{
ErrorLogModel::Item item;
item.dateTime = QDateTime::currentDateTime();
item.severity = toErrorLogModelSeverity(level);
item.message = message;
model->append(item);
}
});

using sink_t = bl::sinks::synchronous_sink<LabelLogSink>;
auto sinkWrapper = boost::make_shared<sink_t>(sink);
sinkWrapper->set_filter(bl::trivial::severity >= bl::trivial::warning);
bl::core::get()->add_sink(sinkWrapper);
}
40 changes: 40 additions & 0 deletions src/Skybolt/SkyboltQt/Widgets/ErrorLogModel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include <QDateTime>
#include <QObject>
#include <QPointer>
#include <QString>

class ErrorLogModel : public QObject
{
Q_OBJECT
public:
ErrorLogModel(QObject* parent = nullptr);

enum class Severity
{
Warning,
Error
};

struct Item
{
QDateTime dateTime;
Severity severity;
QString message;
};

void append(const Item& item);
void clear();

const std::vector<Item>& getItems() const { return mItems; }


Q_SIGNAL void itemAppended(const Item& item);
Q_SIGNAL void cleared();

private:
std::vector<Item> mItems;
};

void connectToBoostLogger(QPointer<ErrorLogModel> model);
76 changes: 76 additions & 0 deletions src/Skybolt/SkyboltQt/Widgets/ErrorLogWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "ErrorLogWidget.h"

#include <QDateTime>
#include <QHeaderView>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>

ErrorLogWidget::ErrorLogWidget(ErrorLogModel* model, QWidget* parent) :
QWidget(parent)
{
// Create the table widget with 3 columns
mTableWidget = new QTableWidget(this);
mTableWidget->setColumnCount(3);
mTableWidget->setHorizontalHeaderLabels({"Time", "Severity", "Message"});
mTableWidget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
mTableWidget->setSortingEnabled(true); // Enable sorting by clicking column headers
mTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); // Make the table non-editable

// Create the "Clear" button
QPushButton* clearButton = new QPushButton("Clear", this);
connect(clearButton, &QPushButton::clicked, model, [model] {
model->clear();
});

// Layout for the dialog
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(mTableWidget);
layout->addWidget(clearButton);

setLayout(layout);

connect(model, &ErrorLogModel::itemAppended, this, [this] (const ErrorLogModel::Item& item) {
addItemToTable(item);
});

connect(model, &ErrorLogModel::cleared, this, [this] {
mTableWidget->setRowCount(0);
});

// Add initial items
for (const auto& item : model->getItems())
{
addItemToTable(item);
}
}

static QString toQString(ErrorLogModel::Severity severity)
{
switch (severity)
{
case ErrorLogModel::Severity::Warning:
return "Warning";
case ErrorLogModel::Severity::Error:
return "Error";
}
return "";
}

std::unique_ptr<QTableWidgetItem> createItemWithTooltip(const QString& text)
{
auto item = std::make_unique<QTableWidgetItem>(text);
item->setToolTip(text);
return item;
}

void ErrorLogWidget::addItemToTable(const ErrorLogModel::Item& item)
{
int row = mTableWidget->rowCount();
mTableWidget->setRowCount(row + 1);

// Insert time, severity, and message into the new row
mTableWidget->setItem(row, 0, createItemWithTooltip(item.dateTime.toString("yyyy-MM-dd HH:mm:ss")).release());
mTableWidget->setItem(row, 1, new QTableWidgetItem(toQString(item.severity)));
mTableWidget->setItem(row, 2, createItemWithTooltip(item.message).release());
}
18 changes: 18 additions & 0 deletions src/Skybolt/SkyboltQt/Widgets/ErrorLogWidget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "ErrorLogModel.h"
#include <QWidget>

class QTableWidget;

class ErrorLogWidget : public QWidget
{
public:
ErrorLogWidget(ErrorLogModel* model, QWidget* parent = nullptr);

private:
void addItemToTable(const ErrorLogModel::Item& item);

private:
QTableWidget* mTableWidget;
};
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ QToolBar* createScenarioObjectCreationToolBar(ScenarioSelectionModel* selectionM
deleteButton->setEnabled(enabled);
});

QObject::connect(deleteButton, &QToolButton::pressed, parent, [selectionModel, scenarioObjectTypes]()
QObject::connect(deleteButton, &QToolButton::clicked, parent, [selectionModel, scenarioObjectTypes]()
{
if (const auto& object = getFirstSelectedScenarioObject(selectionModel->getSelectedItems()); object)
{
Expand Down
78 changes: 26 additions & 52 deletions src/Skybolt/SkyboltQt/Widgets/StatusBar.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include "StatusBar.h"
#include "ErrorLogModel.h"
#include "ErrorLogWidget.h"
#include "SkyboltQt/QtUtil/QtDialogUtil.h"

#include <QApplication>
#include <QBoxLayout>
Expand All @@ -7,36 +10,13 @@
#include <QToolButton>
#include <QStatusBar>
#include <QStyle>
#include <QVariant>

#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/trivial.hpp>
#include <assert.h>

namespace bl = boost::log;

class LabelLogSink : public bl::sinks::basic_formatted_sink_backend<char, bl::sinks::synchronized_feeding>
void addErrorLogStatusBar(QStatusBar& bar, ErrorLogModel* model)
{
public:
LabelLogSink(std::function<void(const QString&)> fn) :
mFn(fn)
{
}

void consume(const bl::record_view& rec, const std::string& str) {
mFn(QString::fromStdString(str));
}

private:
std::function<void(const QString&)> mFn;
assert(model);

private:
QLabel* mLabel;
};

void addErrorLogStatusBar(QStatusBar& bar)
{
auto widget = new QWidget(&bar);
auto layout = new QHBoxLayout(widget);
layout->setMargin(0);
Expand All @@ -56,39 +36,33 @@ void addErrorLogStatusBar(QStatusBar& bar)
infoButton->setVisible(false);
layout->addWidget(infoButton);

auto clearButton = new QToolButton(&bar);
clearButton->setIcon(style->standardIcon(QStyle::SP_TitleBarCloseButton));
clearButton->setToolTip("Close");
clearButton->setFixedHeight(fm.height());
clearButton->setVisible(false);
layout->addWidget(clearButton);

QObject::connect(infoButton, &QToolButton::pressed, [parent = &bar, infoButton] {
QMessageBox::about(parent, "", infoButton->property("messageText").toString());
});

QObject::connect(clearButton, &QToolButton::pressed, [label, infoButton, clearButton] {
label->setText("");
infoButton->setVisible(false);
clearButton->setVisible(false);
QObject::connect(infoButton, &QToolButton::clicked, model, [parent = &bar, infoButton, model] {
auto logWidget = new ErrorLogWidget(model, parent);
QDialog* dialog = createDialogNonModal(logWidget, "Error Log");
dialog->resize(800, 500);
dialog->show();
});

bar.addPermanentWidget(widget);

auto sink = boost::make_shared<LabelLogSink>([label, infoButton, clearButton] (const QString& message) {
QString singleLineMessage = message;
auto sink = [label, infoButton] (const ErrorLogModel::Item& item) {
QString singleLineMessage = item.message;
singleLineMessage = singleLineMessage.replace('\n', ' ');
singleLineMessage.resize(std::min(singleLineMessage.size(), 100));
singleLineMessage += "...";

label->setText(singleLineMessage);
infoButton->setProperty("messageText", message);
infoButton->setVisible(!message.isEmpty());
clearButton->setVisible(!message.isEmpty());
});
infoButton->setVisible(!item.message.isEmpty());
};

using sink_t = bl::sinks::synchronous_sink<LabelLogSink>;
auto sinkWrapper = boost::make_shared<sink_t>(sink);
sinkWrapper->set_filter(bl::trivial::severity >= bl::trivial::error);
bl::core::get()->add_sink(sinkWrapper);
}
QObject::connect(model, &ErrorLogModel::itemAppended, &bar, sink);
if (!model->getItems().empty())
{
sink(model->getItems().back());
}

QObject::connect(model, &ErrorLogModel::cleared, &bar, [label, infoButton] {
label->setText("");
infoButton->setVisible(false);
});
}
3 changes: 2 additions & 1 deletion src/Skybolt/SkyboltQt/Widgets/StatusBar.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

class ErrorLogModel;
class QStatusBar;

void addErrorLogStatusBar(QStatusBar& bar);
void addErrorLogStatusBar(QStatusBar& bar, ErrorLogModel* model);
6 changes: 3 additions & 3 deletions src/Skybolt/SkyboltQt/Widgets/TimeRateDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,19 @@ TimeRateDialog::TimeRateDialog(double initialRate, QWidget* parent) :
toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
setMaximumWidth(toolBar->width());

connect(realtimeButton, &QPushButton::pressed, this, [this, customRateLineEdit] {
connect(realtimeButton, &QPushButton::clicked, this, [this, customRateLineEdit] {
mRate = 1;
customRateLineEdit->setText(QString::number(mRate));
emit rateChanged(mRate);
});

connect(slowDownButton, &QPushButton::pressed, this, [this, customRateLineEdit] {
connect(slowDownButton, &QPushButton::clicked, this, [this, customRateLineEdit] {
mRate /= 2;
customRateLineEdit->setText(QString::number(mRate));
emit rateChanged(mRate);
});

connect(speedUpButton, &QPushButton::pressed, this, [this, customRateLineEdit] {
connect(speedUpButton, &QPushButton::clicked, this, [this, customRateLineEdit] {
mRate *= 2;
customRateLineEdit->setText(QString::number(mRate));
emit rateChanged(mRate);
Expand Down
Loading

0 comments on commit 0d2c777

Please sign in to comment.