diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index affc96666df2..461d95871185 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -472,6 +472,11 @@ true true + + true + true + true + true true @@ -762,6 +767,11 @@ true true + + true + true + true + true true @@ -1072,6 +1082,11 @@ true true + + true + true + true + true true @@ -1362,6 +1377,11 @@ true true + + true + true + true + true true @@ -1537,6 +1557,7 @@ + @@ -2305,6 +2326,24 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index fe82b1474b7e..1c72125b831e 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1090,6 +1090,21 @@ Gui\custom items + + Gui\log + + + Generated Files\Release - LLVM + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug - LLVM + @@ -1427,6 +1442,9 @@ Gui\settings + + Gui\log + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index dbd8fc13acb8..1a3bf5222d8d 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -34,6 +34,7 @@ set(SRC_FILES localized.cpp localized_emu.h log_frame.cpp + log_viewer.cpp main_window.cpp memory_string_searcher.cpp memory_viewer_panel.cpp diff --git a/rpcs3/rpcs3qt/cg_disasm_window.cpp b/rpcs3/rpcs3qt/cg_disasm_window.cpp index e7175c84ed23..deafd861409c 100644 --- a/rpcs3/rpcs3qt/cg_disasm_window.cpp +++ b/rpcs3/rpcs3qt/cg_disasm_window.cpp @@ -63,13 +63,13 @@ cg_disasm_window::cg_disasm_window(std::shared_ptr xSettings): xgu void cg_disasm_window::ShowContextMenu(const QPoint &pos) { - QMenu myMenu; + QMenu menu; QAction* clear = new QAction(tr("&Clear")); QAction* open = new QAction(tr("Open &Cg binary program")); - myMenu.addAction(open); - myMenu.addSeparator(); - myMenu.addAction(clear); + menu.addAction(open); + menu.addSeparator(); + menu.addAction(clear); connect(clear, &QAction::triggered, [this]() { @@ -79,9 +79,10 @@ void cg_disasm_window::ShowContextMenu(const QPoint &pos) connect(open, &QAction::triggered, [this]() { - QString filePath = QFileDialog::getOpenFileName(this, tr("Select Cg program object"), m_path_last, tr("Cg program objects (*.fpo;*.vpo);;")); - if (filePath == NULL) return; - m_path_last = filePath; + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select Cg program object"), m_path_last, tr("Cg program objects (*.fpo;*.vpo);;")); + if (file_path.isEmpty()) + return; + m_path_last = file_path; ShowDisasm(); }); @@ -102,7 +103,7 @@ void cg_disasm_window::ShowContextMenu(const QPoint &pos) origin = mapToGlobal(pos); } - myMenu.exec(origin); + menu.exec(origin); } void cg_disasm_window::ShowDisasm() diff --git a/rpcs3/rpcs3qt/cg_disasm_window.h b/rpcs3/rpcs3qt/cg_disasm_window.h index ae56d3fe16c8..03203f8bd2c0 100644 --- a/rpcs3/rpcs3qt/cg_disasm_window.h +++ b/rpcs3/rpcs3qt/cg_disasm_window.h @@ -4,6 +4,7 @@ #include #include + #include class AsmHighlighter; @@ -16,14 +17,14 @@ class cg_disasm_window : public QWidget private Q_SLOTS: void ShowContextMenu(const QPoint &pos); + +private: void ShowDisasm(); bool IsValidFile(const QMimeData& md, bool save = false); -private: QString m_path_last; QTextEdit* m_disasm_text; QTextEdit* m_glsl_text; - QList m_urls; QAction *openCgBinaryProgram; diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index c093c0791993..87ad01cefc3c 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -129,6 +129,7 @@ namespace gui const gui_save fd_boot_game = gui_save(main_window, "lastExplorePathGAME", ""); const gui_save fd_decrypt_sprx = gui_save(main_window, "lastExplorePathSPRX", ""); const gui_save fd_cg_disasm = gui_save(main_window, "lastExplorePathCGD", ""); + const gui_save fd_log_viewer = gui_save(main_window, "lastExplorePathLOG", ""); const gui_save mw_debugger = gui_save(main_window, "debuggerVisible", false); const gui_save mw_logger = gui_save(main_window, "loggerVisible", true); diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp new file mode 100644 index 000000000000..771bbf2f8e5c --- /dev/null +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -0,0 +1,197 @@ +#include "stdafx.h" + +#include "log_viewer.h" +#include "gui_settings.h" +#include "syntax_highlighter.h" +#include "find_dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +LOG_CHANNEL(gui_log, "GUI"); + +constexpr auto qstr = QString::fromStdString; +inline std::string sstr(const QString& _in) +{ + return _in.toStdString(); +} + +log_viewer::log_viewer(std::shared_ptr settings) + : m_gui_settings(settings) +{ + setWindowTitle(tr("Log Viewer")); + setObjectName("log_viewer"); + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_StyledBackground); + setAcceptDrops(true); + setMinimumSize(QSize(200, 150)); // seems fine on win 10 + resize(QSize(620, 395)); + + m_path_last = m_gui_settings->GetValue(gui::fd_log_viewer).toString(); + + m_log_text = new QTextEdit(this); + m_log_text->setReadOnly(true); + m_log_text->setContextMenuPolicy(Qt::CustomContextMenu); + m_log_text->setWordWrapMode(QTextOption::NoWrap); + m_log_text->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + m_log_text->installEventFilter(this); + + // m_log_text syntax highlighter + m_log_highlighter = new LogHighlighter(m_log_text->document()); + + QHBoxLayout* layout = new QHBoxLayout(); + layout->addWidget(m_log_text); + + setLayout(layout); + + connect(m_log_text, &QWidget::customContextMenuRequested, this, &log_viewer::show_context_menu); + + show_log(); +} + +void log_viewer::show_context_menu(const QPoint& pos) +{ + QMenu menu; + QAction* clear = new QAction(tr("&Clear")); + QAction* open = new QAction(tr("&Open log file")); + + menu.addAction(open); + menu.addSeparator(); + menu.addAction(clear); + + connect(clear, &QAction::triggered, [this]() + { + m_log_text->clear(); + }); + + connect(open, &QAction::triggered, [this]() + { + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select log file"), m_path_last, tr("Log files (*.log);;")); + if (file_path.isEmpty()) + return; + m_path_last = file_path; + show_log(); + }); + + const auto obj = qobject_cast(sender()); + + QPoint origin; + + if (obj == m_log_text) + { + origin = m_log_text->viewport()->mapToGlobal(pos); + } + else + { + origin = mapToGlobal(pos); + } + + menu.exec(origin); +} + +void log_viewer::show_log() +{ + if (m_path_last.isEmpty()) + { + return; + } + + m_log_text->clear(); + + if (QFile file(m_path_last); + file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + m_gui_settings->SetValue(gui::fd_log_viewer, m_path_last); + + QTextStream stream(&file); + + while (!stream.atEnd()) + { + m_log_text->append(stream.readLine()); + } + + file.close(); + } + else + { + gui_log.error("log_viewer: Failed to open %s", sstr(m_path_last)); + } +} + +bool log_viewer::is_valid_file(const QMimeData& md, bool save) +{ + const QList urls = md.urls(); + + if (urls.count() > 1) + { + return false; + } + + const QString suffix = QFileInfo(urls[0].fileName()).suffix().toLower(); + + if (suffix == "log") + { + if (save) + { + m_path_last = urls[0].toLocalFile(); + } + return true; + } + return false; +} + +void log_viewer::dropEvent(QDropEvent* ev) +{ + if (is_valid_file(*ev->mimeData(), true)) + { + show_log(); + } +} + +void log_viewer::dragEnterEvent(QDragEnterEvent* ev) +{ + if (is_valid_file(*ev->mimeData())) + { + ev->accept(); + } +} + +void log_viewer::dragMoveEvent(QDragMoveEvent* ev) +{ + if (is_valid_file(*ev->mimeData())) + { + ev->accept(); + } +} + +void log_viewer::dragLeaveEvent(QDragLeaveEvent* ev) +{ + ev->accept(); +} + +bool log_viewer::eventFilter(QObject* object, QEvent* event) +{ + if (object != m_log_text) + { + return QWidget::eventFilter(object, event); + } + + if (event->type() == QEvent::KeyPress) + { + QKeyEvent* e = static_cast(event); + if (e && e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_F) + { + if (m_find_dialog && m_find_dialog->isVisible()) + m_find_dialog->close(); + + m_find_dialog.reset(new find_dialog(static_cast(object), this)); + } + } + + return QWidget::eventFilter(object, event); +} diff --git a/rpcs3/rpcs3qt/log_viewer.h b/rpcs3/rpcs3qt/log_viewer.h new file mode 100644 index 000000000000..27fe84098342 --- /dev/null +++ b/rpcs3/rpcs3qt/log_viewer.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +class LogHighlighter; +class gui_settings; +class find_dialog; + +class log_viewer : public QWidget +{ + Q_OBJECT + +private Q_SLOTS: + void show_context_menu(const QPoint& pos); + +private: + void show_log(); + bool is_valid_file(const QMimeData& md, bool save = false); + + std::shared_ptr m_gui_settings; + QString m_path_last; + QTextEdit* m_log_text; + LogHighlighter* m_log_highlighter; + std::unique_ptr m_find_dialog; + +public: + explicit log_viewer(std::shared_ptr settings); + +protected: + void dropEvent(QDropEvent* ev) override; + void dragEnterEvent(QDragEnterEvent* ev) override; + void dragMoveEvent(QDragMoveEvent* ev) override; + void dragLeaveEvent(QDragLeaveEvent* ev) override; + bool eventFilter(QObject* object, QEvent* event) override; +}; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 806e7b222fd3..ce689a50910a 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -13,6 +13,7 @@ #include "rpcn_settings_dialog.h" #include "auto_pause_settings_dialog.h" #include "cg_disasm_window.h" +#include "log_viewer.h" #include "memory_string_searcher.h" #include "memory_viewer_panel.h" #include "rsx_debugger.h" @@ -1832,6 +1833,12 @@ void main_window::CreateConnects() cgdw->show(); }); + connect(ui->actionLog_Viewer, &QAction::triggered, [this] + { + log_viewer* viewer = new log_viewer(m_gui_settings); + viewer->show(); + }); + connect(ui->toolskernel_explorerAct, &QAction::triggered, [this] { if (!m_kernel_explorer) diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index e55d0100c141..4b57e140dcf4 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -248,6 +248,7 @@ Utilities + @@ -1110,6 +1111,11 @@ Configure RPCN + + + Log Viewer + + diff --git a/rpcs3/rpcs3qt/syntax_highlighter.cpp b/rpcs3/rpcs3qt/syntax_highlighter.cpp index ac6d3b948ea0..f5468d61972c 100644 --- a/rpcs3/rpcs3qt/syntax_highlighter.cpp +++ b/rpcs3/rpcs3qt/syntax_highlighter.cpp @@ -1,4 +1,5 @@ #include "syntax_highlighter.h" +#include "qt_utils.h" Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) { @@ -14,7 +15,7 @@ void Highlighter::addRule(const QString &pattern, const QBrush &brush) void Highlighter::highlightBlock(const QString &text) { - foreach (const HighlightingRule &rule, highlightingRules) + for (const HighlightingRule &rule : highlightingRules) { QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); while (matchIterator.hasNext()) @@ -34,8 +35,8 @@ void Highlighter::highlightBlock(const QString &text) while (startIndex >= 0) { - QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); - int endIndex = match.capturedStart(); + const QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); + const int endIndex = match.capturedStart(); int commentLength = 0; if (endIndex == -1) @@ -52,6 +53,18 @@ void Highlighter::highlightBlock(const QString &text) } } +LogHighlighter::LogHighlighter(QTextDocument* parent) : Highlighter(parent) +{ + //addRule("^[^·].*$", gui::utils::get_label_color("log_level_always")); // unused for now + addRule("^·F.*$", gui::utils::get_label_color("log_level_fatal")); + addRule("^·E.*$", gui::utils::get_label_color("log_level_error")); + addRule("^·U.*$", gui::utils::get_label_color("log_level_todo")); + addRule("^·S.*$", gui::utils::get_label_color("log_level_success")); + addRule("^·W.*$", gui::utils::get_label_color("log_level_warning")); + addRule("^·!.*$", gui::utils::get_label_color("log_level_notice")); + addRule("^·T.*$", gui::utils::get_label_color("log_level_trace")); +} + AsmHighlighter::AsmHighlighter(QTextDocument *parent) : Highlighter(parent) { addRule("^[A-Z0-9]+", Qt::darkBlue); // Instructions @@ -65,7 +78,7 @@ AsmHighlighter::AsmHighlighter(QTextDocument *parent) : Highlighter(parent) GlslHighlighter::GlslHighlighter(QTextDocument *parent) : Highlighter(parent) { - QStringList keywordPatterns = QStringList() + const QStringList keywordPatterns = QStringList() // Selection-Iteration-Jump Statements: << "if" << "else" << "switch" << "case" << "default" << "for" << "while" << "do" << "foreach" //? @@ -155,7 +168,7 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) : Highlighter(parent) << "r16_snorm" << "r32ui" << "r8_snorm" << "r16ui"; - foreach (const QString &pattern, keywordPatterns) + for (const QString &pattern : keywordPatterns) addRule("\\b" + pattern + "\\b", Qt::darkBlue); // normal words like: soka, nani, or gomen addRule("\\bGL_(?:[A-Z]|_)+\\b", Qt::darkMagenta); // constants like: GL_OMAE_WA_MOU_SHINDEIRU diff --git a/rpcs3/rpcs3qt/syntax_highlighter.h b/rpcs3/rpcs3qt/syntax_highlighter.h index 4cd1d36c9b2a..8e043b9d6d82 100644 --- a/rpcs3/rpcs3qt/syntax_highlighter.h +++ b/rpcs3/rpcs3qt/syntax_highlighter.h @@ -29,6 +29,14 @@ class Highlighter : public QSyntaxHighlighter QTextCharFormat multiLineCommentFormat; }; +class LogHighlighter : public Highlighter +{ + Q_OBJECT + +public: + LogHighlighter(QTextDocument* parent = 0); +}; + class AsmHighlighter : public Highlighter { Q_OBJECT