From 369eeac86a3f5286dfafcd805643e5657673c362 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:31:54 +0000 Subject: [PATCH 1/3] 3rdparty: Add KDDockWidgets --- .../scripts/linux/build-dependencies-qt.sh | 14 +++++++++++++- .../scripts/macos/build-dependencies-universal.sh | 14 +++++++++++++- .../workflows/scripts/macos/build-dependencies.sh | 14 +++++++++++++- .../scripts/windows/build-dependencies-arm64.bat | 11 +++++++++++ .../scripts/windows/build-dependencies.bat | 11 +++++++++++ cmake/BuildParameters.cmake | 2 ++ cmake/SearchForStuff.cmake | 3 +++ 7 files changed, 66 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh index f0d9d8742a..4623301bc1 100755 --- a/.github/workflows/scripts/linux/build-dependencies-qt.sh +++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh @@ -22,6 +22,7 @@ LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29 SDL=SDL2-2.30.11 QT=6.8.1 ZSTD=1.5.6 +KDDOCKWIDGETS=2.2.1 SHADERC=2024.1 SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec @@ -49,6 +50,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528 shaderc-$SHADE aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f shaderc-glslang-$SHADERC_GLSLANG.tar.gz 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697 shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz 03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47 shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz +8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5 KDDockWidgets-$KDDOCKWIDGETS.tar.gz EOF curl -L \ @@ -68,7 +70,8 @@ curl -L \ -o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \ -o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \ -o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \ - -o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" + -o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" \ + -o "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" "https://github.com/KDAB/KDDockWidgets/archive/v$KDDOCKWIDGETS.tar.gz" shasum -a 256 --check SHASUMS @@ -233,6 +236,15 @@ cmake --build . --parallel ninja install cd ../../ +echo "Building KDDockWidgets..." +rm -fr "KDDockWidgets-$KDDOCKWIDGETS" +tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" +cd "KDDockWidgets-$KDDOCKWIDGETS" +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DKDDockWidgets_QT6=true -DKDDockWidgets_STATIC=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja +cmake --build build --parallel +ninja -C build install +cd .. + echo "Building shaderc..." rm -fr "shaderc-$SHADERC" tar xf "shaderc-$SHADERC.tar.gz" diff --git a/.github/workflows/scripts/macos/build-dependencies-universal.sh b/.github/workflows/scripts/macos/build-dependencies-universal.sh index 6bf5f1a69e..5e874104a7 100755 --- a/.github/workflows/scripts/macos/build-dependencies-universal.sh +++ b/.github/workflows/scripts/macos/build-dependencies-universal.sh @@ -49,6 +49,7 @@ LIBWEBP=1.5.0 FFMPEG=6.0 MOLTENVK=1.2.9 QT=6.7.2 +KDDOCKWIDGETS=2.2.1 SHADERC=2024.1 SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec @@ -93,6 +94,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528 shaderc-$SHADE aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f shaderc-glslang-$SHADERC_GLSLANG.tar.gz 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697 shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz 03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47 shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz +8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5 KDDockWidgets-$KDDOCKWIDGETS.tar.gz EOF curl -C - -L \ @@ -114,7 +116,8 @@ curl -C - -L \ -o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \ -o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \ -o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \ - -o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" + -o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" \ + -o "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" "https://github.com/KDAB/KDDockWidgets/archive/v$KDDOCKWIDGETS.tar.gz" shasum -a 256 --check SHASUMS @@ -355,6 +358,15 @@ make -C build "-j$NPROCS" make -C build install cd .. +echo "Building KDDockWidgets..." +rm -fr "KDDockWidgets-$KDDOCKWIDGETS" +tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" +cd "KDDockWidgets-$KDDOCKWIDGETS" +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DKDDockWidgets_QT6=true -DKDDockWidgets_STATIC=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build +cmake --build build --parallel +cmake --install build +cd .. + echo "Installing Qt Translations..." rm -fr "qttranslations-everywhere-src-$QT" tar xf "qttranslations-everywhere-src-$QT.tar.xz" diff --git a/.github/workflows/scripts/macos/build-dependencies.sh b/.github/workflows/scripts/macos/build-dependencies.sh index 7d287c2830..c6d1a767bd 100755 --- a/.github/workflows/scripts/macos/build-dependencies.sh +++ b/.github/workflows/scripts/macos/build-dependencies.sh @@ -31,6 +31,7 @@ LIBWEBP=1.5.0 FFMPEG=6.0 MOLTENVK=1.2.9 QT=6.7.2 +KDDOCKWIDGETS=2.2.1 SHADERC=2024.1 SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec @@ -73,6 +74,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528 shaderc-$SHADE aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f shaderc-glslang-$SHADERC_GLSLANG.tar.gz 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697 shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz 03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47 shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz +8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5 KDDockWidgets-$KDDOCKWIDGETS.tar.gz EOF curl -L \ @@ -94,7 +96,8 @@ curl -L \ -o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \ -o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \ -o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \ - -o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" + -o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" \ + -o "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" "https://github.com/KDAB/KDDockWidgets/archive/v$KDDOCKWIDGETS.tar.gz" shasum -a 256 --check SHASUMS @@ -313,6 +316,15 @@ make -C build "-j$NPROCS" make -C build install cd .. +echo "Building KDDockWidgets..." +rm -fr "KDDockWidgets-$KDDOCKWIDGETS" +tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" +cd "KDDockWidgets-$KDDOCKWIDGETS" +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DKDDockWidgets_QT6=true -DKDDockWidgets_STATIC=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build +cmake --build build --parallel +cmake --install build +cd .. + echo "Installing Qt Translations..." rm -fr "qttranslations-everywhere-src-$QT" tar xf "qttranslations-everywhere-src-$QT.tar.xz" diff --git a/.github/workflows/scripts/windows/build-dependencies-arm64.bat b/.github/workflows/scripts/windows/build-dependencies-arm64.bat index 7eedc41dc4..5081c19eb0 100644 --- a/.github/workflows/scripts/windows/build-dependencies-arm64.bat +++ b/.github/workflows/scripts/windows/build-dependencies-arm64.bat @@ -54,6 +54,7 @@ set WEBP=1.5.0 set ZLIB=1.3.1 set ZLIBSHORT=131 set ZSTD=1.5.6 +set KDDOCKWIDGETS=2.2.1 set SHADERC=2024.1 set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec @@ -75,6 +76,7 @@ call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 3b1c3b46e416d36931efd34663122d7f51b550c87f74de2d38249516fe7d8be5 || goto error call :downloadfile "zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" https://github.com/facebook/zstd/commit/fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch 8df152f4969b308546306c074628de761f0b80265de7de534e3822fab22d7535 || goto error +call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v2.2.1.zip" 78b5e242bf47476e150175b7de934ab84069459e151beb2d5ce84fd067138aa5 || goto error call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" 6c9f42ed6bf42750f5369b089909abfdcf0101488b4a1f41116d5159d00af8e7 || goto error call :downloadfile "shaderc-glslang-%SHADERC_GLSLANG%.zip" "https://github.com/KhronosGroup/glslang/archive/%SHADERC_GLSLANG%.zip" 03ad8a6fa987af4653d0cfe6bdaed41bcf617f1366a151fb1574da75950cd3e8 || goto error @@ -243,6 +245,15 @@ cmake --build . --parallel || goto error ninja install || goto error cd ..\.. || goto error +echo "Building KDDockWidgets..." +rmdir /S /Q "KDDockWidgets-%KDDOCKWIDGETS%" +%SEVENZIP% x "KDDockWidgets-%KDDOCKWIDGETS%.zip" || goto error +cd "KDDockWidgets-%KDDOCKWIDGETS%" || goto error +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=true -DKDDockWidgets_STATIC=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja || goto error +cmake --build build --parallel || goto error +ninja -C build install || goto error +cd .. || goto error + echo Building shaderc... rmdir /S /Q "shaderc-%SHADERC%" %SEVENZIP% x "shaderc-%SHADERC%.zip" || goto error diff --git a/.github/workflows/scripts/windows/build-dependencies.bat b/.github/workflows/scripts/windows/build-dependencies.bat index 3fbc2a0ac5..407ba1954a 100644 --- a/.github/workflows/scripts/windows/build-dependencies.bat +++ b/.github/workflows/scripts/windows/build-dependencies.bat @@ -52,6 +52,7 @@ set WEBP=1.5.0 set ZLIB=1.3.1 set ZLIBSHORT=131 set ZSTD=1.5.6 +set KDDOCKWIDGETS=2.2.1 set SHADERC=2024.1 set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec @@ -73,6 +74,7 @@ call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 3b1c3b46e416d36931efd34663122d7f51b550c87f74de2d38249516fe7d8be5 || goto error call :downloadfile "zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" https://github.com/facebook/zstd/commit/fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch 8df152f4969b308546306c074628de761f0b80265de7de534e3822fab22d7535 || goto error +call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v2.2.1.zip" 78b5e242bf47476e150175b7de934ab84069459e151beb2d5ce84fd067138aa5 || goto error call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" 6c9f42ed6bf42750f5369b089909abfdcf0101488b4a1f41116d5159d00af8e7 || goto error call :downloadfile "shaderc-glslang-%SHADERC_GLSLANG%.zip" "https://github.com/KhronosGroup/glslang/archive/%SHADERC_GLSLANG%.zip" 03ad8a6fa987af4653d0cfe6bdaed41bcf617f1366a151fb1574da75950cd3e8 || goto error @@ -247,6 +249,15 @@ cmake --build . --parallel || goto error ninja install || goto error cd ..\.. || goto error +echo "Building KDDockWidgets..." +rmdir /S /Q "KDDockWidgets-%KDDOCKWIDGETS%" +%SEVENZIP% x "KDDockWidgets-%KDDOCKWIDGETS%.zip" || goto error +cd "KDDockWidgets-%KDDOCKWIDGETS%" || goto error +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=true -DKDDockWidgets_STATIC=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja || goto error +cmake --build build --parallel || goto error +ninja -C build install || goto error +cd .. || goto error + echo Building shaderc... rmdir /S /Q "shaderc-%SHADERC%" %SEVENZIP% x "shaderc-%SHADERC%.zip" || goto error diff --git a/cmake/BuildParameters.cmake b/cmake/BuildParameters.cmake index 21d9940a04..6f0c7d4770 100644 --- a/cmake/BuildParameters.cmake +++ b/cmake/BuildParameters.cmake @@ -145,6 +145,7 @@ if(MSVC AND NOT USE_CLANG_CL) ) endif() +if(FALSE) if(MSVC) # Disable RTTI string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) @@ -158,6 +159,7 @@ else() "$<$:-fno-exceptions>" ) endif() +endif() set(CONFIG_REL_NO_DEB $,$>) set(CONFIG_ANY_REL $,$,$>) diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake index 12306df687..accb270e72 100644 --- a/cmake/SearchForStuff.cmake +++ b/cmake/SearchForStuff.cmake @@ -117,6 +117,9 @@ add_subdirectory(3rdparty/demangler EXCLUDE_FROM_ALL) # Symbol table parser. add_subdirectory(3rdparty/ccc EXCLUDE_FROM_ALL) +# The docking system for the debugger. +find_package(KDDockWidgets-qt6 REQUIRED) + # Architecture-specific. if(_M_X86) add_subdirectory(3rdparty/zydis EXCLUDE_FROM_ALL) From 6647b863a5e6eda53f07f20a6db907666f748c01 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Sat, 7 Dec 2024 17:36:29 +0000 Subject: [PATCH 2/3] Debugger: Redesign UI based on KDDockWidgets --- pcsx2-qt/CMakeLists.txt | 52 +- .../{ => Breakpoints}/BreakpointDialog.cpp | 0 .../{ => Breakpoints}/BreakpointDialog.h | 4 +- .../{ => Breakpoints}/BreakpointDialog.ui | 0 .../BreakpointModel.cpp | 0 .../{Models => Breakpoints}/BreakpointModel.h | 0 .../Debugger/Breakpoints/BreakpointWidget.cpp | 183 +++++ .../Debugger/Breakpoints/BreakpointWidget.h | 41 + .../Debugger/Breakpoints/BreakpointWidget.ui | 43 + pcsx2-qt/Debugger/CpuWidget.cpp | 732 ------------------ pcsx2-qt/Debugger/CpuWidget.h | 97 --- pcsx2-qt/Debugger/CpuWidget.ui | 445 ----------- pcsx2-qt/Debugger/DebuggerSettingsManager.cpp | 6 +- pcsx2-qt/Debugger/DebuggerSettingsManager.h | 4 +- pcsx2-qt/Debugger/DebuggerWidget.cpp | 31 + pcsx2-qt/Debugger/DebuggerWidget.h | 28 + pcsx2-qt/Debugger/DebuggerWindow.cpp | 50 +- pcsx2-qt/Debugger/DebuggerWindow.h | 13 +- pcsx2-qt/Debugger/DebuggerWindow.ui | 79 +- pcsx2-qt/Debugger/DisassemblyWidget.cpp | 90 ++- pcsx2-qt/Debugger/DisassemblyWidget.h | 13 +- pcsx2-qt/Debugger/DockManager.cpp | 108 +++ pcsx2-qt/Debugger/DockManager.h | 42 + .../{ => Memory}/MemorySearchWidget.cpp | 37 +- .../{ => Memory}/MemorySearchWidget.h | 26 +- .../{ => Memory}/MemorySearchWidget.ui | 0 .../{ => Memory}/MemoryViewWidget.cpp | 38 +- .../Debugger/{ => Memory}/MemoryViewWidget.h | 13 +- .../Debugger/{ => Memory}/MemoryViewWidget.ui | 3 + .../SavedAddressesModel.cpp | 0 .../{Models => Memory}/SavedAddressesModel.h | 0 .../Debugger/Memory/SavedAddressesWidget.cpp | 158 ++++ .../Debugger/Memory/SavedAddressesWidget.h | 29 + .../Debugger/Memory/SavedAddressesWidget.ui | 43 + pcsx2-qt/Debugger/RegisterWidget.cpp | 99 ++- pcsx2-qt/Debugger/RegisterWidget.h | 11 +- pcsx2-qt/Debugger/RegisterWidget.ui | 6 +- pcsx2-qt/Debugger/{Models => }/StackModel.cpp | 0 pcsx2-qt/Debugger/{Models => }/StackModel.h | 0 pcsx2-qt/Debugger/StackWidget.cpp | 76 ++ pcsx2-qt/Debugger/StackWidget.h | 26 + pcsx2-qt/Debugger/StackWidget.ui | 43 + .../Debugger/SymbolTree/SymbolTreeWidget.ui | 4 +- .../Debugger/SymbolTree/SymbolTreeWidgets.cpp | 32 +- .../Debugger/SymbolTree/SymbolTreeWidgets.h | 10 +- .../Debugger/{Models => }/ThreadModel.cpp | 0 pcsx2-qt/Debugger/{Models => }/ThreadModel.h | 0 pcsx2-qt/Debugger/ThreadWidget.cpp | 75 ++ pcsx2-qt/Debugger/ThreadWidget.h | 28 + pcsx2-qt/Debugger/ThreadWidget.ui | 43 + pcsx2-qt/MainWindow.cpp | 5 + pcsx2-qt/QtHost.h | 3 +- 52 files changed, 1332 insertions(+), 1537 deletions(-) rename pcsx2-qt/Debugger/{ => Breakpoints}/BreakpointDialog.cpp (100%) rename pcsx2-qt/Debugger/{ => Breakpoints}/BreakpointDialog.h (95%) rename pcsx2-qt/Debugger/{ => Breakpoints}/BreakpointDialog.ui (100%) rename pcsx2-qt/Debugger/{Models => Breakpoints}/BreakpointModel.cpp (100%) rename pcsx2-qt/Debugger/{Models => Breakpoints}/BreakpointModel.h (100%) create mode 100644 pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp create mode 100644 pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h create mode 100644 pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui delete mode 100644 pcsx2-qt/Debugger/CpuWidget.cpp delete mode 100644 pcsx2-qt/Debugger/CpuWidget.h delete mode 100644 pcsx2-qt/Debugger/CpuWidget.ui create mode 100644 pcsx2-qt/Debugger/DebuggerWidget.cpp create mode 100644 pcsx2-qt/Debugger/DebuggerWidget.h create mode 100644 pcsx2-qt/Debugger/DockManager.cpp create mode 100644 pcsx2-qt/Debugger/DockManager.h rename pcsx2-qt/Debugger/{ => Memory}/MemorySearchWidget.cpp (96%) rename pcsx2-qt/Debugger/{ => Memory}/MemorySearchWidget.h (90%) rename pcsx2-qt/Debugger/{ => Memory}/MemorySearchWidget.ui (100%) rename pcsx2-qt/Debugger/{ => Memory}/MemoryViewWidget.cpp (96%) rename pcsx2-qt/Debugger/{ => Memory}/MemoryViewWidget.h (93%) rename pcsx2-qt/Debugger/{ => Memory}/MemoryViewWidget.ui (81%) rename pcsx2-qt/Debugger/{Models => Memory}/SavedAddressesModel.cpp (100%) rename pcsx2-qt/Debugger/{Models => Memory}/SavedAddressesModel.h (100%) create mode 100644 pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp create mode 100644 pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h create mode 100644 pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui rename pcsx2-qt/Debugger/{Models => }/StackModel.cpp (100%) rename pcsx2-qt/Debugger/{Models => }/StackModel.h (100%) create mode 100644 pcsx2-qt/Debugger/StackWidget.cpp create mode 100644 pcsx2-qt/Debugger/StackWidget.h create mode 100644 pcsx2-qt/Debugger/StackWidget.ui rename pcsx2-qt/Debugger/{Models => }/ThreadModel.cpp (100%) rename pcsx2-qt/Debugger/{Models => }/ThreadModel.h (100%) create mode 100644 pcsx2-qt/Debugger/ThreadWidget.cpp create mode 100644 pcsx2-qt/Debugger/ThreadWidget.h create mode 100644 pcsx2-qt/Debugger/ThreadWidget.ui diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 2d8a1af252..1bdff961fc 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -158,37 +158,48 @@ target_sources(pcsx2-qt PRIVATE Debugger/AnalysisOptionsDialog.cpp Debugger/AnalysisOptionsDialog.h Debugger/AnalysisOptionsDialog.ui - Debugger/CpuWidget.cpp - Debugger/CpuWidget.h - Debugger/CpuWidget.ui Debugger/DebuggerSettingsManager.cpp Debugger/DebuggerSettingsManager.h + Debugger/DebuggerWidget.cpp + Debugger/DebuggerWidget.h Debugger/DebuggerWindow.cpp Debugger/DebuggerWindow.h Debugger/DebuggerWindow.ui Debugger/DisassemblyWidget.cpp Debugger/DisassemblyWidget.h Debugger/DisassemblyWidget.ui - Debugger/MemorySearchWidget.cpp - Debugger/MemorySearchWidget.h - Debugger/MemorySearchWidget.ui - Debugger/MemoryViewWidget.cpp - Debugger/MemoryViewWidget.h - Debugger/MemoryViewWidget.ui + Debugger/DockManager.cpp + Debugger/DockManager.h Debugger/RegisterWidget.cpp Debugger/RegisterWidget.h Debugger/RegisterWidget.ui - Debugger/BreakpointDialog.cpp - Debugger/BreakpointDialog.h - Debugger/BreakpointDialog.ui - Debugger/Models/BreakpointModel.cpp - Debugger/Models/BreakpointModel.h - Debugger/Models/ThreadModel.cpp - Debugger/Models/ThreadModel.h - Debugger/Models/StackModel.cpp - Debugger/Models/StackModel.h - Debugger/Models/SavedAddressesModel.cpp - Debugger/Models/SavedAddressesModel.h + Debugger/StackModel.cpp + Debugger/StackModel.h + Debugger/StackWidget.cpp + Debugger/StackWidget.h + Debugger/ThreadModel.cpp + Debugger/ThreadModel.h + Debugger/ThreadWidget.cpp + Debugger/ThreadWidget.h + Debugger/Breakpoints/BreakpointDialog.cpp + Debugger/Breakpoints/BreakpointDialog.h + Debugger/Breakpoints/BreakpointDialog.ui + Debugger/Breakpoints/BreakpointModel.cpp + Debugger/Breakpoints/BreakpointModel.h + Debugger/Breakpoints/BreakpointWidget.cpp + Debugger/Breakpoints/BreakpointWidget.h + Debugger/Breakpoints/BreakpointWidget.ui + Debugger/Memory/MemorySearchWidget.cpp + Debugger/Memory/MemorySearchWidget.h + Debugger/Memory/MemorySearchWidget.ui + Debugger/Memory/MemoryViewWidget.cpp + Debugger/Memory/MemoryViewWidget.h + Debugger/Memory/MemoryViewWidget.ui + Debugger/Memory/SavedAddressesModel.cpp + Debugger/Memory/SavedAddressesModel.h + Debugger/Memory/SavedAddressesWidget.cpp + Debugger/Memory/SavedAddressesWidget.h + Debugger/Memory/SavedAddressesWidget.ui Debugger/SymbolTree/NewSymbolDialogs.cpp Debugger/SymbolTree/NewSymbolDialogs.h Debugger/SymbolTree/NewSymbolDialog.ui @@ -232,6 +243,7 @@ target_link_libraries(pcsx2-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets + KDAB::kddockwidgets ) # Our Qt builds may have exceptions on, so force them off. diff --git a/pcsx2-qt/Debugger/BreakpointDialog.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.cpp similarity index 100% rename from pcsx2-qt/Debugger/BreakpointDialog.cpp rename to pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.cpp diff --git a/pcsx2-qt/Debugger/BreakpointDialog.h b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.h similarity index 95% rename from pcsx2-qt/Debugger/BreakpointDialog.h rename to pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.h index 61fc3fea1c..d1b033e20d 100644 --- a/pcsx2-qt/Debugger/BreakpointDialog.h +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.h @@ -5,9 +5,9 @@ #include "ui_BreakpointDialog.h" -#include "DebugTools/Breakpoints.h" +#include "BreakpointModel.h" -#include "Models/BreakpointModel.h" +#include "DebugTools/Breakpoints.h" #include diff --git a/pcsx2-qt/Debugger/BreakpointDialog.ui b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.ui similarity index 100% rename from pcsx2-qt/Debugger/BreakpointDialog.ui rename to pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.ui diff --git a/pcsx2-qt/Debugger/Models/BreakpointModel.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp similarity index 100% rename from pcsx2-qt/Debugger/Models/BreakpointModel.cpp rename to pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp diff --git a/pcsx2-qt/Debugger/Models/BreakpointModel.h b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.h similarity index 100% rename from pcsx2-qt/Debugger/Models/BreakpointModel.h rename to pcsx2-qt/Debugger/Breakpoints/BreakpointModel.h diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp new file mode 100644 index 0000000000..4617a6e3e1 --- /dev/null +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "BreakpointWidget.h" + +#include "QtUtils.h" +#include "Debugger/DebuggerSettingsManager.h" +#include "BreakpointDialog.h" +#include "BreakpointModel.h" + +#include + +BreakpointWidget::BreakpointWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu, parent) + , m_model(cpu) +{ + m_ui.setupUi(this); + + connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &BreakpointWidget::onContextMenu); + connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &BreakpointWidget::onDoubleClicked); + + m_ui.breakpointList->setModel(&m_model); + for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes) + { + m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode); + i++; + } + + connect(&m_model, &BreakpointModel::dataChanged, &m_model, &BreakpointModel::refreshData); +} + +void BreakpointWidget::onDoubleClicked(const QModelIndex& index) +{ + if (index.isValid() && index.column() == BreakpointModel::OFFSET) + { + not_yet_implemented(); + //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_model.data(index, BreakpointModel::DataRole).toUInt()); + } +} + +void BreakpointWidget::onContextMenu(QPoint pos) +{ + QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList); + if (cpu().isAlive()) + { + + QAction* newAction = new QAction(tr("New"), m_ui.breakpointList); + connect(newAction, &QAction::triggered, this, &BreakpointWidget::contextNew); + contextMenu->addAction(newAction); + + const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); + + if (selModel->hasSelection()) + { + QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList); + connect(editAction, &QAction::triggered, this, &BreakpointWidget::contextEdit); + contextMenu->addAction(editAction); + + if (selModel->selectedIndexes().count() == 1) + { + QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList); + connect(copyAction, &QAction::triggered, this, &BreakpointWidget::contextCopy); + contextMenu->addAction(copyAction); + } + + QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList); + connect(deleteAction, &QAction::triggered, this, &BreakpointWidget::contextDelete); + contextMenu->addAction(deleteAction); + } + } + + contextMenu->addSeparator(); + if (m_model.rowCount() > 0) + { + QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList); + connect(actionExport, &QAction::triggered, [this]() { + // It's important to use the Export Role here to allow pasting to be translation agnostic + QGuiApplication::clipboard()->setText( + QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), + BreakpointModel::ExportRole, true)); + }); + contextMenu->addAction(actionExport); + } + + if (cpu().isAlive()) + { + QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList); + connect(actionImport, &QAction::triggered, this, &BreakpointWidget::contextPasteCSV); + contextMenu->addAction(actionImport); + + QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.breakpointList); + connect(actionLoad, &QAction::triggered, [this]() { + m_model.clear(); + DebuggerSettingsManager::loadGameSettings(&m_model); + }); + contextMenu->addAction(actionLoad); + + QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.breakpointList); + connect(actionSave, &QAction::triggered, this, &BreakpointWidget::saveBreakpointsToDebuggerSettings); + contextMenu->addAction(actionSave); + } + + contextMenu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos)); +} + +void BreakpointWidget::contextCopy() +{ + const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); + + if (!selModel->hasSelection()) + return; + + QGuiApplication::clipboard()->setText(m_model.data(selModel->currentIndex()).toString()); +} + +void BreakpointWidget::contextDelete() +{ + const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); + + if (!selModel->hasSelection()) + return; + + QModelIndexList rows = selModel->selectedIndexes(); + + std::sort(rows.begin(), rows.end(), [](const QModelIndex& a, const QModelIndex& b) { + return a.row() > b.row(); + }); + + for (const QModelIndex& index : rows) + { + m_model.removeRows(index.row(), 1); + } +} + +void BreakpointWidget::contextNew() +{ + BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), m_model); + bpDialog->show(); +} + +void BreakpointWidget::contextEdit() +{ + const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); + + if (!selModel->hasSelection()) + return; + + const int selectedRow = selModel->selectedIndexes().first().row(); + + auto bpObject = m_model.at(selectedRow); + + BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), m_model, bpObject, selectedRow); + bpDialog->show(); +} + +void BreakpointWidget::contextPasteCSV() +{ + QString csv = QGuiApplication::clipboard()->text(); + // Skip header + csv = csv.mid(csv.indexOf('\n') + 1); + + for (const QString& line : csv.split('\n')) + { + QStringList fields; + // In order to handle text with commas in them we must wrap values in quotes to mark + // where a value starts and end so that text commas aren't identified as delimiters. + // So matches each quote pair, parse it out, and removes the quotes to get the value. + QRegularExpression eachQuotePair(R"("([^"]|\\.)*")"); + QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line); + while (it.hasNext()) + { + QRegularExpressionMatch match = it.next(); + QString matchedValue = match.captured(0); + fields << matchedValue.mid(1, matchedValue.length() - 2); + } + m_model.loadBreakpointFromFieldList(fields); + } +} + +void BreakpointWidget::saveBreakpointsToDebuggerSettings() +{ + DebuggerSettingsManager::saveGameSettings(&m_model); +} diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h new file mode 100644 index 0000000000..4bb750ccbc --- /dev/null +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_BreakpointWidget.h" + +#include "BreakpointModel.h" + +#include "Debugger/DebuggerWidget.h" + +#include "DebugTools/DebugInterface.h" +#include "DebugTools/DisassemblyManager.h" + +#include +#include +#include + +class BreakpointWidget : public DebuggerWidget +{ + Q_OBJECT + +public: + BreakpointWidget(DebugInterface& cpu, QWidget* parent = nullptr); + + void onDoubleClicked(const QModelIndex& index); + void onContextMenu(QPoint pos); + + void contextCopy(); + void contextDelete(); + void contextNew(); + void contextEdit(); + void contextPasteCSV(); + + void saveBreakpointsToDebuggerSettings(); + +private: + Ui::BreakpointWidget m_ui; + + BreakpointModel m_model; +}; diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui new file mode 100644 index 0000000000..7dc2bf069c --- /dev/null +++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui @@ -0,0 +1,43 @@ + + + BreakpointWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + + + + + + diff --git a/pcsx2-qt/Debugger/CpuWidget.cpp b/pcsx2-qt/Debugger/CpuWidget.cpp deleted file mode 100644 index 76f49e67b2..0000000000 --- a/pcsx2-qt/Debugger/CpuWidget.cpp +++ /dev/null @@ -1,732 +0,0 @@ -// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team -// SPDX-License-Identifier: GPL-3.0+ - -#include "CpuWidget.h" - -#include "DisassemblyWidget.h" -#include "BreakpointDialog.h" -#include "Models/BreakpointModel.h" -#include "Models/ThreadModel.h" -#include "Models/SavedAddressesModel.h" -#include "Debugger/DebuggerSettingsManager.h" - -#include "DebugTools/DebugInterface.h" -#include "DebugTools/Breakpoints.h" -#include "DebugTools/MipsStackWalk.h" - -#include "QtUtils.h" - -#include "common/Console.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace QtUtils; -using namespace MipsStackWalk; - -CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) - : m_cpu(cpu) - , m_bpModel(cpu) - , m_threadModel(cpu) - , m_stackModel(cpu) - , m_savedAddressesModel(cpu) -{ - m_ui.setupUi(this); - - connect(g_emu_thread, &EmuThread::onVMPaused, this, &CpuWidget::onVMPaused); - connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) { - if (title.isEmpty()) - return; - // Don't overwrite users BPs/Saved Addresses unless they have a clean state. - if (m_bpModel.rowCount() == 0) - DebuggerSettingsManager::loadGameSettings(&m_bpModel); - if (m_savedAddressesModel.rowCount() == 0) - DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel); - }); - - connect(m_ui.registerWidget, &RegisterWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress); - connect(m_ui.memoryviewWidget, &MemoryViewWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress); - connect(m_ui.memoryviewWidget, &MemoryViewWidget::addToSavedAddresses, this, &CpuWidget::addAddressToSavedAddressesList); - - connect(m_ui.registerWidget, &RegisterWidget::gotoInMemory, this, &CpuWidget::onGotoInMemory); - connect(m_ui.disassemblyWidget, &DisassemblyWidget::gotoInMemory, this, &CpuWidget::onGotoInMemory); - - connect(m_ui.memoryviewWidget, &MemoryViewWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets); - connect(m_ui.registerWidget, &RegisterWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets); - connect(m_ui.disassemblyWidget, &DisassemblyWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets); - - connect(m_ui.disassemblyWidget, &DisassemblyWidget::breakpointsChanged, this, &CpuWidget::updateBreakpoints); - - connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &CpuWidget::onBPListContextMenu); - connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &CpuWidget::onBPListDoubleClicked); - - m_ui.breakpointList->setModel(&m_bpModel); - for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes) - { - m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode); - i++; - } - - connect(&m_bpModel, &BreakpointModel::dataChanged, this, &CpuWidget::updateBreakpoints); - - connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu); - connect(m_ui.threadList, &QTableView::doubleClicked, this, &CpuWidget::onThreadListDoubleClick); - - m_threadProxyModel.setSourceModel(&m_threadModel); - m_threadProxyModel.setSortRole(Qt::UserRole); - m_ui.threadList->setModel(&m_threadProxyModel); - m_ui.threadList->setSortingEnabled(true); - m_ui.threadList->sortByColumn(ThreadModel::ThreadColumns::ID, Qt::SortOrder::AscendingOrder); - for (std::size_t i = 0; auto mode : ThreadModel::HeaderResizeModes) - { - m_ui.threadList->horizontalHeader()->setSectionResizeMode(i, mode); - i++; - } - - connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &CpuWidget::onStackListContextMenu); - connect(m_ui.stackList, &QTableView::doubleClicked, this, &CpuWidget::onStackListDoubleClick); - - m_ui.stackList->setModel(&m_stackModel); - for (std::size_t i = 0; auto mode : StackModel::HeaderResizeModes) - { - m_ui.stackList->horizontalHeader()->setSectionResizeMode(i, mode); - i++; - } - - m_ui.disassemblyWidget->SetCpu(&cpu); - m_ui.registerWidget->SetCpu(&cpu); - m_ui.memoryviewWidget->SetCpu(&cpu); - - this->repaint(); - - m_ui.savedAddressesList->setModel(&m_savedAddressesModel); - m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested, this, &CpuWidget::onSavedAddressesListContextMenu); - for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes) - { - m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode); - } - QTableView* savedAddressesTableView = m_ui.savedAddressesList; - connect(m_ui.savedAddressesList->model(), &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) { - savedAddressesTableView->resizeColumnToContents(topLeft.column()); - }); - - setupSymbolTrees(); - - DebuggerSettingsManager::loadGameSettings(&m_bpModel); - DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel); - - connect(m_ui.memorySearchWidget, &MemorySearchWidget::addAddressToSavedAddressesList, this, &CpuWidget::addAddressToSavedAddressesList); - connect(m_ui.memorySearchWidget, &MemorySearchWidget::goToAddressInDisassemblyView, - [this](u32 address) { m_ui.disassemblyWidget->gotoAddress(address, true); }); - connect(m_ui.memorySearchWidget, &MemorySearchWidget::goToAddressInMemoryView, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress); - connect(m_ui.memorySearchWidget, &MemorySearchWidget::switchToMemoryViewTab, - [this]() { m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); }); - m_ui.memorySearchWidget->setCpu(&m_cpu); - - m_refreshDebuggerTimer.setInterval(1000); - connect(&m_refreshDebuggerTimer, &QTimer::timeout, this, &CpuWidget::refreshDebugger); - m_refreshDebuggerTimer.start(); -} - -CpuWidget::~CpuWidget() = default; - -void CpuWidget::setupSymbolTrees() -{ - m_ui.tabFunctions->setLayout(new QVBoxLayout()); - m_ui.tabGlobalVariables->setLayout(new QVBoxLayout()); - m_ui.tabLocalVariables->setLayout(new QVBoxLayout()); - m_ui.tabParameterVariables->setLayout(new QVBoxLayout()); - - m_ui.tabFunctions->layout()->setContentsMargins(0, 0, 0, 0); - m_ui.tabGlobalVariables->layout()->setContentsMargins(0, 0, 0, 0); - m_ui.tabLocalVariables->layout()->setContentsMargins(0, 0, 0, 0); - m_ui.tabParameterVariables->layout()->setContentsMargins(0, 0, 0, 0); - - m_function_tree = new FunctionTreeWidget(m_cpu); - m_global_variable_tree = new GlobalVariableTreeWidget(m_cpu); - m_local_variable_tree = new LocalVariableTreeWidget(m_cpu); - m_parameter_variable_tree = new ParameterVariableTreeWidget(m_cpu); - - m_function_tree->updateModel(); - m_global_variable_tree->updateModel(); - m_local_variable_tree->updateModel(); - m_parameter_variable_tree->updateModel(); - - m_ui.tabFunctions->layout()->addWidget(m_function_tree); - m_ui.tabGlobalVariables->layout()->addWidget(m_global_variable_tree); - m_ui.tabLocalVariables->layout()->addWidget(m_local_variable_tree); - m_ui.tabParameterVariables->layout()->addWidget(m_parameter_variable_tree); - - connect(m_function_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); - connect(m_global_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); - connect(m_local_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); - connect(m_parameter_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); - - connect(m_function_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); - connect(m_global_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); - connect(m_local_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); - connect(m_parameter_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); - - connect(m_function_tree, &SymbolTreeWidget::nameColumnClicked, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); - connect(m_function_tree, &SymbolTreeWidget::locationColumnClicked, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); -} - -void CpuWidget::refreshDebugger() -{ - if (!m_cpu.isAlive()) - return; - - m_ui.registerWidget->update(); - m_ui.disassemblyWidget->update(); - m_ui.memoryviewWidget->update(); - m_ui.memorySearchWidget->update(); - - m_function_tree->updateModel(); - m_global_variable_tree->updateModel(); - m_local_variable_tree->updateModel(); - m_parameter_variable_tree->updateModel(); -} - -void CpuWidget::reloadCPUWidgets() -{ - updateThreads(); - updateStackFrames(); - - m_ui.registerWidget->update(); - m_ui.disassemblyWidget->update(); - m_ui.memoryviewWidget->update(); - - m_function_tree->updateModel(); - m_global_variable_tree->updateModel(); - m_local_variable_tree->updateModel(); - m_parameter_variable_tree->updateModel(); -} - -void CpuWidget::paintEvent(QPaintEvent* event) -{ - m_ui.registerWidget->update(); - m_ui.disassemblyWidget->update(); - m_ui.memoryviewWidget->update(); - m_ui.memorySearchWidget->update(); -} - -// The cpu shouldn't be alive when these are called -// But make sure it isn't just in case -void CpuWidget::onStepInto() -{ - if (!m_cpu.isAlive() || !m_cpu.isCpuPaused()) - return; - - // Allow the cpu to skip this pc if it is a breakpoint - CBreakPoints::SetSkipFirst(m_cpu.getCpuType(), m_cpu.getPC()); - - const u32 pc = m_cpu.getPC(); - const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(&m_cpu, pc); - - u32 bpAddr = pc + 0x4; // Default to the next instruction - - if (info.isBranch) - { - if (!info.isConditional) - { - bpAddr = info.branchTarget; - } - else - { - if (info.conditionMet) - { - bpAddr = info.branchTarget; - } - else - { - bpAddr = pc + (2 * 4); // Skip branch delay slot - } - } - } - - if (info.isSyscall) - bpAddr = info.branchTarget; // Syscalls are always taken - - Host::RunOnCPUThread([cpu = &m_cpu, bpAddr] { - CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true); - cpu->resumeCpu(); - }); - - this->repaint(); -} - -void CpuWidget::onStepOut() -{ - if (!m_cpu.isAlive() || !m_cpu.isCpuPaused()) - return; - - // Allow the cpu to skip this pc if it is a breakpoint - CBreakPoints::SetSkipFirst(m_cpu.getCpuType(), m_cpu.getPC()); - - if (m_stackModel.rowCount() < 2) - return; - - Host::RunOnCPUThread([cpu = &m_cpu, stackModel = &m_stackModel] { - CBreakPoints::AddBreakPoint(cpu->getCpuType(), stackModel->data(stackModel->index(1, StackModel::PC), Qt::UserRole).toUInt(), true); - cpu->resumeCpu(); - }); - - this->repaint(); -} - -void CpuWidget::onStepOver() -{ - if (!m_cpu.isAlive() || !m_cpu.isCpuPaused()) - return; - - const u32 pc = m_cpu.getPC(); - const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(&m_cpu, pc); - - u32 bpAddr = pc + 0x4; // Default to the next instruction - - if (info.isBranch) - { - if (!info.isConditional) - { - if (info.isLinkedBranch) // jal, jalr - { - // it's a function call with a delay slot - skip that too - bpAddr += 4; - } - else // j, ... - { - // in case of absolute branches, set the breakpoint at the branch target - bpAddr = info.branchTarget; - } - } - else // beq, ... - { - if (info.conditionMet) - { - bpAddr = info.branchTarget; - } - else - { - bpAddr = pc + (2 * 4); // Skip branch delay slot - } - } - } - - Host::RunOnCPUThread([cpu = &m_cpu, bpAddr] { - CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true); - cpu->resumeCpu(); - }); - - this->repaint(); -} - -void CpuWidget::onVMPaused() -{ - // Stops us from telling the disassembly dialog to jump somwhere because breakpoint code paused the core. - if (CBreakPoints::GetCorePaused()) - { - CBreakPoints::SetCorePaused(false); - } - else - { - m_ui.disassemblyWidget->gotoProgramCounterOnPause(); - } - - reloadCPUWidgets(); - this->repaint(); -} - -void CpuWidget::updateBreakpoints() -{ - m_bpModel.refreshData(); -} - -void CpuWidget::onBPListDoubleClicked(const QModelIndex& index) -{ - if (index.isValid()) - { - if (index.column() == BreakpointModel::OFFSET) - { - m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_bpModel.data(index, BreakpointModel::DataRole).toUInt()); - } - } -} - -void CpuWidget::onBPListContextMenu(QPoint pos) -{ - QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList); - if (m_cpu.isAlive()) - { - - QAction* newAction = new QAction(tr("New"), m_ui.breakpointList); - connect(newAction, &QAction::triggered, this, &CpuWidget::contextBPListNew); - contextMenu->addAction(newAction); - - const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); - - if (selModel->hasSelection()) - { - QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList); - connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit); - contextMenu->addAction(editAction); - - if (selModel->selectedIndexes().count() == 1) - { - QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList); - connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy); - contextMenu->addAction(copyAction); - } - - QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList); - connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete); - contextMenu->addAction(deleteAction); - } - } - - contextMenu->addSeparator(); - if (m_bpModel.rowCount() > 0) - { - QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList); - connect(actionExport, &QAction::triggered, [this]() { - // It's important to use the Export Role here to allow pasting to be translation agnostic - QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), BreakpointModel::ExportRole, true)); - }); - contextMenu->addAction(actionExport); - } - - if (m_cpu.isAlive()) - { - QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList); - connect(actionImport, &QAction::triggered, this, &CpuWidget::contextBPListPasteCSV); - contextMenu->addAction(actionImport); - - QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.breakpointList); - connect(actionLoad, &QAction::triggered, [this]() { - m_bpModel.clear(); - DebuggerSettingsManager::loadGameSettings(&m_bpModel); - }); - contextMenu->addAction(actionLoad); - - QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.breakpointList); - connect(actionSave, &QAction::triggered, this, &CpuWidget::saveBreakpointsToDebuggerSettings); - contextMenu->addAction(actionSave); - } - - contextMenu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos)); -} - -void CpuWidget::onGotoInMemory(u32 address) -{ - m_ui.memoryviewWidget->gotoAddress(address); - m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); -} - -void CpuWidget::contextBPListCopy() -{ - const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); - - if (!selModel->hasSelection()) - return; - - QGuiApplication::clipboard()->setText(m_bpModel.data(selModel->currentIndex()).toString()); -} - -void CpuWidget::contextBPListDelete() -{ - const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); - - if (!selModel->hasSelection()) - return; - - QModelIndexList rows = selModel->selectedIndexes(); - - std::sort(rows.begin(), rows.end(), [](const QModelIndex& a, const QModelIndex& b) { - return a.row() > b.row(); - }); - - for (const QModelIndex& index : rows) - { - m_bpModel.removeRows(index.row(), 1); - } -} - -void CpuWidget::contextBPListNew() -{ - BreakpointDialog* bpDialog = new BreakpointDialog(this, &m_cpu, m_bpModel); - bpDialog->show(); -} - -void CpuWidget::contextBPListEdit() -{ - const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); - - if (!selModel->hasSelection()) - return; - - const int selectedRow = selModel->selectedIndexes().first().row(); - - auto bpObject = m_bpModel.at(selectedRow); - - BreakpointDialog* bpDialog = new BreakpointDialog(this, &m_cpu, m_bpModel, bpObject, selectedRow); - bpDialog->show(); -} - -void CpuWidget::contextBPListPasteCSV() -{ - QString csv = QGuiApplication::clipboard()->text(); - // Skip header - csv = csv.mid(csv.indexOf('\n') + 1); - - for (const QString& line : csv.split('\n')) - { - QStringList fields; - // In order to handle text with commas in them we must wrap values in quotes to mark - // where a value starts and end so that text commas aren't identified as delimiters. - // So matches each quote pair, parse it out, and removes the quotes to get the value. - QRegularExpression eachQuotePair(R"("([^"]|\\.)*")"); - QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line); - while (it.hasNext()) - { - QRegularExpressionMatch match = it.next(); - QString matchedValue = match.captured(0); - fields << matchedValue.mid(1, matchedValue.length() - 2); - } - m_bpModel.loadBreakpointFromFieldList(fields); - } -} - -void CpuWidget::onSavedAddressesListContextMenu(QPoint pos) -{ - QMenu* contextMenu = new QMenu("Saved Addresses List Context Menu", m_ui.savedAddressesList); - - QAction* newAction = new QAction(tr("New"), m_ui.savedAddressesList); - connect(newAction, &QAction::triggered, this, &CpuWidget::contextSavedAddressesListNew); - contextMenu->addAction(newAction); - - const QModelIndex indexAtPos = m_ui.savedAddressesList->indexAt(pos); - const bool isIndexValid = indexAtPos.isValid(); - - if (isIndexValid) - { - if (m_cpu.isAlive()) - { - QAction* goToAddressMemViewAction = new QAction(tr("Go to in Memory View"), m_ui.savedAddressesList); - connect(goToAddressMemViewAction, &QAction::triggered, this, [this, indexAtPos]() { - const QModelIndex rowAddressIndex = m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex()); - m_ui.memoryviewWidget->gotoAddress(m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt()); - m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); - }); - contextMenu->addAction(goToAddressMemViewAction); - - QAction* goToAddressDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.savedAddressesList); - connect(goToAddressDisassemblyAction, &QAction::triggered, this, [this, indexAtPos]() { - const QModelIndex rowAddressIndex = m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex()); - m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt()); - }); - contextMenu->addAction(goToAddressDisassemblyAction); - } - - QAction* copyAction = new QAction(indexAtPos.column() == 0 ? tr("Copy Address") : tr("Copy Text"), m_ui.savedAddressesList); - connect(copyAction, &QAction::triggered, [this, indexAtPos]() { - QGuiApplication::clipboard()->setText(m_ui.savedAddressesList->model()->data(indexAtPos, Qt::DisplayRole).toString()); - }); - contextMenu->addAction(copyAction); - } - - if (m_ui.savedAddressesList->model()->rowCount() > 0) - { - QAction* actionExportCSV = new QAction(tr("Copy all as CSV"), m_ui.savedAddressesList); - connect(actionExportCSV, &QAction::triggered, [this]() { - QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.savedAddressesList->model(), Qt::DisplayRole, true)); - }); - contextMenu->addAction(actionExportCSV); - } - - QAction* actionImportCSV = new QAction(tr("Paste from CSV"), m_ui.savedAddressesList); - connect(actionImportCSV, &QAction::triggered, this, &CpuWidget::contextSavedAddressesListPasteCSV); - contextMenu->addAction(actionImportCSV); - - if (m_cpu.isAlive()) - { - QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.savedAddressesList); - connect(actionLoad, &QAction::triggered, [this]() { - m_savedAddressesModel.clear(); - DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel); - }); - contextMenu->addAction(actionLoad); - - QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.savedAddressesList); - connect(actionSave, &QAction::triggered, this, &CpuWidget::saveSavedAddressesToDebuggerSettings); - contextMenu->addAction(actionSave); - } - - if (isIndexValid) - { - QAction* deleteAction = new QAction(tr("Delete"), m_ui.savedAddressesList); - connect(deleteAction, &QAction::triggered, this, [this, indexAtPos]() { - m_ui.savedAddressesList->model()->removeRows(indexAtPos.row(), 1); - }); - contextMenu->addAction(deleteAction); - } - - contextMenu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos)); -} - -void CpuWidget::contextSavedAddressesListPasteCSV() -{ - QString csv = QGuiApplication::clipboard()->text(); - // Skip header - csv = csv.mid(csv.indexOf('\n') + 1); - - for (const QString& line : csv.split('\n')) - { - QStringList fields; - // In order to handle text with commas in them we must wrap values in quotes to mark - // where a value starts and end so that text commas aren't identified as delimiters. - // So matches each quote pair, parse it out, and removes the quotes to get the value. - QRegularExpression eachQuotePair(R"("([^"]|\\.)*")"); - QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line); - while (it.hasNext()) - { - QRegularExpressionMatch match = it.next(); - QString matchedValue = match.captured(0); - fields << matchedValue.mid(1, matchedValue.length() - 2); - } - - m_savedAddressesModel.loadSavedAddressFromFieldList(fields); - } -} - -void CpuWidget::contextSavedAddressesListNew() -{ - qobject_cast(m_ui.savedAddressesList->model())->addRow(); - const u32 rowCount = m_ui.savedAddressesList->model()->rowCount(); - m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 0)); -} - -void CpuWidget::addAddressToSavedAddressesList(u32 address) -{ - qobject_cast(m_ui.savedAddressesList->model())->addRow(); - const u32 rowCount = m_ui.savedAddressesList->model()->rowCount(); - const QModelIndex addressIndex = m_ui.savedAddressesList->model()->index(rowCount - 1, 0); - m_ui.tabWidget->setCurrentWidget(m_ui.tab_savedaddresses); - m_ui.savedAddressesList->model()->setData(addressIndex, address, Qt::UserRole); - m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 1)); -} - -void CpuWidget::updateThreads() -{ - m_threadModel.refreshData(); -} - -void CpuWidget::onThreadListContextMenu(QPoint pos) -{ - if (!m_ui.threadList->selectionModel()->hasSelection()) - return; - - QMenu* contextMenu = new QMenu(tr("Thread List Context Menu"), m_ui.threadList); - - QAction* actionCopy = new QAction(tr("Copy"), m_ui.threadList); - connect(actionCopy, &QAction::triggered, [this]() { - const auto* selModel = m_ui.threadList->selectionModel(); - - if (!selModel->hasSelection()) - return; - - QGuiApplication::clipboard()->setText(m_ui.threadList->model()->data(selModel->currentIndex()).toString()); - }); - contextMenu->addAction(actionCopy); - - contextMenu->addSeparator(); - - QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.threadList); - connect(actionExport, &QAction::triggered, [this]() { - QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model())); - }); - contextMenu->addAction(actionExport); - - contextMenu->popup(m_ui.threadList->viewport()->mapToGlobal(pos)); -} - -void CpuWidget::onThreadListDoubleClick(const QModelIndex& index) -{ - switch (index.column()) - { - case ThreadModel::ThreadColumns::ENTRY: - m_ui.memoryviewWidget->gotoAddress(m_ui.threadList->model()->data(index, Qt::UserRole).toUInt()); - m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); - break; - default: // Default to PC - m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.threadList->model()->data(m_ui.threadList->model()->index(index.row(), ThreadModel::ThreadColumns::PC), Qt::UserRole).toUInt()); - break; - } -} - -void CpuWidget::updateStackFrames() -{ - m_stackModel.refreshData(); -} - -void CpuWidget::onStackListContextMenu(QPoint pos) -{ - if (!m_ui.stackList->selectionModel()->hasSelection()) - return; - - QMenu* contextMenu = new QMenu(tr("Stack List Context Menu"), m_ui.stackList); - - QAction* actionCopy = new QAction(tr("Copy"), m_ui.stackList); - connect(actionCopy, &QAction::triggered, [this]() { - const auto* selModel = m_ui.stackList->selectionModel(); - - if (!selModel->hasSelection()) - return; - - QGuiApplication::clipboard()->setText(m_ui.stackList->model()->data(selModel->currentIndex()).toString()); - }); - contextMenu->addAction(actionCopy); - - contextMenu->addSeparator(); - - QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.stackList); - connect(actionExport, &QAction::triggered, [this]() { - QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model())); - }); - contextMenu->addAction(actionExport); - - contextMenu->popup(m_ui.stackList->viewport()->mapToGlobal(pos)); -} - -void CpuWidget::onStackListDoubleClick(const QModelIndex& index) -{ - switch (index.column()) - { - case StackModel::StackModel::ENTRY: - case StackModel::StackModel::ENTRY_LABEL: - m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::ENTRY), Qt::UserRole).toUInt()); - break; - case StackModel::StackModel::SP: - m_ui.memoryviewWidget->gotoAddress(m_ui.stackList->model()->data(index, Qt::UserRole).toUInt()); - m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); - break; - default: // Default to PC - m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::PC), Qt::UserRole).toUInt()); - break; - } -} - -void CpuWidget::saveBreakpointsToDebuggerSettings() -{ - DebuggerSettingsManager::saveGameSettings(&m_bpModel); -} - -void CpuWidget::saveSavedAddressesToDebuggerSettings() -{ - DebuggerSettingsManager::saveGameSettings(&m_savedAddressesModel); -} diff --git a/pcsx2-qt/Debugger/CpuWidget.h b/pcsx2-qt/Debugger/CpuWidget.h deleted file mode 100644 index 2f96ead065..0000000000 --- a/pcsx2-qt/Debugger/CpuWidget.h +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team -// SPDX-License-Identifier: GPL-3.0+ - -#pragma once - -#include "ui_CpuWidget.h" - -#include "DebugTools/DebugInterface.h" - -#include "Models/BreakpointModel.h" -#include "Models/ThreadModel.h" -#include "Models/StackModel.h" -#include "Models/SavedAddressesModel.h" -#include "Debugger/SymbolTree/SymbolTreeWidgets.h" - -#include "QtHost.h" -#include -#include -#include -#include - -#include - -using namespace MipsStackWalk; - -class CpuWidget final : public QWidget -{ - Q_OBJECT - -public: - CpuWidget(QWidget* parent, DebugInterface& cpu); - ~CpuWidget(); - -public slots: - void paintEvent(QPaintEvent* event); - - void onStepInto(); - void onStepOver(); - void onStepOut(); - - void onVMPaused(); - - void updateBreakpoints(); - void onBPListDoubleClicked(const QModelIndex& index); - void onBPListContextMenu(QPoint pos); - void onGotoInMemory(u32 address); - - void contextBPListCopy(); - void contextBPListDelete(); - void contextBPListNew(); - void contextBPListEdit(); - void contextBPListPasteCSV(); - - void onSavedAddressesListContextMenu(QPoint pos); - void contextSavedAddressesListPasteCSV(); - void contextSavedAddressesListNew(); - void addAddressToSavedAddressesList(u32 address); - - void updateThreads(); - void onThreadListDoubleClick(const QModelIndex& index); - void onThreadListContextMenu(QPoint pos); - - void updateStackFrames(); - void onStackListContextMenu(QPoint pos); - void onStackListDoubleClick(const QModelIndex& index); - - void refreshDebugger(); - void reloadCPUWidgets(); - - void saveBreakpointsToDebuggerSettings(); - void saveSavedAddressesToDebuggerSettings(); - -private: - void setupSymbolTrees(); - - std::vector m_registerTableViews; - - QMenu* m_stacklistContextMenu = 0; - QMenu* m_funclistContextMenu = 0; - QMenu* m_moduleTreeContextMenu = 0; - QTimer m_refreshDebuggerTimer; - - Ui::CpuWidget m_ui; - - DebugInterface& m_cpu; - - BreakpointModel m_bpModel; - ThreadModel m_threadModel; - QSortFilterProxyModel m_threadProxyModel; - StackModel m_stackModel; - SavedAddressesModel m_savedAddressesModel; - - FunctionTreeWidget* m_function_tree = nullptr; - GlobalVariableTreeWidget* m_global_variable_tree = nullptr; - LocalVariableTreeWidget* m_local_variable_tree = nullptr; - ParameterVariableTreeWidget* m_parameter_variable_tree = nullptr; -}; diff --git a/pcsx2-qt/Debugger/CpuWidget.ui b/pcsx2-qt/Debugger/CpuWidget.ui deleted file mode 100644 index 587fd31449..0000000000 --- a/pcsx2-qt/Debugger/CpuWidget.ui +++ /dev/null @@ -1,445 +0,0 @@ - - - CpuWidget - - - - 0 - 0 - 668 - 563 - - - - - Monospace - - - - Qt::StrongFocus - - - - - - - - - Qt::Vertical - - - false - - - - Qt::Horizontal - - - false - - - - - 100 - 100 - - - - 0 - - - - Registers - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 320 - 100 - - - - - Monospace - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - true - - - - - - - - Functions - - - - - Memory Search - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - Monospace - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - true - - - - - - - - - - 0 - 1 - - - - - 100 - 100 - - - - - Monospace - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - true - - - - - - - 150 - 0 - - - - QTabWidget::North - - - 0 - - - - - 0 - 0 - - - - Memory - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 250 - 100 - - - - - Monospace - - - - Qt::CustomContextMenu - - - true - - - - - - - - Breakpoints - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - Qt::NoPen - - - - - - - - Threads - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - QAbstractItemView::SingleSelection - - - Qt::NoPen - - - false - - - - - - - - Active Call Stack - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContentsOnFirstShow - - - QAbstractItemView::SingleSelection - - - Qt::NoPen - - - false - - - - - - - - Saved Addresses - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - Qt::NoPen - - - - - - - - Globals - - - - - Locals - - - - - Parameters - - - - - - - - - - DisassemblyWidget - QWidget -
pcsx2-qt/Debugger/DisassemblyWidget.h
- 1 -
- - RegisterWidget - QWidget -
pcsx2-qt/Debugger/RegisterWidget.h
- 1 -
- - MemoryViewWidget - QWidget -
pcsx2-qt/Debugger/MemoryViewWidget.h
- 1 -
- - MemorySearchWidget - QWidget -
pcsx2-qt/Debugger/MemorySearchWidget.h
- 1 -
-
- - -
diff --git a/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp b/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp index f4b7ace1ca..81a23c8858 100644 --- a/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp +++ b/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp @@ -10,12 +10,12 @@ #include "common/Console.h" #include "VMManager.h" -#include "Models/BreakpointModel.h" std::mutex DebuggerSettingsManager::writeLock; const QString DebuggerSettingsManager::settingsFileVersion = "0.00"; -QJsonObject DebuggerSettingsManager::loadGameSettingsJSON() { +QJsonObject DebuggerSettingsManager::loadGameSettingsJSON() +{ std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame(); QFile file(QString::fromStdString(path)); if (!file.open(QIODevice::ReadOnly)) @@ -134,7 +134,7 @@ void DebuggerSettingsManager::saveGameSettings(QAbstractTableModel* abstractTabl { const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame(); if (path.empty()) - return; + return; const std::lock_guard lock(writeLock); QJsonObject loadedSettings = loadGameSettingsJSON(); diff --git a/pcsx2-qt/Debugger/DebuggerSettingsManager.h b/pcsx2-qt/Debugger/DebuggerSettingsManager.h index 4cbf373543..6c264f670c 100644 --- a/pcsx2-qt/Debugger/DebuggerSettingsManager.h +++ b/pcsx2-qt/Debugger/DebuggerSettingsManager.h @@ -6,8 +6,8 @@ #include -#include "Models/BreakpointModel.h" -#include "Models/SavedAddressesModel.h" +#include "Breakpoints/BreakpointModel.h" +#include "Memory/SavedAddressesModel.h" class DebuggerSettingsManager final { diff --git a/pcsx2-qt/Debugger/DebuggerWidget.cpp b/pcsx2-qt/Debugger/DebuggerWidget.cpp new file mode 100644 index 0000000000..c4d2148ba5 --- /dev/null +++ b/pcsx2-qt/Debugger/DebuggerWidget.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "DebuggerWidget.h" + +#include "common/Assertions.h" + +DebuggerWidget::DebuggerWidget(DebugInterface* cpu, QWidget* parent) + : QWidget(parent) + , m_cpu(cpu) +{ +} + +DebugInterface& DebuggerWidget::cpu() const +{ + pxAssertRel(m_cpu, "DebuggerWidget::cpu() called on object that doesn't have a CPU type set."); + return *m_cpu; +} + +void DebuggerWidget::applyMonospaceFont() +{ + // Easiest way to handle cross platform monospace fonts + // There are issues related to TabWidget -> Children font inheritance otherwise +#if defined(WIN32) + setStyleSheet(QStringLiteral("font: 10pt 'Lucida Console'")); +#elif defined(__APPLE__) + setStyleSheet(QStringLiteral("font: 10pt 'Monaco'")); +#else + setStyleSheet(QStringLiteral("font: 10pt 'Monospace'")); +#endif +} diff --git a/pcsx2-qt/Debugger/DebuggerWidget.h b/pcsx2-qt/Debugger/DebuggerWidget.h new file mode 100644 index 0000000000..53fec8c4b4 --- /dev/null +++ b/pcsx2-qt/Debugger/DebuggerWidget.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "DebugTools/DebugInterface.h" + +#include + +inline void not_yet_implemented() +{ + abort(); +} + +class DebuggerWidget : public QWidget +{ + Q_OBJECT + +protected: + DebuggerWidget(DebugInterface* cpu, QWidget* parent = nullptr); + + DebugInterface& cpu() const; + + void applyMonospaceFont(); + +private: + DebugInterface* m_cpu; +}; diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp index ee43eafa06..47be09fc85 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.cpp +++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp @@ -12,20 +12,11 @@ #include "AnalysisOptionsDialog.h" DebuggerWindow::DebuggerWindow(QWidget* parent) - : QMainWindow(parent) + : KDDockWidgets::QtWidgets::MainWindow(QStringLiteral("DebuggerWindow"), {}, parent) + , m_dock_manager(this) { m_ui.setupUi(this); -// Easiest way to handle cross platform monospace fonts -// There are issues related to TabWidget -> Children font inheritance otherwise -#if defined(WIN32) - m_ui.cpuTabs->setStyleSheet(QStringLiteral("font: 8pt 'Lucida Console'")); -#elif defined(__APPLE__) - m_ui.cpuTabs->setStyleSheet(QStringLiteral("font: 10pt 'Monaco'")); -#else - m_ui.cpuTabs->setStyleSheet(QStringLiteral("font: 8pt 'Monospace'")); -#endif - connect(m_ui.actionRun, &QAction::triggered, this, &DebuggerWindow::onRunPause); connect(m_ui.actionStepInto, &QAction::triggered, this, &DebuggerWindow::onStepInto); connect(m_ui.actionStepOver, &QAction::triggered, this, &DebuggerWindow::onStepOver); @@ -39,15 +30,18 @@ DebuggerWindow::DebuggerWindow(QWidget* parent) onVMStateChanged(); // If we missed a state change while we weren't loaded // We can't do this in the designer, but we want to right align the actionOnTop action in the toolbar - QWidget* spacer = new QWidget(this); - spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_ui.toolBar->insertWidget(m_ui.actionAnalyse, spacer); + //QWidget* spacer = new QWidget(this); + //spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + //m_ui.toolBar->insertWidget(m_ui.actionAnalyse, spacer); - m_cpuWidget_r5900 = new CpuWidget(this, r5900Debug); - m_cpuWidget_r3000 = new CpuWidget(this, r3000Debug); + //m_ui.cpuTabs->addTab(m_cpuWidget_r5900, "R5900"); + //m_ui.cpuTabs->addTab(m_cpuWidget_r3000, "R3000"); - m_ui.cpuTabs->addTab(m_cpuWidget_r5900, "R5900"); - m_ui.cpuTabs->addTab(m_cpuWidget_r3000, "R3000"); + m_dock_manager.switchToLayout(0); + + //QTabBar* tabs = new QTabBar(); + //tabs->addTab("Test"); + //m_ui.menuBar->layout()->addWidget(tabs); } DebuggerWindow::~DebuggerWindow() = default; @@ -56,8 +50,8 @@ DebuggerWindow::~DebuggerWindow() = default; // Sorry colour blind people, but this is the best we can do for now void DebuggerWindow::setTabActiveStyle(BreakPointCpu enabledCpu) { - m_ui.cpuTabs->tabBar()->setTabTextColor(m_ui.cpuTabs->indexOf(m_cpuWidget_r5900), (enabledCpu == BREAKPOINT_EE) ? Qt::red : this->palette().text().color()); - m_ui.cpuTabs->tabBar()->setTabTextColor(m_ui.cpuTabs->indexOf(m_cpuWidget_r3000), (enabledCpu == BREAKPOINT_IOP) ? Qt::red : this->palette().text().color()); + //m_ui.cpuTabs->tabBar()->setTabTextColor(m_ui.cpuTabs->indexOf(m_cpuWidget_r5900), (enabledCpu == BREAKPOINT_EE) ? Qt::red : this->palette().text().color()); + //m_ui.cpuTabs->tabBar()->setTabTextColor(m_ui.cpuTabs->indexOf(m_cpuWidget_r3000), (enabledCpu == BREAKPOINT_IOP) ? Qt::red : this->palette().text().color()); } void DebuggerWindow::onVMStateChanged() @@ -87,10 +81,10 @@ void DebuggerWindow::onVMStateChanged() switch (triggeredCpu) { case BREAKPOINT_EE: - m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r5900); + //m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r5900); break; case BREAKPOINT_IOP: - m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r3000); + //m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r3000); break; default: break; @@ -115,20 +109,20 @@ void DebuggerWindow::onRunPause() void DebuggerWindow::onStepInto() { - CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); - currentCpu->onStepInto(); + //CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); + //currentCpu->onStepInto(); } void DebuggerWindow::onStepOver() { - CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); - currentCpu->onStepOver(); + //CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); + //currentCpu->onStepOver(); } void DebuggerWindow::onStepOut() { - CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); - currentCpu->onStepOut(); + //CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); + //currentCpu->onStepOut(); } void DebuggerWindow::onAnalyse() diff --git a/pcsx2-qt/Debugger/DebuggerWindow.h b/pcsx2-qt/Debugger/DebuggerWindow.h index 26b4b0fc5d..65df2dd71a 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.h +++ b/pcsx2-qt/Debugger/DebuggerWindow.h @@ -3,11 +3,13 @@ #pragma once -#include "CpuWidget.h" - #include "ui_DebuggerWindow.h" -class DebuggerWindow : public QMainWindow +#include "DockManager.h" + +#include + +class DebuggerWindow : public KDDockWidgets::QtWidgets::MainWindow { Q_OBJECT @@ -25,7 +27,7 @@ public slots: protected: void showEvent(QShowEvent* event); - void hideEvent(QHideEvent *event); + void hideEvent(QHideEvent* event); private: Ui::DebuggerWindow m_ui; @@ -34,8 +36,7 @@ private: QAction* m_actionStepOver; QAction* m_actionStepOut; - CpuWidget* m_cpuWidget_r5900; - CpuWidget* m_cpuWidget_r3000; + DockManager m_dock_manager; void setTabActiveStyle(BreakPointCpu toggledCPU); }; diff --git a/pcsx2-qt/Debugger/DebuggerWindow.ui b/pcsx2-qt/Debugger/DebuggerWindow.ui index c668a6316b..c0bd9b9487 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.ui +++ b/pcsx2-qt/Debugger/DebuggerWindow.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 600 + 1000 + 750 @@ -18,19 +18,12 @@ :/icons/AppIcon64.png - - - - - - - - + - Qt::ContextMenuPolicy::PreventContextMenu + Qt::PreventContextMenu - false + true @@ -39,10 +32,10 @@ - Qt::ToolButtonStyle::ToolButtonTextBesideIcon + Qt::ToolButtonTextBesideIcon - false + true TopToolBarArea @@ -54,7 +47,63 @@ - + + + + + 0 + 0 + 1000 + 20 + + + + + File + + + + + + Debug + + + + + + + + + Windows + + + + + Layouts + + + + + View + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index 097a5d566a..fb8f9b63f4 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -19,12 +19,16 @@ using namespace QtUtils; -DisassemblyWidget::DisassemblyWidget(QWidget* parent) - : QWidget(parent) +DisassemblyWidget::DisassemblyWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu, parent) { - ui.setupUi(this); + m_ui.setupUi(this); + + m_disassemblyManager.setCpu(&cpu); connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::customMenuRequested); + + applyMonospaceFont(); } DisassemblyWidget::~DisassemblyWidget() = default; @@ -46,7 +50,7 @@ void DisassemblyWidget::contextCopyInstructionText() void DisassemblyWidget::contextAssembleInstruction() { - if (!m_cpu->isCpuPaused()) + if (!cpu().isCpuPaused()) { QMessageBox::warning(this, tr("Assemble Error"), tr("Unable to change assembly while core is running")); return; @@ -63,7 +67,7 @@ void DisassemblyWidget::contextAssembleInstruction() u32 encodedInstruction; std::string errorText; - bool valid = MipsAssembleOpcode(instruction.toLocal8Bit().constData(), m_cpu, m_selectedAddressStart, encodedInstruction, errorText); + bool valid = MipsAssembleOpcode(instruction.toLocal8Bit().constData(), &cpu(), m_selectedAddressStart, encodedInstruction, errorText); if (!valid) { @@ -72,7 +76,7 @@ void DisassemblyWidget::contextAssembleInstruction() } else { - Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu, val = encodedInstruction] { + Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = &cpu(), val = encodedInstruction] { for (u32 i = start; i <= end; i += 4) { this->m_nopedInstructions.insert({i, cpu->read32(i)}); @@ -85,7 +89,7 @@ void DisassemblyWidget::contextAssembleInstruction() void DisassemblyWidget::contextNoopInstruction() { - Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] { + Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = &cpu()] { for (u32 i = start; i <= end; i += 4) { this->m_nopedInstructions.insert({i, cpu->read32(i)}); @@ -97,7 +101,7 @@ void DisassemblyWidget::contextNoopInstruction() void DisassemblyWidget::contextRestoreInstruction() { - Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] { + Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = &cpu()] { for (u32 i = start; i <= end; i += 4) { if (this->m_nopedInstructions.find(i) != this->m_nopedInstructions.end()) @@ -113,7 +117,7 @@ void DisassemblyWidget::contextRestoreInstruction() void DisassemblyWidget::contextRunToCursor() { const u32 selectedAddressStart = m_selectedAddressStart; - Host::RunOnCPUThread([cpu = m_cpu, selectedAddressStart] { + Host::RunOnCPUThread([cpu = &cpu(), selectedAddressStart] { CBreakPoints::AddBreakPoint(cpu->getCpuType(), selectedAddressStart, true); cpu->resumeCpu(); }); @@ -121,17 +125,17 @@ void DisassemblyWidget::contextRunToCursor() void DisassemblyWidget::contextJumpToCursor() { - m_cpu->setPc(m_selectedAddressStart); + cpu().setPc(m_selectedAddressStart); this->repaint(); } void DisassemblyWidget::contextToggleBreakpoint() { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; const u32 selectedAddressStart = m_selectedAddressStart; - const BreakPointCpu cpuType = m_cpu->getCpuType(); + const BreakPointCpu cpuType = cpu().getCpuType(); if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddressStart)) { Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddressStart); }); @@ -171,7 +175,7 @@ void DisassemblyWidget::contextGoToAddress() u64 address = 0; std::string error; - if (!m_cpu->evaluateExpression(targetString.toStdString().c_str(), address, error)) + if (!cpu().evaluateExpression(targetString.toStdString().c_str(), address, error)) { QMessageBox::warning(this, tr("Cannot Go To"), QString::fromStdString(error)); return; @@ -182,7 +186,7 @@ void DisassemblyWidget::contextGoToAddress() void DisassemblyWidget::contextAddFunction() { - NewFunctionDialog* dialog = new NewFunctionDialog(*m_cpu, this); + NewFunctionDialog* dialog = new NewFunctionDialog(cpu(), this); dialog->setName(QString("func_%1").arg(m_selectedAddressStart, 8, 16, QChar('0'))); dialog->setAddress(m_selectedAddressStart); if (m_selectedAddressEnd != m_selectedAddressStart) @@ -193,13 +197,13 @@ void DisassemblyWidget::contextAddFunction() void DisassemblyWidget::contextCopyFunctionName() { - std::string name = m_cpu->GetSymbolGuardian().FunctionStartingAtAddress(m_selectedAddressStart).name; + std::string name = cpu().GetSymbolGuardian().FunctionStartingAtAddress(m_selectedAddressStart).name; QGuiApplication::clipboard()->setText(QString::fromStdString(name)); } void DisassemblyWidget::contextRemoveFunction() { - m_cpu->GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { ccc::Function* curFunc = database.functions.symbol_overlapping_address(m_selectedAddressStart); if (!curFunc) return; @@ -215,7 +219,7 @@ void DisassemblyWidget::contextRemoveFunction() void DisassemblyWidget::contextRenameFunction() { - const FunctionInfo curFunc = m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart); + const FunctionInfo curFunc = cpu().GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart); if (!curFunc.address.valid()) { @@ -236,17 +240,17 @@ void DisassemblyWidget::contextRenameFunction() return; } - m_cpu->GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { database.functions.rename_symbol(curFunc.handle, newName.toStdString()); }); } void DisassemblyWidget::contextStubFunction() { - FunctionInfo function = m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart); + FunctionInfo function = cpu().GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart); u32 address = function.address.valid() ? function.address.value : m_selectedAddressStart; - Host::RunOnCPUThread([this, address, cpu = m_cpu] { + Host::RunOnCPUThread([this, address, cpu = &cpu()] { this->m_stubbedFunctions.insert({address, {cpu->read32(address), cpu->read32(address + 4)}}); cpu->write32(address, 0x03E00008); // jr ra cpu->write32(address + 4, 0x00000000); // nop @@ -257,7 +261,7 @@ void DisassemblyWidget::contextStubFunction() void DisassemblyWidget::contextRestoreFunction() { u32 address = m_selectedAddressStart; - m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { const ccc::Function* function = database.functions.symbol_overlapping_address(m_selectedAddressStart); if (function) address = function->address().value; @@ -266,7 +270,7 @@ void DisassemblyWidget::contextRestoreFunction() auto stub = m_stubbedFunctions.find(address); if (stub != m_stubbedFunctions.end()) { - Host::RunOnCPUThread([this, address, cpu = m_cpu, stub] { + Host::RunOnCPUThread([this, address, cpu = &cpu(), stub] { auto [first_instruction, second_instruction] = stub->second; cpu->write32(address, first_instruction); cpu->write32(address + 4, second_instruction); @@ -286,12 +290,6 @@ void DisassemblyWidget::contextShowOpcode() this->repaint(); } -void DisassemblyWidget::SetCpu(DebugInterface* cpu) -{ - m_cpu = cpu; - m_disassemblyManager.setCpu(cpu); -} - QString DisassemblyWidget::GetLineDisasm(u32 address) { DisassemblyLineInfo lineInfo; @@ -322,7 +320,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) bool inSelectionBlock = false; bool alternate = m_visibleStart % 8; - const u32 curPC = m_cpu->getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs + const u32 curPC = cpu().getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs for (u32 i = 0; i <= m_visibleRows; i++) { @@ -347,7 +345,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) // Breakpoint marker bool enabled; - if (CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), rowAddress, &enabled) && !CBreakPoints::IsTempBreakPoint(m_cpu->getCpuType(), rowAddress)) + if (CBreakPoints::IsAddressBreakPoint(cpu().getCpuType(), rowAddress, &enabled) && !CBreakPoints::IsTempBreakPoint(cpu().getCpuType(), rowAddress)) { if (enabled) { @@ -506,11 +504,11 @@ void DisassemblyWidget::mousePressEvent(QMouseEvent* event) void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event) { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; const u32 selectedAddress = (static_cast(event->position().y()) / m_rowHeight * 4) + m_visibleStart; - const BreakPointCpu cpuType = m_cpu->getCpuType(); + const BreakPointCpu cpuType = cpu().getCpuType(); if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddress)) { Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddress); }); @@ -598,7 +596,7 @@ void DisassemblyWidget::keyPressEvent(QKeyEvent* event) contextFollowBranch(); break; case Qt::Key_Left: - gotoAddressAndSetFocus(m_cpu->getPC()); + gotoAddressAndSetFocus(cpu().getPC()); break; case Qt::Key_O: m_showInstructionOpcode = !m_showInstructionOpcode; @@ -610,7 +608,7 @@ void DisassemblyWidget::keyPressEvent(QKeyEvent* event) void DisassemblyWidget::customMenuRequested(QPoint pos) { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; QMenu* contextMenu = new QMenu(this); @@ -623,7 +621,7 @@ void DisassemblyWidget::customMenuRequested(QPoint pos) contextMenu->addAction(action = new QAction(tr("&Copy Instruction Text"), this)); action->setShortcut(QKeySequence(Qt::Key_C)); connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText); - if (m_cpu->GetSymbolGuardian().FunctionExistsWithStartingAddress(m_selectedAddressStart)) + if (cpu().GetSymbolGuardian().FunctionExistsWithStartingAddress(m_selectedAddressStart)) { contextMenu->addAction(action = new QAction(tr("Copy Function Name"), this)); connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName); @@ -695,18 +693,18 @@ inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFon { DisassemblyLineInfo line; - if (!m_cpu->isValidAddress(address)) + if (!cpu().isValidAddress(address)) return tr("%1 NOT VALID ADDRESS").arg(address, 8, 16, QChar('0')).toUpper(); // Todo? support non symbol view? m_disassemblyManager.getLine(address, true, line); - const bool isConditional = line.info.isConditional && m_cpu->getPC() == address; + const bool isConditional = line.info.isConditional && cpu().getPC() == address; const bool isConditionalMet = line.info.conditionMet; - const bool isCurrentPC = m_cpu->getPC() == address; + const bool isCurrentPC = cpu().getPC() == address; - FunctionInfo function = m_cpu->GetSymbolGuardian().FunctionStartingAtAddress(address); - SymbolInfo symbol = m_cpu->GetSymbolGuardian().SymbolStartingAtAddress(address); - const bool showOpcode = m_showInstructionOpcode && m_cpu->isAlive(); + FunctionInfo function = cpu().GetSymbolGuardian().FunctionStartingAtAddress(address); + SymbolInfo symbol = cpu().GetSymbolGuardian().SymbolStartingAtAddress(address); + const bool showOpcode = m_showInstructionOpcode && cpu().isAlive(); QString lineString; if (showOpcode) @@ -739,7 +737,7 @@ inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFon if (showOpcode) { - const u32 opcode = m_cpu->read32(address); + const u32 opcode = cpu().read32(address); lineString = lineString.arg(QtUtils::FilledQStringFromValue(opcode, 16)); } @@ -789,7 +787,7 @@ QColor DisassemblyWidget::GetAddressFunctionColor(u32 address) // Use the address to pick the colour since the value of the handle may // change from run to run. ccc::Address function_address = - m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(address).address; + cpu().GetSymbolGuardian().FunctionOverlappingAddress(address).address; if (!function_address.valid()) return palette().text().color(); @@ -817,7 +815,7 @@ QString DisassemblyWidget::FetchSelectionInfo(SelectionInfo selInfo) } else // INSTRUCTIONHEX { - infoBlock += FilledQStringFromValue(m_cpu->read32(i), 16); + infoBlock += FilledQStringFromValue(cpu().read32(i), 16); } } return infoBlock; @@ -831,7 +829,7 @@ void DisassemblyWidget::gotoAddressAndSetFocus(u32 address) void DisassemblyWidget::gotoProgramCounterOnPause() { if (m_goToProgramCounterOnPause) - gotoAddress(m_cpu->getPC(), false); + gotoAddress(cpu().getPC(), false); } void DisassemblyWidget::gotoAddress(u32 address, bool should_set_focus) @@ -861,7 +859,7 @@ bool DisassemblyWidget::AddressCanRestore(u32 start, u32 end) bool DisassemblyWidget::FunctionCanRestore(u32 address) { - FunctionInfo function = m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(address); + FunctionInfo function = cpu().GetSymbolGuardian().FunctionOverlappingAddress(address); if (function.address.valid()) address = function.address.value; diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.h b/pcsx2-qt/Debugger/DisassemblyWidget.h index 9d2670c487..85a51f692e 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.h +++ b/pcsx2-qt/Debugger/DisassemblyWidget.h @@ -5,24 +5,22 @@ #include "ui_DisassemblyWidget.h" +#include "DebuggerWidget.h" + #include "pcsx2/DebugTools/DebugInterface.h" #include "pcsx2/DebugTools/DisassemblyManager.h" -#include #include #include -class DisassemblyWidget final : public QWidget +class DisassemblyWidget final : public DebuggerWidget { Q_OBJECT public: - DisassemblyWidget(QWidget* parent); + DisassemblyWidget(DebugInterface& cpu, QWidget* parent = nullptr); ~DisassemblyWidget(); - // Required because our constructor needs to take no extra arguments. - void SetCpu(DebugInterface* cpu); - // Required for the breakpoint list (ugh wtf) QString GetLineDisasm(u32 address); @@ -69,9 +67,8 @@ signals: void VMUpdate(); private: - Ui::DisassemblyWidget ui; + Ui::DisassemblyWidget m_ui; - DebugInterface* m_cpu; u32 m_visibleStart = 0x00336318; // The address of the first opcode shown(row 0) u32 m_visibleRows; u32 m_selectedAddressStart = 0; diff --git a/pcsx2-qt/Debugger/DockManager.cpp b/pcsx2-qt/Debugger/DockManager.cpp new file mode 100644 index 0000000000..409c3cece8 --- /dev/null +++ b/pcsx2-qt/Debugger/DockManager.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "DockManager.h" + +#include "DebuggerWindow.h" +#include "DisassemblyWidget.h" +#include "RegisterWidget.h" +#include "StackWidget.h" +#include "ThreadWidget.h" +#include "Breakpoints/BreakpointWidget.h" +#include "Memory/MemorySearchWidget.h" +#include "Memory/MemoryViewWidget.h" +#include "Memory/SavedAddressesWidget.h" +#include "SymbolTree/SymbolTreeWidgets.h" + +#include + +#include +#include + +#define FOR_EACH_DEBUGGER_DOCK_WIDGET \ + /* Top right. */ \ + X(DisassemblyWidget, QT_TRANSLATE_NOOP("DockWidget", "Disassembly"), OnRight, Root) \ + /* Bottom. */ \ + X(MemoryViewWidget, QT_TRANSLATE_NOOP("DockWidget", "Memory"), OnBottom, DisassemblyWidget) \ + X(BreakpointWidget, QT_TRANSLATE_NOOP("DockWidget", "Breakpoints"), None, MemoryViewWidget) \ + X(ThreadWidget, QT_TRANSLATE_NOOP("DockWidget", "Threads"), None, MemoryViewWidget) \ + X(StackWidget, QT_TRANSLATE_NOOP("DockWidget", "Stack"), None, MemoryViewWidget) \ + X(SavedAddressesWidget, QT_TRANSLATE_NOOP("DockWidget", "Saved Addresses"), None, MemoryViewWidget) \ + X(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Globals"), None, MemoryViewWidget) \ + X(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Locals"), None, MemoryViewWidget) \ + X(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Parameters"), None, MemoryViewWidget) \ + /* Top left. */ \ + X(RegisterWidget, QT_TRANSLATE_NOOP("DockWidget", "Registers"), OnLeft, DisassemblyWidget) \ + X(FunctionTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Functions"), None, RegisterWidget) \ + X(MemorySearchWidget, QT_TRANSLATE_NOOP("DockWidget", "Memory Search"), None, RegisterWidget) + +DockManager::DockManager(DebuggerWindow* window) + : m_window(window) +{ + createDefaultLayout("R5900", r5900Debug); + //createDefaultLayout("R3000", r3000Debug); + loadLayouts(); +} + +void DockManager::configure_docking_system() +{ + KDDockWidgets::Config::self().setFlags( + KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible | + KDDockWidgets::Config::Flag_AlwaysShowTabs | + KDDockWidgets::Config::Flag_AllowReorderTabs | + KDDockWidgets::Config::Flag_TabsHaveCloseButton | + KDDockWidgets::Config::Flag_TitleBarIsFocusable); +} + +const std::vector& DockManager::layouts() +{ + return m_layouts; +} + +void DockManager::switchToLayout(size_t layout) +{ + //m_layouts.at(m_current_layout).dock_manager->setParent(nullptr); + //m_window->setCentralWidget(m_layouts.at(layout).dock_manager); + //m_current_layout = layout; +} + +size_t DockManager::cloneLayout(size_t existing_layout, std::string new_name) +{ + return 0; +} + +bool DockManager::deleteLayout(size_t layout) +{ + return false; +} + +void DockManager::loadLayouts() +{ +} + +void DockManager::saveLayouts() +{ +} + +size_t DockManager::createDefaultLayout(const char* name, DebugInterface& cpu) +{ + size_t index = m_layouts.size(); + + Layout& layout = m_layouts.emplace_back(); + layout.name = name; + layout.cpu = cpu.getCpuType(); + layout.user_defined = false; + + KDDockWidgets::QtWidgets::DockWidget* dock_Root = nullptr; +#define X(Type, title, Location, Parent) \ + KDDockWidgets::QtWidgets::DockWidget* dock_##Type = new KDDockWidgets::QtWidgets::DockWidget(title); \ + dock_##Type->setWidget(new Type(cpu)); \ + if (KDDockWidgets::Location_##Location != KDDockWidgets::Location_None) \ + m_window->addDockWidget(dock_##Type, KDDockWidgets::Location_##Location, dock_##Parent); \ + else \ + dock_##Parent->addDockWidgetAsTab(dock_##Type); + FOR_EACH_DEBUGGER_DOCK_WIDGET +#undef X + + return index; +} diff --git a/pcsx2-qt/Debugger/DockManager.h b/pcsx2-qt/Debugger/DockManager.h new file mode 100644 index 0000000000..430908a8a1 --- /dev/null +++ b/pcsx2-qt/Debugger/DockManager.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "DebugTools/DebugInterface.h" + +#include +#include + +class DebuggerWindow; + +class DockManager +{ +public: + struct Layout + { + std::string name; + BreakPointCpu cpu; + bool user_defined = false; + }; + + DockManager(DebuggerWindow* window); + + static void configure_docking_system(); + + const std::vector& layouts(); + void switchToLayout(size_t layout); + size_t cloneLayout(size_t existing_layout, std::string new_name); + bool deleteLayout(size_t layout); + + void loadLayouts(); + void saveLayouts(); + +protected: + size_t createDefaultLayout(const char* name, DebugInterface& cpu); + + KDDockWidgets::QtWidgets::MainWindow* m_window; + + std::vector m_layouts; + size_t m_current_layout = 0; +}; diff --git a/pcsx2-qt/Debugger/MemorySearchWidget.cpp b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp similarity index 96% rename from pcsx2-qt/Debugger/MemorySearchWidget.cpp rename to pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp index 7ebdcce089..47bb99f0ad 100644 --- a/pcsx2-qt/Debugger/MemorySearchWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.cpp @@ -23,8 +23,8 @@ using SearchResult = MemorySearchWidget::SearchResult; using namespace QtUtils; -MemorySearchWidget::MemorySearchWidget(QWidget* parent) - : QWidget(parent) +MemorySearchWidget::MemorySearchWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu, parent) { m_ui.setupUi(this); this->repaint(); @@ -32,8 +32,7 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent) m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked); - connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) - { + connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { emit switchToMemoryViewTab(); emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16)); }); @@ -47,11 +46,6 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent) connect(&m_resultsLoadTimer, &QTimer::timeout, this, &MemorySearchWidget::loadSearchResults); } -void MemorySearchWidget::setCpu(DebugInterface* cpu) -{ - m_cpu = cpu; -} - void MemorySearchWidget::contextSearchResultGoToDisassembly() { const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel(); @@ -120,15 +114,15 @@ void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos) contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos)); } -template +template T readValueAtAddress(DebugInterface* cpu, u32 addr); -template<> +template <> float readValueAtAddress(DebugInterface* cpu, u32 addr) { return std::bit_cast(cpu->read32(addr)); } -template<> +template <> double readValueAtAddress(DebugInterface* cpu, u32 addr) { return std::bit_cast(cpu->read64(addr)); @@ -229,7 +223,7 @@ template bool handleSearchComparison(SearchComparison searchComparison, u32 searchAddress, const SearchResult* priorResult, T searchValue, T readValue) { const bool isNotOperator = searchComparison == SearchComparison::NotEquals || searchComparison == SearchComparison::NotChanged; - switch (searchComparison) + switch (searchComparison) { case SearchComparison::Equals: case SearchComparison::NotEquals: @@ -298,7 +292,7 @@ void searchWorker(DebugInterface* cpu, std::vector& searchResults, { if (!cpu->isValidAddress(addr)) continue; - + T readValue = readValueAtAddress(cpu, addr); if (handleSearchComparison(searchComparison, addr, nullptr, searchValue, readValue)) { @@ -311,7 +305,7 @@ void searchWorker(DebugInterface* cpu, std::vector& searchResults, auto removeIt = std::remove_if(searchResults.begin(), searchResults.end(), [cpu, searchType, searchComparison, searchValue](SearchResult& searchResult) -> bool { const u32 addr = searchResult.getAddress(); if (!cpu->isValidAddress(addr)) - return true; + return true; const auto readValue = readValueAtAddress(cpu, addr); @@ -411,7 +405,7 @@ static void searchWorkerByteArray(DebugInterface* cpu, SearchType searchType, Se } else { - auto removeIt = std::remove_if(searchResults.begin(), searchResults.end(), [ searchComparison, searchType, searchValue, cpu ](SearchResult& searchResult) -> bool { + auto removeIt = std::remove_if(searchResults.begin(), searchResults.end(), [searchComparison, searchType, searchValue, cpu](SearchResult& searchResult) -> bool { const u32 addr = searchResult.getAddress(); if (!cpu->isValidAddress(addr)) return true; @@ -472,7 +466,7 @@ std::vector startWorker(DebugInterface* cpu, const SearchType type void MemorySearchWidget::onSearchButtonClicked() { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; const SearchType searchType = getCurrentSearchType(); @@ -556,10 +550,7 @@ void MemorySearchWidget::onSearchButtonClicked() return; } - if (!isFilterSearch && (searchComparison == SearchComparison::Changed || searchComparison == SearchComparison::ChangedBy - || searchComparison == SearchComparison::Decreased || searchComparison == SearchComparison::DecreasedBy - || searchComparison == SearchComparison::Increased || searchComparison == SearchComparison::IncreasedBy - || searchComparison == SearchComparison::NotChanged)) + if (!isFilterSearch && (searchComparison == SearchComparison::Changed || searchComparison == SearchComparison::ChangedBy || searchComparison == SearchComparison::Decreased || searchComparison == SearchComparison::DecreasedBy || searchComparison == SearchComparison::Increased || searchComparison == SearchComparison::IncreasedBy || searchComparison == SearchComparison::NotChanged)) { QMessageBox::critical(this, tr("Debugger"), tr("This search comparison can only be used with filter searches.")); return; @@ -587,7 +578,7 @@ void MemorySearchWidget::onSearchButtonClicked() m_searchResults.clear(); } - QFuture> workerFuture = QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, std::move(m_searchResults), searchStart, searchEnd, searchValue, searchHex ? 16 : 10); + QFuture> workerFuture = QtConcurrent::run(startWorker, &cpu(), searchType, searchComparison, std::move(m_searchResults), searchStart, searchEnd, searchValue, searchHex ? 16 : 10); workerWatcher->setFuture(workerFuture); connect(workerWatcher, &QFutureWatcher>::finished, onSearchFinished); m_searchResults.clear(); @@ -676,7 +667,7 @@ void MemorySearchWidget::updateSearchComparisonSelections() std::vector MemorySearchWidget::getValidSearchComparisonsForState(SearchType type, std::vector& existingResults) { const bool hasResults = existingResults.size() > 0; - std::vector comparisons = { SearchComparison::Equals }; + std::vector comparisons = {SearchComparison::Equals}; if (type == SearchType::ArrayType || type == SearchType::StringType) { diff --git a/pcsx2-qt/Debugger/MemorySearchWidget.h b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.h similarity index 90% rename from pcsx2-qt/Debugger/MemorySearchWidget.h rename to pcsx2-qt/Debugger/Memory/MemorySearchWidget.h index aa4c779b67..e3947c0a61 100644 --- a/pcsx2-qt/Debugger/MemorySearchWidget.h +++ b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.h @@ -5,22 +5,23 @@ #include "ui_MemorySearchWidget.h" +#include "Debugger/DebuggerWidget.h" + #include "DebugTools/DebugInterface.h" #include #include #include -class MemorySearchWidget final : public QWidget +class MemorySearchWidget final : public DebuggerWidget { - Q_OBJECT + Q_OBJECT public: - MemorySearchWidget(QWidget* parent); - ~MemorySearchWidget() = default; - void setCpu(DebugInterface* cpu); + MemorySearchWidget(DebugInterface& cpu, QWidget* parent = nullptr); + ~MemorySearchWidget() = default; - enum class SearchType + enum class SearchType { ByteType, Int16Type, @@ -75,9 +76,11 @@ public: { return labelToEnumMap.value(comparisonLabel, SearchComparison::Invalid); } - QString enumToLabel(SearchComparison comparison) { + QString enumToLabel(SearchComparison comparison) + { return enumToLabelMap.value(comparison, ""); } + private: QMap enumToLabelMap; QMap labelToEnumMap; @@ -98,7 +101,9 @@ public: public: SearchResult() {} SearchResult(u32 address, const QVariant& value, SearchType type) - : address(address), value(value), type(type) + : address(address) + , value(value) + , type(type) { } bool isIntegerValue() const { return type == SearchType::ByteType || type == SearchType::Int16Type || type == SearchType::Int32Type || type == SearchType::Int64Type; } @@ -109,7 +114,7 @@ public: SearchType getType() const { return type; } QByteArray getArrayValue() const { return isArrayValue() ? value.toByteArray() : QByteArray(); } - template + template T getValue() const { return value.value(); @@ -136,14 +141,13 @@ private: std::vector m_searchResults; SearchComparisonLabelMap m_searchComparisonLabelMap; Ui::MemorySearchWidget m_ui; - DebugInterface* m_cpu; QTimer m_resultsLoadTimer; u32 m_initialResultsLoadLimit = 20000; u32 m_numResultsAddedPerLoad = 10000; void updateSearchComparisonSelections(); - std::vector getValidSearchComparisonsForState(SearchType type, std::vector &existingResults); + std::vector getValidSearchComparisonsForState(SearchType type, std::vector& existingResults); SearchType getCurrentSearchType(); SearchComparison getCurrentSearchComparison(); }; diff --git a/pcsx2-qt/Debugger/MemorySearchWidget.ui b/pcsx2-qt/Debugger/Memory/MemorySearchWidget.ui similarity index 100% rename from pcsx2-qt/Debugger/MemorySearchWidget.ui rename to pcsx2-qt/Debugger/Memory/MemorySearchWidget.ui diff --git a/pcsx2-qt/Debugger/MemoryViewWidget.cpp b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp similarity index 96% rename from pcsx2-qt/Debugger/MemoryViewWidget.cpp rename to pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp index 5753bcce5d..bbb25a1ec8 100644 --- a/pcsx2-qt/Debugger/MemoryViewWidget.cpp +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.cpp @@ -173,6 +173,10 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 void MemoryViewTable::SelectAt(QPoint pos) { + // Check if SelectAt was called before DrawTable. + if (rowHeight == 0) + return; + const u32 selectedRow = (pos.y() - 2) / (rowHeight); const s32 x = pos.x(); const s32 avgSegmentWidth = segmentXAxis[1] - segmentXAxis[0]; @@ -447,37 +451,37 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar) /* MemoryViewWidget */ -MemoryViewWidget::MemoryViewWidget(QWidget* parent) - : QWidget(parent) +MemoryViewWidget::MemoryViewWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu) , m_table(this) { ui.setupUi(this); this->setFocusPolicy(Qt::FocusPolicy::ClickFocus); connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::customMenuRequested); + + m_table.SetCpu(&cpu); + m_table.UpdateStartAddress(0x480000); + + applyMonospaceFont(); } MemoryViewWidget::~MemoryViewWidget() = default; -void MemoryViewWidget::SetCpu(DebugInterface* cpu) -{ - m_cpu = cpu; - m_table.SetCpu(cpu); - m_table.UpdateStartAddress(0x480000); -} - void MemoryViewWidget::paintEvent(QPaintEvent* event) { - if (!m_cpu->isAlive()) - return; - QPainter painter(this); + painter.fillRect(rect(), palette().window()); + + if (!cpu().isAlive()) + return; + m_table.DrawTable(painter, this->palette(), this->height()); } void MemoryViewWidget::mousePressEvent(QMouseEvent* event) { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; m_table.SelectAt(event->pos()); @@ -486,7 +490,7 @@ void MemoryViewWidget::mousePressEvent(QMouseEvent* event) void MemoryViewWidget::customMenuRequested(QPoint pos) { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; if (!m_contextMenu) @@ -571,7 +575,7 @@ void MemoryViewWidget::customMenuRequested(QPoint pos) void MemoryViewWidget::contextCopyByte() { - QApplication::clipboard()->setText(QString::number(m_cpu->read8(m_table.selectedAddress), 16).toUpper()); + QApplication::clipboard()->setText(QString::number(cpu().read8(m_table.selectedAddress), 16).toUpper()); } void MemoryViewWidget::contextCopySegment() @@ -581,7 +585,7 @@ void MemoryViewWidget::contextCopySegment() void MemoryViewWidget::contextCopyCharacter() { - QApplication::clipboard()->setText(QChar::fromLatin1(m_cpu->read8(m_table.selectedAddress)).toUpper()); + QApplication::clipboard()->setText(QChar::fromLatin1(cpu().read8(m_table.selectedAddress)).toUpper()); } void MemoryViewWidget::contextPaste() @@ -600,7 +604,7 @@ void MemoryViewWidget::contextGoToAddress() u64 address = 0; std::string error; - if (!m_cpu->evaluateExpression(targetString.toStdString().c_str(), address, error)) + if (!cpu().evaluateExpression(targetString.toStdString().c_str(), address, error)) { QMessageBox::warning(this, tr("Cannot Go To"), QString::fromStdString(error)); return; diff --git a/pcsx2-qt/Debugger/MemoryViewWidget.h b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.h similarity index 93% rename from pcsx2-qt/Debugger/MemoryViewWidget.h rename to pcsx2-qt/Debugger/Memory/MemoryViewWidget.h index 03bc5baac8..e410e799a2 100644 --- a/pcsx2-qt/Debugger/MemoryViewWidget.h +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.h @@ -3,7 +3,9 @@ #pragma once -#include "ui_RegisterWidget.h" +#include "ui_MemoryViewWidget.h" + +#include "Debugger/DebuggerWidget.h" #include "DebugTools/DebugInterface.h" #include "DebugTools/DisassemblyManager.h" @@ -105,16 +107,14 @@ public: }; -class MemoryViewWidget final : public QWidget +class MemoryViewWidget final : public DebuggerWidget { Q_OBJECT public: - MemoryViewWidget(QWidget* parent); + MemoryViewWidget(DebugInterface& cpu, QWidget* parent = nullptr); ~MemoryViewWidget(); - void SetCpu(DebugInterface* cpu); - protected: void paintEvent(QPaintEvent* event); void mousePressEvent(QMouseEvent* event); @@ -138,7 +138,7 @@ signals: void VMUpdate(); private: - Ui::RegisterWidget ui; + Ui::MemoryViewWidget ui; QMenu* m_contextMenu = 0x0; QAction* m_actionLittleEndian; @@ -147,6 +147,5 @@ private: QAction* m_actionWORD; QAction* m_actionDWORD; - DebugInterface* m_cpu; MemoryViewTable m_table; }; diff --git a/pcsx2-qt/Debugger/MemoryViewWidget.ui b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui similarity index 81% rename from pcsx2-qt/Debugger/MemoryViewWidget.ui rename to pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui index c9d13501bb..54103700aa 100644 --- a/pcsx2-qt/Debugger/MemoryViewWidget.ui +++ b/pcsx2-qt/Debugger/Memory/MemoryViewWidget.ui @@ -10,6 +10,9 @@ 300 + + Qt::CustomContextMenu + Memory diff --git a/pcsx2-qt/Debugger/Models/SavedAddressesModel.cpp b/pcsx2-qt/Debugger/Memory/SavedAddressesModel.cpp similarity index 100% rename from pcsx2-qt/Debugger/Models/SavedAddressesModel.cpp rename to pcsx2-qt/Debugger/Memory/SavedAddressesModel.cpp diff --git a/pcsx2-qt/Debugger/Models/SavedAddressesModel.h b/pcsx2-qt/Debugger/Memory/SavedAddressesModel.h similarity index 100% rename from pcsx2-qt/Debugger/Models/SavedAddressesModel.h rename to pcsx2-qt/Debugger/Memory/SavedAddressesModel.h diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp new file mode 100644 index 0000000000..356c53f5c3 --- /dev/null +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "SavedAddressesWidget.h" + +#include "QtUtils.h" +#include "Debugger/DebuggerSettingsManager.h" + +#include +#include + +SavedAddressesWidget::SavedAddressesWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu, parent) + , m_model(cpu) +{ + //m_ui.savedAddressesList->setModel(&m_model); + //m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu); + //connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested, this, &CpuWidget::onSavedAddressesListContextMenu); + //for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes) + //{ + // m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode); + //} + //QTableView* savedAddressesTableView = m_ui.savedAddressesList; + //connect(m_ui.savedAddressesList->model(), &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) { + // savedAddressesTableView->resizeColumnToContents(topLeft.column()); + //}); +} + +void SavedAddressesWidget::onContextMenu(QPoint pos) +{ + QMenu* contextMenu = new QMenu("Saved Addresses List Context Menu", m_ui.savedAddressesList); + + QAction* newAction = new QAction(tr("New"), m_ui.savedAddressesList); + connect(newAction, &QAction::triggered, this, &SavedAddressesWidget::contextNew); + contextMenu->addAction(newAction); + + const QModelIndex indexAtPos = m_ui.savedAddressesList->indexAt(pos); + const bool isIndexValid = indexAtPos.isValid(); + + if (isIndexValid) + { + if (cpu().isAlive()) + { + QAction* goToAddressMemViewAction = new QAction(tr("Go to in Memory View"), m_ui.savedAddressesList); + connect(goToAddressMemViewAction, &QAction::triggered, this, [this, indexAtPos]() { + const QModelIndex rowAddressIndex = m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex()); + //m_ui.memoryviewWidget->gotoAddress(m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt()); + //m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); + not_yet_implemented(); + }); + contextMenu->addAction(goToAddressMemViewAction); + + QAction* goToAddressDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.savedAddressesList); + connect(goToAddressDisassemblyAction, &QAction::triggered, this, [this, indexAtPos]() { + const QModelIndex rowAddressIndex = + m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex()); + //m_ui.disassemblyWidget->gotoAddressAndSetFocus( + // m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt()); + not_yet_implemented(); + }); + contextMenu->addAction(goToAddressDisassemblyAction); + } + + QAction* copyAction = new QAction(indexAtPos.column() == 0 ? tr("Copy Address") : tr("Copy Text"), m_ui.savedAddressesList); + connect(copyAction, &QAction::triggered, [this, indexAtPos]() { + QGuiApplication::clipboard()->setText( + m_ui.savedAddressesList->model()->data(indexAtPos, Qt::DisplayRole).toString()); + }); + contextMenu->addAction(copyAction); + } + + if (m_ui.savedAddressesList->model()->rowCount() > 0) + { + QAction* actionExportCSV = new QAction(tr("Copy all as CSV"), m_ui.savedAddressesList); + connect(actionExportCSV, &QAction::triggered, [this]() { + QGuiApplication::clipboard()->setText( + QtUtils::AbstractItemModelToCSV(m_ui.savedAddressesList->model(), Qt::DisplayRole, true)); + }); + contextMenu->addAction(actionExportCSV); + } + + QAction* actionImportCSV = new QAction(tr("Paste from CSV"), m_ui.savedAddressesList); + connect(actionImportCSV, &QAction::triggered, this, &SavedAddressesWidget::contextPasteCSV); + contextMenu->addAction(actionImportCSV); + + if (cpu().isAlive()) + { + QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.savedAddressesList); + connect(actionLoad, &QAction::triggered, [this]() { + m_model.clear(); + DebuggerSettingsManager::loadGameSettings(&m_model); + }); + contextMenu->addAction(actionLoad); + + QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.savedAddressesList); + connect(actionSave, &QAction::triggered, this, &SavedAddressesWidget::saveToDebuggerSettings); + contextMenu->addAction(actionSave); + } + + if (isIndexValid) + { + QAction* deleteAction = new QAction(tr("Delete"), m_ui.savedAddressesList); + connect(deleteAction, &QAction::triggered, this, [this, indexAtPos]() { + m_ui.savedAddressesList->model()->removeRows(indexAtPos.row(), 1); + }); + contextMenu->addAction(deleteAction); + } + + contextMenu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos)); +} + +void SavedAddressesWidget::contextPasteCSV() +{ + QString csv = QGuiApplication::clipboard()->text(); + // Skip header + csv = csv.mid(csv.indexOf('\n') + 1); + + for (const QString& line : csv.split('\n')) + { + QStringList fields; + // In order to handle text with commas in them we must wrap values in quotes to mark + // where a value starts and end so that text commas aren't identified as delimiters. + // So matches each quote pair, parse it out, and removes the quotes to get the value. + QRegularExpression eachQuotePair(R"("([^"]|\\.)*")"); + QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line); + while (it.hasNext()) + { + QRegularExpressionMatch match = it.next(); + QString matchedValue = match.captured(0); + fields << matchedValue.mid(1, matchedValue.length() - 2); + } + + m_model.loadSavedAddressFromFieldList(fields); + } +} + +void SavedAddressesWidget::contextNew() +{ + qobject_cast(m_ui.savedAddressesList->model())->addRow(); + const u32 rowCount = m_ui.savedAddressesList->model()->rowCount(); + m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 0)); +} + +void SavedAddressesWidget::addAddress(u32 address) +{ + qobject_cast(m_ui.savedAddressesList->model())->addRow(); + const u32 rowCount = m_ui.savedAddressesList->model()->rowCount(); + const QModelIndex addressIndex = m_ui.savedAddressesList->model()->index(rowCount - 1, 0); + //m_ui.tabWidget->setCurrentWidget(m_ui.tab_savedaddresses); + not_yet_implemented(); + m_ui.savedAddressesList->model()->setData(addressIndex, address, Qt::UserRole); + m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 1)); +} + +void SavedAddressesWidget::saveToDebuggerSettings() +{ + DebuggerSettingsManager::saveGameSettings(&m_model); +} diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h new file mode 100644 index 0000000000..ed73046c65 --- /dev/null +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_SavedAddressesWidget.h" + +#include "SavedAddressesModel.h" + +#include "Debugger/DebuggerWidget.h" + +class SavedAddressesWidget : public DebuggerWidget +{ + Q_OBJECT + +public: + SavedAddressesWidget(DebugInterface& cpu, QWidget* parent = nullptr); + + void onContextMenu(QPoint pos); + void contextPasteCSV(); + void contextNew(); + void addAddress(u32 address); + void saveToDebuggerSettings(); + +private: + Ui::SavedAddressesWidget m_ui; + + SavedAddressesModel m_model; +}; diff --git a/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui new file mode 100644 index 0000000000..a8d756f623 --- /dev/null +++ b/pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui @@ -0,0 +1,43 @@ + + + SavedAddressesWidget + + + + 0 + 0 + 400 + 300 + + + + Saved Addresses + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + + + + + + diff --git a/pcsx2-qt/Debugger/RegisterWidget.cpp b/pcsx2-qt/Debugger/RegisterWidget.cpp index b0c80a0bd0..49e4fbf65c 100644 --- a/pcsx2-qt/Debugger/RegisterWidget.cpp +++ b/pcsx2-qt/Debugger/RegisterWidget.cpp @@ -20,8 +20,8 @@ using namespace QtUtils; -RegisterWidget::RegisterWidget(QWidget* parent) - : QWidget(parent) +RegisterWidget::RegisterWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu) { this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); @@ -30,21 +30,19 @@ RegisterWidget::RegisterWidget(QWidget* parent) connect(this, &RegisterWidget::customContextMenuRequested, this, &RegisterWidget::customMenuRequested); connect(ui.registerTabs, &QTabBar::currentChanged, this, &RegisterWidget::tabCurrentChanged); -}; -RegisterWidget::~RegisterWidget() -{ -} - -void RegisterWidget::SetCpu(DebugInterface* cpu) -{ - m_cpu = cpu; - for (int i = 0; i < m_cpu->getRegisterCategoryCount(); i++) + for (int i = 0; i < cpu.getRegisterCategoryCount(); i++) { - ui.registerTabs->addTab(m_cpu->getRegisterCategoryName(i)); + ui.registerTabs->addTab(cpu.getRegisterCategoryName(i)); } connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); }); + + applyMonospaceFont(); +} + +RegisterWidget::~RegisterWidget() +{ } void RegisterWidget::tabCurrentChanged(int cur) @@ -54,9 +52,6 @@ void RegisterWidget::tabCurrentChanged(int cur) void RegisterWidget::paintEvent(QPaintEvent* event) { - if (!m_cpu) - return; - QPainter painter(this); painter.setPen(this->palette().text().color()); m_renderStart = QPoint(0, ui.registerTabs->pos().y() + ui.registerTabs->size().height()); @@ -94,9 +89,9 @@ void RegisterWidget::paintEvent(QPaintEvent* event) // off of that. // Can probably constexpr the loop out as register names are known during runtime int safeValueStartX = 0; - for (int i = 0; i < m_cpu->getRegisterCount(categoryIndex); i++) + for (int i = 0; i < cpu().getRegisterCount(categoryIndex); i++) { - const int registerNameWidth = strlen(m_cpu->getRegisterName(categoryIndex, i)); + const int registerNameWidth = strlen(cpu().getRegisterName(categoryIndex, i)); if (safeValueStartX < registerNameWidth) { safeValueStartX = registerNameWidth; @@ -110,7 +105,7 @@ void RegisterWidget::paintEvent(QPaintEvent* event) // Make it relative to where we start rendering safeValueStartX += m_renderStart.x(); - for (s32 i = 0; i < m_cpu->getRegisterCount(categoryIndex) - m_rowStart; i++) + for (s32 i = 0; i < cpu().getRegisterCount(categoryIndex) - m_rowStart; i++) { const s32 registerIndex = i + m_rowStart; const int yStart = (i * m_rowHeight) + m_renderStart.y(); @@ -120,11 +115,11 @@ void RegisterWidget::paintEvent(QPaintEvent* event) // Draw register name painter.setPen(this->palette().text().color()); - painter.drawText(m_renderStart.x() + painter.fontMetrics().averageCharWidth(), yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, m_cpu->getRegisterName(categoryIndex, registerIndex)); + painter.drawText(m_renderStart.x() + painter.fontMetrics().averageCharWidth(), yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, cpu().getRegisterName(categoryIndex, registerIndex)); - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) { - const u128 curRegister = m_cpu->getRegister(categoryIndex, registerIndex); + const u128 curRegister = cpu().getRegister(categoryIndex, registerIndex); int regIndex = 3; for (int j = 0; j < 4; j++) @@ -136,7 +131,7 @@ void RegisterWidget::paintEvent(QPaintEvent* event) if (categoryIndex == EECAT_VU0F && m_showVU0FFloat) painter.drawText(m_fieldStartX[j], yStart, m_fieldWidth, m_rowHeight, Qt::AlignLeft, - painter.fontMetrics().elidedText(QString::number(std::bit_cast(m_cpu->getRegister(categoryIndex, registerIndex)._u32[regIndex])), Qt::ElideRight, m_fieldWidth - painter.fontMetrics().averageCharWidth())); + painter.fontMetrics().elidedText(QString::number(std::bit_cast(cpu().getRegister(categoryIndex, registerIndex)._u32[regIndex])), Qt::ElideRight, m_fieldWidth - painter.fontMetrics().averageCharWidth())); else painter.drawText(m_fieldStartX[j], yStart, m_fieldWidth, m_rowHeight, Qt::AlignLeft, FilledQStringFromValue(curRegister._u32[regIndex], 16)); @@ -153,13 +148,13 @@ void RegisterWidget::paintEvent(QPaintEvent* event) if (categoryIndex == EECAT_FPR && m_showFPRFloat) painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, - QString("%1").arg(QString::number(std::bit_cast(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0]))).toUpper()); - else if (m_cpu->getRegisterSize(categoryIndex) == 64) + QString("%1").arg(QString::number(std::bit_cast(cpu().getRegister(categoryIndex, registerIndex)._u32[0]))).toUpper()); + else if (cpu().getRegisterSize(categoryIndex) == 64) painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, - FilledQStringFromValue(m_cpu->getRegister(categoryIndex, registerIndex).lo, 16)); + FilledQStringFromValue(cpu().getRegister(categoryIndex, registerIndex).lo, 16)); else painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, - FilledQStringFromValue(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0], 16)); + FilledQStringFromValue(cpu().getRegister(categoryIndex, registerIndex)._u32[0], 16)); } } painter.end(); @@ -171,7 +166,7 @@ void RegisterWidget::mousePressEvent(QMouseEvent* event) m_selectedRow = static_cast(((event->position().y() - m_renderStart.y()) / m_rowHeight)) + m_rowStart; // For 128 bit types, support selecting segments - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) { constexpr auto inRange = [](u32 low, u32 high, u32 val) { return (low <= val && val <= high); @@ -190,7 +185,7 @@ void RegisterWidget::mousePressEvent(QMouseEvent* event) void RegisterWidget::wheelEvent(QWheelEvent* event) { - if (event->angleDelta().y() < 0 && m_rowEnd < m_cpu->getRegisterCount(ui.registerTabs->currentIndex())) + if (event->angleDelta().y() < 0 && m_rowEnd < cpu().getRegisterCount(ui.registerTabs->currentIndex())) { m_rowStart += 1; } @@ -204,12 +199,12 @@ void RegisterWidget::wheelEvent(QWheelEvent* event) void RegisterWidget::mouseDoubleClickEvent(QMouseEvent* event) { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative) return; const int categoryIndex = ui.registerTabs->currentIndex(); - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) contextChangeSegment(); else contextChangeValue(); @@ -217,7 +212,7 @@ void RegisterWidget::mouseDoubleClickEvent(QMouseEvent* event) void RegisterWidget::customMenuRequested(QPoint pos) { - if (!m_cpu->isAlive()) + if (!cpu().isAlive()) return; if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative) @@ -248,7 +243,7 @@ void RegisterWidget::customMenuRequested(QPoint pos) m_contextMenu->addSeparator(); } - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) { m_contextMenu->addAction(action = new QAction(tr("Copy Top Half"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyTop); @@ -265,7 +260,7 @@ void RegisterWidget::customMenuRequested(QPoint pos) m_contextMenu->addSeparator(); - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) { m_contextMenu->addAction(action = new QAction(tr("Change Top Half"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeTop); @@ -295,7 +290,7 @@ void RegisterWidget::customMenuRequested(QPoint pos) void RegisterWidget::contextCopyValue() { const int categoryIndex = ui.registerTabs->currentIndex(); - const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); + const u128 val = cpu().getRegister(categoryIndex, m_selectedRow); if (CAT_SHOW_FLOAT) QApplication::clipboard()->setText(QString("%1").arg(QString::number(std::bit_cast(val._u32[0])).toUpper(), 16)); else @@ -305,21 +300,21 @@ void RegisterWidget::contextCopyValue() void RegisterWidget::contextCopyTop() { const int categoryIndex = ui.registerTabs->currentIndex(); - const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); + const u128 val = cpu().getRegister(categoryIndex, m_selectedRow); QApplication::clipboard()->setText(FilledQStringFromValue(val.hi, 16)); } void RegisterWidget::contextCopyBottom() { const int categoryIndex = ui.registerTabs->currentIndex(); - const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); + const u128 val = cpu().getRegister(categoryIndex, m_selectedRow); QApplication::clipboard()->setText(FilledQStringFromValue(val.lo, 16)); } void RegisterWidget::contextCopySegment() { const int categoryIndex = ui.registerTabs->currentIndex(); - const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); + const u128 val = cpu().getRegister(categoryIndex, m_selectedRow); if (CAT_SHOW_FLOAT) QApplication::clipboard()->setText(FilledQStringFromValue(std::bit_cast(val._u32[3 - m_selected128Field]), 10)); else @@ -330,7 +325,7 @@ bool RegisterWidget::contextFetchNewValue(u64& out, u64 currentValue, bool segme { const int categoryIndex = ui.registerTabs->currentIndex(); const bool floatingPoint = CAT_SHOW_FLOAT && segment; - const int regSize = m_cpu->getRegisterSize(categoryIndex); + const int regSize = cpu().getRegisterSize(categoryIndex); bool ok = false; QString existingValue("%1"); @@ -341,7 +336,7 @@ bool RegisterWidget::contextFetchNewValue(u64& out, u64 currentValue, bool segme existingValue = existingValue.arg(std::bit_cast((u32)currentValue)); //: Changing the value in a CPU register (e.g. "Change t0") - QString input = QInputDialog::getText(this, tr("Change %1").arg(m_cpu->getRegisterName(categoryIndex, m_selectedRow)), "", + QString input = QInputDialog::getText(this, tr("Change %1").arg(cpu().getRegisterName(categoryIndex, m_selectedRow)), "", QLineEdit::Normal, existingValue, &ok); if (!ok) @@ -373,9 +368,9 @@ void RegisterWidget::contextChangeValue() { const int categoryIndex = ui.registerTabs->currentIndex(); u64 newVal; - if (contextFetchNewValue(newVal, m_cpu->getRegister(categoryIndex, m_selectedRow).lo)) + if (contextFetchNewValue(newVal, cpu().getRegister(categoryIndex, m_selectedRow).lo)) { - m_cpu->setRegister(categoryIndex, m_selectedRow, u128::From64(newVal)); + cpu().setRegister(categoryIndex, m_selectedRow, u128::From64(newVal)); VMUpdate(); } } @@ -383,11 +378,11 @@ void RegisterWidget::contextChangeValue() void RegisterWidget::contextChangeTop() { u64 newVal; - u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow); + u128 oldVal = cpu().getRegister(ui.registerTabs->currentIndex(), m_selectedRow); if (contextFetchNewValue(newVal, oldVal.hi)) { oldVal.hi = newVal; - m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); + cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); VMUpdate(); } } @@ -395,11 +390,11 @@ void RegisterWidget::contextChangeTop() void RegisterWidget::contextChangeBottom() { u64 newVal; - u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow); + u128 oldVal = cpu().getRegister(ui.registerTabs->currentIndex(), m_selectedRow); if (contextFetchNewValue(newVal, oldVal.lo)) { oldVal.lo = newVal; - m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); + cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); VMUpdate(); } } @@ -407,11 +402,11 @@ void RegisterWidget::contextChangeBottom() void RegisterWidget::contextChangeSegment() { u64 newVal; - u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow); + u128 oldVal = cpu().getRegister(ui.registerTabs->currentIndex(), m_selectedRow); if (contextFetchNewValue(newVal, oldVal._u32[3 - m_selected128Field], true)) { oldVal._u32[3 - m_selected128Field] = (u32)newVal; - m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); + cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); VMUpdate(); } } @@ -419,15 +414,15 @@ void RegisterWidget::contextChangeSegment() void RegisterWidget::contextGotoDisasm() { const int categoryIndex = ui.registerTabs->currentIndex(); - u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow); + u128 regVal = cpu().getRegister(categoryIndex, m_selectedRow); u32 addr = 0; - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) addr = regVal._u32[3 - m_selected128Field]; else addr = regVal._u32[0]; - if (m_cpu->isValidAddress(addr)) + if (cpu().isValidAddress(addr)) gotoInDisasm(addr); else QMessageBox::warning(this, tr("Invalid target address"), ("This register holds an invalid address.")); @@ -436,10 +431,10 @@ void RegisterWidget::contextGotoDisasm() void RegisterWidget::contextGotoMemory() { const int categoryIndex = ui.registerTabs->currentIndex(); - u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow); + u128 regVal = cpu().getRegister(categoryIndex, m_selectedRow); u32 addr = 0; - if (m_cpu->getRegisterSize(categoryIndex) == 128) + if (cpu().getRegisterSize(categoryIndex) == 128) addr = regVal._u32[3 - m_selected128Field]; else addr = regVal._u32[0]; diff --git a/pcsx2-qt/Debugger/RegisterWidget.h b/pcsx2-qt/Debugger/RegisterWidget.h index cfe9cb4799..f114cca55e 100644 --- a/pcsx2-qt/Debugger/RegisterWidget.h +++ b/pcsx2-qt/Debugger/RegisterWidget.h @@ -5,24 +5,23 @@ #include "ui_RegisterWidget.h" +#include "DebuggerWidget.h" + #include "DebugTools/DebugInterface.h" #include "DebugTools/DisassemblyManager.h" -#include #include #include #include -class RegisterWidget final : public QWidget +class RegisterWidget final : public DebuggerWidget { Q_OBJECT public: - RegisterWidget(QWidget* parent); + RegisterWidget(DebugInterface& cpu, QWidget* parent = nullptr); ~RegisterWidget(); - void SetCpu(DebugInterface* cpu); - protected: void paintEvent(QPaintEvent* event); void mousePressEvent(QMouseEvent* event); @@ -58,8 +57,6 @@ private: // Returns true on success bool contextFetchNewValue(u64& out, u64 currentValue, bool segment = false); - DebugInterface* m_cpu; - // Used for the height offset the tab bar creates // because we share a widget QPoint m_renderStart; diff --git a/pcsx2-qt/Debugger/RegisterWidget.ui b/pcsx2-qt/Debugger/RegisterWidget.ui index 63388d9f9d..fb5f5dd971 100644 --- a/pcsx2-qt/Debugger/RegisterWidget.ui +++ b/pcsx2-qt/Debugger/RegisterWidget.ui @@ -11,9 +11,9 @@ - - 1 - 1 + + 0 + 0 diff --git a/pcsx2-qt/Debugger/Models/StackModel.cpp b/pcsx2-qt/Debugger/StackModel.cpp similarity index 100% rename from pcsx2-qt/Debugger/Models/StackModel.cpp rename to pcsx2-qt/Debugger/StackModel.cpp diff --git a/pcsx2-qt/Debugger/Models/StackModel.h b/pcsx2-qt/Debugger/StackModel.h similarity index 100% rename from pcsx2-qt/Debugger/Models/StackModel.h rename to pcsx2-qt/Debugger/StackModel.h diff --git a/pcsx2-qt/Debugger/StackWidget.cpp b/pcsx2-qt/Debugger/StackWidget.cpp new file mode 100644 index 0000000000..805376ead7 --- /dev/null +++ b/pcsx2-qt/Debugger/StackWidget.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "StackWidget.h" + +#include "QtUtils.h" + +#include +#include + +StackWidget::StackWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu, parent) + , m_model(cpu) +{ + m_ui.setupUi(this); + + connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &StackWidget::onContextMenu); + connect(m_ui.stackList, &QTableView::doubleClicked, this, &StackWidget::onDoubleClick); + + m_ui.stackList->setModel(&m_model); + for (std::size_t i = 0; auto mode : StackModel::HeaderResizeModes) + { + m_ui.stackList->horizontalHeader()->setSectionResizeMode(i, mode); + i++; + } +} + +void StackWidget::onContextMenu(QPoint pos) +{ + if (!m_ui.stackList->selectionModel()->hasSelection()) + return; + + QMenu* contextMenu = new QMenu(tr("Stack List Context Menu"), m_ui.stackList); + + QAction* actionCopy = new QAction(tr("Copy"), m_ui.stackList); + connect(actionCopy, &QAction::triggered, [this]() { + const auto* selModel = m_ui.stackList->selectionModel(); + + if (!selModel->hasSelection()) + return; + + QGuiApplication::clipboard()->setText(m_ui.stackList->model()->data(selModel->currentIndex()).toString()); + }); + contextMenu->addAction(actionCopy); + + contextMenu->addSeparator(); + + QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.stackList); + connect(actionExport, &QAction::triggered, [this]() { + QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model())); + }); + contextMenu->addAction(actionExport); + + contextMenu->popup(m_ui.stackList->viewport()->mapToGlobal(pos)); +} + +void StackWidget::onDoubleClick(const QModelIndex& index) +{ + switch (index.column()) + { + case StackModel::StackModel::ENTRY: + case StackModel::StackModel::ENTRY_LABEL: + //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::ENTRY), Qt::UserRole).toUInt()); + not_yet_implemented(); + break; + case StackModel::StackModel::SP: + //m_ui.memoryviewWidget->gotoAddress(m_ui.stackList->model()->data(index, Qt::UserRole).toUInt()); + //m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); + not_yet_implemented(); + break; + default: // Default to PC + //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::PC), Qt::UserRole).toUInt()); + not_yet_implemented(); + break; + } +} diff --git a/pcsx2-qt/Debugger/StackWidget.h b/pcsx2-qt/Debugger/StackWidget.h new file mode 100644 index 0000000000..32b5907da2 --- /dev/null +++ b/pcsx2-qt/Debugger/StackWidget.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_StackWidget.h" + +#include "StackModel.h" + +#include "DebuggerWidget.h" + +class StackWidget final : public DebuggerWidget +{ + Q_OBJECT + +public: + StackWidget(DebugInterface& cpu, QWidget* parent = nullptr); + + void onContextMenu(QPoint pos); + void onDoubleClick(const QModelIndex& index); + +private: + Ui::StackWidget m_ui; + + StackModel m_model; +}; diff --git a/pcsx2-qt/Debugger/StackWidget.ui b/pcsx2-qt/Debugger/StackWidget.ui new file mode 100644 index 0000000000..44830715af --- /dev/null +++ b/pcsx2-qt/Debugger/StackWidget.ui @@ -0,0 +1,43 @@ + + + StackWidget + + + + 0 + 0 + 400 + 300 + + + + Stack + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + + + + + + diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui index 4fb07245f4..de979d7e08 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui @@ -6,12 +6,12 @@ 0 0 - 419 + 400 300 - Form + Symbol Tree diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp index a8b8ade04f..e86d8af394 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp @@ -14,8 +14,12 @@ static bool testName(const QString& name, const QString& filter); -SymbolTreeWidget::SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent) - : QWidget(parent) +SymbolTreeWidget::SymbolTreeWidget( + u32 flags, + s32 symbol_address_alignment, + DebugInterface& cpu, + QWidget* parent) + : DebuggerWidget(&cpu, parent) , m_cpu(cpu) , m_flags(flags) , m_symbol_address_alignment(symbol_address_alignment) @@ -661,7 +665,11 @@ SymbolTreeNode* SymbolTreeWidget::currentNode() // ***************************************************************************** FunctionTreeWidget::FunctionTreeWidget(DebugInterface& cpu, QWidget* parent) - : SymbolTreeWidget(ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS, 4, cpu, parent) + : SymbolTreeWidget( + ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS, + 4, + cpu, + parent) { } @@ -745,7 +753,11 @@ void FunctionTreeWidget::onNewButtonPressed() // ***************************************************************************** GlobalVariableTreeWidget::GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent) - : SymbolTreeWidget(ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS | ALLOW_MANGLED_NAME_ACTIONS, 1, cpu, parent) + : SymbolTreeWidget( + ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS | ALLOW_MANGLED_NAME_ACTIONS, + 1, + cpu, + parent) { } @@ -884,7 +896,11 @@ void GlobalVariableTreeWidget::onNewButtonPressed() // ***************************************************************************** LocalVariableTreeWidget::LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent) - : SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent) + : SymbolTreeWidget( + ALLOW_TYPE_ACTIONS, + 1, + cpu, + parent) { } @@ -1009,7 +1025,11 @@ void LocalVariableTreeWidget::onNewButtonPressed() // ***************************************************************************** ParameterVariableTreeWidget::ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent) - : SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent) + : SymbolTreeWidget( + ALLOW_TYPE_ACTIONS, + 1, + cpu, + parent) { } diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h index b74138bbc9..0186b46dbf 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h @@ -3,7 +3,7 @@ #pragma once -#include +#include "Debugger/DebuggerWidget.h" #include "SymbolTreeModel.h" #include "ui_SymbolTreeWidget.h" @@ -12,7 +12,7 @@ struct SymbolFilters; // A symbol tree widget with its associated refresh button, filter box and // right-click menu. Supports grouping, sorting and various other settings. -class SymbolTreeWidget : public QWidget +class SymbolTreeWidget : public DebuggerWidget { Q_OBJECT @@ -41,7 +41,11 @@ protected: const ccc::SourceFile* source_file = nullptr; }; - SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent = nullptr); + SymbolTreeWidget( + u32 flags, + s32 symbol_address_alignment, + DebugInterface& cpu, + QWidget* parent = nullptr); void resizeEvent(QResizeEvent* event) override; diff --git a/pcsx2-qt/Debugger/Models/ThreadModel.cpp b/pcsx2-qt/Debugger/ThreadModel.cpp similarity index 100% rename from pcsx2-qt/Debugger/Models/ThreadModel.cpp rename to pcsx2-qt/Debugger/ThreadModel.cpp diff --git a/pcsx2-qt/Debugger/Models/ThreadModel.h b/pcsx2-qt/Debugger/ThreadModel.h similarity index 100% rename from pcsx2-qt/Debugger/Models/ThreadModel.h rename to pcsx2-qt/Debugger/ThreadModel.h diff --git a/pcsx2-qt/Debugger/ThreadWidget.cpp b/pcsx2-qt/Debugger/ThreadWidget.cpp new file mode 100644 index 0000000000..73401586ad --- /dev/null +++ b/pcsx2-qt/Debugger/ThreadWidget.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "ThreadWidget.h" + +#include "QtUtils.h" + +#include +#include + +ThreadWidget::ThreadWidget(DebugInterface& cpu, QWidget* parent) + : DebuggerWidget(&cpu, parent) + , m_model(cpu) +{ + m_ui.setupUi(this); + + connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &ThreadWidget::onContextMenu); + connect(m_ui.threadList, &QTableView::doubleClicked, this, &ThreadWidget::onDoubleClick); + + m_proxy_model.setSourceModel(&m_model); + m_proxy_model.setSortRole(Qt::UserRole); + m_ui.threadList->setModel(&m_proxy_model); + m_ui.threadList->setSortingEnabled(true); + m_ui.threadList->sortByColumn(ThreadModel::ThreadColumns::ID, Qt::SortOrder::AscendingOrder); + for (std::size_t i = 0; auto mode : ThreadModel::HeaderResizeModes) + { + m_ui.threadList->horizontalHeader()->setSectionResizeMode(i, mode); + i++; + } +} + +void ThreadWidget::onContextMenu(QPoint pos) +{ + if (!m_ui.threadList->selectionModel()->hasSelection()) + return; + + QMenu* contextMenu = new QMenu(tr("Thread List Context Menu"), m_ui.threadList); + + QAction* actionCopy = new QAction(tr("Copy"), m_ui.threadList); + connect(actionCopy, &QAction::triggered, [this]() { + const auto* selModel = m_ui.threadList->selectionModel(); + + if (!selModel->hasSelection()) + return; + + QGuiApplication::clipboard()->setText(m_ui.threadList->model()->data(selModel->currentIndex()).toString()); + }); + contextMenu->addAction(actionCopy); + + contextMenu->addSeparator(); + + QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.threadList); + connect(actionExport, &QAction::triggered, [this]() { + QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model())); + }); + contextMenu->addAction(actionExport); + + contextMenu->popup(m_ui.threadList->viewport()->mapToGlobal(pos)); +} + +void ThreadWidget::onDoubleClick(const QModelIndex& index) +{ + switch (index.column()) + { + case ThreadModel::ThreadColumns::ENTRY: + not_yet_implemented(); + //m_ui.memoryviewWidget->gotoAddress(m_ui.threadList->model()->data(index, Qt::UserRole).toUInt()); + //m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); + break; + default: // Default to PC + not_yet_implemented(); + //m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.threadList->model()->data(m_ui.threadList->model()->index(index.row(), ThreadModel::ThreadColumns::PC), Qt::UserRole).toUInt()); + break; + } +} diff --git a/pcsx2-qt/Debugger/ThreadWidget.h b/pcsx2-qt/Debugger/ThreadWidget.h new file mode 100644 index 0000000000..f0d71064d7 --- /dev/null +++ b/pcsx2-qt/Debugger/ThreadWidget.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_ThreadWidget.h" + +#include "DebuggerWidget.h" +#include "ThreadModel.h" + +#include + +class ThreadWidget final : public DebuggerWidget +{ + Q_OBJECT + +public: + ThreadWidget(DebugInterface& cpu, QWidget* parent = nullptr); + + void onContextMenu(QPoint pos); + void onDoubleClick(const QModelIndex& index); + +private: + Ui::ThreadWidget m_ui; + + ThreadModel m_model; + QSortFilterProxyModel m_proxy_model; +}; diff --git a/pcsx2-qt/Debugger/ThreadWidget.ui b/pcsx2-qt/Debugger/ThreadWidget.ui new file mode 100644 index 0000000000..4d498c2146 --- /dev/null +++ b/pcsx2-qt/Debugger/ThreadWidget.ui @@ -0,0 +1,43 @@ + + + ThreadWidget + + + + 0 + 0 + 400 + 300 + + + + Threads + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + + + + + + diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 7b3eaa8ec6..43ab03f136 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -2603,8 +2603,13 @@ void MainWindow::doSettings(const char* category /* = nullptr */) DebuggerWindow* MainWindow::getDebuggerWindow() { if (!m_debugger_window) + { + // Setup KDDockWidgets. + DockManager::configure_docking_system(); + // Don't pass us (this) as the parent, otherwise the window is always on top of the mainwindow (on windows at least) m_debugger_window = new DebuggerWindow(nullptr); + } return m_debugger_window; } diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h index 1b89dc42e0..0aa1986568 100644 --- a/pcsx2-qt/QtHost.h +++ b/pcsx2-qt/QtHost.h @@ -43,7 +43,6 @@ Q_DECLARE_METATYPE(std::optional); Q_DECLARE_METATYPE(GSRendererType); Q_DECLARE_METATYPE(InputBindingKey); Q_DECLARE_METATYPE(CDVD_SourceType); -Q_DECLARE_METATYPE(Achievements::LoginRequestReason); class EmuThread : public QThread { @@ -114,7 +113,7 @@ public Q_SLOTS: void endCapture(); void setAudioOutputVolume(int volume, int fast_forward_volume); void setAudioOutputMuted(bool muted); - + Q_SIGNALS: bool messageConfirmed(const QString& title, const QString& message); void statusMessage(const QString& message); From cdc89aa574d60b8cee7c4390086e1a72e9dc1a25 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:49:39 +0000 Subject: [PATCH 3/3] Debugger: Add support for multiple UI layouts --- pcsx2-qt/CMakeLists.txt | 8 +- pcsx2-qt/Debugger/DebuggerWidget.cpp | 41 +- pcsx2-qt/Debugger/DebuggerWidget.h | 16 +- pcsx2-qt/Debugger/DebuggerWindow.cpp | 8 + pcsx2-qt/Debugger/DebuggerWindow.h | 2 +- pcsx2-qt/Debugger/DebuggerWindow.ui | 6 - pcsx2-qt/Debugger/DockManager.cpp | 108 ---- pcsx2-qt/Debugger/DockManager.h | 42 -- pcsx2-qt/Debugger/Docking/DockManager.cpp | 593 ++++++++++++++++++ pcsx2-qt/Debugger/Docking/DockManager.h | 109 ++++ .../Debugger/Docking/LayoutEditorDialog.cpp | 63 ++ .../Debugger/Docking/LayoutEditorDialog.h | 33 + .../Debugger/Docking/LayoutEditorDialog.ui | 98 +++ pcsx2-qt/Debugger/JsonValueWrapper.h | 34 + pcsx2-qt/MainWindow.cpp | 2 +- .../Settings/DebugAnalysisSettingsWidget.h | 1 - pcsx2-qt/Settings/DebugSettingsWidget.ui | 11 +- pcsx2/Config.h | 3 +- pcsx2/Pcsx2Config.cpp | 8 +- 19 files changed, 1011 insertions(+), 175 deletions(-) delete mode 100644 pcsx2-qt/Debugger/DockManager.cpp delete mode 100644 pcsx2-qt/Debugger/DockManager.h create mode 100644 pcsx2-qt/Debugger/Docking/DockManager.cpp create mode 100644 pcsx2-qt/Debugger/Docking/DockManager.h create mode 100644 pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp create mode 100644 pcsx2-qt/Debugger/Docking/LayoutEditorDialog.h create mode 100644 pcsx2-qt/Debugger/Docking/LayoutEditorDialog.ui create mode 100644 pcsx2-qt/Debugger/JsonValueWrapper.h diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 1bdff961fc..61b2c0d237 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -168,8 +168,7 @@ target_sources(pcsx2-qt PRIVATE Debugger/DisassemblyWidget.cpp Debugger/DisassemblyWidget.h Debugger/DisassemblyWidget.ui - Debugger/DockManager.cpp - Debugger/DockManager.h + Debugger/JsonValueWrapper.h Debugger/RegisterWidget.cpp Debugger/RegisterWidget.h Debugger/RegisterWidget.ui @@ -189,6 +188,11 @@ target_sources(pcsx2-qt PRIVATE Debugger/Breakpoints/BreakpointWidget.cpp Debugger/Breakpoints/BreakpointWidget.h Debugger/Breakpoints/BreakpointWidget.ui + Debugger/Docking/DockManager.cpp + Debugger/Docking/DockManager.h + Debugger/Docking/LayoutEditorDialog.cpp + Debugger/Docking/LayoutEditorDialog.h + Debugger/Docking/LayoutEditorDialog.ui Debugger/Memory/MemorySearchWidget.cpp Debugger/Memory/MemorySearchWidget.h Debugger/Memory/MemorySearchWidget.ui diff --git a/pcsx2-qt/Debugger/DebuggerWidget.cpp b/pcsx2-qt/Debugger/DebuggerWidget.cpp index c4d2148ba5..a8da35ba09 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.cpp +++ b/pcsx2-qt/Debugger/DebuggerWidget.cpp @@ -3,13 +3,9 @@ #include "DebuggerWidget.h" -#include "common/Assertions.h" +#include "JsonValueWrapper.h" -DebuggerWidget::DebuggerWidget(DebugInterface* cpu, QWidget* parent) - : QWidget(parent) - , m_cpu(cpu) -{ -} +#include "common/Assertions.h" DebugInterface& DebuggerWidget::cpu() const { @@ -17,6 +13,33 @@ DebugInterface& DebuggerWidget::cpu() const return *m_cpu; } +void DebuggerWidget::setCpu(DebugInterface* cpu) +{ + m_cpu = cpu; +} + +void DebuggerWidget::toJson(JsonValueWrapper& json) +{ + rapidjson::Value cpu_name; + switch (cpu().getCpuType()) + { + case BREAKPOINT_EE: + cpu_name.SetString("EE"); + break; + case BREAKPOINT_IOP: + cpu_name.SetString("IOP"); + break; + default: + return; + } + + json.value().AddMember("cpu", cpu_name, json.allocator()); +} + +void DebuggerWidget::fromJson(JsonValueWrapper& json) +{ +} + void DebuggerWidget::applyMonospaceFont() { // Easiest way to handle cross platform monospace fonts @@ -29,3 +52,9 @@ void DebuggerWidget::applyMonospaceFont() setStyleSheet(QStringLiteral("font: 10pt 'Monospace'")); #endif } + +DebuggerWidget::DebuggerWidget(DebugInterface* cpu, QWidget* parent) + : QWidget(parent) + , m_cpu(cpu) +{ +} diff --git a/pcsx2-qt/Debugger/DebuggerWidget.h b/pcsx2-qt/Debugger/DebuggerWidget.h index 53fec8c4b4..99895dd39e 100644 --- a/pcsx2-qt/Debugger/DebuggerWidget.h +++ b/pcsx2-qt/Debugger/DebuggerWidget.h @@ -12,17 +12,27 @@ inline void not_yet_implemented() abort(); } +class JsonValueWrapper; + class DebuggerWidget : public QWidget { Q_OBJECT -protected: - DebuggerWidget(DebugInterface* cpu, QWidget* parent = nullptr); - +public: DebugInterface& cpu() const; + void setCpu(DebugInterface* cpu); + + virtual void toJson(JsonValueWrapper& json); + virtual void fromJson(JsonValueWrapper& json); void applyMonospaceFont(); + size_t widget_description_index = SIZE_MAX; + QString unique_name; + +protected: + DebuggerWidget(DebugInterface* cpu, QWidget* parent = nullptr); + private: DebugInterface* m_cpu; }; diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp index 47be09fc85..1c760c3eed 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.cpp +++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp @@ -42,6 +42,14 @@ DebuggerWindow::DebuggerWindow(QWidget* parent) //QTabBar* tabs = new QTabBar(); //tabs->addTab("Test"); //m_ui.menuBar->layout()->addWidget(tabs); + + QMenuBar* menu_bar = menuBar(); + + setMenuWidget(m_dock_manager.createLayoutSwitcher(menu_bar)); + + connect(m_ui.menuWindows, &QMenu::aboutToShow, this, [this]() { + m_dock_manager.createWindowsMenu(m_ui.menuWindows); + }); } DebuggerWindow::~DebuggerWindow() = default; diff --git a/pcsx2-qt/Debugger/DebuggerWindow.h b/pcsx2-qt/Debugger/DebuggerWindow.h index 65df2dd71a..039a7051af 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.h +++ b/pcsx2-qt/Debugger/DebuggerWindow.h @@ -5,7 +5,7 @@ #include "ui_DebuggerWindow.h" -#include "DockManager.h" +#include "Docking/DockManager.h" #include diff --git a/pcsx2-qt/Debugger/DebuggerWindow.ui b/pcsx2-qt/Debugger/DebuggerWindow.ui index c0bd9b9487..a406de537d 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.ui +++ b/pcsx2-qt/Debugger/DebuggerWindow.ui @@ -77,11 +77,6 @@ Windows - - - Layouts - - View @@ -92,7 +87,6 @@ - diff --git a/pcsx2-qt/Debugger/DockManager.cpp b/pcsx2-qt/Debugger/DockManager.cpp deleted file mode 100644 index 409c3cece8..0000000000 --- a/pcsx2-qt/Debugger/DockManager.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team -// SPDX-License-Identifier: GPL-3.0+ - -#include "DockManager.h" - -#include "DebuggerWindow.h" -#include "DisassemblyWidget.h" -#include "RegisterWidget.h" -#include "StackWidget.h" -#include "ThreadWidget.h" -#include "Breakpoints/BreakpointWidget.h" -#include "Memory/MemorySearchWidget.h" -#include "Memory/MemoryViewWidget.h" -#include "Memory/SavedAddressesWidget.h" -#include "SymbolTree/SymbolTreeWidgets.h" - -#include - -#include -#include - -#define FOR_EACH_DEBUGGER_DOCK_WIDGET \ - /* Top right. */ \ - X(DisassemblyWidget, QT_TRANSLATE_NOOP("DockWidget", "Disassembly"), OnRight, Root) \ - /* Bottom. */ \ - X(MemoryViewWidget, QT_TRANSLATE_NOOP("DockWidget", "Memory"), OnBottom, DisassemblyWidget) \ - X(BreakpointWidget, QT_TRANSLATE_NOOP("DockWidget", "Breakpoints"), None, MemoryViewWidget) \ - X(ThreadWidget, QT_TRANSLATE_NOOP("DockWidget", "Threads"), None, MemoryViewWidget) \ - X(StackWidget, QT_TRANSLATE_NOOP("DockWidget", "Stack"), None, MemoryViewWidget) \ - X(SavedAddressesWidget, QT_TRANSLATE_NOOP("DockWidget", "Saved Addresses"), None, MemoryViewWidget) \ - X(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Globals"), None, MemoryViewWidget) \ - X(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Locals"), None, MemoryViewWidget) \ - X(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Parameters"), None, MemoryViewWidget) \ - /* Top left. */ \ - X(RegisterWidget, QT_TRANSLATE_NOOP("DockWidget", "Registers"), OnLeft, DisassemblyWidget) \ - X(FunctionTreeWidget, QT_TRANSLATE_NOOP("DockWidget", "Functions"), None, RegisterWidget) \ - X(MemorySearchWidget, QT_TRANSLATE_NOOP("DockWidget", "Memory Search"), None, RegisterWidget) - -DockManager::DockManager(DebuggerWindow* window) - : m_window(window) -{ - createDefaultLayout("R5900", r5900Debug); - //createDefaultLayout("R3000", r3000Debug); - loadLayouts(); -} - -void DockManager::configure_docking_system() -{ - KDDockWidgets::Config::self().setFlags( - KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible | - KDDockWidgets::Config::Flag_AlwaysShowTabs | - KDDockWidgets::Config::Flag_AllowReorderTabs | - KDDockWidgets::Config::Flag_TabsHaveCloseButton | - KDDockWidgets::Config::Flag_TitleBarIsFocusable); -} - -const std::vector& DockManager::layouts() -{ - return m_layouts; -} - -void DockManager::switchToLayout(size_t layout) -{ - //m_layouts.at(m_current_layout).dock_manager->setParent(nullptr); - //m_window->setCentralWidget(m_layouts.at(layout).dock_manager); - //m_current_layout = layout; -} - -size_t DockManager::cloneLayout(size_t existing_layout, std::string new_name) -{ - return 0; -} - -bool DockManager::deleteLayout(size_t layout) -{ - return false; -} - -void DockManager::loadLayouts() -{ -} - -void DockManager::saveLayouts() -{ -} - -size_t DockManager::createDefaultLayout(const char* name, DebugInterface& cpu) -{ - size_t index = m_layouts.size(); - - Layout& layout = m_layouts.emplace_back(); - layout.name = name; - layout.cpu = cpu.getCpuType(); - layout.user_defined = false; - - KDDockWidgets::QtWidgets::DockWidget* dock_Root = nullptr; -#define X(Type, title, Location, Parent) \ - KDDockWidgets::QtWidgets::DockWidget* dock_##Type = new KDDockWidgets::QtWidgets::DockWidget(title); \ - dock_##Type->setWidget(new Type(cpu)); \ - if (KDDockWidgets::Location_##Location != KDDockWidgets::Location_None) \ - m_window->addDockWidget(dock_##Type, KDDockWidgets::Location_##Location, dock_##Parent); \ - else \ - dock_##Parent->addDockWidgetAsTab(dock_##Type); - FOR_EACH_DEBUGGER_DOCK_WIDGET -#undef X - - return index; -} diff --git a/pcsx2-qt/Debugger/DockManager.h b/pcsx2-qt/Debugger/DockManager.h deleted file mode 100644 index 430908a8a1..0000000000 --- a/pcsx2-qt/Debugger/DockManager.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team -// SPDX-License-Identifier: GPL-3.0+ - -#pragma once - -#include "DebugTools/DebugInterface.h" - -#include -#include - -class DebuggerWindow; - -class DockManager -{ -public: - struct Layout - { - std::string name; - BreakPointCpu cpu; - bool user_defined = false; - }; - - DockManager(DebuggerWindow* window); - - static void configure_docking_system(); - - const std::vector& layouts(); - void switchToLayout(size_t layout); - size_t cloneLayout(size_t existing_layout, std::string new_name); - bool deleteLayout(size_t layout); - - void loadLayouts(); - void saveLayouts(); - -protected: - size_t createDefaultLayout(const char* name, DebugInterface& cpu); - - KDDockWidgets::QtWidgets::MainWindow* m_window; - - std::vector m_layouts; - size_t m_current_layout = 0; -}; diff --git a/pcsx2-qt/Debugger/Docking/DockManager.cpp b/pcsx2-qt/Debugger/Docking/DockManager.cpp new file mode 100644 index 0000000000..b1c5c52c0b --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/DockManager.cpp @@ -0,0 +1,593 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "DockManager.h" + +#include "Debugger/DebuggerWindow.h" +#include "Debugger/DisassemblyWidget.h" +#include "Debugger/JsonValueWrapper.h" +#include "Debugger/RegisterWidget.h" +#include "Debugger/StackWidget.h" +#include "Debugger/ThreadWidget.h" +#include "Debugger/Breakpoints/BreakpointWidget.h" +#include "Debugger/Docking/LayoutEditorDialog.h" +#include "Debugger/Memory/MemorySearchWidget.h" +#include "Debugger/Memory/MemoryViewWidget.h" +#include "Debugger/Memory/SavedAddressesWidget.h" +#include "Debugger/SymbolTree/SymbolTreeWidgets.h" + +#include "common/Assertions.h" +#include "common/FileSystem.h" +#include "common/Path.h" + +#include +#include +#include +#include +#include +#include +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" + +#include +#include +#include + +// Independent of the KDDockWidgets file format version number. +const u32 DEBUGGER_LAYOUT_FILE_VERSION = 1; + +struct DebuggerWidgetDescription +{ + DebuggerWidget* (*create_widget)(DebugInterface& cpu); + QString title; +}; + +#define DEBUGGER_WIDGET(type, title) \ + { \ + #type, \ + { \ + [](DebugInterface& cpu) -> DebuggerWidget* { return new type(cpu); }, \ + title \ + } \ + } + +static const std::map DEBUGGER_WIDGETS = { + DEBUGGER_WIDGET(BreakpointWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Breakpoints")), + DEBUGGER_WIDGET(DisassemblyWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Disassembly")), + DEBUGGER_WIDGET(FunctionTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Functions")), + DEBUGGER_WIDGET(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Globals")), + DEBUGGER_WIDGET(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Locals")), + DEBUGGER_WIDGET(MemorySearchWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory Search")), + DEBUGGER_WIDGET(MemoryViewWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory")), + DEBUGGER_WIDGET(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Parameters")), + DEBUGGER_WIDGET(RegisterWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Registers")), + DEBUGGER_WIDGET(SavedAddressesWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Saved Addresses")), + DEBUGGER_WIDGET(StackWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Stack")), + DEBUGGER_WIDGET(ThreadWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Threads")), +}; + +#undef DEBUGGER_WIDGET + +enum DefaultDockGroup +{ + ROOT = -1, + TOP_RIGHT = 0, + BOTTOM = 1, + TOP_LEFT = 2, + COUNT = 3 +}; + +struct DefaultDockGroupDescription +{ + KDDockWidgets::Location location; + DefaultDockGroup parent; +}; + +static const std::vector DEFAULT_DOCK_GROUPS = { + /* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT}, + /* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT}, + /* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT}, +}; + +struct DefaultDockWidgetDescription +{ + std::string type; + DefaultDockGroup group; +}; + +static const std::vector DEFAULT_DOCK_WIDGETS = { + /* DefaultDockGroup::TOP_RIGHT */ + {"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT}, + /* DefaultDockGroup::BOTTOM */ + {"MemoryViewWidget", DefaultDockGroup::BOTTOM}, + {"BreakpointWidget", DefaultDockGroup::BOTTOM}, + {"ThreadWidget", DefaultDockGroup::BOTTOM}, + {"StackWidget", DefaultDockGroup::BOTTOM}, + {"SavedAddressesWidget", DefaultDockGroup::BOTTOM}, + {"GlobalVariableTreeWidget", DefaultDockGroup::BOTTOM}, + {"LocalVariableTreeWidget", DefaultDockGroup::BOTTOM}, + {"ParameterVariableTreeWidget", DefaultDockGroup::BOTTOM}, + /* DefaultDockGroup::TOP_LEFT */ + {"RegisterWidget", DefaultDockGroup::TOP_LEFT}, + {"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT}, + {"MemorySearchWidget", DefaultDockGroup::TOP_LEFT}, +}; + +DockManager::DockManager(DebuggerWindow* window) + : QObject(window) + , m_window(window) +{ + loadLayouts(); +} + +DockManager::~DockManager() +{ + saveLayout(m_current_layout); + + for (Layout& layout : m_layouts) + for (QPointer widget : layout.widgets) + delete widget; +} + +void DockManager::configureDockingSystem() +{ + KDDockWidgets::Config::self().setFlags( + KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible | + KDDockWidgets::Config::Flag_AlwaysShowTabs | + KDDockWidgets::Config::Flag_AllowReorderTabs | + KDDockWidgets::Config::Flag_TabsHaveCloseButton | + KDDockWidgets::Config::Flag_TitleBarIsFocusable); +} + +s32 DockManager::createLayout(std::string name, BreakPointCpu cpu, LayoutCreationMode mode) +{ + s32 layout_index = static_cast(m_layouts.size()); + + Layout& layout = m_layouts.emplace_back(); + layout.name = std::move(name); + layout.cpu = cpu; + + DebugInterface& debug_interface = r5900Debug; + if (cpu == BREAKPOINT_IOP) + debug_interface = r3000Debug; + + switch (mode) + { + case DEFAULT_LAYOUT: + { + for (size_t i = 0; i < DEFAULT_DOCK_WIDGETS.size(); i++) + { + auto iterator = DEBUGGER_WIDGETS.find(DEFAULT_DOCK_WIDGETS[i].type); + pxAssertRel(iterator != DEBUGGER_WIDGETS.end(), "Invalid default layout."); + const DebuggerWidgetDescription& dock_description = iterator->second; + + DebuggerWidget* widget = dock_description.create_widget(debug_interface); + widget->widget_description_index = i; + widget->unique_name = dock_description.title; + layout.widgets.emplace_back(widget); + } + break; + } + case CLONE_LAYOUT: + { + // TODO + break; + } + case BLANK_LAYOUT: + { + // Nothing to do. + break; + } + } + + return layout_index; +} + +bool DockManager::deleteLayout(s32 layout_index) +{ + if (layout_index < 0 || layout_index >= static_cast(m_layouts.size())) + return false; + + if (layout_index == m_current_layout) + { + int other_layout = -1; + if (layout_index + 1 <= static_cast(m_layouts.size())) + other_layout = layout_index + 1; + else if (layout_index > 0) + other_layout = layout_index - 1; + + switchToLayout(other_layout); + } + + Layout& layout = m_layouts[layout_index]; + for (QPointer widget : layout.widgets) + { + delete widget; + } + + m_layouts.erase(m_layouts.begin() + layout_index); + + if (m_current_layout > layout_index) + m_current_layout--; + + return true; +} + +void DockManager::switchToLayout(s32 layout_index) +{ + if (layout_index == m_current_layout || layout_index >= static_cast(m_layouts.size())) + return; + + if (m_current_layout > -1) + { + freezeLayout(m_layouts[m_current_layout]); + saveLayout(m_current_layout); + } + + m_current_layout = layout_index; + + if (m_current_layout > -1) + { + thawLayout(m_layouts[m_current_layout]); + } +} + +void DockManager::loadLayouts() +{ + m_layouts.clear(); + + // Load the layouts. + FileSystem::FindResultsArray files; + FileSystem::FindFiles( + EmuFolders::DebuggerLayouts.c_str(), + "*.json", + FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, + &files); + + for (const FILESYSTEM_FIND_DATA& ffd : files) + loadLayout(ffd.FileName); + + if (m_layouts.empty()) + setupDefaultLayouts(); +} + +s32 DockManager::loadLayout(const std::string& path) +{ + s32 layout_index = 0; // = createLayout(name); + + return layout_index; +} + +bool DockManager::saveLayouts() +{ + for (s32 i = 0; i < static_cast(m_layouts.size()); i++) + if (!saveLayout(i)) + return false; + + return true; +} + +bool DockManager::saveLayout(s32 layout_index) +{ + if (layout_index < 0) + return false; + + Layout& layout = m_layouts[layout_index]; + + // Serialize the layout as JSON. + rapidjson::Document json(rapidjson::kObjectType); + rapidjson::Document geometry; + + json.AddMember("format", "PCSX2 Debugger User Interface Layout", json.GetAllocator()); + json.AddMember("version", DEBUGGER_LAYOUT_FILE_VERSION, json.GetAllocator()); + + rapidjson::Value name; + name.SetString(layout.name.c_str(), strlen(layout.name.c_str())); + json.AddMember("name", name, json.GetAllocator()); + + rapidjson::Value widgets(rapidjson::kArrayType); + for (QPointer widget : layout.widgets) + { + rapidjson::Value object(rapidjson::kObjectType); + + JsonValueWrapper wrapper(object, json.GetAllocator()); + widget->toJson(wrapper); + + widgets.PushBack(object, json.GetAllocator()); + } + json.AddMember("widgets", widgets, json.GetAllocator()); + + if (!layout.geometry.isEmpty() && !geometry.Parse(layout.geometry).HasParseError()) + json.AddMember("geometry", geometry, json.GetAllocator()); + + rapidjson::StringBuffer string_buffer; + rapidjson::PrettyWriter writer(string_buffer); + json.Accept(writer); + + // Write out the JSON to a file. + std::string temp_file_path = Path::Combine(EmuFolders::DebuggerLayouts, layout.name + ".tmp"); + + if (!FileSystem::WriteStringToFile(temp_file_path.c_str(), string_buffer.GetString())) + return false; + + // Generate a name if a file doesn't already exist. + if (layout.layout_file_path.empty()) + layout.layout_file_path = Path::Combine(EmuFolders::DebuggerLayouts, layout.name + ".json"); + + return FileSystem::RenamePath(temp_file_path.c_str(), layout.layout_file_path.c_str()); +} + +void DockManager::renameLayout(s32 layout_index, std::string new_name) +{ +} + +void DockManager::setupDefaultLayouts() +{ + switchToLayout(-1); + + m_layouts.clear(); + + createLayout("R5900 (EE)", BREAKPOINT_EE, DEFAULT_LAYOUT); + createLayout("R3000 (IOP)", BREAKPOINT_IOP, DEFAULT_LAYOUT); + + switchToLayout(0); +} + +void DockManager::createWindowsMenu(QMenu* menu) +{ + menu->clear(); + + QAction* reset_all_layouts_action = new QAction(tr("Reset All Layouts"), menu); + connect(reset_all_layouts_action, &QAction::triggered, [this]() { + QMessageBox::StandardButton result = QMessageBox::question( + m_window, tr("Confirmation"), tr("Are you sure you want to reset all layouts?")); + + if (result == QMessageBox::Yes) + setupDefaultLayouts(); + }); + menu->addAction(reset_all_layouts_action); + + menu->addSeparator(); + + for (const auto& [type, desc] : DEBUGGER_WIDGETS) + { + QAction* action = new QAction(menu); + action->setText(QCoreApplication::translate("DockWidget", desc.title.toStdString().c_str())); + action->setCheckable(true); + action->setChecked(true); + menu->addAction(action); + } +} + +QWidget* DockManager::createLayoutSwitcher(QWidget* menu_bar) +{ + QWidget* container = new QWidget; + QHBoxLayout* layout = new QHBoxLayout; + container->setLayout(layout); + + layout->setContentsMargins(0, 2, 0, 0); + + QWidget* menu_wrapper = new QWidget; + menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + layout->addWidget(menu_wrapper); + + QHBoxLayout* menu_layout = new QHBoxLayout; + menu_layout->setContentsMargins(0, 4, 0, 4); + menu_wrapper->setLayout(menu_layout); + + menu_layout->addWidget(menu_bar); + + m_switcher = new QTabBar; + m_switcher->setContentsMargins(0, 0, 0, 0); + m_switcher->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + m_switcher->setContextMenuPolicy(Qt::CustomContextMenu); + m_switcher->setMovable(true); + layout->addWidget(m_switcher); + + updateLayoutSwitcher(); + + connect(m_switcher, &QTabBar::tabMoved, this, &DockManager::layoutSwitcherTabMoved); + connect(m_switcher, &QTabBar::customContextMenuRequested, this, &DockManager::layoutSwitcherContextMenu); + + QWidget* spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->addWidget(spacer); + + return container; +} + +void DockManager::updateLayoutSwitcher() +{ + if (!m_switcher) + return; + + disconnect(m_tab_connection); + + for (int i = m_switcher->count(); i > 0; i--) + m_switcher->removeTab(i - 1); + + for (Layout& layout : m_layouts) + layout.switcher_tab_index = m_switcher->addTab(QString::fromStdString(layout.name)); + + m_plus_tab_index = m_switcher->addTab("+"); + m_current_tab_index = m_current_layout; + + m_switcher->setCurrentIndex(m_current_layout); + + m_tab_connection = connect(m_switcher, &QTabBar::currentChanged, this, &DockManager::layoutSwitcherTabChanged); +} + +void DockManager::layoutSwitcherTabChanged(s32 index) +{ + if (index == m_plus_tab_index) + { + if (m_current_tab_index >= 0 && m_current_tab_index < m_plus_tab_index) + m_switcher->setCurrentIndex(m_current_tab_index); + + LayoutEditorDialog* dialog = new LayoutEditorDialog(m_window); + if (dialog->exec() == QDialog::Accepted) + { + s32 layout_index = createLayout(dialog->name(), dialog->cpu(), dialog->initial_state()); + switchToLayout(layout_index); + updateLayoutSwitcher(); + } + } + else + { + switchToLayout(index); + m_current_tab_index = index; + } +} + +void DockManager::layoutSwitcherTabMoved(s32 from, s32 to) +{ + updateLayoutSwitcher(); +} + +void DockManager::layoutSwitcherContextMenu(QPoint pos) +{ + s32 layout_index = m_switcher->tabAt(pos); + if (layout_index < 0) + return; + + QMenu* menu = new QMenu(tr("Layout Switcher Context Menu"), m_switcher); + + QAction* edit_action = new QAction(tr("Edit Layout"), menu); + connect(edit_action, &QAction::triggered, [this, layout_index]() { + if (layout_index < 0 || layout_index >= static_cast(m_layouts.size())) + return; + + Layout& layout = m_layouts[layout_index]; + + LayoutEditorDialog* dialog = new LayoutEditorDialog(layout.name, layout.cpu, m_window); + + if (dialog->exec() == QDialog::Accepted) + { + layout.name = dialog->name(); + layout.cpu = dialog->cpu(); + updateLayoutSwitcher(); + } + }); + menu->addAction(edit_action); + + QAction* delete_action = new QAction(tr("Delete Layout"), menu); + connect(delete_action, &QAction::triggered, [this, layout_index]() { + deleteLayout(layout_index); + updateLayoutSwitcher(); + }); + menu->addAction(delete_action); + + menu->popup(m_switcher->mapToGlobal(pos)); +} + +void DockManager::freezeLayout(Layout& layout) +{ + pxAssertRel(!layout.is_frozen, "DockManager::freezeLayout called on already frozen layout."); + layout.is_frozen = true; + + KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow); + + // Store the geometry of all the dock widgets as JSON. + layout.geometry = saver.serializeLayout(); + + // Delete the dock widgets. + for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets()) + { + // Make sure the dock widget releases ownership of its content. + KDDockWidgets::QtWidgets::DockWidget* view = + static_cast(dock->view()); + view->setWidget(new QWidget()); + + delete dock; + } +} + +void DockManager::thawLayout(Layout& layout) +{ + pxAssertRel(layout.is_frozen, "DockManager::thawLayout called on already thawed layout."); + layout.is_frozen = false; + + KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow); + + if (layout.geometry.isEmpty()) + { + // This is a newly created layout with no geometry information. + populateDefaultLayout(); + return; + } + + // Create any dock widgets that were previously frozen during this session. + for (QPointer widget : layout.widgets) + { + KDDockWidgets::QtWidgets::DockWidget* view = new KDDockWidgets::QtWidgets::DockWidget(widget->unique_name); + view->setWidget(widget); + m_window->addDockWidget(view, KDDockWidgets::Location_OnBottom); + } + + // Restore the geometry of the dock widgets we just recreated. + if (!saver.restoreLayout(layout.geometry)) + { + for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets()) + { + // Make sure the dock widget releases ownership of its content. + KDDockWidgets::QtWidgets::DockWidget* view = + static_cast(dock->view()); + view->setWidget(new QWidget()); + + delete dock; + } + + // We failed to restore the geometry, so just setup the default layout. + populateDefaultLayout(); + } +} + +KDDockWidgets::Core::DockWidget* DockManager::createDockWidget(const QString& name) +{ + + return nullptr; +} + +void DockManager::populateDefaultLayout() +{ + if (m_current_layout < 0) + return; + + Layout& layout = m_layouts[m_current_layout]; + + KDDockWidgets::QtWidgets::DockWidget* groups[DefaultDockGroup::COUNT] = {}; + + for (QPointer widget : layout.widgets) + { + if (widget->widget_description_index >= DEFAULT_DOCK_WIDGETS.size()) + continue; + + const DefaultDockWidgetDescription& dock_description = DEFAULT_DOCK_WIDGETS[widget->widget_description_index]; + const DefaultDockGroupDescription& group = DEFAULT_DOCK_GROUPS[dock_description.group]; + + auto debug_description_iterator = DEBUGGER_WIDGETS.find(dock_description.type); + pxAssertRel(debug_description_iterator != DEBUGGER_WIDGETS.end(), "Invalid default dock layout."); + const DebuggerWidgetDescription& debug_description = debug_description_iterator->second; + + KDDockWidgets::QtWidgets::DockWidget* dock = new KDDockWidgets::QtWidgets::DockWidget(debug_description.title); + dock->setWidget(widget); + + if (!groups[dock_description.group]) + { + KDDockWidgets::QtWidgets::DockWidget* parent = nullptr; + if (group.parent != DefaultDockGroup::ROOT) + parent = groups[group.parent]; + + m_window->addDockWidget(dock, group.location, parent); + + groups[dock_description.group] = dock; + } + else + { + groups[dock_description.group]->addDockWidgetAsTab(dock); + } + } + + for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups()) + group->setCurrentTabIndex(0); +} diff --git a/pcsx2-qt/Debugger/Docking/DockManager.h b/pcsx2-qt/Debugger/Docking/DockManager.h new file mode 100644 index 0000000000..bf519c9116 --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/DockManager.h @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "Debugger/DebuggerWidget.h" + +#include "DebugTools/DebugInterface.h" + +#include +#include + +#include + +class DebuggerWindow; + +extern const u32 DEBUGGER_LAYOUT_FILE_VERSION; + +class DockManager : public QObject +{ + Q_OBJECT + +public: + DockManager(DebuggerWindow* window); + ~DockManager(); + + static void configureDockingSystem(); + + enum LayoutCreationMode + { + DEFAULT_LAYOUT, + CLONE_LAYOUT, + BLANK_LAYOUT, + }; + + s32 createLayout(std::string name, BreakPointCpu cpu, LayoutCreationMode mode); + bool deleteLayout(s32 layout_index); + + void switchToLayout(s32 layout_index); + + void loadLayouts(); + s32 loadLayout(const std::string& path); + + bool saveLayouts(); + bool saveLayout(s32 layout_index); + + void renameLayout(s32 layout_index, std::string new_name); + + void setupDefaultLayouts(); + + void createWindowsMenu(QMenu* menu); + + QWidget* createLayoutSwitcher(QWidget* menu_bar); + void updateLayoutSwitcher(); + void layoutSwitcherTabChanged(s32 index); + void layoutSwitcherTabMoved(s32 from, s32 to); + void layoutSwitcherContextMenu(QPoint pos); + +private: + struct Layout + { + // The name displayed in the user interface. Also used to determine the + // file name for the layout file. + std::string name; + + // The default target for dock widgets in this layout. This can be + // overriden on a per-widget basis. + BreakPointCpu cpu; + + // All the dock widgets currently open in this layout. If this is the + // active layout then these will be owned by the docking system, + // otherwise they won't be and will need to be cleaned up separately. + std::vector> widgets; + + int switcher_tab_index = -1; + + // The geometry of all the dock widgets, converted to JSON by the + // LayoutSaver class from KDDockWidgets. + QByteArray geometry; + bool geometry_modified = false; + + // The absolute file path of the corresponding layout file as it + // currently exists exists on disk, or empty if no such file exists. + std::string layout_file_path; + + bool is_frozen = true; + }; + + // Save the current state of all the dock widgets to a layout. + void freezeLayout(Layout& layout); + + // Restore the state of all the dock widgets from a layout. + void thawLayout(Layout& layout); + + KDDockWidgets::Core::DockWidget* createDockWidget(const QString& name); + + void populateDefaultLayout(); + + KDDockWidgets::QtWidgets::MainWindow* m_window; + + std::vector m_layouts; + s32 m_current_layout = -1; + + QTabBar* m_switcher = nullptr; + s32 m_plus_tab_index = -1; + s32 m_current_tab_index = -1; + + QMetaObject::Connection m_tab_connection; +}; diff --git a/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp new file mode 100644 index 0000000000..bfaa4fe733 --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "LayoutEditorDialog.h" + +LayoutEditorDialog::LayoutEditorDialog(QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + setWindowTitle(tr("New Layout")); + + setupComboBoxes(BREAKPOINT_EE, DockManager::DEFAULT_LAYOUT); +} + +LayoutEditorDialog::LayoutEditorDialog( + std::string& name, BreakPointCpu cpu, QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + setWindowTitle(tr("Edit Layout")); + + m_ui.nameEditor->setText(QString::fromStdString(name)); + + setupComboBoxes(cpu, DockManager::DEFAULT_LAYOUT); + + m_ui.initialStateLabel->hide(); + m_ui.initialStateEditor->hide(); +} + +std::string LayoutEditorDialog::name() +{ + return m_ui.nameEditor->text().toStdString(); +} + +BreakPointCpu LayoutEditorDialog::cpu() +{ + return static_cast(m_ui.cpuEditor->currentData().toInt()); +} + +DockManager::LayoutCreationMode LayoutEditorDialog::initial_state() +{ + return static_cast(m_ui.initialStateEditor->currentData().toInt()); +} + +void LayoutEditorDialog::setupComboBoxes(BreakPointCpu cpu, DockManager::LayoutCreationMode initial_state) +{ + m_ui.cpuEditor->addItem(tr("EE"), BREAKPOINT_EE); + m_ui.cpuEditor->addItem(tr("IOP"), BREAKPOINT_IOP); + + for (int i = 0; i < m_ui.cpuEditor->count(); i++) + if (m_ui.cpuEditor->itemData(i).toInt() == cpu) + m_ui.cpuEditor->setCurrentIndex(i); + + m_ui.initialStateEditor->addItem(tr("Create Default Layout"), DockManager::DEFAULT_LAYOUT); + m_ui.initialStateEditor->addItem(tr("Create Blank Layout"), DockManager::BLANK_LAYOUT); + m_ui.initialStateEditor->addItem(tr("Clone Current Layout"), DockManager::CLONE_LAYOUT); + + for (int i = 0; i < m_ui.initialStateEditor->count(); i++) + if (m_ui.initialStateEditor->itemData(i).toInt() == initial_state) + m_ui.initialStateEditor->setCurrentIndex(i); +} diff --git a/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.h b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.h new file mode 100644 index 0000000000..f301689a27 --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_LayoutEditorDialog.h" + +#include "Debugger/Docking/DockManager.h" + +#include "DebugTools/DebugInterface.h" + +#include + +class LayoutEditorDialog : public QDialog +{ + Q_OBJECT + +public: + // Create a "New Layout" dialog. + LayoutEditorDialog(QWidget* parent = nullptr); + + // Create a "Edit Layout" dialog. + LayoutEditorDialog(std::string& name, BreakPointCpu cpu, QWidget* parent = nullptr); + + std::string name(); + BreakPointCpu cpu(); + DockManager::LayoutCreationMode initial_state(); + +private: + void setupComboBoxes(BreakPointCpu cpu, DockManager::LayoutCreationMode initial_state); + + Ui::LayoutEditorDialog m_ui; +}; diff --git a/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.ui b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.ui new file mode 100644 index 0000000000..170bc70283 --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/LayoutEditorDialog.ui @@ -0,0 +1,98 @@ + + + LayoutEditorDialog + + + + 0 + 0 + 400 + 150 + + + + + + + + + + + + Name + + + + + + + CPU + + + + + + + Initial State + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + LayoutEditorDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LayoutEditorDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/pcsx2-qt/Debugger/JsonValueWrapper.h b/pcsx2-qt/Debugger/JsonValueWrapper.h new file mode 100644 index 0000000000..28f8cd0ec4 --- /dev/null +++ b/pcsx2-qt/Debugger/JsonValueWrapper.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "rapidjson/document.h" + +// Container for a JSON value. This exists solely so that we can forward declare +// it to avoid pulling in rapidjson for the entire debugger. +class JsonValueWrapper +{ +public: + JsonValueWrapper( + rapidjson::Value& value, + rapidjson::MemoryPoolAllocator& allocator) + : m_value(value) + , m_allocator(allocator) + { + } + + rapidjson::Value& value() + { + return m_value; + } + + rapidjson::MemoryPoolAllocator& allocator() + { + return m_allocator; + } + +private: + rapidjson::Value& m_value; + rapidjson::MemoryPoolAllocator& m_allocator; +}; diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 43ab03f136..35978dc93d 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -2605,7 +2605,7 @@ DebuggerWindow* MainWindow::getDebuggerWindow() if (!m_debugger_window) { // Setup KDDockWidgets. - DockManager::configure_docking_system(); + DockManager::configureDockingSystem(); // Don't pass us (this) as the parent, otherwise the window is always on top of the mainwindow (on windows at least) m_debugger_window = new DebuggerWindow(nullptr); diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h index 410b109aee..cce32395b3 100644 --- a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h @@ -7,7 +7,6 @@ #include "Config.h" #include -#include class SettingsWindow; diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.ui b/pcsx2-qt/Settings/DebugSettingsWidget.ui index e735510dcb..e4b4cd8f27 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.ui +++ b/pcsx2-qt/Settings/DebugSettingsWidget.ui @@ -6,7 +6,7 @@ 0 0 - 527 + 647 501 @@ -31,6 +31,11 @@ true + + + User Interface + + Analysis @@ -58,8 +63,8 @@ 0 0 - 523 - 464 + 645 + 469 diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 37e4a7dffc..050e21adea 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -1363,7 +1363,6 @@ namespace EmuFolders extern std::string AppRoot; extern std::string DataRoot; extern std::string Settings; - extern std::string DebuggerSettings; extern std::string Bios; extern std::string Snapshots; extern std::string Savestates; @@ -1379,6 +1378,8 @@ namespace EmuFolders extern std::string Textures; extern std::string InputProfiles; extern std::string Videos; + extern std::string DebuggerLayouts; + extern std::string DebuggerSettings; /// Initializes critical folders (AppRoot, DataRoot, Settings). Call once on startup. void SetAppRoot(); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index ae9fcb2605..8dff5f1a7d 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -152,6 +152,7 @@ namespace EmuFolders std::string AppRoot; std::string DataRoot; std::string Settings; + std::string DebuggerLayouts; std::string DebuggerSettings; std::string Bios; std::string Snapshots; @@ -2203,6 +2204,8 @@ void EmuFolders::SetDefaults(SettingsInterface& si) si.SetStringValue("Folders", "Textures", "textures"); si.SetStringValue("Folders", "InputProfiles", "inputprofiles"); si.SetStringValue("Folders", "Videos", "videos"); + si.SetStringValue("Folders", "DebuggerLayouts", "debuggerlayouts"); + si.SetStringValue("Folders", "DebuggerSettings", "debuggersettings"); } static std::string LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* name, const char* def) @@ -2229,6 +2232,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si) Textures = LoadPathFromSettings(si, DataRoot, "Textures", "textures"); InputProfiles = LoadPathFromSettings(si, DataRoot, "InputProfiles", "inputprofiles"); Videos = LoadPathFromSettings(si, DataRoot, "Videos", "videos"); + DebuggerLayouts = LoadPathFromSettings(si, Settings, "DebuggerLayouts", "debuggerlayouts"); DebuggerSettings = LoadPathFromSettings(si, Settings, "DebuggerSettings", "debuggersettings"); Console.WriteLn("BIOS Directory: %s", Bios.c_str()); @@ -2246,6 +2250,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si) Console.WriteLn("Textures Directory: %s", Textures.c_str()); Console.WriteLn("Input Profile Directory: %s", InputProfiles.c_str()); Console.WriteLn("Video Dumping Directory: %s", Videos.c_str()); + Console.WriteLn("Debugger Layouts Directory: %s", DebuggerLayouts.c_str()); Console.WriteLn("Debugger Settings Directory: %s", DebuggerSettings.c_str()); } @@ -2262,11 +2267,12 @@ bool EmuFolders::EnsureFoldersExist() result = FileSystem::CreateDirectoryPath(Covers.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(GameSettings.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(UserResources.c_str(), false) && result; - result = FileSystem::CreateDirectoryPath(DebuggerSettings.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Cache.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Textures.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(InputProfiles.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Videos.c_str(), false) && result; + result = FileSystem::CreateDirectoryPath(DebuggerLayouts.c_str(), false) && result; + result = FileSystem::CreateDirectoryPath(DebuggerSettings.c_str(), false) && result; return result; }