From 1aa8d7bc13bf53d00eb60a4fd439404446f53105 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 3 Jun 2025 15:44:11 +0800 Subject: [PATCH] Reuse OtherLogsPage directly Signed-off-by: Yihe Li --- launcher/Application.cpp | 4 +- launcher/CMakeLists.txt | 3 - launcher/InstancePageProvider.h | 2 +- launcher/ui/pages/global/LauncherLogPage.cpp | 578 ------------------- launcher/ui/pages/global/LauncherLogPage.h | 119 ---- launcher/ui/pages/global/LauncherLogPage.ui | 233 -------- launcher/ui/pages/instance/LogPage.cpp | 76 +++ launcher/ui/pages/instance/LogPage.h | 14 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 160 ++++- launcher/ui/pages/instance/OtherLogsPage.h | 16 +- launcher/ui/pages/instance/OtherLogsPage.ui | 10 + 11 files changed, 251 insertions(+), 964 deletions(-) delete mode 100644 launcher/ui/pages/global/LauncherLogPage.cpp delete mode 100644 launcher/ui/pages/global/LauncherLogPage.h delete mode 100644 launcher/ui/pages/global/LauncherLogPage.ui diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ef2530e0d..86e454802 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -63,10 +63,10 @@ #include "ui/pages/global/ExternalToolsPage.h" #include "ui/pages/global/JavaPage.h" #include "ui/pages/global/LanguagePage.h" -#include "ui/pages/global/LauncherLogPage.h" #include "ui/pages/global/LauncherPage.h" #include "ui/pages/global/MinecraftPage.h" #include "ui/pages/global/ProxyPage.h" +#include "ui/pages/instance/OtherLogsPage.h" #include "ui/setupwizard/AutoJavaWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h" @@ -905,7 +905,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPageCreator([]() { return new OtherLogsPage("launcher-logs", tr("Logs"), "Launcher-Logs"); }); } PixmapCache::setInstance(new PixmapCache(this)); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index cd9903067..a7ccb809d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -959,8 +959,6 @@ SET(LAUNCHER_SOURCES ui/pages/global/MinecraftPage.h ui/pages/global/LauncherPage.cpp ui/pages/global/LauncherPage.h - ui/pages/global/LauncherLogPage.cpp - ui/pages/global/LauncherLogPage.h ui/pages/global/AppearancePage.h ui/pages/global/ProxyPage.cpp ui/pages/global/ProxyPage.h @@ -1205,7 +1203,6 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/global/AccountListPage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui - ui/pages/global/LauncherLogPage.ui ui/pages/global/APIPage.ui ui/pages/global/ProxyPage.ui ui/pages/global/ExternalToolsPage.ui diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 2c2b0b580..258ed5aa5 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -46,7 +46,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { // values.append(new GameOptionsPage(onesix.get())); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); values.append(new InstanceSettingsPage(onesix)); - values.append(new OtherLogsPage(inst)); + values.append(new OtherLogsPage("logs", tr("Other logs"), "Other-Logs", inst)); return values; } diff --git a/launcher/ui/pages/global/LauncherLogPage.cpp b/launcher/ui/pages/global/LauncherLogPage.cpp deleted file mode 100644 index 2f8dbac53..000000000 --- a/launcher/ui/pages/global/LauncherLogPage.cpp +++ /dev/null @@ -1,578 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2024 TheKodeToad - * Copyright (c) 2025 Yihe Li - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "LauncherLogPage.h" -#include "ui_LauncherLogPage.h" - -#include - -#include "ui/GuiUtil.h" -#include "ui/themes/ThemeManager.h" - -#include -#include -#include -#include -#include -#include -#include - -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); - - 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; - } - } - - return QIdentityProxyModel::data(index, role); -} - -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(); - } - 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(); -} - -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(); - - 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_proxy->setSourceModel(m_model.get()); - modelStateToUI(); - - connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &LauncherLogPage::populateSelectLogBox); - - auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); - connect(findShortcut, &QShortcut::activated, this, &LauncherLogPage::findActivated); - - auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); - connect(findNextShortcut, &QShortcut::activated, this, &LauncherLogPage::findNextActivated); - - auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); - connect(findPreviousShortcut, &QShortcut::activated, this, &LauncherLogPage::findPreviousActivated); - - connect(ui->searchBar, &QLineEdit::returnPressed, this, &LauncherLogPage::on_findButton_clicked); -} - -LauncherLogPage::~LauncherLogPage() -{ - delete ui; -} - -void LauncherLogPage::modelStateToUI() -{ - if (m_model->wrapLines()) { - ui->text->setWordWrap(true); - ui->wrapCheckbox->setCheckState(Qt::Checked); - } else { - 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 { - ui->trackLogCheckbox->setCheckState(Qt::Checked); - } -} - -void LauncherLogPage::UIToModelState() -{ - if (!m_model) { - 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); -} - -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() -{ - GuiUtil::uploadPaste(m_currentFile, ui->text->toPlainText(), this); -} - -void LauncherLogPage::on_btnCopy_clicked() -{ - GuiUtil::setClipboardText(ui->text->toPlainText()); -} - -void LauncherLogPage::on_btnBottom_clicked() -{ - ui->text->scrollToBottom(); -} - -void LauncherLogPage::on_trackLogCheckbox_clicked(bool checked) -{ - if (!m_model) - return; - 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 (!m_model) - return; - m_model->setLineWrap(checked); - ui->text->scrollToBottom(); -} - -void LauncherLogPage::on_colorCheckbox_clicked(bool checked) -{ - ui->text->setColorLines(checked); - if (!m_model) - return; - 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() -{ - auto modifiers = QApplication::keyboardModifiers(); - bool reverse = modifiers & Qt::ShiftModifier; - ui->text->findNext(ui->searchBar->text(), reverse); -} - -void LauncherLogPage::findNextActivated() -{ - ui->text->findNext(ui->searchBar->text(), false); -} - -void LauncherLogPage::findPreviousActivated() -{ - ui->text->findNext(ui->searchBar->text(), true); -} - -void LauncherLogPage::findActivated() -{ - // focus the search bar if it doesn't have focus - if (!ui->searchBar->hasFocus()) { - ui->searchBar->setFocus(); - ui->searchBar->selectAll(); - } -} diff --git a/launcher/ui/pages/global/LauncherLogPage.h b/launcher/ui/pages/global/LauncherLogPage.h deleted file mode 100644 index 4a6fb5882..000000000 --- a/launcher/ui/pages/global/LauncherLogPage.h +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * Copyright (c) 2025 Yihe Li - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include -#include "BaseInstance.h" -#include "launch/LaunchTask.h" -#include "ui/pages/BasePage.h" - -namespace Ui { -class LauncherLogPage; -} -class QTextCharFormat; -class RecursiveFileSystemWatcher; - -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 LauncherLogPage : public QWidget, public BasePage { - Q_OBJECT - - public: - explicit LauncherLogPage(QWidget* parent = 0); - ~LauncherLogPage(); - - QString displayName() const override { return tr("Logs"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("log"); } - QString id() const override { return "launcher-console"; } - 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_btnDelete_clicked(); - void on_btnClean_clicked(); - void on_btnBottom_clicked(); - - void on_trackLogCheckbox_clicked(bool checked); - void on_wrapCheckbox_clicked(bool checked); - void on_colorCheckbox_clicked(bool checked); - - void on_findButton_clicked(); - void findActivated(); - void findNextActivated(); - 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 deleted file mode 100644 index 189f2fe78..000000000 --- a/launcher/ui/pages/global/LauncherLogPage.ui +++ /dev/null @@ -1,233 +0,0 @@ - - - LauncherLogPage - - - - 0 - 0 - 825 - 782 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - Tab 1 - - - - - - Search: - - - - - - - - - - &Find - - - - - - - Qt::Vertical - - - - - - - Scroll all the way to bottom - - - &Bottom - - - - - - - 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 - - - - - - - - - - - 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 - - - - - - - - - - - - - - - - LogView - QPlainTextEdit -
ui/widgets/LogView.h
-
-
- - tabWidget - selectLogBox - btnReload - btnCopy - btnPaste - btnDelete - btnClean - wrapCheckbox - colorCheckbox - text - searchBar - findButton - - - -
diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index d1691ff16..7897a2932 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -52,6 +52,82 @@ #include +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); + + 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; + } + } + + return QIdentityProxyModel::data(index, role); +} + +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(); + } + 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(); +} + LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { ui->setupUi(this); diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index caa870cbc..b4d74fb9c 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -42,11 +42,23 @@ #include "BaseInstance.h" #include "launch/LaunchTask.h" #include "ui/pages/BasePage.h" -#include "ui/pages/global/LauncherLogPage.h" namespace Ui { class LogPage; } +class QTextCharFormat; + +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 diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index a90969503..281e5be27 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -40,6 +40,7 @@ #include #include "ui/GuiUtil.h" +#include "ui/themes/ThemeManager.h" #include #include @@ -49,18 +50,26 @@ #include #include -OtherLogsPage::OtherLogsPage(InstancePtr instance, QWidget* parent) +OtherLogsPage::OtherLogsPage(QString id, QString displayName, QString helpPage, InstancePtr instance, QWidget* parent) : QWidget(parent) + , m_id(id) + , m_displayName(displayName) + , m_helpPage(helpPage) , ui(new Ui::OtherLogsPage) , m_instance(instance) - , m_basePath(instance->gameRoot()) - , m_logSearchPaths(instance->getLogFileSearchPaths()) - , m_model(new LogModel(this)) + , m_basePath(instance ? instance->gameRoot() : APPLICATION->dataRoot()) + , m_logSearchPaths(instance ? instance->getLogFileSearchPaths() : QStringList{ "logs" }) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); m_proxy = new LogFormatProxyModel(this); + if (m_instance) { + m_model.reset(new LogModel(this)); + ui->trackLogCheckbox->setVisible(false); + } else { + m_model = APPLICATION->logModel; + } // set up fonts in the log proxy { @@ -75,9 +84,13 @@ OtherLogsPage::OtherLogsPage(InstancePtr instance, QWidget* parent) 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())); + if (m_instance) { + 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())); + } else { + modelStateToUI(); + } m_proxy->setSourceModel(m_model.get()); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &OtherLogsPage::populateSelectLogBox); @@ -99,6 +112,39 @@ OtherLogsPage::~OtherLogsPage() delete ui; } +void OtherLogsPage::modelStateToUI() +{ + if (m_model->wrapLines()) { + ui->text->setWordWrap(true); + ui->wrapCheckbox->setCheckState(Qt::Checked); + } else { + 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 { + ui->trackLogCheckbox->setCheckState(Qt::Checked); + } +} + +void OtherLogsPage::UIToModelState() +{ + if (!m_model) { + 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); +} + void OtherLogsPage::retranslate() { ui->retranslateUi(this); @@ -136,6 +182,8 @@ void OtherLogsPage::populateSelectLogBox() ui->selectLogBox->blockSignals(true); ui->selectLogBox->clear(); + if (!m_instance) + ui->selectLogBox->addItem("Current logs"); ui->selectLogBox->addItems(getPaths()); ui->selectLogBox->blockSignals(false); @@ -151,6 +199,9 @@ void OtherLogsPage::populateSelectLogBox() } else { setControlsEnabled(false); } + } else if (!m_instance) { + ui->selectLogBox->setCurrentIndex(0); + setControlsEnabled(true); } on_selectLogBox_currentIndexChanged(ui->selectLogBox->currentIndex()); @@ -159,27 +210,49 @@ void OtherLogsPage::populateSelectLogBox() void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) { QString file; - if (index != -1) { + if (index > 0 || (index == 0 && m_instance)) { file = ui->selectLogBox->itemText(index); } - if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_basePath, file))) { + if ((index != 0 || m_instance) && (file.isEmpty() || !QFile::exists(FS::PathCombine(m_basePath, file)))) { m_currentFile = QString(); ui->text->clear(); setControlsEnabled(false); } else { m_currentFile = file; - on_btnReload_clicked(); + reload(); setControlsEnabled(true); } } void OtherLogsPage::on_btnReload_clicked() +{ + if (!m_instance && m_currentFile.isEmpty()) { + if (!m_model) + return; + m_model->clear(); + m_container->refreshContainer(); + } else { + reload(); + } +} + +void OtherLogsPage::reload() { if (m_currentFile.isEmpty()) { - setControlsEnabled(false); + if (m_instance) { + setControlsEnabled(false); + } else { + 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); @@ -210,16 +283,20 @@ void OtherLogsPage::on_btnReload_clicked() line = line.remove(line.size() - 1, 1); MessageLevel::Enum level = MessageLevel::Unknown; - // if the launcher part set a log level, use it QString lineTemp = line; // don't edit out the time and level for clarity - auto innerLevel = MessageLevel::fromLine(lineTemp); - if (innerLevel != MessageLevel::Unknown) { - level = innerLevel; - } + if (!m_instance) { + level = MessageLevel::fromLauncherLine(lineTemp); + } else { + // if the launcher part set a log level, use it + auto innerLevel = MessageLevel::fromLine(lineTemp); + 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 = LogParser::guessLevel(line, last); + // If the level is still undetermined, guess level + if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { + level = LogParser::guessLevel(line, last); + } } last = level; @@ -230,6 +307,12 @@ void OtherLogsPage::on_btnReload_clicked() // Try to determine a level for each line ui->text->clear(); ui->text->setModel(nullptr); + if (!m_instance) { + 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; @@ -259,8 +342,17 @@ void OtherLogsPage::on_btnReload_clicked() while (!file.atEnd() && !handleLine(QString::fromUtf8(file.readLine()))) { } } - ui->text->setModel(m_proxy); - ui->text->scrollToBottom(); + + if (m_instance) { + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); + } else { + m_proxy->setSourceModel(m_model.get()); + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); + UIToModelState(); + setControlsEnabled(true); + } } } @@ -279,6 +371,13 @@ void OtherLogsPage::on_btnBottom_clicked() ui->text->scrollToBottom(); } +void OtherLogsPage::on_trackLogCheckbox_clicked(bool checked) +{ + if (!m_model) + return; + m_model->suspend(!checked); +} + void OtherLogsPage::on_btnDelete_clicked() { if (m_currentFile.isEmpty()) { @@ -377,12 +476,27 @@ void OtherLogsPage::on_colorCheckbox_clicked(bool checked) void OtherLogsPage::setControlsEnabled(const bool enabled) { + if (m_instance) { + ui->btnDelete->setEnabled(enabled); + ui->btnClean->setEnabled(enabled); + } else 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->btnDelete->setEnabled(enabled); ui->btnCopy->setEnabled(enabled); ui->btnPaste->setEnabled(enabled); ui->text->setEnabled(enabled); - ui->btnClean->setEnabled(enabled); } QStringList OtherLogsPage::getPaths() diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 70eb145fb..4104d8f3c 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -53,13 +53,13 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(InstancePtr instance, QWidget* parent = 0); + explicit OtherLogsPage(QString id, QString displayName, QString helpPage, InstancePtr instance = nullptr, QWidget* parent = 0); ~OtherLogsPage(); - QString id() const override { return "logs"; } - QString displayName() const override { return tr("Other logs"); } + QString id() const override { return m_id; } + QString displayName() const override { return m_displayName; } QIcon icon() const override { return APPLICATION->getThemedIcon("log"); } - QString helpPage() const override { return "other-Logs"; } + QString helpPage() const override { return m_helpPage; } void retranslate() override; void openedImpl() override; @@ -75,6 +75,7 @@ class OtherLogsPage : public QWidget, public BasePage { void on_btnClean_clicked(); void on_btnBottom_clicked(); + void on_trackLogCheckbox_clicked(bool checked); void on_wrapCheckbox_clicked(bool checked); void on_colorCheckbox_clicked(bool checked); @@ -84,11 +85,18 @@ class OtherLogsPage : public QWidget, public BasePage { void findPreviousActivated(); private: + void reload(); + void modelStateToUI(); + void UIToModelState(); void setControlsEnabled(bool enabled); QStringList getPaths(); private: + QString m_id; + QString m_displayName; + QString m_helpPage; + Ui::OtherLogsPage* ui; InstancePtr m_instance; /** Path to display log paths relative to. */ diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 6d1a46139..7d60de5c4 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -127,6 +127,16 @@ + + + + Keep updating + + + true + + +