From 84b44fa4678fe21b9c892696cb52f2fcd8cd9b88 Mon Sep 17 00:00:00 2001 From: spycrab Date: Sun, 25 Jun 2017 19:40:01 +0200 Subject: [PATCH] Qt: Implement logging widget (+ configuration) --- Source/Core/DolphinQt2/CMakeLists.txt | 1 + .../Core/DolphinQt2/Config/LoggerWidget.cpp | 411 ++++++++++++++++++ Source/Core/DolphinQt2/Config/LoggerWidget.h | 75 ++++ Source/Core/DolphinQt2/DolphinQt2.vcxproj | 3 + Source/Core/DolphinQt2/MainWindow.cpp | 6 + Source/Core/DolphinQt2/MainWindow.h | 2 + Source/Core/DolphinQt2/Settings.cpp | 8 +- 7 files changed, 502 insertions(+), 4 deletions(-) create mode 100644 Source/Core/DolphinQt2/Config/LoggerWidget.cpp create mode 100644 Source/Core/DolphinQt2/Config/LoggerWidget.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 104ca2ffd3..aea18542f8 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRCS Config/Graphics/GraphicsWindow.cpp Config/Graphics/SoftwareRendererWidget.cpp Config/InfoWidget.cpp + Config/LoggerWidget.cpp Config/Mapping/GCKeyboardEmu.cpp Config/Mapping/GCPadEmu.cpp Config/Mapping/GCPadWiiU.cpp diff --git a/Source/Core/DolphinQt2/Config/LoggerWidget.cpp b/Source/Core/DolphinQt2/Config/LoggerWidget.cpp new file mode 100644 index 0000000000..ca41ebd5a6 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/LoggerWidget.cpp @@ -0,0 +1,411 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Config/LoggerWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "DolphinQt2/Settings.h" + +// Delay in ms between calls of UpdateLog() +constexpr int UPDATE_LOG_DELAY = 100; +// Maximum lines to process at a time +constexpr int MAX_LOG_LINES = 200; +// Timestamp length +constexpr int TIMESTAMP_LENGTH = 10; + +LoggerWidget::LoggerWidget(QWidget* parent) + : QDockWidget(tr("Logging"), parent), m_timer(new QTimer(this)) +{ + setAllowedAreas(Qt::AllDockWidgetAreas); + + CreateWidgets(); + CreateMainLayout(); + LoadSettings(); + + ConnectWidgets(); + OnTabVisibilityChanged(); + + LogManager::GetInstance()->RegisterListener(LogListener::LOG_WINDOW_LISTENER, this); + + connect(m_timer, &QTimer::timeout, this, &LoggerWidget::UpdateLog); + connect(&Settings::Instance(), &Settings::LogVisibilityChanged, this, + &LoggerWidget::OnTabVisibilityChanged); + connect(&Settings::Instance(), &Settings::LogConfigVisibilityChanged, this, + &LoggerWidget::OnTabVisibilityChanged); + + m_timer->start(UPDATE_LOG_DELAY); + + QSettings settings; + + restoreGeometry(settings.value(QStringLiteral("logging/geometry")).toByteArray()); + setFloating(settings.value(QStringLiteral("logging/floating")).toBool()); + + installEventFilter(this); +} + +LoggerWidget::~LoggerWidget() +{ + SaveSettings(); + + LogManager::GetInstance()->RegisterListener(LogListener::LOG_WINDOW_LISTENER, nullptr); +} + +void LoggerWidget::UpdateLog() +{ + std::lock_guard lock(m_log_mutex); + + if (m_log_queue.empty()) + return; + + auto* vscroll = m_log_text->verticalScrollBar(); + auto* hscroll = m_log_text->horizontalScrollBar(); + + // If the vertical scrollbar is within 50 units of the maximum value, count it as being at the + // bottom + bool vscroll_bottom = vscroll->maximum() - vscroll->value() < 50; + + int old_horizontal = hscroll->value(); + int old_vertical = vscroll->value(); + + for (int i = 0; !m_log_queue.empty() && i < MAX_LOG_LINES; i++) + { + m_log_text->append(m_log_queue.front()); + m_log_queue.pop(); + } + + if (hscroll->value() != old_horizontal) + hscroll->setValue(old_horizontal); + + if (vscroll->value() != old_vertical) + { + if (vscroll_bottom) + vscroll->setValue(vscroll->maximum()); + else + vscroll->setValue(old_vertical); + } +} + +void LoggerWidget::UpdateFont() +{ + QFont f; + + switch (m_log_font->currentIndex()) + { + case 0: // Default font + break; + case 1: // Monospace font + f = QFont(QStringLiteral("Monospace")); + f.setStyleHint(QFont::TypeWriter); + break; + } + m_log_text->setFont(f); +} + +void LoggerWidget::CreateWidgets() +{ + m_tab_widget = new QTabWidget; + + // Log + m_tab_log = new QWidget; + m_log_text = new QTextEdit; + m_log_wrap = new QCheckBox(tr("Wrap Text")); + m_log_font = new QComboBox; + m_log_clear = new QPushButton(tr("Clear")); + + m_log_font->addItems({tr("Default Font"), tr("Monospaced Font")}); + + auto* log_layout = new QGridLayout; + m_tab_log->setLayout(log_layout); + log_layout->addWidget(m_log_wrap, 0, 0); + log_layout->addWidget(m_log_font, 0, 1); + log_layout->addWidget(m_log_clear, 0, 2); + log_layout->addWidget(m_log_text, 1, 0, 1, -1); + + m_log_text->setReadOnly(true); + + QPalette palette = m_log_text->palette(); + palette.setColor(QPalette::Base, Qt::black); + palette.setColor(QPalette::Text, Qt::white); + m_log_text->setPalette(palette); + + // Configuration + m_tab_config = new QWidget(); + auto* config_layout = new QVBoxLayout; + m_tab_config->setLayout(config_layout); + + auto* config_verbosity = new QGroupBox(tr("Verbosity")); + auto* verbosity_layout = new QVBoxLayout; + config_verbosity->setLayout(verbosity_layout); + m_verbosity_notice = new QRadioButton(tr("Notice")); + m_verbosity_error = new QRadioButton(tr("Error")); + m_verbosity_warning = new QRadioButton(tr("Warning")); + m_verbosity_info = new QRadioButton(tr("Info")); + + auto* config_outputs = new QGroupBox(tr("Logger Outputs")); + auto* outputs_layout = new QVBoxLayout; + config_outputs->setLayout(outputs_layout); + m_out_file = new QCheckBox(tr("Write to File")); + m_out_console = new QCheckBox(tr("Write to Console")); + m_out_window = new QCheckBox(tr("Write to Window")); + + auto* config_types = new QGroupBox(tr("Log Types")); + auto* types_layout = new QVBoxLayout; + config_types->setLayout(types_layout); + m_types_toggle = new QPushButton(tr("Toggle All Log Types")); + m_types_list = new QListWidget; + + for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; i++) + { + QListWidgetItem* widget = new QListWidgetItem(QString::fromStdString( + LogManager::GetInstance()->GetFullName(static_cast(i)))); + widget->setCheckState(Qt::Unchecked); + m_types_list->addItem(widget); + } + + config_layout->addWidget(config_verbosity); + verbosity_layout->addWidget(m_verbosity_notice); + verbosity_layout->addWidget(m_verbosity_error); + verbosity_layout->addWidget(m_verbosity_warning); + verbosity_layout->addWidget(m_verbosity_info); + + config_layout->addWidget(config_outputs); + outputs_layout->addWidget(m_out_file); + outputs_layout->addWidget(m_out_console); + outputs_layout->addWidget(m_out_window); + + config_layout->addWidget(config_types); + types_layout->addWidget(m_types_toggle); + types_layout->addWidget(m_types_list); +} + +void LoggerWidget::CreateMainLayout() +{ + QWidget* widget = new QWidget; + QVBoxLayout* layout = new QVBoxLayout; + + widget->setLayout(layout); + layout->addWidget(m_tab_widget); + + setWidget(widget); +} + +void LoggerWidget::ConnectWidgets() +{ + // Configuration + connect(m_verbosity_notice, &QRadioButton::toggled, this, &LoggerWidget::SaveSettings); + connect(m_verbosity_error, &QRadioButton::toggled, this, &LoggerWidget::SaveSettings); + connect(m_verbosity_warning, &QRadioButton::toggled, this, &LoggerWidget::SaveSettings); + connect(m_verbosity_info, &QRadioButton::toggled, this, &LoggerWidget::SaveSettings); + + connect(m_out_file, &QCheckBox::toggled, this, &LoggerWidget::SaveSettings); + connect(m_out_console, &QCheckBox::toggled, this, &LoggerWidget::SaveSettings); + connect(m_out_window, &QCheckBox::toggled, this, &LoggerWidget::SaveSettings); + + connect(m_types_toggle, &QPushButton::clicked, [this] { + m_all_enabled = !m_all_enabled; + + // Don't save every time we change an item + m_block_save = true; + + for (int i = 0; i < m_types_list->count(); i++) + m_types_list->item(i)->setCheckState(m_all_enabled ? Qt::Checked : Qt::Unchecked); + + m_block_save = false; + + SaveSettings(); + }); + + connect(m_types_list, &QListWidget::itemChanged, this, &LoggerWidget::SaveSettings); + + // Log + connect(m_log_clear, &QPushButton::clicked, m_log_text, &QTextEdit::clear); + connect(m_log_wrap, &QCheckBox::toggled, this, &LoggerWidget::SaveSettings); + connect(m_log_font, static_cast(&QComboBox::currentIndexChanged), this, + &LoggerWidget::SaveSettings); + + // Window tracking + connect(this, &QDockWidget::topLevelChanged, this, &LoggerWidget::SaveSettings); +} + +void LoggerWidget::LoadSettings() +{ + auto* logmanager = LogManager::GetInstance(); + QSettings settings; + + // Config - Verbosity + int verbosity = logmanager->GetLogLevel(); + m_verbosity_notice->setChecked(verbosity == 1); + m_verbosity_error->setChecked(verbosity == 2); + m_verbosity_warning->setChecked(verbosity == 3); + m_verbosity_info->setChecked(verbosity == 4); + + // Config - Outputs + m_out_file->setChecked(logmanager->IsListenerEnabled(LogListener::FILE_LISTENER)); + m_out_console->setChecked(logmanager->IsListenerEnabled(LogListener::CONSOLE_LISTENER)); + m_out_window->setChecked(logmanager->IsListenerEnabled(LogListener::LOG_WINDOW_LISTENER)); + + // Config - Log Types + for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) + { + bool log_enabled = LogManager::GetInstance()->IsEnabled(static_cast(i)); + + if (!log_enabled) + m_all_enabled = false; + + m_types_list->item(i)->setCheckState(log_enabled ? Qt::Checked : Qt::Unchecked); + } + + // Log - Wrap Lines + m_log_wrap->setChecked(settings.value(QStringLiteral("logging/wraplines")).toBool()); + m_log_text->setLineWrapMode(m_log_wrap->isChecked() ? QTextEdit::WidgetWidth : QTextEdit::NoWrap); + + // Log - Font Selection + // Currently "Debugger Font" is not supported as there is no Qt Debugger, defaulting to Monospace + m_log_font->setCurrentIndex(std::min(settings.value(QStringLiteral("logging/font")).toInt(), 1)); + UpdateFont(); +} + +void LoggerWidget::SaveSettings() +{ + if (m_block_save) + return; + + QSettings settings; + + // Config - Verbosity + int verbosity = 1; + + if (m_verbosity_notice->isChecked()) + verbosity = 1; + + if (m_verbosity_error->isChecked()) + verbosity = 2; + + if (m_verbosity_warning->isChecked()) + verbosity = 3; + + if (m_verbosity_info->isChecked()) + verbosity = 4; + + // Config - Verbosity + LogManager::GetInstance()->SetLogLevel(static_cast(verbosity)); + + // Config - Outputs + LogManager::GetInstance()->EnableListener(LogListener::FILE_LISTENER, m_out_file->isChecked()); + LogManager::GetInstance()->EnableListener(LogListener::CONSOLE_LISTENER, + m_out_console->isChecked()); + LogManager::GetInstance()->EnableListener(LogListener::LOG_WINDOW_LISTENER, + m_out_window->isChecked()); + // Config - Log Types + for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) + { + const auto type = static_cast(i); + bool enabled = m_types_list->item(i)->checkState() == Qt::Checked; + bool was_enabled = LogManager::GetInstance()->IsEnabled(type); + + if (enabled != was_enabled) + LogManager::GetInstance()->SetEnable(type, enabled); + } + + // Log - Wrap Lines + settings.setValue(QStringLiteral("logging/wraplines"), m_log_wrap->isChecked()); + m_log_text->setLineWrapMode(m_log_wrap->isChecked() ? QTextEdit::WidgetWidth : QTextEdit::NoWrap); + + // Log - Font Selection + settings.setValue(QStringLiteral("logging/font"), m_log_font->currentIndex()); + UpdateFont(); + + // Save window geometry + settings.setValue(QStringLiteral("logging/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("logging/floating"), isFloating()); +} + +void LoggerWidget::OnTabVisibilityChanged() +{ + bool log_visible = Settings::Instance().IsLogVisible(); + bool config_visible = Settings::Instance().IsLogConfigVisible(); + + m_tab_widget->clear(); + + if (log_visible) + m_tab_widget->addTab(m_tab_log, tr("Log")); + + if (config_visible) + m_tab_widget->addTab(m_tab_config, tr("Configuration")); + + if (!log_visible && !config_visible) + hide(); + else + show(); +} + +void LoggerWidget::Log(LogTypes::LOG_LEVELS level, const char* text) +{ + std::lock_guard lock(m_log_mutex); + + const char* color = "white"; + + switch (level) + { + case LogTypes::LOG_LEVELS::LERROR: + color = "red"; + break; + case LogTypes::LOG_LEVELS::LWARNING: + color = "yellow"; + break; + case LogTypes::LOG_LEVELS::LNOTICE: + color = "green"; + break; + case LogTypes::LOG_LEVELS::LINFO: + color = "cyan"; + break; + case LogTypes::LOG_LEVELS::LDEBUG: + color = "lightgrey"; + break; + } + + m_log_queue.push(QStringLiteral("%1 %3") + .arg(QString::fromStdString(std::string(text).substr(0, TIMESTAMP_LENGTH)), + QString::fromStdString(color), + QString::fromStdString(std::string(text).substr(TIMESTAMP_LENGTH)))); +} + +bool LoggerWidget::eventFilter(QObject* object, QEvent* event) +{ + QSettings settings; + + if (event->type() == QEvent::Hide || event->type() == QEvent::Resize || + event->type() == QEvent::Move) + { + SaveSettings(); + } + + if (event->type() == QEvent::Close) + { + Settings::Instance().SetLogVisible(false); + Settings::Instance().SetLogConfigVisible(false); + } + + return false; +} diff --git a/Source/Core/DolphinQt2/Config/LoggerWidget.h b/Source/Core/DolphinQt2/Config/LoggerWidget.h new file mode 100644 index 0000000000..958dcbb8f4 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/LoggerWidget.h @@ -0,0 +1,75 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include +#include + +#include "Common/Logging/LogManager.h" + +class QCheckBox; +class QCloseEvent; +class QComboBox; +class QListWidget; +class QPushButton; +class QRadioButton; +class QVBoxLayout; +class QTabWidget; +class QTextEdit; +class QTimer; + +class LoggerWidget final : public QDockWidget, LogListener +{ + Q_OBJECT +public: + explicit LoggerWidget(QWidget* parent = nullptr); + ~LoggerWidget(); + + bool eventFilter(QObject* object, QEvent* event) override; + +private: + void UpdateLog(); + void UpdateFont(); + void CreateWidgets(); + void ConnectWidgets(); + void CreateMainLayout(); + void LoadSettings(); + void SaveSettings(); + + void OnTabVisibilityChanged(); + + void Log(LogTypes::LOG_LEVELS level, const char* text) override; + + // Log + QCheckBox* m_log_wrap; + QComboBox* m_log_font; + QPushButton* m_log_clear; + QVBoxLayout* m_main_layout; + QTabWidget* m_tab_widget; + QTextEdit* m_log_text; + QWidget* m_tab_log; + + // Configuration + QWidget* m_tab_config; + QRadioButton* m_verbosity_notice; + QRadioButton* m_verbosity_error; + QRadioButton* m_verbosity_warning; + QRadioButton* m_verbosity_info; + QCheckBox* m_out_file; + QCheckBox* m_out_console; + QCheckBox* m_out_window; + QPushButton* m_types_toggle; + QListWidget* m_types_list; + + QTimer* m_timer; + + std::mutex m_log_mutex; + std::queue m_log_queue; + + bool m_all_enabled = true; + bool m_block_save = false; +}; diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 18af6e387d..d132c7abc8 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -66,6 +66,7 @@ + @@ -127,6 +128,7 @@ + @@ -172,6 +174,7 @@ + diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 44cc7413f0..8801e39be3 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -34,6 +34,7 @@ #include "DolphinQt2/Config/ControllersWindow.h" #include "DolphinQt2/Config/Graphics/GraphicsWindow.h" +#include "DolphinQt2/Config/LoggerWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" #include "DolphinQt2/Host.h" @@ -134,6 +135,7 @@ void MainWindow::CreateComponents() m_controllers_window = new ControllersWindow(this); m_settings_window = new SettingsWindow(this); m_hotkey_window = new MappingWindow(this, 0); + m_logger_widget = new LoggerWidget(this); connect(this, &MainWindow::EmulationStarted, m_settings_window, &SettingsWindow::EmulationStarted); @@ -199,10 +201,12 @@ void MainWindow::ConnectMenuBar() connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView); connect(m_menu_bar, &MenuBar::ColumnVisibilityToggled, m_game_list, &GameList::OnColumnVisibilityToggled); + connect(m_menu_bar, &MenuBar::GameListPlatformVisibilityToggled, m_game_list, &GameList::OnGameListVisibilityChanged); connect(m_menu_bar, &MenuBar::GameListRegionVisibilityToggled, m_game_list, &GameList::OnGameListVisibilityChanged); + connect(m_menu_bar, &MenuBar::ShowAboutDialog, this, &MainWindow::ShowAboutDialog); connect(this, &MainWindow::EmulationStarted, m_menu_bar, &MenuBar::EmulationStarted); @@ -269,7 +273,9 @@ void MainWindow::ConnectRenderWidget() void MainWindow::ConnectStack() { m_stack->addWidget(m_game_list); + setCentralWidget(m_stack); + addDockWidget(Qt::RightDockWidgetArea, m_logger_widget); } void MainWindow::Open() diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 245428911a..1987a95de2 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -15,6 +15,7 @@ #include "DolphinQt2/ToolBar.h" class HotkeyScheduler; +class LoggerWidget; class MappingWindow; class SettingsWindow; class ControllersWindow; @@ -109,4 +110,5 @@ private: SettingsWindow* m_settings_window; MappingWindow* m_hotkey_window; GraphicsWindow* m_graphics_window; + LoggerWidget* m_logger_widget; }; diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index 0e8a076c88..4e88f3d427 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -146,28 +146,28 @@ QVector Settings::GetProfiles(const InputConfig* config) const bool Settings::IsLogVisible() const { - return SConfig::GetInstance().m_InterfaceLogWindow; + return QSettings().value(QStringLiteral("logging/logvisible")).toBool(); } void Settings::SetLogVisible(bool visible) { if (IsLogVisible() != visible) { - SConfig::GetInstance().m_InterfaceLogWindow = visible; + QSettings().setValue(QStringLiteral("logging/logvisible"), visible); emit LogVisibilityChanged(visible); } } bool Settings::IsLogConfigVisible() const { - return SConfig::GetInstance().m_InterfaceLogConfigWindow; + return QSettings().value(QStringLiteral("logging/logconfigvisible")).toBool(); } void Settings::SetLogConfigVisible(bool visible) { if (IsLogConfigVisible() != visible) { - SConfig::GetInstance().m_InterfaceLogConfigWindow = visible; + QSettings().setValue(QStringLiteral("logging/logconfigvisible"), visible); emit LogConfigVisibilityChanged(visible); } }