NOISSUE use model/view for Minecraft log data
This commit is contained in:
parent
9aff21c181
commit
67eca08b22
@ -119,6 +119,8 @@ set(LAUNCH_SOURCES
|
|||||||
launch/LaunchTask.h
|
launch/LaunchTask.h
|
||||||
launch/LoggedProcess.cpp
|
launch/LoggedProcess.cpp
|
||||||
launch/LoggedProcess.h
|
launch/LoggedProcess.h
|
||||||
|
launch/LogModel.cpp
|
||||||
|
launch/LogModel.h
|
||||||
launch/MessageLevel.cpp
|
launch/MessageLevel.cpp
|
||||||
launch/MessageLevel.h
|
launch/MessageLevel.h
|
||||||
)
|
)
|
||||||
|
@ -167,6 +167,15 @@ bool LaunchTask::abort()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
|
||||||
|
{
|
||||||
|
if(!m_logModel)
|
||||||
|
{
|
||||||
|
m_logModel.reset(new LogModel());
|
||||||
|
}
|
||||||
|
return m_logModel;
|
||||||
|
}
|
||||||
|
|
||||||
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
|
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
|
||||||
{
|
{
|
||||||
for (auto & line: lines)
|
for (auto & line: lines)
|
||||||
@ -193,7 +202,8 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
|
|||||||
// censor private user info
|
// censor private user info
|
||||||
line = censorPrivateInfo(line);
|
line = censorPrivateInfo(line);
|
||||||
|
|
||||||
emit log(line, level);
|
auto &model = *getLogModel();
|
||||||
|
model.append(level, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchTask::emitSucceeded()
|
void LaunchTask::emitSucceeded()
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QObjectPtr.h>
|
||||||
|
#include "LogModel.h"
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "MessageLevel.h"
|
#include "MessageLevel.h"
|
||||||
#include "LoggedProcess.h"
|
#include "LoggedProcess.h"
|
||||||
@ -80,6 +82,8 @@ public: /* methods */
|
|||||||
*/
|
*/
|
||||||
virtual bool abort() override;
|
virtual bool abort() override;
|
||||||
|
|
||||||
|
shared_qobject_ptr<LogModel> getLogModel();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QString substituteVariables(const QString &cmd) const;
|
QString substituteVariables(const QString &cmd) const;
|
||||||
QString censorPrivateInfo(QString in);
|
QString censorPrivateInfo(QString in);
|
||||||
@ -98,13 +102,6 @@ signals:
|
|||||||
|
|
||||||
void requestLogging();
|
void requestLogging();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief emitted when we want to log something
|
|
||||||
* @param text the text to log
|
|
||||||
* @param level the level to log at
|
|
||||||
*/
|
|
||||||
void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
||||||
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
||||||
@ -114,6 +111,7 @@ public slots:
|
|||||||
|
|
||||||
protected: /* data */
|
protected: /* data */
|
||||||
InstancePtr m_instance;
|
InstancePtr m_instance;
|
||||||
|
shared_qobject_ptr<LogModel> m_logModel;
|
||||||
QList <std::shared_ptr<LaunchStep>> m_steps;
|
QList <std::shared_ptr<LaunchStep>> m_steps;
|
||||||
QMap<QString, QString> m_censorFilter;
|
QMap<QString, QString> m_censorFilter;
|
||||||
int currentStep = -1;
|
int currentStep = -1;
|
||||||
|
135
api/logic/launch/LogModel.cpp
Normal file
135
api/logic/launch/LogModel.cpp
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#include "LogModel.h"
|
||||||
|
|
||||||
|
LogModel::LogModel(QObject *parent):QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
m_content.resize(m_maxLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LogModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return m_numLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant LogModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (index.row() < 0 || index.row() >= m_numLines)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
auto row = index.row();
|
||||||
|
auto realRow = (row + m_firstLine) % m_maxLines;
|
||||||
|
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||||
|
{
|
||||||
|
return m_content[realRow].line;
|
||||||
|
}
|
||||||
|
if(role == LevelRole)
|
||||||
|
{
|
||||||
|
return m_content[realRow].level;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogModel::append(MessageLevel::Enum level, QString line)
|
||||||
|
{
|
||||||
|
int lineNum = (m_firstLine + m_numLines) % m_maxLines;
|
||||||
|
// overflow
|
||||||
|
if(m_numLines == m_maxLines)
|
||||||
|
{
|
||||||
|
if(m_stopOnOverflow)
|
||||||
|
{
|
||||||
|
// nothing more to do, the buffer is full
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginRemoveRows(QModelIndex(), 0, 0);
|
||||||
|
m_firstLine = (m_firstLine + 1) % m_maxLines;
|
||||||
|
m_numLines --;
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow)
|
||||||
|
{
|
||||||
|
level = MessageLevel::Fatal;
|
||||||
|
line = m_overflowMessage;
|
||||||
|
}
|
||||||
|
beginInsertRows(QModelIndex(), m_numLines, m_numLines);
|
||||||
|
m_numLines ++;
|
||||||
|
m_content[lineNum].level = level;
|
||||||
|
m_content[lineNum].line = line;
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogModel::clear()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_firstLine = 0;
|
||||||
|
m_numLines = 0;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LogModel::toPlainText()
|
||||||
|
{
|
||||||
|
QString out;
|
||||||
|
out.reserve(m_numLines * 80);
|
||||||
|
for(int i = 0; i < m_numLines; i++)
|
||||||
|
{
|
||||||
|
QString & line = m_content[(m_firstLine + i) % m_maxLines].line;
|
||||||
|
out.append(line + '\n');
|
||||||
|
}
|
||||||
|
out.squeeze();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogModel::setMaxLines(int maxLines)
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
if(maxLines == m_maxLines)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if it all still fits in the buffer, just resize it
|
||||||
|
if(m_firstLine + m_numLines < maxLines)
|
||||||
|
{
|
||||||
|
m_maxLines = maxLines;
|
||||||
|
m_content.resize(maxLines);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// otherwise, we need to reorganize the data because it crosses the wrap boundary
|
||||||
|
QVector<entry> newContent;
|
||||||
|
newContent.resize(maxLines);
|
||||||
|
if(m_numLines <= maxLines)
|
||||||
|
{
|
||||||
|
// if it all fits in the new buffer, just copy it over
|
||||||
|
for(int i = 0; i < m_numLines; i++)
|
||||||
|
{
|
||||||
|
newContent[i] = m_content[(m_firstLine + i) % m_maxLines];
|
||||||
|
}
|
||||||
|
m_content.swap(newContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if it doesn't fit, part of the data needs to be thrown away (the oldest log messages)
|
||||||
|
int lead = m_numLines - maxLines;
|
||||||
|
beginRemoveRows(QModelIndex(), 0, lead - 1);
|
||||||
|
for(int i = 0; i < maxLines; i++)
|
||||||
|
{
|
||||||
|
newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines];
|
||||||
|
}
|
||||||
|
m_numLines = m_maxLines;
|
||||||
|
m_content.swap(newContent);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
m_firstLine = 0;
|
||||||
|
m_maxLines = maxLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogModel::setStopOnOverflow(bool stop)
|
||||||
|
{
|
||||||
|
m_stopOnOverflow = stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogModel::setOverflowMessage(const QString& overflowMessage)
|
||||||
|
{
|
||||||
|
m_overflowMessage = overflowMessage;
|
||||||
|
}
|
51
api/logic/launch/LogModel.h
Normal file
51
api/logic/launch/LogModel.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QString>
|
||||||
|
#include "MessageLevel.h"
|
||||||
|
|
||||||
|
#include <multimc_logic_export.h>
|
||||||
|
|
||||||
|
class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit LogModel(QObject *parent = 0);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const;
|
||||||
|
|
||||||
|
void append(MessageLevel::Enum, QString line);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
QString toPlainText();
|
||||||
|
|
||||||
|
void setMaxLines(int maxLines);
|
||||||
|
void setStopOnOverflow(bool stop);
|
||||||
|
void setOverflowMessage(const QString & overflowMessage);
|
||||||
|
|
||||||
|
enum Roles
|
||||||
|
{
|
||||||
|
LevelRole = Qt::UserRole
|
||||||
|
};
|
||||||
|
|
||||||
|
private /* types */:
|
||||||
|
struct entry
|
||||||
|
{
|
||||||
|
MessageLevel::Enum level;
|
||||||
|
QString line;
|
||||||
|
};
|
||||||
|
|
||||||
|
private: /* data */
|
||||||
|
QVector <entry> m_content;
|
||||||
|
int m_maxLines = 1000;
|
||||||
|
// first line in the circular buffer
|
||||||
|
int m_firstLine = 0;
|
||||||
|
// number of lines occupied in the circular buffer
|
||||||
|
int m_numLines = 0;
|
||||||
|
bool m_stopOnOverflow = false;
|
||||||
|
QString m_overflowMessage = "OVERFLOW";
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(LogModel)
|
||||||
|
};
|
@ -210,6 +210,8 @@ SET(MULTIMC_SOURCES
|
|||||||
widgets/LabeledToolButton.h
|
widgets/LabeledToolButton.h
|
||||||
widgets/LineSeparator.cpp
|
widgets/LineSeparator.cpp
|
||||||
widgets/LineSeparator.h
|
widgets/LineSeparator.h
|
||||||
|
widgets/LogView.cpp
|
||||||
|
widgets/LogView.h
|
||||||
widgets/MCModInfoFrame.cpp
|
widgets/MCModInfoFrame.cpp
|
||||||
widgets/MCModInfoFrame.h
|
widgets/MCModInfoFrame.h
|
||||||
widgets/ModListView.cpp
|
widgets/ModListView.cpp
|
||||||
|
@ -12,15 +12,118 @@
|
|||||||
#include "GuiUtil.h"
|
#include "GuiUtil.h"
|
||||||
#include <ColorCache.h>
|
#include <ColorCache.h>
|
||||||
|
|
||||||
|
class LogFormatProxyModel : public QIdentityProxyModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override
|
||||||
|
{
|
||||||
|
switch(role)
|
||||||
|
{
|
||||||
|
case Qt::FontRole:
|
||||||
|
return m_font;
|
||||||
|
case Qt::TextColorRole:
|
||||||
|
{
|
||||||
|
MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
|
||||||
|
return m_colors->getFront(level);
|
||||||
|
}
|
||||||
|
case Qt::BackgroundRole:
|
||||||
|
{
|
||||||
|
MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
|
||||||
|
return m_colors->getBack(level);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return QIdentityProxyModel::data(index, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFont(QFont font)
|
||||||
|
{
|
||||||
|
m_font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColors(LogColorCache* colors)
|
||||||
|
{
|
||||||
|
m_colors.reset(colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const
|
||||||
|
{
|
||||||
|
QModelIndex parentIndex = parent(start);
|
||||||
|
auto compare = [&](int r) -> QModelIndex
|
||||||
|
{
|
||||||
|
QModelIndex idx = index(r, start.column(), parentIndex);
|
||||||
|
if (!idx.isValid() || idx == start)
|
||||||
|
{
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
QVariant v = data(idx, Qt::DisplayRole);
|
||||||
|
QString t = v.toString();
|
||||||
|
if (t.contains(value, Qt::CaseInsensitive))
|
||||||
|
return idx;
|
||||||
|
return QModelIndex();
|
||||||
|
};
|
||||||
|
if(reverse)
|
||||||
|
{
|
||||||
|
int from = start.row();
|
||||||
|
int to = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
for (int r = from; (r >= to); --r)
|
||||||
|
{
|
||||||
|
auto idx = compare(r);
|
||||||
|
if(idx.isValid())
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
// prepare for the next iteration
|
||||||
|
from = rowCount() - 1;
|
||||||
|
to = start.row();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int from = start.row();
|
||||||
|
int to = rowCount(parentIndex);
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
for (int r = from; (r < to); ++r)
|
||||||
|
{
|
||||||
|
auto idx = compare(r);
|
||||||
|
if(idx.isValid())
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
// prepare for the next iteration
|
||||||
|
from = 0;
|
||||||
|
to = start.row();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QFont m_font;
|
||||||
|
std::unique_ptr<LogColorCache> m_colors;
|
||||||
|
};
|
||||||
|
|
||||||
LogPage::LogPage(InstancePtr instance, QWidget *parent)
|
LogPage::LogPage(InstancePtr instance, QWidget *parent)
|
||||||
: QWidget(parent), ui(new Ui::LogPage), m_instance(instance)
|
: QWidget(parent), ui(new Ui::LogPage), m_instance(instance)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->tabWidget->tabBar()->hide();
|
ui->tabWidget->tabBar()->hide();
|
||||||
|
|
||||||
// create the format and set its font
|
m_proxy = new LogFormatProxyModel(this);
|
||||||
|
// set up text colors in the log proxy and adapt them to the current theme foreground and background
|
||||||
|
{
|
||||||
|
auto origForeground = ui->text->palette().color(ui->text->foregroundRole());
|
||||||
|
auto origBackground = ui->text->palette().color(ui->text->backgroundRole());
|
||||||
|
m_proxy->setColors(new LogColorCache(origForeground, origBackground));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up fonts in the log proxy
|
||||||
{
|
{
|
||||||
defaultFormat = new QTextCharFormat(ui->text->currentCharFormat());
|
|
||||||
QString fontFamily = MMC->settings()->get("ConsoleFont").toString();
|
QString fontFamily = MMC->settings()->get("ConsoleFont").toString();
|
||||||
bool conversionOk = false;
|
bool conversionOk = false;
|
||||||
int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk);
|
int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk);
|
||||||
@ -28,23 +131,10 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
|
|||||||
{
|
{
|
||||||
fontSize = 11;
|
fontSize = 11;
|
||||||
}
|
}
|
||||||
defaultFormat->setFont(QFont(fontFamily, fontSize));
|
m_proxy->setFont(QFont(fontFamily, fontSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure we don't eat all the RAM
|
ui->text->setModel(m_proxy);
|
||||||
{
|
|
||||||
auto lineSetting = MMC->settings()->getSetting("ConsoleMaxLines");
|
|
||||||
bool conversionOk = false;
|
|
||||||
int maxLines = lineSetting->get().toInt(&conversionOk);
|
|
||||||
if(!conversionOk)
|
|
||||||
{
|
|
||||||
maxLines = lineSetting->defValue().toInt();
|
|
||||||
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
|
|
||||||
}
|
|
||||||
ui->text->setMaximumBlockCount(maxLines);
|
|
||||||
|
|
||||||
m_stopOnOverflow = MMC->settings()->get("ConsoleOverflowStop").toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up instance and launch process recognition
|
// set up instance and launch process recognition
|
||||||
{
|
{
|
||||||
@ -53,16 +143,10 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
|
|||||||
{
|
{
|
||||||
on_InstanceLaunchTask_changed(launchTask);
|
on_InstanceLaunchTask_changed(launchTask);
|
||||||
}
|
}
|
||||||
connect(m_instance.get(), &BaseInstance::launchTaskChanged,
|
connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::on_InstanceLaunchTask_changed);
|
||||||
this, &LogPage::on_InstanceLaunchTask_changed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up text colors and adapt them to the current theme foreground and background
|
ui->text->setWordWrap(true);
|
||||||
{
|
|
||||||
auto origForeground = ui->text->palette().color(ui->text->foregroundRole());
|
|
||||||
auto origBackground = ui->text->palette().color(ui->text->backgroundRole());
|
|
||||||
m_colors.reset(new LogColorCache(origForeground, origBackground));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
|
auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
|
||||||
connect(findShortcut, SIGNAL(activated()), SLOT(findActivated()));
|
connect(findShortcut, SIGNAL(activated()), SLOT(findActivated()));
|
||||||
@ -76,20 +160,33 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
|
|||||||
LogPage::~LogPage()
|
LogPage::~LogPage()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
delete defaultFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
|
void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
|
||||||
{
|
{
|
||||||
if(m_process)
|
|
||||||
{
|
|
||||||
disconnect(m_process.get(), &LaunchTask::log, this, &LogPage::write);
|
|
||||||
}
|
|
||||||
m_process = proc;
|
m_process = proc;
|
||||||
if(m_process)
|
if(m_process)
|
||||||
{
|
{
|
||||||
ui->text->clear();
|
m_model = proc->getLogModel();
|
||||||
connect(m_process.get(), &LaunchTask::log, this, &LogPage::write);
|
auto lineSetting = MMC->settings()->getSetting("ConsoleMaxLines");
|
||||||
|
bool conversionOk = false;
|
||||||
|
int maxLines = lineSetting->get().toInt(&conversionOk);
|
||||||
|
if(!conversionOk)
|
||||||
|
{
|
||||||
|
maxLines = lineSetting->defValue().toInt();
|
||||||
|
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
|
||||||
|
}
|
||||||
|
m_model->setMaxLines(maxLines);
|
||||||
|
m_model->setStopOnOverflow(MMC->settings()->get("ConsoleOverflowStop").toBool());
|
||||||
|
m_model->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n"
|
||||||
|
"You may have to fix your mods because the game is still loggging to files and"
|
||||||
|
" likely wasting harddrive space at an alarming rate!").arg(maxLines));
|
||||||
|
m_proxy->setSourceModel(m_model.get());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_proxy->setSourceModel(nullptr);
|
||||||
|
m_model.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,38 +197,45 @@ bool LogPage::apply()
|
|||||||
|
|
||||||
bool LogPage::shouldDisplay() const
|
bool LogPage::shouldDisplay() const
|
||||||
{
|
{
|
||||||
return m_instance->isRunning() || ui->text->blockCount() > 1;
|
return m_instance->isRunning() || m_proxy->rowCount() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_btnPaste_clicked()
|
void LogPage::on_btnPaste_clicked()
|
||||||
{
|
{
|
||||||
|
if(!m_model)
|
||||||
|
return;
|
||||||
|
|
||||||
//FIXME: turn this into a proper task and move the upload logic out of GuiUtil!
|
//FIXME: turn this into a proper task and move the upload logic out of GuiUtil!
|
||||||
write(tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)), MessageLevel::MultiMC);
|
m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)));
|
||||||
auto url = GuiUtil::uploadPaste(ui->text->toPlainText(), this);
|
auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this);
|
||||||
if(!url.isEmpty())
|
if(!url.isEmpty())
|
||||||
{
|
{
|
||||||
write(tr("MultiMC: Log uploaded to: %1").arg(url), MessageLevel::MultiMC);
|
m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write(tr("MultiMC: Log upload failed!"), MessageLevel::Error);
|
m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_btnCopy_clicked()
|
void LogPage::on_btnCopy_clicked()
|
||||||
{
|
{
|
||||||
write(QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)), MessageLevel::MultiMC);
|
if(!m_model)
|
||||||
GuiUtil::setClipboardText(ui->text->toPlainText());
|
return;
|
||||||
|
m_model->append(MessageLevel::MultiMC, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)));
|
||||||
|
GuiUtil::setClipboardText(m_model->toPlainText());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_btnClear_clicked()
|
void LogPage::on_btnClear_clicked()
|
||||||
{
|
{
|
||||||
ui->text->clear();
|
if(!m_model)
|
||||||
|
return;
|
||||||
|
m_model->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_btnBottom_clicked()
|
void LogPage::on_btnBottom_clicked()
|
||||||
{
|
{
|
||||||
ui->text->verticalScrollBar()->setSliderPosition(ui->text->verticalScrollBar()->maximum());
|
ui->text->scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_trackLogCheckbox_clicked(bool checked)
|
void LogPage::on_trackLogCheckbox_clicked(bool checked)
|
||||||
@ -141,27 +245,24 @@ void LogPage::on_trackLogCheckbox_clicked(bool checked)
|
|||||||
|
|
||||||
void LogPage::on_wrapCheckbox_clicked(bool checked)
|
void LogPage::on_wrapCheckbox_clicked(bool checked)
|
||||||
{
|
{
|
||||||
if(checked)
|
ui->text->setWordWrap(checked);
|
||||||
{
|
|
||||||
ui->text->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ui->text->setWordWrapMode(QTextOption::WrapMode::NoWrap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::on_findButton_clicked()
|
void LogPage::on_findButton_clicked()
|
||||||
{
|
{
|
||||||
auto modifiers = QApplication::keyboardModifiers();
|
auto modifiers = QApplication::keyboardModifiers();
|
||||||
if (modifiers & Qt::ShiftModifier)
|
bool reverse = modifiers & Qt::ShiftModifier;
|
||||||
{
|
ui->text->findNext(ui->searchBar->text(), reverse);
|
||||||
findPreviousActivated();
|
}
|
||||||
}
|
|
||||||
else
|
void LogPage::findNextActivated()
|
||||||
{
|
{
|
||||||
findNextActivated();
|
ui->text->findNext(ui->searchBar->text(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogPage::findPreviousActivated()
|
||||||
|
{
|
||||||
|
ui->text->findNext(ui->searchBar->text(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::findActivated()
|
void LogPage::findActivated()
|
||||||
@ -169,118 +270,12 @@ void LogPage::findActivated()
|
|||||||
// focus the search bar if it doesn't have focus
|
// focus the search bar if it doesn't have focus
|
||||||
if (!ui->searchBar->hasFocus())
|
if (!ui->searchBar->hasFocus())
|
||||||
{
|
{
|
||||||
auto searchForCursor = ui->text->textCursor();
|
|
||||||
auto searchForString = searchForCursor.selectedText();
|
|
||||||
if (searchForString.size())
|
|
||||||
{
|
|
||||||
ui->searchBar->setText(searchForString);
|
|
||||||
}
|
|
||||||
ui->searchBar->setFocus();
|
ui->searchBar->setFocus();
|
||||||
ui->searchBar->selectAll();
|
ui->searchBar->selectAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::findNextActivated()
|
|
||||||
{
|
|
||||||
auto toSearch = ui->searchBar->text();
|
|
||||||
if (toSearch.size())
|
|
||||||
{
|
|
||||||
ui->text->find(toSearch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogPage::findPreviousActivated()
|
|
||||||
{
|
|
||||||
auto toSearch = ui->searchBar->text();
|
|
||||||
if (toSearch.size())
|
|
||||||
{
|
|
||||||
ui->text->find(toSearch, QTextDocument::FindBackward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogPage::setParentContainer(BasePageContainer * container)
|
void LogPage::setParentContainer(BasePageContainer * container)
|
||||||
{
|
{
|
||||||
m_parentContainer = container;
|
m_parentContainer = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPage::write(QString data, MessageLevel::Enum mode)
|
|
||||||
{
|
|
||||||
if (!m_write_active)
|
|
||||||
{
|
|
||||||
if (mode != MessageLevel::MultiMC)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(m_stopOnOverflow && m_write_active)
|
|
||||||
{
|
|
||||||
if(mode != MessageLevel::MultiMC)
|
|
||||||
{
|
|
||||||
if(ui->text->blockCount() >= ui->text->maximumBlockCount())
|
|
||||||
{
|
|
||||||
m_write_active = false;
|
|
||||||
data = tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n"
|
|
||||||
"You may have to fix your mods because the game is still loggging to files and"
|
|
||||||
" likely wasting harddrive space at an alarming rate!")
|
|
||||||
.arg(ui->text->maximumBlockCount());
|
|
||||||
mode = MessageLevel::Fatal;
|
|
||||||
ui->trackLogCheckbox->setCheckState(Qt::Unchecked);
|
|
||||||
if(!isVisible())
|
|
||||||
{
|
|
||||||
m_parentContainer->selectPage(id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the cursor so it can be restored.
|
|
||||||
auto savedCursor = ui->text->cursor();
|
|
||||||
|
|
||||||
QScrollBar *bar = ui->text->verticalScrollBar();
|
|
||||||
int max_bar = bar->maximum();
|
|
||||||
int val_bar = bar->value();
|
|
||||||
if (isVisible())
|
|
||||||
{
|
|
||||||
if (m_scroll_active)
|
|
||||||
{
|
|
||||||
m_scroll_active = (max_bar - val_bar) <= 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_scroll_active = val_bar == max_bar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.endsWith('\n'))
|
|
||||||
data = data.left(data.length() - 1);
|
|
||||||
QStringList paragraphs = data.split('\n');
|
|
||||||
QStringList filtered;
|
|
||||||
for (QString ¶graph : paragraphs)
|
|
||||||
{
|
|
||||||
//TODO: implement filtering here.
|
|
||||||
filtered.append(paragraph);
|
|
||||||
}
|
|
||||||
QListIterator<QString> iter(filtered);
|
|
||||||
QTextCharFormat format(*defaultFormat);
|
|
||||||
|
|
||||||
format.setForeground(m_colors->getFront(mode));
|
|
||||||
format.setBackground(m_colors->getBack(mode));
|
|
||||||
|
|
||||||
while (iter.hasNext())
|
|
||||||
{
|
|
||||||
// append a paragraph/line
|
|
||||||
auto workCursor = ui->text->textCursor();
|
|
||||||
workCursor.movePosition(QTextCursor::End);
|
|
||||||
workCursor.insertText(iter.next(), format);
|
|
||||||
workCursor.insertBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVisible())
|
|
||||||
{
|
|
||||||
if (m_scroll_active)
|
|
||||||
{
|
|
||||||
bar->setValue(bar->maximum());
|
|
||||||
}
|
|
||||||
m_last_scroll_value = bar->value();
|
|
||||||
}
|
|
||||||
ui->text->setCursor(savedCursor);
|
|
||||||
}
|
|
||||||
|
@ -21,13 +21,13 @@
|
|||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
#include "BasePage.h"
|
#include "BasePage.h"
|
||||||
#include <MultiMC.h>
|
#include <MultiMC.h>
|
||||||
#include <ColorCache.h>
|
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class LogPage;
|
class LogPage;
|
||||||
}
|
}
|
||||||
class QTextCharFormat;
|
class QTextCharFormat;
|
||||||
|
class LogFormatProxyModel;
|
||||||
|
|
||||||
class LogPage : public QWidget, public BasePage
|
class LogPage : public QWidget, public BasePage
|
||||||
{
|
{
|
||||||
@ -57,13 +57,6 @@ public:
|
|||||||
virtual void setParentContainer(BasePageContainer *) override;
|
virtual void setParentContainer(BasePageContainer *) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
/**
|
|
||||||
* @brief write a string
|
|
||||||
* @param data the string
|
|
||||||
* @param level the @MessageLevel the string should be written under
|
|
||||||
* lines have to be put through this as a whole!
|
|
||||||
*/
|
|
||||||
void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC);
|
|
||||||
void on_btnPaste_clicked();
|
void on_btnPaste_clicked();
|
||||||
void on_btnCopy_clicked();
|
void on_btnCopy_clicked();
|
||||||
void on_btnClear_clicked();
|
void on_btnClear_clicked();
|
||||||
@ -88,8 +81,9 @@ private:
|
|||||||
int m_saved_offset = 0;
|
int m_saved_offset = 0;
|
||||||
bool m_write_active = true;
|
bool m_write_active = true;
|
||||||
bool m_stopOnOverflow = true;
|
bool m_stopOnOverflow = true;
|
||||||
|
bool m_autoScroll = false;
|
||||||
|
|
||||||
QTextCharFormat * defaultFormat;
|
|
||||||
BasePageContainer * m_parentContainer;
|
BasePageContainer * m_parentContainer;
|
||||||
std::unique_ptr<LogColorCache> m_colors;
|
LogFormatProxyModel * m_proxy;
|
||||||
|
shared_qobject_ptr <LogModel> m_model;
|
||||||
};
|
};
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="1" column="0" colspan="5">
|
<item row="1" column="0" colspan="5">
|
||||||
<widget class="QPlainTextEdit" name="text">
|
<widget class="LogView" name="text">
|
||||||
<property name="undoRedoEnabled">
|
<property name="undoRedoEnabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -159,6 +159,13 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>LogView</class>
|
||||||
|
<extends>QPlainTextEdit</extends>
|
||||||
|
<header>widgets/LogView.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabWidget</tabstop>
|
<tabstop>tabWidget</tabstop>
|
||||||
<tabstop>trackLogCheckbox</tabstop>
|
<tabstop>trackLogCheckbox</tabstop>
|
||||||
|
143
application/widgets/LogView.cpp
Normal file
143
application/widgets/LogView.cpp
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#include "LogView.h"
|
||||||
|
#include <QTextBlock>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
|
||||||
|
{
|
||||||
|
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||||
|
m_defaultFormat = new QTextCharFormat(currentCharFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
LogView::~LogView()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::setWordWrap(bool wrapping)
|
||||||
|
{
|
||||||
|
if(wrapping)
|
||||||
|
{
|
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
setLineWrapMode(QPlainTextEdit::WidgetWidth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||||
|
setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::setModel(QAbstractItemModel* model)
|
||||||
|
{
|
||||||
|
if(m_model)
|
||||||
|
{
|
||||||
|
disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate);
|
||||||
|
disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted);
|
||||||
|
disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted);
|
||||||
|
disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved);
|
||||||
|
}
|
||||||
|
m_model = model;
|
||||||
|
if(m_model)
|
||||||
|
{
|
||||||
|
connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate);
|
||||||
|
connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted);
|
||||||
|
connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted);
|
||||||
|
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved);
|
||||||
|
connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed);
|
||||||
|
}
|
||||||
|
repopulate();
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemModel * LogView::model() const
|
||||||
|
{
|
||||||
|
return m_model;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::modelDestroyed(QObject* model)
|
||||||
|
{
|
||||||
|
if(m_model == model)
|
||||||
|
{
|
||||||
|
setModel(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::repopulate()
|
||||||
|
{
|
||||||
|
auto doc = document();
|
||||||
|
doc->clear();
|
||||||
|
if(!m_model)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int last)
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
Q_UNUSED(first)
|
||||||
|
Q_UNUSED(last)
|
||||||
|
QScrollBar *bar = verticalScrollBar();
|
||||||
|
int max_bar = bar->maximum();
|
||||||
|
int val_bar = bar->value();
|
||||||
|
if (m_scroll)
|
||||||
|
{
|
||||||
|
m_scroll = (max_bar - val_bar) <= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_scroll = val_bar == max_bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
|
||||||
|
{
|
||||||
|
for(int i = first; i <= last; i++)
|
||||||
|
{
|
||||||
|
auto idx = m_model->index(i, 0, parent);
|
||||||
|
auto text = m_model->data(idx, Qt::DisplayRole).toString();
|
||||||
|
QTextCharFormat format(*m_defaultFormat);
|
||||||
|
auto font = m_model->data(idx, Qt::FontRole);
|
||||||
|
if(font.isValid())
|
||||||
|
{
|
||||||
|
format.setFont(font.value<QFont>());
|
||||||
|
}
|
||||||
|
auto fg = m_model->data(idx, Qt::TextColorRole);
|
||||||
|
if(fg.isValid())
|
||||||
|
{
|
||||||
|
format.setForeground(fg.value<QColor>());
|
||||||
|
}
|
||||||
|
auto bg = m_model->data(idx, Qt::BackgroundRole);
|
||||||
|
if(bg.isValid())
|
||||||
|
{
|
||||||
|
format.setBackground(bg.value<QColor>());
|
||||||
|
}
|
||||||
|
auto workCursor = textCursor();
|
||||||
|
workCursor.movePosition(QTextCursor::End);
|
||||||
|
workCursor.insertText(text, format);
|
||||||
|
workCursor.insertBlock();
|
||||||
|
}
|
||||||
|
if(m_scroll && !m_scrolling)
|
||||||
|
{
|
||||||
|
m_scrolling = true;
|
||||||
|
QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::rowsRemoved(const QModelIndex& parent, int first, int last)
|
||||||
|
{
|
||||||
|
// TODO: some day... maybe
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
Q_UNUSED(first)
|
||||||
|
Q_UNUSED(last)
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::scrollToBottom()
|
||||||
|
{
|
||||||
|
m_scrolling = false;
|
||||||
|
verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogView::findNext(const QString& what, bool reverse)
|
||||||
|
{
|
||||||
|
find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0));
|
||||||
|
}
|
36
application/widgets/LogView.h
Normal file
36
application/widgets/LogView.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QAbstractItemView>
|
||||||
|
|
||||||
|
class QAbstractItemModel;
|
||||||
|
|
||||||
|
class LogView: public QPlainTextEdit
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit LogView(QWidget *parent = nullptr);
|
||||||
|
virtual ~LogView();
|
||||||
|
|
||||||
|
virtual void setModel(QAbstractItemModel *model);
|
||||||
|
QAbstractItemModel *model() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setWordWrap(bool wrapping);
|
||||||
|
void findNext(const QString & what, bool reverse);
|
||||||
|
void scrollToBottom();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void repopulate();
|
||||||
|
// note: this supports only appending
|
||||||
|
void rowsInserted(const QModelIndex &parent, int first, int last);
|
||||||
|
void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last);
|
||||||
|
// note: this supports only removing from front
|
||||||
|
void rowsRemoved(const QModelIndex &parent, int first, int last);
|
||||||
|
void modelDestroyed(QObject * model);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QAbstractItemModel *m_model = nullptr;
|
||||||
|
QTextCharFormat *m_defaultFormat = nullptr;
|
||||||
|
bool m_scroll = false;
|
||||||
|
bool m_scrolling = false;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user