diff --git a/launcher/Application.cpp b/launcher/Application.cpp index e3018db0d..ef2530e0d 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -697,16 +697,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ConsoleMaxLines", 100000); m_settings->registerSetting("ConsoleOverflowStop", true); - auto lineSetting = 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; - } - logModel->setMaxLines(maxLines); - logModel->setStopOnOverflow(settings()->get("ConsoleOverflowStop").toBool()); - logModel->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(maxLines)); + logModel->setMaxLines(getConsoleMaxLines()); + logModel->setStopOnOverflow(shouldStopOnConsoleOverflow()); + logModel->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(logModel->getMaxLines())); // Folders m_settings->registerSetting("InstanceDir", "instances"); @@ -1614,6 +1607,23 @@ void Application::updateIsRunning(bool running) m_updateRunning = running; } +int Application::getConsoleMaxLines() const +{ + auto lineSetting = 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; + } + return maxLines; +} + +bool Application::shouldStopOnConsoleOverflow() const +{ + return settings()->get("ConsoleOverflowStop").toBool(); +} + void Application::controllerSucceeded() { auto controller = qobject_cast(QObject::sender()); diff --git a/launcher/Application.h b/launcher/Application.h index 548345c18..3c2c6e11c 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -162,6 +162,9 @@ class Application : public QApplication { QString getModrinthAPIToken(); QString getUserAgent(); + int getConsoleMaxLines() const; + bool shouldStopOnConsoleOverflow() const; + /// this is the root of the 'installation'. Used for automatic updates const QString& root() { return m_rootPath; } diff --git a/launcher/MessageLevel.cpp b/launcher/MessageLevel.cpp index 3516dbdd6..2440f644e 100644 --- a/launcher/MessageLevel.cpp +++ b/launcher/MessageLevel.cpp @@ -54,3 +54,18 @@ MessageLevel::Enum MessageLevel::fromLine(QString& line) } return MessageLevel::Unknown; } + +MessageLevel::Enum MessageLevel::fromLauncherLine(QString& line) +{ + // Level prefix + int startMark = 0; + while (startMark < line.size() && (line[startMark].isDigit() || line[startMark].isSpace() || line[startMark] == '.')) + ++startMark; + int endmark = line.indexOf(":"); + if (startMark < line.size() && endmark != -1) { + auto level = MessageLevel::getLevel(line.left(endmark).mid(startMark)); + line = line.mid(endmark + 2); + return level; + } + return MessageLevel::Unknown; +} diff --git a/launcher/MessageLevel.h b/launcher/MessageLevel.h index 794e2ac39..ce4e8263f 100644 --- a/launcher/MessageLevel.h +++ b/launcher/MessageLevel.h @@ -26,4 +26,7 @@ MessageLevel::Enum getLevel(QtMsgType type); /* Get message level from a line. Line is modified if it was successful. */ MessageLevel::Enum fromLine(QString& line); + +/* Get message level from a line from the launcher log. Line is modified if it was successful. */ +MessageLevel::Enum fromLauncherLine(QString& line); } // namespace MessageLevel diff --git a/launcher/ui/pages/global/LauncherLogPage.cpp b/launcher/ui/pages/global/LauncherLogPage.cpp index 62c866b75..2f8dbac53 100644 --- a/launcher/ui/pages/global/LauncherLogPage.cpp +++ b/launcher/ui/pages/global/LauncherLogPage.cpp @@ -2,7 +2,6 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2024 TheKodeToad * Copyright (c) 2025 Yihe Li * @@ -39,19 +38,18 @@ #include "LauncherLogPage.h" #include "ui_LauncherLogPage.h" -#include "Application.h" - -#include -#include -#include - -#include "launch/LaunchTask.h" -#include "settings/Setting.h" +#include #include "ui/GuiUtil.h" #include "ui/themes/ThemeManager.h" -#include +#include +#include +#include +#include +#include +#include +#include QVariant LogFormatProxyModel::data(const QModelIndex& index, int role) const { @@ -129,7 +127,12 @@ QModelIndex LogFormatProxyModel::find(const QModelIndex& start, const QString& v return QModelIndex(); } -LauncherLogPage::LauncherLogPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherLogPage) +LauncherLogPage::LauncherLogPage(QWidget* parent) + : QWidget(parent) + , ui(new Ui::LauncherLogPage) + , m_model(APPLICATION->logModel) + , m_basePath(APPLICATION->dataRoot()) + , m_logSearchPaths({ "logs" }) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); @@ -148,16 +151,21 @@ LauncherLogPage::LauncherLogPage(QWidget* parent) : QWidget(parent), ui(new Ui:: } ui->text->setModel(m_proxy); - m_proxy->setSourceModel(APPLICATION->logModel.get()); + m_proxy->setSourceModel(m_model.get()); modelStateToUI(); + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &LauncherLogPage::populateSelectLogBox); + auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); - connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); + connect(findShortcut, &QShortcut::activated, this, &LauncherLogPage::findActivated); + auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); - connect(findNextShortcut, SIGNAL(activated()), SLOT(findNextActivated())); - connect(ui->searchBar, SIGNAL(returnPressed()), SLOT(on_findButton_clicked())); + connect(findNextShortcut, &QShortcut::activated, this, &LauncherLogPage::findNextActivated); + auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); - connect(findPreviousShortcut, SIGNAL(activated()), SLOT(findPreviousActivated())); + connect(findPreviousShortcut, &QShortcut::activated, this, &LauncherLogPage::findPreviousActivated); + + connect(ui->searchBar, &QLineEdit::returnPressed, this, &LauncherLogPage::on_findButton_clicked); } LauncherLogPage::~LauncherLogPage() @@ -167,21 +175,21 @@ LauncherLogPage::~LauncherLogPage() void LauncherLogPage::modelStateToUI() { - if (APPLICATION->logModel->wrapLines()) { + if (m_model->wrapLines()) { ui->text->setWordWrap(true); ui->wrapCheckbox->setCheckState(Qt::Checked); } else { ui->text->setWordWrap(false); ui->wrapCheckbox->setCheckState(Qt::Unchecked); } - if (APPLICATION->logModel->colorLines()) { + if (m_model->colorLines()) { ui->text->setColorLines(true); ui->colorCheckbox->setCheckState(Qt::Checked); } else { ui->text->setColorLines(false); ui->colorCheckbox->setCheckState(Qt::Unchecked); } - if (APPLICATION->logModel->suspended()) { + if (m_model->suspended()) { ui->trackLogCheckbox->setCheckState(Qt::Unchecked); } else { ui->trackLogCheckbox->setCheckState(Qt::Checked); @@ -190,47 +198,205 @@ void LauncherLogPage::modelStateToUI() void LauncherLogPage::UIToModelState() { - if (!APPLICATION->logModel) { + if (!m_model) { return; } - APPLICATION->logModel->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); - APPLICATION->logModel->setColorLines(ui->colorCheckbox->checkState() == Qt::Checked); - APPLICATION->logModel->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); + m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); + m_model->setColorLines(ui->colorCheckbox->checkState() == Qt::Checked); + m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); +} + +void LauncherLogPage::retranslate() +{ + ui->retranslateUi(this); +} + +void LauncherLogPage::openedImpl() +{ + const QStringList failedPaths = m_watcher.addPaths(m_logSearchPaths); + + for (const QString& path : m_logSearchPaths) { + if (failedPaths.contains(path)) + qDebug() << "Failed to start watching" << path; + else + qDebug() << "Started watching" << path; + } + + populateSelectLogBox(); +} + +void LauncherLogPage::closedImpl() +{ + const QStringList failedPaths = m_watcher.removePaths(m_logSearchPaths); + + for (const QString& path : m_logSearchPaths) { + if (failedPaths.contains(path)) + qDebug() << "Failed to stop watching" << path; + else + qDebug() << "Stopped watching" << path; + } +} + +void LauncherLogPage::populateSelectLogBox() +{ + const QString prevCurrentFile = m_currentFile; + + ui->selectLogBox->blockSignals(true); + ui->selectLogBox->clear(); + ui->selectLogBox->addItem("Current logs"); + ui->selectLogBox->addItems(getPaths()); + ui->selectLogBox->blockSignals(false); + + if (!prevCurrentFile.isEmpty()) { + const int index = ui->selectLogBox->findText(prevCurrentFile); + if (index != -1) { + ui->selectLogBox->blockSignals(true); + ui->selectLogBox->setCurrentIndex(index); + ui->selectLogBox->blockSignals(false); + setControlsEnabled(true); + // don't refresh file + return; + } else { + setControlsEnabled(false); + } + } else { + ui->selectLogBox->setCurrentIndex(0); + setControlsEnabled(true); + } + + on_selectLogBox_currentIndexChanged(ui->selectLogBox->currentIndex()); +} + +void LauncherLogPage::on_selectLogBox_currentIndexChanged(const int index) +{ + QString file; + if (index > 0) { + file = ui->selectLogBox->itemText(index); + } + + if (index != 0 && (file.isEmpty() || !QFile::exists(FS::PathCombine(m_basePath, file)))) { + m_currentFile = QString(); + ui->text->clear(); + setControlsEnabled(false); + } else { + m_currentFile = file; + reload(); + setControlsEnabled(true); + } +} + +void LauncherLogPage::on_btnReload_clicked() +{ + if (m_currentFile.isEmpty()) { + if (!m_model) + return; + m_model->clear(); + m_container->refreshContainer(); + } else { + reload(); + } +} + +void LauncherLogPage::reload() +{ + if (m_currentFile.isEmpty()) { + m_model = APPLICATION->logModel; + m_proxy->setSourceModel(m_model.get()); + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); + UIToModelState(); + setControlsEnabled(true); + return; + } + + QFile file(FS::PathCombine(m_basePath, m_currentFile)); + if (!file.open(QFile::ReadOnly)) { + setControlsEnabled(false); + ui->btnReload->setEnabled(true); // allow reload + m_currentFile = QString(); + 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) { + QTextDocument* doc = ui->text->document(); + doc->setDefaultFont(m_proxy->getFont()); + ui->text->setPlainText(text); + }; + auto showTooBig = [setPlainText, &file]() { + setPlainText(tr("The file (%1) is too big. You may want to open it in a viewer optimized " + "for large files.") + .arg(file.fileName())); + }; + if (file.size() > (1024ll * 1024ll * 12ll)) { + showTooBig(); + return; + } + MessageLevel::Enum last = MessageLevel::Unknown; + + auto handleLine = [this, &last](QString line) { + if (line.isEmpty()) + return false; + if (line.back() == '\n') + line = line.remove(line.size() - 1, 1); + QString lineTemp = line; // don't edit out the time and level for clarity + MessageLevel::Enum level = MessageLevel::fromLauncherLine(lineTemp); + + last = level; + m_model->append(level, line); + return m_model->isOverFlow(); + }; + + // Try to determine a level for each line + ui->text->clear(); + ui->text->setModel(nullptr); + m_model.reset(new LogModel(this)); + m_model->setMaxLines(APPLICATION->getConsoleMaxLines()); + m_model->setStopOnOverflow(APPLICATION->shouldStopOnConsoleOverflow()); + m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); + m_model->clear(); + if (file.fileName().endsWith(".gz")) { + QString line; + auto error = GZip::readGzFileByBlocks(&file, [&line, handleLine](const QByteArray& d) { + auto block = d; + int newlineIndex = block.indexOf('\n'); + while (newlineIndex != -1) { + line += QString::fromUtf8(block).left(newlineIndex); + block.remove(0, newlineIndex + 1); + if (handleLine(line)) { + line.clear(); + return false; + } + line.clear(); + newlineIndex = block.indexOf('\n'); + } + line += QString::fromUtf8(block); + return true; + }); + if (!error.isEmpty()) { + setPlainText(tr("The file (%1) encountered an error when reading: %2.").arg(file.fileName(), error)); + return; + } else if (!line.isEmpty()) { + handleLine(line); + } + } else { + while (!file.atEnd() && !handleLine(QString::fromUtf8(file.readLine()))) { + } + } + m_proxy->setSourceModel(m_model.get()); + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); + UIToModelState(); + setControlsEnabled(true); + } } void LauncherLogPage::on_btnPaste_clicked() { - if (!APPLICATION->logModel) - return; - - // FIXME: turn this into a proper task and move the upload logic out of GuiUtil! - APPLICATION->logModel->append(MessageLevel::Launcher, - QString("Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); - auto url = GuiUtil::uploadPaste(tr("Launcher Log"), APPLICATION->logModel->toPlainText(), this); - if (!url.has_value()) { - APPLICATION->logModel->append(MessageLevel::Error, QString("Log upload canceled")); - } else if (url->isNull()) { - APPLICATION->logModel->append(MessageLevel::Error, QString("Log upload failed!")); - } else { - APPLICATION->logModel->append(MessageLevel::Launcher, QString("Log uploaded to: %1").arg(url.value())); - } + GuiUtil::uploadPaste(m_currentFile, ui->text->toPlainText(), this); } void LauncherLogPage::on_btnCopy_clicked() { - if (!APPLICATION->logModel) - return; - APPLICATION->logModel->append(MessageLevel::Launcher, - QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); - GuiUtil::setClipboardText(APPLICATION->logModel->toPlainText()); -} - -void LauncherLogPage::on_btnClear_clicked() -{ - if (!APPLICATION->logModel) - return; - APPLICATION->logModel->clear(); - m_container->refreshContainer(); + GuiUtil::setClipboardText(ui->text->toPlainText()); } void LauncherLogPage::on_btnBottom_clicked() @@ -240,25 +406,149 @@ void LauncherLogPage::on_btnBottom_clicked() void LauncherLogPage::on_trackLogCheckbox_clicked(bool checked) { - if (!APPLICATION->logModel) + if (!m_model) return; - APPLICATION->logModel->suspend(!checked); + m_model->suspend(!checked); +} + +void LauncherLogPage::on_btnDelete_clicked() +{ + if (m_currentFile.isEmpty()) { + setControlsEnabled(false); + return; + } + if (QMessageBox::question(this, tr("Confirm Deletion"), + tr("You are about to delete \"%1\".\n" + "This may be permanent and it will be gone from the logs folder.\n\n" + "Are you sure?") + .arg(m_currentFile), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + return; + } + QFile file(FS::PathCombine(m_basePath, m_currentFile)); + + if (FS::trash(file.fileName())) { + return; + } + + if (!file.remove()) { + QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2").arg(m_currentFile, file.errorString())); + } +} + +void LauncherLogPage::on_btnClean_clicked() +{ + auto toDelete = getPaths(); + if (toDelete.isEmpty()) { + return; + } + QMessageBox* messageBox = new QMessageBox(this); + messageBox->setWindowTitle(tr("Confirm Cleanup")); + if (toDelete.size() > 5) { + messageBox->setText(tr("Are you sure you want to delete all log files?")); + messageBox->setDetailedText(toDelete.join('\n')); + } else { + messageBox->setText(tr("Are you sure you want to delete all these files?\n%1").arg(toDelete.join('\n'))); + } + messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + messageBox->setDefaultButton(QMessageBox::Ok); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(QMessageBox::Question); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + + if (messageBox->exec() != QMessageBox::Ok) { + return; + } + QStringList failed; + for (auto item : toDelete) { + QString absolutePath = FS::PathCombine(m_basePath, item); + QFile file(absolutePath); + qDebug() << "Deleting log" << absolutePath; + if (FS::trash(file.fileName())) { + continue; + } + if (!file.remove()) { + failed.push_back(item); + } + } + if (!failed.empty()) { + QMessageBox* messageBoxFailure = new QMessageBox(this); + messageBoxFailure->setWindowTitle(tr("Error")); + if (failed.size() > 5) { + messageBoxFailure->setText(tr("Couldn't delete some files!")); + messageBoxFailure->setDetailedText(failed.join('\n')); + } else { + messageBoxFailure->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n'))); + } + messageBoxFailure->setStandardButtons(QMessageBox::Ok); + messageBoxFailure->setDefaultButton(QMessageBox::Ok); + messageBoxFailure->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBoxFailure->setIcon(QMessageBox::Critical); + messageBoxFailure->setTextInteractionFlags(Qt::TextBrowserInteraction); + messageBoxFailure->exec(); + } } void LauncherLogPage::on_wrapCheckbox_clicked(bool checked) { ui->text->setWordWrap(checked); - if (!APPLICATION->logModel) + if (!m_model) return; - APPLICATION->logModel->setLineWrap(checked); + m_model->setLineWrap(checked); + ui->text->scrollToBottom(); } void LauncherLogPage::on_colorCheckbox_clicked(bool checked) { ui->text->setColorLines(checked); - if (!APPLICATION->logModel) + if (!m_model) return; - APPLICATION->logModel->setColorLines(checked); + m_model->setColorLines(checked); + ui->text->scrollToBottom(); +} + +void LauncherLogPage::setControlsEnabled(const bool enabled) +{ + if (!m_currentFile.isEmpty()) { + ui->btnReload->setText("&Reload"); + ui->btnReload->setToolTip("Reload the contents of the log from the disk"); + ui->btnDelete->setEnabled(enabled); + ui->btnClean->setEnabled(enabled); + ui->trackLogCheckbox->setEnabled(false); + } else { + ui->btnReload->setText("Clear"); + ui->btnReload->setToolTip("Clear the log"); + ui->btnDelete->setEnabled(false); + ui->btnClean->setEnabled(false); + ui->trackLogCheckbox->setEnabled(enabled); + } + ui->btnReload->setEnabled(enabled); + ui->btnCopy->setEnabled(enabled); + ui->btnPaste->setEnabled(enabled); + ui->text->setEnabled(enabled); +} + +QStringList LauncherLogPage::getPaths() +{ + QDir baseDir(m_basePath); + + QStringList result; + + for (QString searchPath : m_logSearchPaths) { + QDir searchDir(searchPath); + + QStringList filters{ "*.log", "*.log.gz" }; + + if (searchPath != m_basePath) + filters.append("*.txt"); + + QStringList entries = searchDir.entryList(filters, QDir::Files | QDir::Readable, QDir::SortFlag::Time); + + for (const QString& name : entries) + result.append(baseDir.relativeFilePath(searchDir.filePath(name))); + } + + return result; } void LauncherLogPage::on_findButton_clicked() @@ -286,8 +576,3 @@ void LauncherLogPage::findActivated() ui->searchBar->selectAll(); } } - -void LauncherLogPage::retranslate() -{ - ui->retranslateUi(this); -} diff --git a/launcher/ui/pages/global/LauncherLogPage.h b/launcher/ui/pages/global/LauncherLogPage.h index bab8a3a1a..4a6fb5882 100644 --- a/launcher/ui/pages/global/LauncherLogPage.h +++ b/launcher/ui/pages/global/LauncherLogPage.h @@ -36,6 +36,7 @@ #pragma once +#include #include #include @@ -48,6 +49,7 @@ namespace Ui { class LauncherLogPage; } class QTextCharFormat; +class RecursiveFileSystemWatcher; class LogFormatProxyModel : public QIdentityProxyModel { public: @@ -74,10 +76,17 @@ class LauncherLogPage : public QWidget, public BasePage { QString helpPage() const override { return "Launcher-Logs"; } void retranslate() override; + void openedImpl() override; + void closedImpl() override; + private slots: + void populateSelectLogBox(); + void on_selectLogBox_currentIndexChanged(int index); + void on_btnReload_clicked(); void on_btnPaste_clicked(); void on_btnCopy_clicked(); - void on_btnClear_clicked(); + void on_btnDelete_clicked(); + void on_btnClean_clicked(); void on_btnBottom_clicked(); void on_trackLogCheckbox_clicked(bool checked); @@ -90,10 +99,21 @@ class LauncherLogPage : public QWidget, public BasePage { void findPreviousActivated(); private: + void reload(); void modelStateToUI(); void UIToModelState(); + void setControlsEnabled(bool enabled); + + QStringList getPaths(); private: Ui::LauncherLogPage* ui; LogFormatProxyModel* m_proxy; + shared_qobject_ptr m_model; + + /** Path to display log paths relative to. */ + QString m_basePath; + QStringList m_logSearchPaths; + QString m_currentFile; + QFileSystemWatcher m_watcher; }; diff --git a/launcher/ui/pages/global/LauncherLogPage.ui b/launcher/ui/pages/global/LauncherLogPage.ui index 44e564f68..189f2fe78 100644 --- a/launcher/ui/pages/global/LauncherLogPage.ui +++ b/launcher/ui/pages/global/LauncherLogPage.ui @@ -33,6 +33,40 @@ Tab 1 + + + + Search: + + + + + + + + + + &Find + + + + + + + Qt::Vertical + + + + + + + Scroll all the way to bottom + + + &Bottom + + + @@ -53,116 +87,120 @@ - - - - - Keep updating - - - true - - + + + + + + + + 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 - - - - - - - Clear the log - - - Clear - - + + + + + + Keep updating + + + true + + + + + + + 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 + + + + - - - - Search: - - - - - - - Find - - - - - - - - - - Scroll all the way to bottom - - - Bottom - - - - - - - Qt::Vertical - - - @@ -178,12 +216,14 @@ tabWidget - trackLogCheckbox - wrapCheckbox - colorCheckbox + selectLogBox + btnReload btnCopy btnPaste - btnClear + btnDelete + btnClean + wrapCheckbox + colorCheckbox text searchBar findButton diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index afd1ff1c1..a90969503 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -211,7 +211,8 @@ void OtherLogsPage::on_btnReload_clicked() MessageLevel::Enum level = MessageLevel::Unknown; // if the launcher part set a log level, use it - auto innerLevel = MessageLevel::fromLine(line); + QString lineTemp = line; // don't edit out the time and level for clarity + auto innerLevel = MessageLevel::fromLine(lineTemp); if (innerLevel != MessageLevel::Unknown) { level = innerLevel; }