diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 1d7c193f8..acc7fce58 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -46,7 +46,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new InstanceSettingsPage(onesix)); auto logMatcher = inst->getLogFileMatcher(); if (logMatcher) { - values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); + values.append(new OtherLogsPage(inst, logMatcher)); } return values; } diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 23a33ae18..dd32d46a2 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -149,3 +149,15 @@ bool LogModel::wrapLines() const { return m_lineWrap; } + +void LogModel::setColorLines(bool state) +{ + if (m_colorLines != state) { + m_colorLines = state; + } +} + +bool LogModel::colorLines() const +{ + return m_colorLines; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 167f74190..6c2a8cff3 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -27,6 +27,8 @@ class LogModel : public QAbstractListModel { void setLineWrap(bool state); bool wrapLines() const; + void setColorLines(bool state); + bool colorLines() const; enum Roles { LevelRole = Qt::UserRole }; @@ -47,6 +49,7 @@ class LogModel : public QAbstractListModel { QString m_overflowMessage = "OVERFLOW"; bool m_suspended = false; bool m_lineWrap = true; + bool m_colorLines = true; private: Q_DISABLE_COPY(LogModel) diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 4962f90ce..7897a2932 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -52,90 +52,81 @@ #include -class LogFormatProxyModel : public QIdentityProxyModel { - public: - LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} - QVariant data(const QModelIndex& index, int role) const override - { - const LogColors& colors = APPLICATION->themeManager()->getLogColors(); +QVariant LogFormatProxyModel::data(const QModelIndex& index, int role) const +{ + const LogColors& colors = APPLICATION->themeManager()->getLogColors(); - switch (role) { - case Qt::FontRole: - return m_font; - case Qt::ForegroundRole: { - auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); - QColor result = colors.foreground.value(level); + switch (role) { + case Qt::FontRole: + return m_font; + case Qt::ForegroundRole: { + auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); + QColor result = colors.foreground.value(level); - if (result.isValid()) - return result; + if (result.isValid()) + return result; - break; - } - case Qt::BackgroundRole: { - auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); - QColor result = colors.background.value(level); - - if (result.isValid()) - return result; - - break; - } + break; } + case Qt::BackgroundRole: { + auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); + QColor result = colors.background.value(level); - return QIdentityProxyModel::data(index, role); + if (result.isValid()) + return result; + + break; + } } - void setFont(QFont font) { m_font = font; } + return QIdentityProxyModel::data(index, role); +} - QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const - { - QModelIndex parentIndex = parent(start); - auto compare = [this, start, parentIndex, value](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; +QModelIndex LogFormatProxyModel::find(const QModelIndex& start, const QString& value, bool reverse) const +{ + QModelIndex parentIndex = parent(start); + auto compare = [this, start, parentIndex, value](int r) -> QModelIndex { + QModelIndex idx = index(r, start.column(), parentIndex); + if (!idx.isValid() || idx == start) { 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(); - } } + 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; - private: - QFont m_font; -}; + 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(); +} LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { @@ -189,6 +180,13 @@ void LogPage::modelStateToUI() ui->text->setWordWrap(false); ui->wrapCheckbox->setCheckState(Qt::Unchecked); } + if (m_model->colorLines()) { + ui->text->setColorLines(true); + ui->colorCheckbox->setCheckState(Qt::Checked); + } else { + ui->text->setColorLines(false); + ui->colorCheckbox->setCheckState(Qt::Unchecked); + } if (m_model->suspended()) { ui->trackLogCheckbox->setCheckState(Qt::Unchecked); } else { @@ -202,6 +200,7 @@ void LogPage::UIToModelState() return; } m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); + m_model->setColorLines(ui->colorCheckbox->checkState() == Qt::Checked); m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } @@ -291,6 +290,14 @@ void LogPage::on_wrapCheckbox_clicked(bool checked) m_model->setLineWrap(checked); } +void LogPage::on_colorCheckbox_clicked(bool checked) +{ + ui->text->setColorLines(checked); + if (!m_model) + return; + m_model->setColorLines(checked); +} + void LogPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index 6c259891d..b4d74fb9c 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -35,6 +35,7 @@ #pragma once +#include #include #include @@ -46,7 +47,18 @@ namespace Ui { class LogPage; } class QTextCharFormat; -class LogFormatProxyModel; + +class LogFormatProxyModel : public QIdentityProxyModel { + public: + LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} + QVariant data(const QModelIndex& index, int role) const override; + QFont getFont() const { return m_font; } + void setFont(QFont font) { m_font = font; } + QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const; + + private: + QFont m_font; +}; class LogPage : public QWidget, public BasePage { Q_OBJECT @@ -70,6 +82,7 @@ class LogPage : public QWidget, public BasePage { void on_trackLogCheckbox_clicked(bool checked); void on_wrapCheckbox_clicked(bool checked); + void on_colorCheckbox_clicked(bool checked); void on_findButton_clicked(); void findActivated(); diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui index 31bb368c8..fb8690581 100644 --- a/launcher/ui/pages/instance/LogPage.ui +++ b/launcher/ui/pages/instance/LogPage.ui @@ -74,6 +74,16 @@ + + + + Color lines + + + true + + + @@ -170,6 +180,7 @@ tabWidget trackLogCheckbox wrapCheckbox + colorCheckbox btnCopy btnPaste btnClear diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index ed8ef68d9..0d94843c0 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -46,12 +46,38 @@ #include #include "RecursiveFileSystemWatcher.h" -OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget* parent) - : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), m_watcher(new RecursiveFileSystemWatcher(this)) +OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent) + : QWidget(parent) + , ui(new Ui::OtherLogsPage) + , m_instance(instance) + , m_path(instance->getLogFileRoot()) + , m_fileFilter(fileFilter) + , m_watcher(new RecursiveFileSystemWatcher(this)) + , m_model(new LogModel(this)) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); + m_proxy = new LogFormatProxyModel(this); + + // set up fonts in the log proxy + { + QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if (!conversionOk) { + fontSize = 11; + } + m_proxy->setFont(QFont(fontFamily, fontSize)); + } + + ui->text->setModel(m_proxy); + + m_model->setMaxLines(m_instance->getConsoleMaxLines()); + m_model->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); + m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); + m_proxy->setSourceModel(m_model.get()); + m_watcher->setMatcher(fileFilter); m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); @@ -139,14 +165,8 @@ void OtherLogsPage::on_btnReload_clicked() QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2").arg(m_currentFile, file.errorString())); } else { auto setPlainText = [this](const QString& text) { - QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if (!conversionOk) { - fontSize = 11; - } QTextDocument* doc = ui->text->document(); - doc->setDefaultFont(QFont(fontFamily, fontSize)); + doc->setDefaultFont(m_proxy->getFont()); ui->text->setPlainText(text); }; auto showTooBig = [setPlainText, &file]() { @@ -173,7 +193,37 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - setPlainText(content); + + // If the file is not too big for display, but too slow for syntax highlighting, just show content as plain text + if (content.size() >= 10000000ll || content.isEmpty()) { + setPlainText(content); + return; + } + + // Try to determine a level for each line + if (content.back() == '\n') + content = content.remove(content.size() - 1, 1); + ui->text->clear(); + ui->text->setModel(nullptr); + m_model->clear(); + for (auto& line : content.split('\n')) { + MessageLevel::Enum level = MessageLevel::Unknown; + + // if the launcher part set a log level, use it + auto innerLevel = MessageLevel::fromLine(line); + if (innerLevel != MessageLevel::Unknown) { + level = innerLevel; + } + + // If the level is still undetermined, guess level + if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { + level = m_instance->guessLevel(line, level); + } + + m_model->append(level, line); + } + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); } } @@ -187,6 +237,11 @@ void OtherLogsPage::on_btnCopy_clicked() GuiUtil::setClipboardText(ui->text->toPlainText()); } +void OtherLogsPage::on_btnBottom_clicked() +{ + ui->text->scrollToBottom(); +} + void OtherLogsPage::on_btnDelete_clicked() { if (m_currentFile.isEmpty()) { @@ -263,6 +318,24 @@ void OtherLogsPage::on_btnClean_clicked() } } +void OtherLogsPage::on_wrapCheckbox_clicked(bool checked) +{ + ui->text->setWordWrap(checked); + if (!m_model) + return; + m_model->setLineWrap(checked); + ui->text->scrollToBottom(); +} + +void OtherLogsPage::on_colorCheckbox_clicked(bool checked) +{ + ui->text->setColorLines(checked); + if (!m_model) + return; + m_model->setColorLines(checked); + ui->text->scrollToBottom(); +} + void OtherLogsPage::setControlsEnabled(const bool enabled) { ui->btnReload->setEnabled(enabled); @@ -273,27 +346,21 @@ void OtherLogsPage::setControlsEnabled(const bool enabled) ui->btnClean->setEnabled(enabled); } -// FIXME: HACK, use LogView instead? -static void findNext(QPlainTextEdit* _this, const QString& what, bool reverse) -{ - _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); -} - void OtherLogsPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); bool reverse = modifiers & Qt::ShiftModifier; - findNext(ui->text, ui->searchBar->text(), reverse); + ui->text->findNext(ui->searchBar->text(), reverse); } void OtherLogsPage::findNextActivated() { - findNext(ui->text, ui->searchBar->text(), false); + ui->text->findNext(ui->searchBar->text(), false); } void OtherLogsPage::findPreviousActivated() { - findNext(ui->text, ui->searchBar->text(), true); + ui->text->findNext(ui->searchBar->text(), true); } void OtherLogsPage::findActivated() diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 85a3a2dbc..9394ab9b8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,6 +39,7 @@ #include #include +#include "LogPage.h" #include "ui/pages/BasePage.h" namespace Ui { @@ -51,7 +52,7 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); + explicit OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); ~OtherLogsPage(); QString id() const override { return "logs"; } @@ -71,6 +72,10 @@ class OtherLogsPage : public QWidget, public BasePage { void on_btnCopy_clicked(); void on_btnDelete_clicked(); void on_btnClean_clicked(); + void on_btnBottom_clicked(); + + void on_wrapCheckbox_clicked(bool checked); + void on_colorCheckbox_clicked(bool checked); void on_findButton_clicked(); void findActivated(); @@ -82,8 +87,12 @@ class OtherLogsPage : public QWidget, public BasePage { private: Ui::OtherLogsPage* ui; + InstancePtr m_instance; QString m_path; QString m_currentFile; IPathMatcher::Ptr m_fileFilter; RecursiveFileSystemWatcher* m_watcher; + + LogFormatProxyModel* m_proxy; + shared_qobject_ptr m_model; }; diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 3fdb023fe..b4bb25b08 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -33,90 +33,6 @@ Tab 1 - - - - - - - Find - - - - - - - false - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - Copy the whole log into the clipboard - - - &Copy - - - - - - - Clear the log - - - Delete - - - - - - - Upload the log to the paste service configured in preferences. - - - Upload - - - - - - - Clear the log - - - Clean - - - - - - - Reload - - - - - - - - 0 - 0 - - - - - - @@ -124,12 +40,173 @@ + + + + + + + &Find + + + + + + + Qt::Vertical + + + + + + + Scroll all the way to bottom + + + &Bottom + + + + + + + false + + + false + + + true + + + + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + false + + + + + + + + + + + + 0 + 0 + + + + + + + + Delete the selected log + + + &Delete Selected + + + + + + + Delete all the logs + + + Delete &All + + + + + + + + + + + Wrap lines + + + true + + + + + + + Color lines + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy the whole log into the clipboard + + + &Copy + + + + + + + Upload the log to the paste service configured in preferences + + + &Upload + + + + + + + Reload the contents of the log from the disk + + + &Reload + + + + + + + + + + LogView + QPlainTextEdit +
ui/widgets/LogView.h
+
+
tabWidget selectLogBox @@ -138,6 +215,8 @@ btnPaste btnDelete btnClean + wrapCheckbox + colorCheckbox text searchBar findButton diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 6578b1f12..181893af4 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -60,6 +60,14 @@ void LogView::setWordWrap(bool wrapping) } } +void LogView::setColorLines(bool colorLines) +{ + if (m_colorLines == colorLines) + return; + m_colorLines = colorLines; + repopulate(); +} + void LogView::setModel(QAbstractItemModel* model) { if (m_model) { @@ -130,11 +138,11 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) format.setFont(font.value()); } auto fg = m_model->data(idx, Qt::ForegroundRole); - if (fg.isValid()) { + if (fg.isValid() && m_colorLines) { format.setForeground(fg.value()); } auto bg = m_model->data(idx, Qt::BackgroundRole); - if (bg.isValid()) { + if (bg.isValid() && m_colorLines) { format.setBackground(bg.value()); } cursor.movePosition(QTextCursor::End); diff --git a/launcher/ui/widgets/LogView.h b/launcher/ui/widgets/LogView.h index dde5f8f76..69ca332bb 100644 --- a/launcher/ui/widgets/LogView.h +++ b/launcher/ui/widgets/LogView.h @@ -15,6 +15,7 @@ class LogView : public QPlainTextEdit { public slots: void setWordWrap(bool wrapping); + void setColorLines(bool colorLines); void findNext(const QString& what, bool reverse); void scrollToBottom(); @@ -32,4 +33,5 @@ class LogView : public QPlainTextEdit { QTextCharFormat* m_defaultFormat = nullptr; bool m_scroll = false; bool m_scrolling = false; + bool m_colorLines = true; };