mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42c576cf99 | ||
|
|
1fd26c919b | ||
|
|
c957b558e0 | ||
|
|
ec047c5972 | ||
|
|
08388d12d1 | ||
|
|
850aeaf05e | ||
|
|
6195f4b40e | ||
|
|
92baf77509 | ||
|
|
ab1cdb4c9d | ||
|
|
4f4ff00ecf | ||
|
|
6fe97b42c3 | ||
|
|
c9ac4960bc | ||
|
|
e4d7d22e78 | ||
|
|
eb83cb70ea | ||
|
|
59210dffa9 | ||
|
|
c76cca874b | ||
|
|
d989ce5b44 | ||
|
|
fbc95f2c86 | ||
|
|
9fb8dacadb | ||
|
|
d332aee542 | ||
|
|
f2c97fc2c3 | ||
|
|
5ab84aaa29 | ||
|
|
9842b11815 | ||
|
|
083fb5a1e6 | ||
|
|
98cdd3446b | ||
|
|
9bcbf43695 | ||
|
|
e3c988aa8b | ||
|
|
06be543d32 | ||
|
|
1fd22dcc1c | ||
|
|
e3afdde981 | ||
|
|
698df49e5e | ||
|
|
ff5c90ec5e | ||
|
|
1e075d23b2 | ||
|
|
2b172903b9 | ||
|
|
4654a3ef6c | ||
|
|
9996061f74 | ||
|
|
247a4c40d1 | ||
|
|
1ffbdd9c08 | ||
|
|
f67c0cbd2e | ||
|
|
ff7cc0867b | ||
|
|
ac1a6d3348 | ||
|
|
582bba6c91 | ||
|
|
aaf156478e | ||
|
|
0539c177ab | ||
|
|
fb1323b72f | ||
|
|
dc557dd0e5 | ||
|
|
2d0cfc9c2c | ||
|
|
625a25cd50 | ||
|
|
b8a29d1cd8 | ||
|
|
0fabdf9a01 | ||
|
|
9c3ae795c8 | ||
|
|
de26226fa1 | ||
|
|
121920c074 | ||
|
|
05e19470b2 | ||
|
|
b6680e4aca | ||
|
|
f9d70af841 | ||
|
|
7587581d1f | ||
|
|
8f19976c10 | ||
|
|
8567d68433 | ||
|
|
6542301566 | ||
|
|
a359f77cf6 | ||
|
|
4c9a81f3d8 | ||
|
|
9234b493a3 | ||
|
|
f84425b67c | ||
|
|
8a0c1874dd | ||
|
|
fa23628ae2 | ||
|
|
8a594e673d | ||
|
|
92b9390c51 | ||
|
|
c5c5b2a7b9 | ||
|
|
32a9d0e48b | ||
|
|
80a961bb25 | ||
|
|
d4e227286e | ||
|
|
ba705c8c24 | ||
|
|
b6ae4b173e | ||
|
|
23a28be346 | ||
|
|
a0e24dd36f | ||
|
|
a2cde5e17b | ||
|
|
ecc46e9294 | ||
|
|
20b1190d47 | ||
|
|
29b736bcf7 | ||
|
|
a48bc76ca6 | ||
|
|
305c01cdfa | ||
|
|
88bbdf4696 | ||
|
|
afc11279a9 | ||
|
|
a3fb2a84d5 | ||
|
|
4db23e6677 | ||
|
|
5dd36a7969 | ||
|
|
35a3d0027e | ||
|
|
02789ebd86 | ||
|
|
dfd1846b93 | ||
|
|
872205abc6 | ||
|
|
c52cebd20a | ||
|
|
f449b54f87 | ||
|
|
ffcb6e2f6f | ||
|
|
5daa1aa115 | ||
|
|
1dc009f752 | ||
|
|
009b4ff5e7 | ||
|
|
f1a947af92 |
13
.github/workflows/scripts/common/kddockwidgets-dodgy-include.patch
vendored
Normal file
13
.github/workflows/scripts/common/kddockwidgets-dodgy-include.patch
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/src/core/indicators/ClassicDropIndicatorOverlay.h b/src/core/indicators/ClassicDropIndicatorOverlay.h
|
||||
index 2dfb9718a..9b01f002e 100644
|
||||
--- a/src/core/indicators/ClassicDropIndicatorOverlay.h
|
||||
+++ b/src/core/indicators/ClassicDropIndicatorOverlay.h
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
-#include "core/DropIndicatorOverlay.h"
|
||||
+#include <kddockwidgets/core/DropIndicatorOverlay.h>
|
||||
|
||||
namespace KDDockWidgets {
|
||||
|
||||
11
.github/workflows/scripts/linux/appimage-qt.sh
vendored
11
.github/workflows/scripts/linux/appimage-qt.sh
vendored
@@ -187,17 +187,6 @@ echo "Generating AppStream metainfo..."
|
||||
mkdir -p "$OUTDIR/usr/share/metainfo"
|
||||
"$SCRIPTDIR/generate-metainfo.sh" "$OUTDIR/usr/share/metainfo/net.pcsx2.PCSX2.appdata.xml"
|
||||
|
||||
# Copy in AppRun hooks.
|
||||
# Unfortunately linuxdeploy is a bit lame and doesn't let us provide our own AppRun hooks, instead
|
||||
# they have to come from plugins.. and screw writing one of those just to disable Wayland.
|
||||
echo "Copying AppRun hooks..."
|
||||
mkdir -p "$OUTDIR/apprun-hooks"
|
||||
for hookpath in "$SCRIPTDIR/apprun-hooks"/*; do
|
||||
hookname=$(basename "$hookpath")
|
||||
cp -v "$hookpath" "$OUTDIR/apprun-hooks/$hookname"
|
||||
sed -i -e 's/exec /source "$this_dir"\/apprun-hooks\/"'"$hookname"'"\nexec /' "$OUTDIR/AppRun"
|
||||
done
|
||||
|
||||
echo "Generating AppImage..."
|
||||
GIT_VERSION=$(git tag --points-at HEAD)
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
if [[ -z "$I_WANT_A_BROKEN_WAYLAND_UI" ]]; then
|
||||
echo "Forcing X11 instead of Wayland, due to various protocol limitations"
|
||||
echo "and Qt issues. If you want to use Wayland, launch PCSX2 with"
|
||||
echo "I_WANT_A_BROKEN_WAYLAND_UI=YES set."
|
||||
export QT_QPA_PLATFORM=xcb
|
||||
else
|
||||
echo "Wayland is not being disabled. Do not complain when things break."
|
||||
fi
|
||||
|
||||
@@ -19,9 +19,10 @@ LIBJPEG=9f
|
||||
LIBPNG=1.6.45
|
||||
LIBWEBP=1.5.0
|
||||
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
|
||||
SDL=SDL2-2.30.12
|
||||
SDL=SDL3-3.2.8
|
||||
QT=6.8.2
|
||||
ZSTD=1.5.6
|
||||
ZSTD=1.5.7
|
||||
KDDOCKWIDGETS=2.2.1
|
||||
|
||||
SHADERC=2024.1
|
||||
SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
|
||||
@@ -37,8 +38,8 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz
|
||||
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
|
||||
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
|
||||
ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz
|
||||
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
|
||||
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 $SDL.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
012043ce6d411e6e8a91fdc4e05e6bedcfa10fcb1347d3c33908f7fdd10dfe05 qtbase-everywhere-src-$QT.tar.xz
|
||||
d2a1bbb84707b8a0aec29227b170be00f04383fbf2361943596d09e7e443c8e1 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
aa2579f21ca66d19cbcf31d87e9067e07932635d36869c8239d4decd0a9dc1fa qtsvg-everywhere-src-$QT.tar.xz
|
||||
@@ -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,16 @@ cmake --build . --parallel
|
||||
ninja install
|
||||
cd ../../
|
||||
|
||||
echo "Building KDDockWidgets..."
|
||||
rm -fr "KDDockWidgets-$KDDOCKWIDGETS"
|
||||
tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz"
|
||||
cd "KDDockWidgets-$KDDOCKWIDGETS"
|
||||
patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DKDDockWidgets_QT6=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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "sdl2",
|
||||
"name": "sdl3",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
@@ -14,8 +14,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://libsdl.org/release/SDL2-2.30.12.tar.gz",
|
||||
"sha256": "ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea"
|
||||
"url": "https://libsdl.org/release/SDL3-3.2.8.tar.gz",
|
||||
"sha256": "13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03"
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
32
.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json
vendored
Normal file
32
.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "kddockwidgets",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DKDDockWidgets_QT6=true",
|
||||
"-DKDDockWidgets_EXAMPLES=false",
|
||||
"-DKDDockWidgets_FRONTENDS=qtwidgets"
|
||||
],
|
||||
"build-options": {
|
||||
"strip": true
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/KDAB/KDDockWidgets.git",
|
||||
"tag": "v2.2.1",
|
||||
"commit": "3aaccddc00a11a643e0959a24677838993de15ac",
|
||||
"disable-submodules": true
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "../../../common/kddockwidgets-dodgy-include.patch"
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
"/share/doc/KDDockWidgets-qt6",
|
||||
"/mkspecs/modules/qt_KDDockWidgets.pri",
|
||||
"/lib/cmake",
|
||||
"/include"
|
||||
]
|
||||
}
|
||||
@@ -19,16 +19,17 @@
|
||||
"--device=all",
|
||||
"--share=network",
|
||||
"--share=ipc",
|
||||
"--socket=x11",
|
||||
"--socket=wayland",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=pulseaudio",
|
||||
"--talk-name=org.freedesktop.ScreenSaver",
|
||||
"--env=QT_QPA_PLATFORM=xcb"
|
||||
"--talk-name=org.freedesktop.ScreenSaver"
|
||||
],
|
||||
"modules": [
|
||||
"modules/10-libpcap.json",
|
||||
"modules/20-sdl2.json",
|
||||
"modules/20-sdl3.json",
|
||||
"modules/21-libbacktrace.json",
|
||||
"modules/22-shaderc.json",
|
||||
"modules/23-kddockwidgets.json",
|
||||
{
|
||||
"name": "pcsx2",
|
||||
"buildsystem": "cmake-ninja",
|
||||
|
||||
@@ -40,8 +40,8 @@ fi
|
||||
|
||||
FREETYPE=2.13.3
|
||||
HARFBUZZ=10.0.1
|
||||
SDL=SDL2-2.30.12
|
||||
ZSTD=1.5.6
|
||||
SDL=SDL3-3.2.8
|
||||
ZSTD=1.5.7
|
||||
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
|
||||
LIBPNG=1.6.45
|
||||
LIBJPEG=9f
|
||||
@@ -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
|
||||
@@ -76,8 +77,8 @@ CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
|
||||
cat > SHASUMS <<EOF
|
||||
0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289 freetype-$FREETYPE.tar.xz
|
||||
e7358ea86fe10fb9261931af6f010d4358dac64f7074420ca9bc94aae2bdd542 harfbuzz-$HARFBUZZ.tar.gz
|
||||
ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz
|
||||
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
|
||||
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 $SDL.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz
|
||||
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
|
||||
@@ -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
|
||||
|
||||
@@ -366,6 +369,16 @@ make "-j$NPROCS"
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
echo "Building KDDockWidgets..."
|
||||
rm -fr "KDDockWidgets-$KDDOCKWIDGETS"
|
||||
tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz"
|
||||
cd "KDDockWidgets-$KDDOCKWIDGETS"
|
||||
patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch"
|
||||
cmake "${CMAKE_COMMON[@]}" "$CMAKE_ARCH_UNIVERSAL" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build
|
||||
cmake --build build --parallel
|
||||
cmake --install build
|
||||
cd ..
|
||||
|
||||
echo "Cleaning up..."
|
||||
cd ..
|
||||
rm -rf deps-build
|
||||
|
||||
@@ -22,8 +22,8 @@ fi
|
||||
|
||||
FREETYPE=2.13.3
|
||||
HARFBUZZ=10.0.1
|
||||
SDL=SDL2-2.30.12
|
||||
ZSTD=1.5.6
|
||||
SDL=SDL3-3.2.8
|
||||
ZSTD=1.5.7
|
||||
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
|
||||
LIBPNG=1.6.45
|
||||
LIBJPEG=9f
|
||||
@@ -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
|
||||
@@ -56,8 +57,8 @@ CMAKE_COMMON=(
|
||||
cat > SHASUMS <<EOF
|
||||
0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289 freetype-$FREETYPE.tar.xz
|
||||
e7358ea86fe10fb9261931af6f010d4358dac64f7074420ca9bc94aae2bdd542 harfbuzz-$HARFBUZZ.tar.gz
|
||||
ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz
|
||||
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
|
||||
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 $SDL.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz
|
||||
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
|
||||
@@ -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
|
||||
|
||||
@@ -324,6 +327,16 @@ make "-j$NPROCS"
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
echo "Building KDDockWidgets..."
|
||||
rm -fr "KDDockWidgets-$KDDOCKWIDGETS"
|
||||
tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz"
|
||||
cd "KDDockWidgets-$KDDOCKWIDGETS"
|
||||
patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch"
|
||||
cmake "${CMAKE_COMMON[@]}" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build
|
||||
cmake --build build --parallel
|
||||
cmake --install build
|
||||
cd ..
|
||||
|
||||
echo "Cleaning up..."
|
||||
cd ..
|
||||
rm -rf deps-build
|
||||
|
||||
337
.github/workflows/scripts/releases/generate-release-notes/package-lock.json
generated
vendored
337
.github/workflows/scripts/releases/generate-release-notes/package-lock.json
generated
vendored
@@ -1,22 +1,33 @@
|
||||
{
|
||||
"name": "generate-release-notes",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": {
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@octokit/plugin-retry": "^3.0.9",
|
||||
"@octokit/plugin-throttling": "^3.5.2",
|
||||
"@octokit/rest": "^21.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"@octokit/core": {
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz",
|
||||
"integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.0",
|
||||
@@ -26,76 +37,60 @@
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/endpoint": {
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.1.0.tgz",
|
||||
"integrity": "sha512-dWZfYvCCdjZzDYA3lIAMF72Q0jld8xidqCq5Ryw09eBJXZdcM6he0vWBTvw/b5UnGYqexxOyHWgfrsTlUJL3Gw=="
|
||||
},
|
||||
"@octokit/plugin-paginate-rest": {
|
||||
"version": "2.16.9",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.9.tgz",
|
||||
"integrity": "sha512-gfSCMgz5scFKsR0dW4jaYsDJVt/UwCHp4dF7sHlmSekZvwzvLiOAGZ4MQkEsL5DW9hIk2W+UQkYZMTA1b6Wsqw==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.33.0"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-request-log": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
|
||||
"integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="
|
||||
},
|
||||
"@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.12.1.tgz",
|
||||
"integrity": "sha512-0nY3htfl6x9UkPcqv8pm9vOC/bTA7f4IMDWln13neHRdNWQvOQgZ9fRxK7BAc74rye4yVINEFi9Yb9rnGUvosA==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.33.0",
|
||||
"deprecation": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-retry": {
|
||||
"node_modules/@octokit/plugin-retry": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-3.0.9.tgz",
|
||||
"integrity": "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ==",
|
||||
"requires": {
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"bottleneck": "^2.15.3"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-throttling": {
|
||||
"node_modules/@octokit/plugin-throttling": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.5.2.tgz",
|
||||
"integrity": "sha512-Eu7kfJxU8vmHqWGNszWpg+GVp2tnAfax3XQV5CkYPEE69C+KvInJXW9WajgSeW+cxYe0UVdouzCtcreGNuJo7A==",
|
||||
"requires": {
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.1",
|
||||
"bottleneck": "^2.15.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz",
|
||||
"integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
@@ -104,99 +99,295 @@
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/request-error": {
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"@octokit/rest": {
|
||||
"version": "18.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
|
||||
"integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
|
||||
"requires": {
|
||||
"@octokit/core": "^3.5.1",
|
||||
"@octokit/plugin-paginate-rest": "^2.16.8",
|
||||
"@octokit/plugin-request-log": "^1.0.4",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.12.0"
|
||||
"node_modules/@octokit/rest": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz",
|
||||
"integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/core": "^6.1.4",
|
||||
"@octokit/plugin-paginate-rest": "^11.4.2",
|
||||
"@octokit/plugin-request-log": "^5.3.1",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^13.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
|
||||
"integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/core": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz",
|
||||
"integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^5.0.0",
|
||||
"@octokit/graphql": "^8.1.2",
|
||||
"@octokit/request": "^9.2.1",
|
||||
"@octokit/request-error": "^6.1.7",
|
||||
"@octokit/types": "^13.6.2",
|
||||
"before-after-hook": "^3.0.2",
|
||||
"universal-user-agent": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
|
||||
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.6.2",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz",
|
||||
"integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^9.2.2",
|
||||
"@octokit/types": "^13.8.0",
|
||||
"universal-user-agent": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
|
||||
"version": "23.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
|
||||
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "11.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.2.tgz",
|
||||
"integrity": "sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz",
|
||||
"integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "13.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.1.tgz",
|
||||
"integrity": "sha512-o8uOBdsyR+WR8MK9Cco8dCgvG13H1RlM1nWnK/W7TEACQBFux/vPREgKucxUfuDQ5yi1T3hGf4C5ZmZXAERgwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
|
||||
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^10.1.3",
|
||||
"@octokit/request-error": "^6.1.7",
|
||||
"@octokit/types": "^13.6.2",
|
||||
"fast-content-type-parse": "^2.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
|
||||
"integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/types": {
|
||||
"version": "13.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
|
||||
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^23.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/before-after-hook": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
|
||||
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/universal-user-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.33.0.tgz",
|
||||
"integrity": "sha512-0zffZ048M0UhthyPXQHLz4038Ak46nMWZXkzlXvXB/M/L1jYPBceq4iZj4qjKVrvveaJrrgKdJ9+3yUuITfcCw==",
|
||||
"requires": {
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"before-after-hook": {
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==",
|
||||
"peer": true
|
||||
},
|
||||
"bottleneck": {
|
||||
"node_modules/bottleneck": {
|
||||
"version": "2.19.5",
|
||||
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
||||
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
|
||||
},
|
||||
"deprecation": {
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
|
||||
"peer": true
|
||||
},
|
||||
"is-plain-object": {
|
||||
"node_modules/fast-content-type-parse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
|
||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
|
||||
"peer": true
|
||||
},
|
||||
"universal-user-agent": {
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
|
||||
"peer": true
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
|
||||
"peer": true
|
||||
},
|
||||
"whatwg-url": {
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"requires": {
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"dependencies": {
|
||||
"@octokit/plugin-retry": "^3.0.9",
|
||||
"@octokit/plugin-throttling": "^3.5.2",
|
||||
"@octokit/rest": "^18.12.0"
|
||||
"@octokit/rest": "^21.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +49,12 @@ set LIBPNG=1645
|
||||
set LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
|
||||
set QT=6.8.2
|
||||
set QTMINOR=6.8
|
||||
set SDL=SDL2-2.30.12
|
||||
set SDL=SDL3-3.2.8
|
||||
set WEBP=1.5.0
|
||||
set ZLIB=1.3.1
|
||||
set ZLIBSHORT=131
|
||||
set ZSTD=1.5.6
|
||||
set ZSTD=1.5.7
|
||||
set KDDOCKWIDGETS=2.2.1
|
||||
|
||||
set SHADERC=2024.1
|
||||
set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
|
||||
@@ -66,15 +67,15 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
|
||||
call :downloadfile "jpegsr%LIBJPEG%.zip" https://ijg.org/files/jpegsr%LIBJPEG%.zip 6255da8c89e09d694e6800688c76145eb6870a76ac0d36c74fccd61b3940aafa || goto error
|
||||
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c || goto error
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/%LZ4%.zip" 0c33119688d6b180c7e760b0acd70059222389cfd581632623784bee27e51a31 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" aa2808d0f2dc6b383c6689bf6d166e2de62db4d58be989e4b052acb31df0fba3 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 7f8ff5c8246db4145301bc122601a5f8cef25ee2c326eddb3e88668849c61ddf || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 44087aec0caa4aa81437e787917d29d97536484a682a5d51ec035878e57c0b5c || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 83c72b5dfad04854acf61d592e3f9cdc2ed894779aab8d0470d966715266caaf || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 144d55e4d199793a76c53f19872633a79aec0314039f6f99b6a10b5be7a78fbf || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" 102539447c1c76d206f24bcca2c911270cf53991548d9c3d7d0d01855f651e3b || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 33ccac9f99a357ffd83cb2d7179a0c0ffcba85a14d23d86619d5dc9721ded42f || goto error
|
||||
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 "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || 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
|
||||
@@ -159,7 +160,6 @@ echo Building Zstandard...
|
||||
rmdir /S /Q "zstd-%ZSTD%"
|
||||
%SEVENZIP% x "-x^!zstd-%ZSTD%\tests\cli-tests\bin" "zstd-%ZSTD%.zip" || goto error
|
||||
cd "zstd-%ZSTD%"
|
||||
%PATCH% -p1 < "..\zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" || goto error
|
||||
cmake %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DZSTD_BUILD_SHARED=ON -DZSTD_BUILD_STATIC=OFF -DZSTD_BUILD_PROGRAMS=OFF -B build -G Ninja build/cmake
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
@@ -181,7 +181,7 @@ cd "%SDL%" || goto error
|
||||
cmake -B build %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
copy build\SDL2.pdb "%INSTALLDIR%\bin" || goto error
|
||||
copy build\SDL3.pdb "%INSTALLDIR%\bin" || goto error
|
||||
cd .. || goto error
|
||||
|
||||
if %DEBUG%==1 (
|
||||
@@ -243,6 +243,16 @@ 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
|
||||
%PATCH% -p1 < "%SCRIPTDIR%\..\common\kddockwidgets-dodgy-include.patch" || goto error
|
||||
cmake %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=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
|
||||
|
||||
@@ -47,11 +47,12 @@ set LIBPNG=1645
|
||||
set LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
|
||||
set QT=6.8.2
|
||||
set QTMINOR=6.8
|
||||
set SDL=SDL2-2.30.12
|
||||
set SDL=SDL3-3.2.8
|
||||
set WEBP=1.5.0
|
||||
set ZLIB=1.3.1
|
||||
set ZLIBSHORT=131
|
||||
set ZSTD=1.5.6
|
||||
set ZSTD=1.5.7
|
||||
set KDDOCKWIDGETS=2.2.1
|
||||
|
||||
set SHADERC=2024.1
|
||||
set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
|
||||
@@ -64,15 +65,15 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
|
||||
call :downloadfile "jpegsr%LIBJPEG%.zip" https://ijg.org/files/jpegsr%LIBJPEG%.zip 6255da8c89e09d694e6800688c76145eb6870a76ac0d36c74fccd61b3940aafa || goto error
|
||||
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c || goto error
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/%LZ4%.zip" 0c33119688d6b180c7e760b0acd70059222389cfd581632623784bee27e51a31 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" aa2808d0f2dc6b383c6689bf6d166e2de62db4d58be989e4b052acb31df0fba3 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 7f8ff5c8246db4145301bc122601a5f8cef25ee2c326eddb3e88668849c61ddf || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 44087aec0caa4aa81437e787917d29d97536484a682a5d51ec035878e57c0b5c || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 83c72b5dfad04854acf61d592e3f9cdc2ed894779aab8d0470d966715266caaf || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 144d55e4d199793a76c53f19872633a79aec0314039f6f99b6a10b5be7a78fbf || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" 102539447c1c76d206f24bcca2c911270cf53991548d9c3d7d0d01855f651e3b || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 33ccac9f99a357ffd83cb2d7179a0c0ffcba85a14d23d86619d5dc9721ded42f || goto error
|
||||
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 "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || 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
|
||||
@@ -156,7 +157,6 @@ echo Building Zstandard...
|
||||
rmdir /S /Q "zstd-%ZSTD%"
|
||||
%SEVENZIP% x "-x^!zstd-%ZSTD%\tests\cli-tests\bin" "zstd-%ZSTD%.zip" || goto error
|
||||
cd "zstd-%ZSTD%"
|
||||
%PATCH% -p1 < "..\zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" || goto error
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DZSTD_BUILD_SHARED=ON -DZSTD_BUILD_STATIC=OFF -DZSTD_BUILD_PROGRAMS=OFF -B build -G Ninja build/cmake
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
@@ -178,7 +178,7 @@ cd "%SDL%" || goto error
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
copy build\SDL2.pdb "%INSTALLDIR%\bin" || goto error
|
||||
copy build\SDL3.pdb "%INSTALLDIR%\bin" || goto error
|
||||
cd .. || goto error
|
||||
|
||||
if %DEBUG%==1 (
|
||||
@@ -247,6 +247,16 @@ 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
|
||||
%PATCH% -p1 < "%SCRIPTDIR%\..\common\kddockwidgets-dodgy-include.patch" || goto error
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=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
|
||||
|
||||
21
.github/workflows/windows_build_qt.yml
vendored
21
.github/workflows/windows_build_qt.yml
vendored
@@ -167,8 +167,27 @@ jobs:
|
||||
!./bin/**/*.pdb
|
||||
!./bin/**/*.lib
|
||||
|
||||
- name: Install the Breakpad Symbol Generator
|
||||
uses: baptiste0928/cargo-install@v3
|
||||
with:
|
||||
crate: dump_syms
|
||||
|
||||
- name: Generate Breakpad Symbols # Also flatten pdbs to a 'symbols' directory for upload-artifact
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir -Force symbols
|
||||
Get-ChildItem -Path ./bin -Recurse -File | Where-Object {
|
||||
($_.Extension -eq ".exe" -or $_.Extension -eq ".pdb") -and ($_.Name -notmatch "updater")
|
||||
} | ForEach-Object {
|
||||
& dump_syms $_.FullName >> symbols/pcsx2-qt.bpsym
|
||||
}
|
||||
Get-ChildItem -Path ./bin -Recurse -Filter "*.pdb" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination symbols/
|
||||
}
|
||||
|
||||
- name: Upload artifact - with symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
|
||||
path: ./bin/**/*.pdb
|
||||
path: |
|
||||
./symbols
|
||||
|
||||
@@ -1270,6 +1270,7 @@ SCAJ-10015:
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SCAJ-20001:
|
||||
name: "Ratchet & Clank"
|
||||
region: "NTSC-Unk"
|
||||
@@ -2015,7 +2016,6 @@ SCAJ-20132:
|
||||
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes ghosting characters.
|
||||
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
|
||||
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
|
||||
SCAJ-20133:
|
||||
name: "Kagero 2 - Dark Illusion"
|
||||
@@ -2237,6 +2237,7 @@ SCAJ-20163:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes ghosting.
|
||||
autoFlush: 2 # Fixes post lighting.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SCAJ-20164:
|
||||
name: "Kingdom Hearts II"
|
||||
region: "NTSC-Unk"
|
||||
@@ -10418,15 +10419,6 @@ SCUS-21295:
|
||||
recommendedBlendingLevel: 3 # Fixes water and grass textures.
|
||||
halfPixelOffset: 4 # Mostly aligns post processing.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SCUS-21494:
|
||||
name: "Need for Speed - Carbon Collector's Edition"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
clampModes:
|
||||
eeClampMode: 3 # Fixes game hang after opening intro.
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
roundSprite: 2 # Fixes blurriness.
|
||||
SCUS-90174:
|
||||
name: "Disney/Pixar Toy Story 3 [PlayStation 2 Bundle]"
|
||||
region: "NTSC-U"
|
||||
@@ -12805,9 +12797,7 @@ SLAJ-25075:
|
||||
- "SLAJ-25075"
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLPM-65766"
|
||||
- "SLPM-66051"
|
||||
- "SLPM-66960"
|
||||
- "SLAJ-25054" # Underground 2 save grants extra money.
|
||||
SLAJ-25076:
|
||||
name: "Harry Potter and the Goblet of Fire"
|
||||
region: "NTSC-Unk"
|
||||
@@ -12879,6 +12869,13 @@ SLAJ-25091:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLAJ-25091"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLAJ-25075"
|
||||
- "SLPM-66232"
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLAJ-25054"
|
||||
SLAJ-25092:
|
||||
name: "Shin Sangoku Musou 4 [PlayStation2 the Best]"
|
||||
region: "NTSC-Unk"
|
||||
@@ -13630,7 +13627,7 @@ SLED-53954:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLED-53977:
|
||||
name: "Dragon Quest - The Journey of the Cursed King"
|
||||
region: "PAL-E"
|
||||
@@ -15351,7 +15348,6 @@ SLES-50723:
|
||||
name: "TOCA Race Driver"
|
||||
region: "PAL-M3"
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes lighting.
|
||||
halfPixelOffset: 1 # Fixes vertical lines
|
||||
SLES-50725:
|
||||
name: "V-Rally 3"
|
||||
@@ -15449,7 +15445,6 @@ SLES-50767:
|
||||
name: "V8 Supercars Australia - Race Driver"
|
||||
region: "PAL-A"
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes lighting.
|
||||
halfPixelOffset: 1 # Fixes vertical lines
|
||||
SLES-50768:
|
||||
name: "Rally Championship"
|
||||
@@ -15616,13 +15611,11 @@ SLES-50816:
|
||||
region: "PAL-M3"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes lighting.
|
||||
halfPixelOffset: 1 # Fixes vertical lines
|
||||
SLES-50818:
|
||||
name: "Pro Race Driver"
|
||||
region: "PAL-I"
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes lighting.
|
||||
halfPixelOffset: 1 # Fixes vertical lines
|
||||
SLES-50820:
|
||||
name: "Micro Machines"
|
||||
@@ -18283,7 +18276,7 @@ SLES-51914:
|
||||
- "SLES-51914"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 4 # Mostly aligns post processing.
|
||||
nativeScaling: 2 # Fixes post processing smoothness and position.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
|
||||
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
|
||||
SLES-51915:
|
||||
@@ -20081,7 +20074,7 @@ SLES-52636:
|
||||
autoFlush: 1 # Fixes incorrect colors.
|
||||
alignSprite: 1 # Fixes vertical lines such as in FMVs.
|
||||
SLES-52637:
|
||||
name: "TOCA Racer Driver 2"
|
||||
name: "TOCA Race Driver 2"
|
||||
region: "PAL-M5"
|
||||
gsHWFixes:
|
||||
alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex.
|
||||
@@ -21524,6 +21517,9 @@ SLES-53087:
|
||||
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
|
||||
halfPixelOffset: 2 # Fixes depth of field alignment.
|
||||
nativeScaling: 1 # Fixes depth of field.
|
||||
memcardFilters:
|
||||
- "SLES-53087"
|
||||
- "SLES-52637" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
|
||||
SLES-53088:
|
||||
name: "DTM Race Driver 3"
|
||||
region: "PAL-M5"
|
||||
@@ -21534,6 +21530,9 @@ SLES-53088:
|
||||
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
|
||||
halfPixelOffset: 2 # Fixes depth of field alignment.
|
||||
nativeScaling: 1 # Fixes depth of field.
|
||||
memcardFilters:
|
||||
- "SLES-53088"
|
||||
- "SLES-52638" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
|
||||
SLES-53089:
|
||||
name: "V8 Supercars Australia 3"
|
||||
region: "PAL-E"
|
||||
@@ -21544,6 +21543,9 @@ SLES-53089:
|
||||
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
|
||||
halfPixelOffset: 2 # Fixes depth of field alignment.
|
||||
nativeScaling: 1 # Fixes depth of field.
|
||||
memcardFilters:
|
||||
- "SLES-53089"
|
||||
- "SLES-52639" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
|
||||
SLES-53090:
|
||||
name: "Circuit Blasters"
|
||||
region: "PAL-M5"
|
||||
@@ -22913,7 +22915,7 @@ SLES-53556:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLES-53557:
|
||||
name: "Need for Speed - Most Wanted"
|
||||
region: "PAL-E"
|
||||
@@ -22923,12 +22925,12 @@ SLES-53557:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
cpuCLUTRender: 1 # Final colour adjustment LUT.
|
||||
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
|
||||
memcardFilters: # Reads Underground 2 save for extra money.
|
||||
memcardFilters:
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
- "SLES-52725" # Underground 2 save grants extra money.
|
||||
SLES-53558:
|
||||
name: "Need for Speed - Most Wanted"
|
||||
region: "PAL-M8"
|
||||
@@ -22942,7 +22944,7 @@ SLES-53558:
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
- "SLES-52725" # Underground 2 save grants extra money.
|
||||
SLES-53559:
|
||||
name: "Need for Speed - Most Wanted"
|
||||
region: "PAL-M7"
|
||||
@@ -22956,7 +22958,7 @@ SLES-53559:
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
- "SLES-52725" # Underground 2 save grants extra money.
|
||||
SLES-53560:
|
||||
name: "Sonic Riders"
|
||||
region: "PAL-M5"
|
||||
@@ -23703,7 +23705,6 @@ SLES-53794:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 4 # Fix effects upscaling.
|
||||
nativeScaling: 2 # Fixes post effects.
|
||||
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
|
||||
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
|
||||
SLES-53796:
|
||||
name: "FIFA Street 2"
|
||||
@@ -23895,7 +23896,7 @@ SLES-53857:
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
- "SLES-52725" # Underground 2 save grants extra money.
|
||||
SLES-53860:
|
||||
name: "Dynasty Warriors 5 - Xtreme Legends"
|
||||
region: "PAL-E"
|
||||
@@ -24385,7 +24386,7 @@ SLES-54027:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLES-54030:
|
||||
name: "Black"
|
||||
region: "PAL-E"
|
||||
@@ -25141,7 +25142,7 @@ SLES-54317:
|
||||
region: "PAL-M5"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes alignment on fire effects.
|
||||
halfPixelOffset: 4 # Fixes alignment on fire effects.
|
||||
SLES-54319:
|
||||
name: "Biker Mice from Mars"
|
||||
region: "PAL-M5"
|
||||
@@ -25159,6 +25160,18 @@ SLES-54321:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54322:
|
||||
name: "Need for Speed - Carbon"
|
||||
region: "PAL-M8"
|
||||
@@ -25167,6 +25180,18 @@ SLES-54322:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54323:
|
||||
name: "Need for Speed - Carbon"
|
||||
region: "PAL-I-S"
|
||||
@@ -25175,6 +25200,18 @@ SLES-54323:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54324:
|
||||
name: "Need for Speed - Carbon"
|
||||
region: "PAL-R"
|
||||
@@ -25183,6 +25220,18 @@ SLES-54324:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54326:
|
||||
name: "Raceway - Drag Stock Racing"
|
||||
region: "PAL-E"
|
||||
@@ -25457,6 +25506,17 @@ SLES-54402:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54410:
|
||||
name: "Monster Trux Arenas - Special Edition"
|
||||
region: "PAL-Unk"
|
||||
@@ -25713,7 +25773,6 @@ SLES-54483:
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 4 # Fixes car reflections.
|
||||
mergeSprite: 1 # Fixes bluriness.
|
||||
halfPixelOffset: 4 # Fixes bluriness.
|
||||
nativeScaling: 2 # Fixes post lighting.
|
||||
autoFlush: 1 # Fixes post alignment.
|
||||
@@ -25754,6 +25813,17 @@ SLES-54492:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54493:
|
||||
name: "Need for Speed - Carbon [Collector's Edition]"
|
||||
region: "PAL-F-G"
|
||||
@@ -25762,6 +25832,17 @@ SLES-54493:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-54494:
|
||||
name: "Little Britain - The Video Game"
|
||||
region: "PAL-E"
|
||||
@@ -27325,26 +27406,132 @@ SLES-55002:
|
||||
region: "PAL-E"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-55002"
|
||||
- "SLES-55003"
|
||||
- "SLES-55004"
|
||||
- "SLES-55005"
|
||||
- "SLES-55006"
|
||||
# Carbon save grants extra money.
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-55003:
|
||||
name: "Need for Speed - ProStreet"
|
||||
region: "PAL-F-G"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-55002"
|
||||
- "SLES-55003"
|
||||
- "SLES-55004"
|
||||
- "SLES-55005"
|
||||
- "SLES-55006"
|
||||
# Carbon save grants extra money.
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-55004:
|
||||
name: "Need for Speed - ProStreet"
|
||||
region: "PAL-I-S"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-55002"
|
||||
- "SLES-55003"
|
||||
- "SLES-55004"
|
||||
- "SLES-55005"
|
||||
- "SLES-55006"
|
||||
# Carbon save grants extra money.
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-55005:
|
||||
name: "Need for Speed - ProStreet"
|
||||
region: "PAL-M8"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-55002"
|
||||
- "SLES-55003"
|
||||
- "SLES-55004"
|
||||
- "SLES-55005"
|
||||
- "SLES-55006"
|
||||
# Carbon save grants extra money.
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-55006:
|
||||
name: "Need for Speed - ProStreet"
|
||||
region: "PAL-R"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLES-55002"
|
||||
- "SLES-55003"
|
||||
- "SLES-55004"
|
||||
- "SLES-55005"
|
||||
- "SLES-55006"
|
||||
# Carbon save grants extra money. Detection is bugged by default, can be fixed with a patch.
|
||||
- "SLES-54321"
|
||||
- "SLES-54322"
|
||||
- "SLES-54323"
|
||||
- "SLES-54324"
|
||||
- "SLES-54402"
|
||||
- "SLES-54492"
|
||||
- "SLES-54493"
|
||||
# Most Wanted save grants extra money. Detection is bugged by default, can be fixed with a patch.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLUS-21267" # Original wrong Most Wanted serial.
|
||||
# UG2 saves can be detected, but the code is unused.
|
||||
- "SLES-52725"
|
||||
SLES-55007:
|
||||
name: "Boogie"
|
||||
region: "PAL-M7"
|
||||
@@ -28289,6 +28476,18 @@ SLES-55349:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLES-55349"
|
||||
- "SLES-55350"
|
||||
- "SLES-55351"
|
||||
- "SLES-55352"
|
||||
- "SLES-55353"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
SLES-55350:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-F-G"
|
||||
@@ -28299,6 +28498,18 @@ SLES-55350:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLES-55349"
|
||||
- "SLES-55350"
|
||||
- "SLES-55351"
|
||||
- "SLES-55352"
|
||||
- "SLES-55353"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
SLES-55351:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-I-S"
|
||||
@@ -28309,6 +28520,18 @@ SLES-55351:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLES-55349"
|
||||
- "SLES-55350"
|
||||
- "SLES-55351"
|
||||
- "SLES-55352"
|
||||
- "SLES-55353"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
SLES-55352:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-SC"
|
||||
@@ -28320,6 +28543,18 @@ SLES-55352:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLES-55349"
|
||||
- "SLES-55350"
|
||||
- "SLES-55351"
|
||||
- "SLES-55352"
|
||||
- "SLES-55353"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
SLES-55353:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-M6"
|
||||
@@ -28330,6 +28565,18 @@ SLES-55353:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLES-55349"
|
||||
- "SLES-55350"
|
||||
- "SLES-55351"
|
||||
- "SLES-55352"
|
||||
- "SLES-55353"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLES-53557"
|
||||
- "SLES-53558"
|
||||
- "SLES-53559"
|
||||
- "SLES-53857"
|
||||
- "SLES-52725"
|
||||
SLES-55354:
|
||||
name: "Shin Megami Tensei - Persona 3 FES"
|
||||
region: "PAL-E"
|
||||
@@ -29913,7 +30160,7 @@ SLKA-25093:
|
||||
- "SLKA-25093"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 4 # Mostly aligns post processing.
|
||||
nativeScaling: 2 # Fixes post processing smoothness and position.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
|
||||
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
|
||||
SLKA-25095:
|
||||
@@ -30261,6 +30508,11 @@ SLKA-25185:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
roundSprite: 2 # Fixes blurriness.
|
||||
memcardFilters:
|
||||
- "SLKA-25185"
|
||||
- "SLKA-25334" # Most Wanted save grants extra money.
|
||||
- "SLAJ-25075" # Original wrong Black Edition serial.
|
||||
- "SLKA-25241" # UG2 saves can be detected, but the code is unused.
|
||||
SLKA-25186:
|
||||
name: "The King of Fighters - Maximum Impact [Limited Edition]"
|
||||
name-sort: "King of Fighters, The - Maximum Impact [Limited Edition]"
|
||||
@@ -30947,6 +31199,9 @@ SLKA-25334:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
cpuCLUTRender: 1 # Final colour adjustment LUT.
|
||||
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
|
||||
memcardFilters:
|
||||
- "SLKA-25334"
|
||||
- "SLKA-25241" # Underground 2 save grants extra money.
|
||||
SLKA-25335:
|
||||
name: "Shadow the Hedgehog"
|
||||
region: "NTSC-K"
|
||||
@@ -30986,7 +31241,7 @@ SLKA-25341:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLKA-25342:
|
||||
name: "Ryu ga Gotoku"
|
||||
region: "NTSC-K"
|
||||
@@ -31308,6 +31563,11 @@ SLKA-25411:
|
||||
region: "NTSC-K"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLKA-25411"
|
||||
- "SLKA-25185" # Carbon save grants extra money.
|
||||
- "SLKA-25334" # Most Wanted save grants extra money.
|
||||
- "SLKA-25241" # UG2 saves can be detected, but the code is unused.
|
||||
SLKA-25412:
|
||||
name: "Sengoku Basara 2 - Heroes"
|
||||
region: "NTSC-K"
|
||||
@@ -31424,6 +31684,13 @@ SLKA-25446:
|
||||
recommendedBlendingLevel: 3 # Improves car reflections.
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLKA-25446"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLKA-25185"
|
||||
- "SLKA-25334"
|
||||
- "SLAJ-25075"
|
||||
- "SLKA-25241"
|
||||
SLKA-25447:
|
||||
name: "FIFA 09"
|
||||
region: "NTSC-K"
|
||||
@@ -31619,6 +31886,9 @@ SLPM-55003:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
cpuCLUTRender: 1 # Final colour adjustment LUT.
|
||||
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
|
||||
memcardFilters:
|
||||
- "SLPM-55003"
|
||||
- "SLPM-65766" # Underground 2 save grants extra money.
|
||||
SLPM-55004:
|
||||
name: "バーンアウト リベンジ [EA:SY! 1980]"
|
||||
name-sort: "ばーんあうと りべんじ [EA:SY! 1980])"
|
||||
@@ -31872,6 +32142,9 @@ SLPM-55046:
|
||||
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
|
||||
halfPixelOffset: 2 # Fixes depth of field alignment.
|
||||
nativeScaling: 1 # Fixes depth of field.
|
||||
memcardFilters:
|
||||
- "SLPM-55046"
|
||||
- "SLPM-66498" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
|
||||
SLPM-55047:
|
||||
name: "Sugar+Spice! ~あの子のステキな何もかも~"
|
||||
name-sort: "しゅがーすぱいす あのこのすてきななにもかも"
|
||||
@@ -31946,6 +32219,13 @@ SLPM-55061:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLPM-55061"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLAJ-25075" # Original wrong Black Edition serial.
|
||||
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
|
||||
SLPM-55062:
|
||||
name: "実況パワフルメジャーリーグ 3"
|
||||
name-sort: "じっきょうぱわふるめじゃーりーぐ 3"
|
||||
@@ -32048,7 +32328,6 @@ SLPM-55081:
|
||||
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes ghosting characters.
|
||||
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
|
||||
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
|
||||
SLPM-55082:
|
||||
name: "真・三國無双5 Special [ディスク 1]"
|
||||
@@ -32264,6 +32543,13 @@ SLPM-55127:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLPM-55127"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLAJ-25075"
|
||||
- "SLPM-65766"
|
||||
SLPM-55128:
|
||||
name: "ラグビー08 [英語版] [EA:SY! 1980]"
|
||||
name-sort: "らぐびー08 [えいごばん] [EA:SY! 1980]"
|
||||
@@ -32401,6 +32687,16 @@ SLPM-55151:
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLPM-55151"
|
||||
# Carbon save grants extra money.
|
||||
- "SLPM-55061"
|
||||
- "SLPM-66617"
|
||||
- "SLPM-66869"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
|
||||
SLPM-55152:
|
||||
name: "スロッターUPコア11 巨人の星Ⅳ 青春群像編"
|
||||
name-sort: "すろったーUPこあ11 きょじんのほし4 せいしゅんぐんぞうへん"
|
||||
@@ -32842,6 +33138,13 @@ SLPM-55244:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLPM-55244"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLAJ-25075"
|
||||
- "SLPM-65766"
|
||||
SLPM-55245:
|
||||
name: "ひまわり -Pebble in the Sky-"
|
||||
name-sort: "ひまわり -Pebble in the Sky-"
|
||||
@@ -34510,7 +34813,6 @@ SLPM-61120:
|
||||
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes ghosting characters.
|
||||
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
|
||||
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
|
||||
SLPM-61121:
|
||||
name: "KAIDO ~峠の伝説~ [体験版]"
|
||||
@@ -41214,7 +41516,7 @@ SLPM-65413:
|
||||
- "SLPM-65413"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 4 # Mostly aligns post processing.
|
||||
nativeScaling: 2 # Fixes post processing smoothness and position.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
|
||||
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
|
||||
SLPM-65414:
|
||||
@@ -44547,7 +44849,6 @@ SLPM-65999:
|
||||
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes ghosting characters.
|
||||
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
|
||||
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
|
||||
SLPM-66000:
|
||||
name: "コンフリクトデルタⅡ ~湾岸戦争1991~"
|
||||
@@ -46063,7 +46364,8 @@ SLPM-66232:
|
||||
- "SLAJ-25075"
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLPM-65766"
|
||||
- "SLPM-65766" # Underground 2 save grants extra money.
|
||||
# Unconfirmed UG2 serials.
|
||||
- "SLPM-66051"
|
||||
- "SLPM-66960"
|
||||
SLPM-66233:
|
||||
@@ -48094,7 +48396,8 @@ SLPM-66562:
|
||||
- "SLAJ-25075"
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLPM-65766"
|
||||
- "SLPM-65766" # Underground 2 save grants extra money.
|
||||
# Unconfirmed UG2 serials.
|
||||
- "SLPM-66051"
|
||||
- "SLPM-66960"
|
||||
SLPM-66563:
|
||||
@@ -48135,7 +48438,7 @@ SLPM-66567:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLPM-66568:
|
||||
name: "ブラザー イン アームズ ロード トゥ ヒル サーティ [UBISOFT BEST]"
|
||||
name-sort: "ぶらざー いん あーむず ろーど とぅ ひる さーてぃ [UBISOFT BEST]"
|
||||
@@ -48443,6 +48746,13 @@ SLPM-66617:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLPM-66617"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLAJ-25075" # Original wrong Black Edition serial.
|
||||
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
|
||||
SLPM-66618:
|
||||
name: "夢見師 [初回限定版]"
|
||||
name-sort: "ゆめみし [しょかいげんていばん]"
|
||||
@@ -49971,6 +50281,13 @@ SLPM-66869:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLPM-66869"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLAJ-25075" # Original wrong Black Edition serial.
|
||||
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
|
||||
SLPM-66870:
|
||||
name: "星色のおくりもの [初回スペシャル限定版]"
|
||||
name-sort: "ほしいろのおくりもの [しょかいすぺしゃるげんていばん]"
|
||||
@@ -50039,6 +50356,9 @@ SLPM-66881:
|
||||
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
|
||||
halfPixelOffset: 2 # Fixes depth of field alignment.
|
||||
nativeScaling: 1 # Fixes depth of field.
|
||||
memcardFilters:
|
||||
- "SLPM-66881"
|
||||
- "SLPM-66498" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
|
||||
SLPM-66882:
|
||||
name: "はかれなはーと ~君がために輝きを~ [限定版・サントラボイスCD付]"
|
||||
name-sort: "はかれなはーと きみがためにかがやきを [げんていばん さんとらぼいすCDつき]"
|
||||
@@ -50319,6 +50639,16 @@ SLPM-66932:
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLPM-66932"
|
||||
# Carbon save grants extra money.
|
||||
- "SLPM-55061"
|
||||
- "SLPM-66617"
|
||||
- "SLPM-66869"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLPM-66232"
|
||||
- "SLPM-66562"
|
||||
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
|
||||
SLPM-66933:
|
||||
name: "君が主で執事が俺で~お仕え日記~ [初回限定版]"
|
||||
name-sort: "きみがあるじでしつじがおれで おつかえにっき [しょかいげんていばん]"
|
||||
@@ -54359,6 +54689,7 @@ SLPS-20485:
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SLPS-20486:
|
||||
name: "太鼓の達人 ドカッ!と大盛り七代目 [ソフト単体]"
|
||||
name-sort: "たいこのたつじん どかっ!とおおもりななだいめ [そふとたんたい]"
|
||||
@@ -54366,6 +54697,7 @@ SLPS-20486:
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SLPS-20487:
|
||||
name: "パチスロキング! 科学忍者隊ガッチャマン"
|
||||
name-sort: "ぱちすろきんぐ! かがくにんじゃたいがっちゃまん"
|
||||
@@ -57943,6 +58275,7 @@ SLPS-25586:
|
||||
halfPixelOffset: 4 # Fixes post positioning.
|
||||
nativeScaling: 1 # Fixes post effects.
|
||||
autoFlush: 2 # Fixes post lighting.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SLPS-25587:
|
||||
name: "シュガシュガルーン 恋もおしゃれもピックアップ!"
|
||||
name-sort: "しゅがしゅがるーん こいもおしゃれもぴっくあっぷ!"
|
||||
@@ -60976,6 +61309,7 @@ SLPS-73252:
|
||||
halfPixelOffset: 4 # Fixes post positioning.
|
||||
nativeScaling: 1 # Fixes post effects.
|
||||
autoFlush: 2 # Fixes post lighting.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SLPS-73253:
|
||||
name: "るろうに剣心-明治剣客浪漫譚- 炎上!京都輪廻 [PlayStation2 the Best]"
|
||||
name-sort: "るろうにけんしん めいじけんかくろまんたん えんじょう きょうとりんね [PlayStation2 the Best]"
|
||||
@@ -62650,7 +62984,6 @@ SLUS-20329:
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes lighting.
|
||||
halfPixelOffset: 1 # Fixes vertical lines
|
||||
SLUS-20330:
|
||||
name: "NBA 2K2 - Sega Sports"
|
||||
@@ -64556,7 +64889,7 @@ SLUS-20694:
|
||||
- "SLUS-20710"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 4 # Mostly aligns post processing.
|
||||
nativeScaling: 2 # Fixes post processing smoothness and position.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
|
||||
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
|
||||
SLUS-20695:
|
||||
@@ -67351,6 +67684,9 @@ SLUS-21182:
|
||||
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
|
||||
halfPixelOffset: 2 # Fixes depth of field alignment.
|
||||
nativeScaling: 1 # Fixes depth of field.
|
||||
memcardFilters:
|
||||
- "SLUS-21182"
|
||||
- "SLUS-21039" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
|
||||
SLUS-21183:
|
||||
name: "Teen Titans"
|
||||
region: "NTSC-U"
|
||||
@@ -67869,6 +68205,9 @@ SLUS-21257:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
cpuCLUTRender: 1 # Final colour adjustment LUT.
|
||||
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
|
||||
memcardFilters:
|
||||
- "SLUS-21257"
|
||||
- "SLUS-21065" # Underground 2 save grants extra money.
|
||||
SLUS-21258:
|
||||
name: ".hack//G.U. Vol.1 - Rebirth"
|
||||
region: "NTSC-U"
|
||||
@@ -67955,7 +68294,7 @@ SLUS-21267:
|
||||
memcardFilters:
|
||||
- "SLUS-21267"
|
||||
- "SLUS-21351"
|
||||
- "SLUS-21065"
|
||||
- "SLUS-21065" # Underground 2 save grants extra money.
|
||||
SLUS-21268:
|
||||
name: "24 - The Game"
|
||||
region: "NTSC-U"
|
||||
@@ -67988,7 +68327,7 @@ SLUS-21271:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLUS-21272:
|
||||
name: "Super Monkey Ball Adventure"
|
||||
region: "NTSC-U"
|
||||
@@ -68244,7 +68583,7 @@ SLUS-21306:
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
mergeSprite: 1 # Fixes alignment on fire effects.
|
||||
halfPixelOffset: 4 # Fixes alignment on fire effects.
|
||||
SLUS-21307:
|
||||
name: "Ice Age 2 - The Meltdown"
|
||||
region: "NTSC-U"
|
||||
@@ -68544,7 +68883,7 @@ SLUS-21351:
|
||||
memcardFilters:
|
||||
- "SLUS-21267"
|
||||
- "SLUS-21351"
|
||||
- "SLUS-21065"
|
||||
- "SLUS-21065" # Underground 2 save grants extra money.
|
||||
SLUS-21352:
|
||||
name: "Dai Senryaku VII Exceed"
|
||||
region: "NTSC-U"
|
||||
@@ -68706,7 +69045,6 @@ SLUS-21373:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 4 # Fix effects upscaling.
|
||||
nativeScaling: 2 # Fixes post effects.
|
||||
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
|
||||
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
|
||||
SLUS-21374:
|
||||
name: "Marvel - Ultimate Alliance"
|
||||
@@ -68805,6 +69143,7 @@ SLUS-21386:
|
||||
halfPixelOffset: 4 # Fixes post positioning.
|
||||
nativeScaling: 1 # Fixes post effects.
|
||||
autoFlush: 2 # Fixes post lighting.
|
||||
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
|
||||
SLUS-21387:
|
||||
name: "Warship Gunner 2"
|
||||
region: "NTSC-U"
|
||||
@@ -68886,7 +69225,7 @@ SLUS-21399:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLUS-21400:
|
||||
name: "Monster House"
|
||||
region: "NTSC-U"
|
||||
@@ -69212,7 +69551,6 @@ SLUS-21449:
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 4 # Fixes car reflections.
|
||||
mergeSprite: 1 # Fixes bluriness.
|
||||
halfPixelOffset: 4 # Fixes bluriness.
|
||||
nativeScaling: 2 # Fixes post lighting.
|
||||
autoFlush: 1 # Fixes post alignment.
|
||||
@@ -69488,14 +69826,29 @@ SLUS-21493:
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLUS-21493"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLUS-21267"
|
||||
- "SLUS-21351" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
- "SLAJ-25075" # Original wrong Black Edition serial.
|
||||
- "SLUS-21065" # UG2 saves can be detected, but the code is unused.
|
||||
SLUS-21494:
|
||||
name: "Need for Speed - Carbon [Collector's Edition]"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
clampModes:
|
||||
eeClampMode: 3 # Fixes game hang after opening intro.
|
||||
gsHWFixes:
|
||||
recommendedBlendingLevel: 3 # Fixes car headlights.
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLUS-21494"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLUS-21267"
|
||||
- "SLUS-21351" # Black Edition detection is bugged by default, can be fixed with a patch.
|
||||
- "SLAJ-25075" # Original wrong Black Edition serial.
|
||||
- "SLUS-21065" # UG2 saves can be detected, but the code is unused.
|
||||
SLUS-21495:
|
||||
name: "Sudoku, Carol Vorderman's"
|
||||
region: "NTSC-U"
|
||||
@@ -70253,6 +70606,15 @@ SLUS-21658:
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes depth line.
|
||||
memcardFilters:
|
||||
- "SLUS-21658"
|
||||
# Carbon save grants extra money.
|
||||
- "SLUS-21493"
|
||||
- "SLUS-21494"
|
||||
# Most Wanted save grants extra money.
|
||||
- "SLUS-21267"
|
||||
- "SLUS-21351"
|
||||
- "SLUS-21065" # UG2 saves can be detected, but the code is unused.
|
||||
SLUS-21660:
|
||||
name: "Disney Princess - Enchanted Journey"
|
||||
region: "NTSC-U"
|
||||
@@ -71031,6 +71393,13 @@ SLUS-21801:
|
||||
gpuTargetCLUT: 1 # Fixes sun penetration.
|
||||
nativeScaling: 2 # Fixes post alignment.
|
||||
getSkipCount: "GSC_NFSUndercover"
|
||||
memcardFilters:
|
||||
- "SLUS-21801"
|
||||
# Checks from Carbon are left in the code, unused.
|
||||
- "SLUS-21267"
|
||||
- "SLUS-21351"
|
||||
- "SLAJ-25075"
|
||||
- "SLUS-21065"
|
||||
SLUS-21802:
|
||||
name: "Naked Brothers Band - The Video Game"
|
||||
region: "NTSC-U"
|
||||
@@ -72687,7 +73056,7 @@ SLUS-29185:
|
||||
cpuSpriteRenderBW: 2 # Fixes some bad textures.
|
||||
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
|
||||
roundSprite: 1 # Reduces misaligned bloom.
|
||||
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
|
||||
nativeScaling: 1 # Fixes post processing smoothness and position.
|
||||
SLUS-29188:
|
||||
name: "Steambot Chronicles [Regular Demo]"
|
||||
region: "NTSC-U"
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
03000000380700006652000000000000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700005032000000000000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700005082000000000000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008031000000000000,Mad Catz FightStick Alpha PS3 ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008031000000000000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000003807000038b7000000000000,Mad Catz Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000380700008433000000000000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008483000000000000,Mad Catz Fightstick TE S PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
|
||||
@@ -480,6 +480,7 @@
|
||||
030000008916000000fd000000000000,Onza TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
|
||||
0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000362800000100000000000000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,
|
||||
03000000120c0000f60e000000000000,P4 Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000790000002201000000000000,PC Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
@@ -665,7 +666,7 @@
|
||||
03000000952e00002577000000000000,Scuf PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
|
||||
03000000a30c00002500000000000000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Windows,
|
||||
03000000a30c00002400000000000000,Sega Mega Drive Mini 6B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000d804000086e6000000000000,Sega Multi Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
|
||||
03000000d804000086e6000000000000,Sega Multi Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a2,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
|
||||
0300000000050000289b000000000000,Sega Saturn Adapter,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
0300000000f000000800000000000000,Sega Saturn Controller,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b7,righttrigger:b3,start:b0,x:b5,y:b6,platform:Windows,
|
||||
03000000730700000601000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,
|
||||
@@ -967,6 +968,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000380700005082000000010000,Mad Catz PS4 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000380700008483000000010000,Mad Catz PS4 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
|
||||
0300000049190000020400001b010000,Manba One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000790000000600000007010000,Marvo GT-004,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
030000008f0e00001330000011010000,Mayflash Controller Adapter,a:b2,b:b4,back:b16,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b12,lefttrigger:b16,leftx:a0,lefty:a2,rightshoulder:b14,rightx:a6~,righty:a4,start:b18,x:b0,y:b6,platform:Mac OS X,
|
||||
03000000790000004318000000010000,Mayflash GameCube Adapter,a:b4,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,
|
||||
@@ -998,7 +1000,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000550900001472000025050000,NVIDIA Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X,
|
||||
030000004b120000014d000000010000,Nyko Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000790000001c18000000010000,PB Tails Choc,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000006f0e00000901000002010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000008f0e00000300000000000000,Piranha Xtreme PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000d620000011a7000000020000,PowerA Core Plus Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
|
||||
@@ -1070,6 +1071,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000457500002211000000010000,SZMY Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000e40a00000307000001000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,
|
||||
03000000e40a00000207000001000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,
|
||||
03000000790000001c18000000010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000790000001c18000003100000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000591c00002400000021000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000591c00002600000021000000,THEGamepad,a:b2,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Mac OS X,
|
||||
@@ -1380,6 +1382,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000380700005032000011010000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700005082000011010000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
|
||||
03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000380700008031000011010000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008081000011010000,Mad Catz FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008034000011010000,Mad Catz Fightstick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008084000011010000,Mad Catz Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008433000011010000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
@@ -1519,6 +1523,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000c62400001a54000001010000,PowerA Xbox One Mini Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000d62000000240000001010000,PowerA Xbox One Spectra Infinity,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000d62000000520000050010000,PowerA Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000d62000000b20000001010000,PowerA Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,
|
||||
|
||||
@@ -146,15 +146,11 @@ if(MSVC AND NOT USE_CLANG_CL)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
# Disable RTTI
|
||||
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
|
||||
# Disable Exceptions
|
||||
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
else()
|
||||
add_compile_options(-pipe -fvisibility=hidden -pthread)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -17,7 +17,7 @@ find_package(ZLIB REQUIRED) # v1.3, but Mac uses the SDK version.
|
||||
find_package(Zstd 1.5.5 REQUIRED)
|
||||
find_package(LZ4 REQUIRED)
|
||||
find_package(WebP REQUIRED) # v1.3.2, spews an error on Linux because no pkg-config.
|
||||
find_package(SDL2 2.30.4 REQUIRED)
|
||||
find_package(SDL3 3.2.6 REQUIRED)
|
||||
find_package(Freetype 2.11.1 REQUIRED)
|
||||
|
||||
if(USE_VULKAN)
|
||||
@@ -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)
|
||||
|
||||
@@ -288,8 +288,14 @@ std::string Path::RealPath(const std::string_view path)
|
||||
{
|
||||
// Resolve non-absolute paths first.
|
||||
std::vector<std::string_view> components;
|
||||
// We need to keep the full combined path in scope
|
||||
// as SplitNativePath() returns string_views to it.
|
||||
std::string buf;
|
||||
if (!IsAbsolute(path))
|
||||
components = Path::SplitNativePath(Path::Combine(FileSystem::GetWorkingDirectory(), path));
|
||||
{
|
||||
buf = Path::Combine(FileSystem::GetWorkingDirectory(), path);
|
||||
components = Path::SplitNativePath(buf);
|
||||
}
|
||||
else
|
||||
components = Path::SplitNativePath(path);
|
||||
|
||||
@@ -968,7 +974,7 @@ std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error*
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const std::wstring wfilename = GetWin32Path(filename);
|
||||
const std::wstring wmode = GetWin32Path(mode);
|
||||
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
|
||||
if (!wfilename.empty() && !wmode.empty())
|
||||
{
|
||||
std::FILE* fp;
|
||||
@@ -1060,7 +1066,7 @@ std::FILE* FileSystem::OpenSharedCFile(const char* filename, const char* mode, F
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const std::wstring wfilename = GetWin32Path(filename);
|
||||
const std::wstring wmode = GetWin32Path(mode);
|
||||
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
|
||||
if (wfilename.empty() || wmode.empty())
|
||||
return nullptr;
|
||||
|
||||
@@ -1354,8 +1360,11 @@ static u32 TranslateWin32Attributes(u32 Win32Attributes)
|
||||
}
|
||||
|
||||
static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path, const char* path, const char* pattern,
|
||||
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited)
|
||||
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited, ProgressCallback* cancel)
|
||||
{
|
||||
if (cancel && cancel->IsCancelled())
|
||||
return 0;
|
||||
|
||||
std::string search_dir;
|
||||
if (path)
|
||||
{
|
||||
@@ -1427,11 +1436,11 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
|
||||
if (parent_path)
|
||||
{
|
||||
const std::string recurse_dir = fmt::format("{}\\{}", parent_path, path);
|
||||
nFiles += RecursiveFindFiles(origin_path, recurse_dir.c_str(), utf8_filename.c_str(), pattern, flags, results, visited);
|
||||
nFiles += RecursiveFindFiles(origin_path, recurse_dir.c_str(), utf8_filename.c_str(), pattern, flags, results, visited, cancel);
|
||||
}
|
||||
else
|
||||
{
|
||||
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited);
|
||||
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited, cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1494,7 +1503,7 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
|
||||
return nFiles;
|
||||
}
|
||||
|
||||
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
|
||||
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel)
|
||||
{
|
||||
// has a path
|
||||
if (path[0] == '\0')
|
||||
@@ -1514,7 +1523,7 @@ bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, Fin
|
||||
}
|
||||
|
||||
// enter the recursive function
|
||||
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) == 0)
|
||||
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited, cancel) == 0)
|
||||
return false;
|
||||
|
||||
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
|
||||
@@ -2046,8 +2055,11 @@ bool FileSystem::DeleteSymbolicLink(const char* path, Error* error)
|
||||
static_assert(sizeof(off_t) == sizeof(s64));
|
||||
|
||||
static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern,
|
||||
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited)
|
||||
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited, ProgressCallback* cancel)
|
||||
{
|
||||
if (cancel && cancel->IsCancelled())
|
||||
return 0;
|
||||
|
||||
std::string tempStr;
|
||||
if (Path)
|
||||
{
|
||||
@@ -2118,11 +2130,11 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
|
||||
if (ParentPath)
|
||||
{
|
||||
const std::string recursive_dir = fmt::format("{}/{}", ParentPath, Path);
|
||||
nFiles += RecursiveFindFiles(OriginPath, recursive_dir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults, visited);
|
||||
nFiles += RecursiveFindFiles(OriginPath, recursive_dir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults, visited, cancel);
|
||||
}
|
||||
else
|
||||
{
|
||||
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited);
|
||||
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited, cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2177,7 +2189,7 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
|
||||
return nFiles;
|
||||
}
|
||||
|
||||
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
|
||||
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel)
|
||||
{
|
||||
// has a path
|
||||
if (path[0] == '\0')
|
||||
@@ -2197,7 +2209,7 @@ bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, Fin
|
||||
}
|
||||
|
||||
// enter the recursive function
|
||||
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) == 0)
|
||||
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited, cancel) == 0)
|
||||
return false;
|
||||
|
||||
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace FileSystem
|
||||
std::vector<std::string> GetRootDirectoryList();
|
||||
|
||||
/// Search for files
|
||||
bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results);
|
||||
bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel = nullptr);
|
||||
|
||||
/// Stat file
|
||||
bool StatFile(const char* path, struct stat* st);
|
||||
|
||||
@@ -119,6 +119,12 @@ namespace x86Emitter
|
||||
xImplSimd_DestRegSSE VPD;
|
||||
};
|
||||
|
||||
struct xImplSimd_PBlend
|
||||
{
|
||||
xImplSimd_DestRegImmSSE W;
|
||||
xImplSimd_DestRegSSE VB;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// xImplSimd_PMove
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
@@ -500,6 +500,7 @@ namespace x86Emitter
|
||||
extern const xImplSimd_MovHL_RtoR xMOVLH;
|
||||
extern const xImplSimd_MovHL_RtoR xMOVHL;
|
||||
|
||||
extern const xImplSimd_PBlend xPBLEND;
|
||||
extern const xImplSimd_Blend xBLEND;
|
||||
extern const xImplSimd_PMove xPMOVSX;
|
||||
extern const xImplSimd_PMove xPMOVZX;
|
||||
|
||||
@@ -556,12 +556,18 @@ namespace x86Emitter
|
||||
const xImplSimd_MovHL_RtoR xMOVLH = {0x16};
|
||||
const xImplSimd_MovHL_RtoR xMOVHL = {0x12};
|
||||
|
||||
const xImplSimd_PBlend xPBLEND =
|
||||
{
|
||||
{0x66, 0x0e3a}, // W
|
||||
{0x66, 0x1038}, // VB
|
||||
};
|
||||
|
||||
const xImplSimd_Blend xBLEND =
|
||||
{
|
||||
{0x66, 0x0c3a}, // PS
|
||||
{0x66, 0x0d3a}, // PD
|
||||
{0x66, 0x1438}, // VPS
|
||||
{0x66, 0x1538}, // VPD
|
||||
{
|
||||
{0x66, 0x0c3a}, // PS
|
||||
{0x66, 0x0d3a}, // PD
|
||||
{0x66, 0x1438}, // VPS
|
||||
{0x66, 0x1538}, // VPD
|
||||
};
|
||||
|
||||
const xImplSimd_PMove xPMOVSX = {0x2038};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(DepsLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL2.lib;zlib.lib;zstd.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL3.lib;zlib.lib;zstd.lib;kddockwidgets-qt62.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@@ -15,10 +15,11 @@
|
||||
<DepsDLLs Include="$(DepsBinDir)libsharpyuv.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)libwebp.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)lz4.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)SDL2.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)SDL3.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)shaderc_shared.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)zlib1.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)zstd.dll" />
|
||||
<DepsDLLs Include="$(DepsBinDir)kddockwidgets-qt62.dll" />
|
||||
</ItemGroup>
|
||||
<Target Name="DepsCopyDLLs"
|
||||
AfterTargets="Build"
|
||||
@@ -31,4 +32,4 @@
|
||||
SkipUnchangedFiles="true"
|
||||
/>
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtToolOutDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir)\QtCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir)\QtGui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir)\QtWidgets;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(QtLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
|
||||
@@ -169,11 +169,25 @@ void Host::SetDefaultUISettings(SettingsInterface& si)
|
||||
// nothing
|
||||
}
|
||||
|
||||
bool Host::LocaleCircleConfirm()
|
||||
{
|
||||
// not running any UI, so no settings requests will come in
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ProgressCallback> Host::CreateHostProgressCallback()
|
||||
{
|
||||
return ProgressCallback::CreateNullProgressCallback();
|
||||
}
|
||||
|
||||
void Host::ReportInfoAsync(const std::string_view title, const std::string_view message)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
INFO_LOG("ReportInfoAsync: {}: {}", title, message);
|
||||
else if (!message.empty())
|
||||
INFO_LOG("ReportInfoAsync: {}", message);
|
||||
}
|
||||
|
||||
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
@@ -517,17 +531,17 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "dump", true);
|
||||
|
||||
if (str.find("rt") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "save", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveRT", true);
|
||||
if (str.find("f") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "savef", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrame", true);
|
||||
if (str.find("tex") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "savet", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTexture", true);
|
||||
if (str.find("z") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "savez", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDepth", true);
|
||||
if (str.find("a") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "savea", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveAlpha", true);
|
||||
if (str.find("i") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "savei", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-dumprange"))
|
||||
@@ -550,9 +564,9 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
{
|
||||
by = std::max(1, StringUtil::FromChars<int>(split[2]).value_or(1));
|
||||
}
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "saven", start);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "savel", num);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "saveb", by);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "SaveDrawStart", start);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "SaveDrawCount", num);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "SaveDrawBy", by);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-dumprangef"))
|
||||
@@ -575,9 +589,9 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
{
|
||||
by = std::max(1, StringUtil::FromChars<int>(split[2]).value_or(1));
|
||||
}
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "savenf", start);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "savelf", num);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "savebf", by);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "SaveFrameStart", start);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "SaveFrameCount", num);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "SaveFrameBy", by);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-dumpdirhw"))
|
||||
|
||||
@@ -86,6 +86,9 @@ target_sources(pcsx2-qt PRIVATE
|
||||
Settings/DebugAnalysisSettingsWidget.cpp
|
||||
Settings/DebugAnalysisSettingsWidget.h
|
||||
Settings/DebugAnalysisSettingsWidget.ui
|
||||
Settings/DebugUserInterfaceSettingsWidget.cpp
|
||||
Settings/DebugUserInterfaceSettingsWidget.h
|
||||
Settings/DebugUserInterfaceSettingsWidget.ui
|
||||
Settings/DebugSettingsWidget.cpp
|
||||
Settings/DebugSettingsWidget.h
|
||||
Settings/DebugSettingsWidget.ui
|
||||
@@ -158,37 +161,66 @@ 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/DebuggerEvents.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/JsonValueWrapper.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/Docking/DockLayout.cpp
|
||||
Debugger/Docking/DockLayout.h
|
||||
Debugger/Docking/DockManager.cpp
|
||||
Debugger/Docking/DockManager.h
|
||||
Debugger/Docking/DockTables.cpp
|
||||
Debugger/Docking/DockTables.h
|
||||
Debugger/Docking/DockUtils.cpp
|
||||
Debugger/Docking/DockUtils.h
|
||||
Debugger/Docking/DockViews.cpp
|
||||
Debugger/Docking/DockViews.h
|
||||
Debugger/Docking/DropIndicators.cpp
|
||||
Debugger/Docking/DropIndicators.h
|
||||
Debugger/Docking/LayoutEditorDialog.cpp
|
||||
Debugger/Docking/LayoutEditorDialog.h
|
||||
Debugger/Docking/LayoutEditorDialog.ui
|
||||
Debugger/Docking/NoLayoutsWidget.cpp
|
||||
Debugger/Docking/NoLayoutsWidget.h
|
||||
Debugger/Docking/NoLayoutsWidget.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 +264,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.
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#include "ui_BreakpointDialog.h"
|
||||
|
||||
#include "DebugTools/Breakpoints.h"
|
||||
#include "BreakpointModel.h"
|
||||
|
||||
#include "Models/BreakpointModel.h"
|
||||
#include "DebugTools/Breakpoints.h"
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
<string>4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -3,21 +3,48 @@
|
||||
|
||||
#include "BreakpointModel.h"
|
||||
|
||||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
#include "Debugger/DebuggerSettingsManager.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/Breakpoints.h"
|
||||
#include "DebugTools/DisassemblyManager.h"
|
||||
#include "common/Console.h"
|
||||
|
||||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
std::map<BreakPointCpu, BreakpointModel*> BreakpointModel::s_instances;
|
||||
|
||||
BreakpointModel::BreakpointModel(DebugInterface& cpu, QObject* parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_cpu(cpu)
|
||||
{
|
||||
if (m_cpu.getCpuType() == BREAKPOINT_EE)
|
||||
{
|
||||
connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) {
|
||||
if (title.isEmpty())
|
||||
return;
|
||||
|
||||
if (rowCount() == 0)
|
||||
DebuggerSettingsManager::loadGameSettings(this);
|
||||
});
|
||||
|
||||
DebuggerSettingsManager::loadGameSettings(this);
|
||||
}
|
||||
|
||||
connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::refreshData);
|
||||
}
|
||||
|
||||
BreakpointModel* BreakpointModel::getInstance(DebugInterface& cpu)
|
||||
{
|
||||
auto iterator = s_instances.find(cpu.getCpuType());
|
||||
if (iterator == s_instances.end())
|
||||
iterator = s_instances.emplace(cpu.getCpuType(), new BreakpointModel(cpu)).first;
|
||||
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
int BreakpointModel::rowCount(const QModelIndex&) const
|
||||
@@ -32,10 +59,14 @@ int BreakpointModel::columnCount(const QModelIndex&) const
|
||||
|
||||
QVariant BreakpointModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
size_t row = static_cast<size_t>(index.row());
|
||||
if (!index.isValid() || row >= m_breakpoints.size())
|
||||
return QVariant();
|
||||
|
||||
const BreakpointMemcheck& bp_mc = m_breakpoints[row];
|
||||
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
auto bp_mc = m_breakpoints.at(index.row());
|
||||
|
||||
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
||||
{
|
||||
switch (index.column())
|
||||
@@ -87,8 +118,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
else if (role == BreakpointModel::DataRole)
|
||||
{
|
||||
auto bp_mc = m_breakpoints.at(index.row());
|
||||
|
||||
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
||||
{
|
||||
switch (index.column())
|
||||
@@ -133,8 +162,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
else if (role == BreakpointModel::ExportRole)
|
||||
{
|
||||
auto bp_mc = m_breakpoints.at(index.row());
|
||||
|
||||
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
||||
{
|
||||
switch (index.column())
|
||||
@@ -181,8 +208,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (index.column() == 0)
|
||||
{
|
||||
auto bp_mc = m_breakpoints.at(index.row());
|
||||
|
||||
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
||||
{
|
||||
return bp->enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
|
||||
@@ -273,12 +298,14 @@ Qt::ItemFlags BreakpointModel::flags(const QModelIndex& index) const
|
||||
|
||||
bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
std::string error;
|
||||
size_t row = static_cast<size_t>(index.row());
|
||||
if (!index.isValid() || row >= m_breakpoints.size())
|
||||
return false;
|
||||
|
||||
const BreakpointMemcheck& bp_mc = m_breakpoints[row];
|
||||
|
||||
if (role == Qt::CheckStateRole && index.column() == BreakpointColumns::ENABLED)
|
||||
{
|
||||
auto bp_mc = m_breakpoints.at(index.row());
|
||||
|
||||
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
||||
{
|
||||
Host::RunOnCPUThread([cpu = this->m_cpu.getCpuType(), bp = *bp, enabled = value.toBool()] {
|
||||
@@ -297,8 +324,6 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
|
||||
}
|
||||
else if (role == Qt::EditRole && index.column() == BreakpointColumns::CONDITION)
|
||||
{
|
||||
auto bp_mc = m_breakpoints.at(index.row());
|
||||
|
||||
if (auto* bp = std::get_if<BreakPoint>(&bp_mc))
|
||||
{
|
||||
const QString condValue = value.toString();
|
||||
@@ -316,6 +341,7 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
|
||||
{
|
||||
PostfixExpression expr;
|
||||
|
||||
std::string error;
|
||||
if (!m_cpu.initExpression(condValue.toLocal8Bit().constData(), expr, error))
|
||||
{
|
||||
QMessageBox::warning(nullptr, "Condition Error", QString::fromStdString(error));
|
||||
@@ -349,6 +375,7 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
|
||||
{
|
||||
PostfixExpression expr;
|
||||
|
||||
std::string error;
|
||||
if (!m_cpu.initExpression(condValue.toLocal8Bit().constData(), expr, error))
|
||||
{
|
||||
QMessageBox::warning(nullptr, "Condition Error", QString::fromStdString(error));
|
||||
@@ -34,8 +34,7 @@ public:
|
||||
ExportRole = Qt::UserRole + 1,
|
||||
};
|
||||
|
||||
static constexpr QHeaderView::ResizeMode HeaderResizeModes[BreakpointColumns::COLUMN_COUNT] =
|
||||
{
|
||||
static constexpr QHeaderView::ResizeMode HeaderResizeModes[BreakpointColumns::COLUMN_COUNT] = {
|
||||
QHeaderView::ResizeMode::ResizeToContents,
|
||||
QHeaderView::ResizeMode::ResizeToContents,
|
||||
QHeaderView::ResizeMode::ResizeToContents,
|
||||
@@ -45,7 +44,7 @@ public:
|
||||
QHeaderView::ResizeMode::ResizeToContents,
|
||||
};
|
||||
|
||||
explicit BreakpointModel(DebugInterface& cpu, QObject* parent = nullptr);
|
||||
static BreakpointModel* getInstance(DebugInterface& cpu);
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
@@ -63,6 +62,10 @@ public:
|
||||
void clear();
|
||||
|
||||
private:
|
||||
BreakpointModel(DebugInterface& cpu, QObject* parent = nullptr);
|
||||
|
||||
DebugInterface& m_cpu;
|
||||
std::vector<BreakpointMemcheck> m_breakpoints;
|
||||
|
||||
static std::map<BreakPointCpu, BreakpointModel*> s_instances;
|
||||
};
|
||||
176
pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp
Normal file
176
pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
// 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 <QtGui/QClipboard>
|
||||
|
||||
BreakpointWidget::BreakpointWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, DISALLOW_MULTIPLE_INSTANCES)
|
||||
, m_model(BreakpointModel::getInstance(cpu()))
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.breakpointList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &BreakpointWidget::openContextMenu);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointWidget::onDoubleClicked(const QModelIndex& index)
|
||||
{
|
||||
if (index.isValid() && index.column() == BreakpointModel::OFFSET)
|
||||
goToInDisassembler(m_model->data(index, BreakpointModel::DataRole).toUInt(), true);
|
||||
}
|
||||
|
||||
void BreakpointWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
QMenu* menu = new QMenu(m_ui.breakpointList);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (cpu().isAlive())
|
||||
{
|
||||
QAction* newAction = menu->addAction(tr("New"));
|
||||
connect(newAction, &QAction::triggered, this, &BreakpointWidget::contextNew);
|
||||
|
||||
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
|
||||
|
||||
if (selModel->hasSelection())
|
||||
{
|
||||
QAction* editAction = menu->addAction(tr("Edit"));
|
||||
connect(editAction, &QAction::triggered, this, &BreakpointWidget::contextEdit);
|
||||
|
||||
if (selModel->selectedIndexes().count() == 1)
|
||||
{
|
||||
QAction* copyAction = menu->addAction(tr("Copy"));
|
||||
connect(copyAction, &QAction::triggered, this, &BreakpointWidget::contextCopy);
|
||||
}
|
||||
|
||||
QAction* deleteAction = menu->addAction(tr("Delete"));
|
||||
connect(deleteAction, &QAction::triggered, this, &BreakpointWidget::contextDelete);
|
||||
}
|
||||
}
|
||||
|
||||
menu->addSeparator();
|
||||
if (m_model->rowCount() > 0)
|
||||
{
|
||||
QAction* actionExport = menu->addAction(tr("Copy all as CSV"));
|
||||
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_model, BreakpointModel::ExportRole, true));
|
||||
});
|
||||
}
|
||||
|
||||
if (cpu().isAlive())
|
||||
{
|
||||
QAction* actionImport = menu->addAction(tr("Paste from CSV"));
|
||||
connect(actionImport, &QAction::triggered, this, &BreakpointWidget::contextPasteCSV);
|
||||
|
||||
if (cpu().getCpuType() == BREAKPOINT_EE)
|
||||
{
|
||||
QAction* actionLoad = menu->addAction(tr("Load from Settings"));
|
||||
connect(actionLoad, &QAction::triggered, [this]() {
|
||||
m_model->clear();
|
||||
DebuggerSettingsManager::loadGameSettings(m_model);
|
||||
});
|
||||
|
||||
QAction* actionSave = menu->addAction(tr("Save to Settings"));
|
||||
connect(actionSave, &QAction::triggered, this, &BreakpointWidget::saveBreakpointsToDebuggerSettings);
|
||||
}
|
||||
}
|
||||
|
||||
menu->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->setAttribute(Qt::WA_DeleteOnClose);
|
||||
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->setAttribute(Qt::WA_DeleteOnClose);
|
||||
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);
|
||||
}
|
||||
41
pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h
Normal file
41
pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.h
Normal file
@@ -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 <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QTabBar>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
class BreakpointWidget : public DebuggerWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BreakpointWidget(const DebuggerWidgetParameters& parameters);
|
||||
|
||||
void onDoubleClicked(const QModelIndex& index);
|
||||
void openContextMenu(QPoint pos);
|
||||
|
||||
void contextCopy();
|
||||
void contextDelete();
|
||||
void contextNew();
|
||||
void contextEdit();
|
||||
void contextPasteCSV();
|
||||
|
||||
void saveBreakpointsToDebuggerSettings();
|
||||
|
||||
private:
|
||||
Ui::BreakpointWidget m_ui;
|
||||
|
||||
BreakpointModel* m_model;
|
||||
};
|
||||
39
pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui
Normal file
39
pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.ui
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BreakpointWidget</class>
|
||||
<widget class="QWidget" name="BreakpointWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="breakpointList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -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 <QtGui/QClipboard>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtCore/QFutureWatcher>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QRegularExpressionMatchIterator>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
|
||||
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<SavedAddressesModel*>(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<SavedAddressesModel*>(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);
|
||||
}
|
||||
@@ -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 <QtWidgets/QWidget>
|
||||
#include <QtWidgets/QTableWidget>
|
||||
#include <QtCore/QSortFilterProxyModel>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<QTableWidget*> 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;
|
||||
};
|
||||
@@ -1,445 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CpuWidget</class>
|
||||
<widget class="QWidget" name="CpuWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>668</width>
|
||||
<height>563</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="verticalSplitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QSplitter" name="horizontalSplitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QTabWidget" name="tabWidgetRegFunc">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tabRegisters">
|
||||
<attribute name="title">
|
||||
<string>Registers</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="registerLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="RegisterWidget" name="registerWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>320</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabFunctions">
|
||||
<attribute name="title">
|
||||
<string>Functions</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabMemorySearch">
|
||||
<attribute name="title">
|
||||
<string>Memory Search</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="memorySearchLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="MemorySearchWidget" name="memorySearchWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="DisassemblyWidget" name="disassemblyWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::North</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_memory">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<attribute name="title">
|
||||
<string>Memory</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="memoryLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="MemoryViewWidget" name="memoryviewWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_breakpoints">
|
||||
<attribute name="title">
|
||||
<string>Breakpoints</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="breakpointsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="breakpointList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="gridStyle">
|
||||
<enum>Qt::NoPen</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_threads">
|
||||
<attribute name="title">
|
||||
<string>Threads</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="threadsLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="threadList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="gridStyle">
|
||||
<enum>Qt::NoPen</enum>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_callstack">
|
||||
<attribute name="title">
|
||||
<string>Active Call Stack</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="callStackLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="stackList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="gridStyle">
|
||||
<enum>Qt::NoPen</enum>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_savedaddresses">
|
||||
<attribute name="title">
|
||||
<string>Saved Addresses</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="savedAddressesLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="savedAddressesList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="gridStyle">
|
||||
<enum>Qt::NoPen</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabGlobalVariables">
|
||||
<attribute name="title">
|
||||
<string>Globals</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabLocalVariables">
|
||||
<attribute name="title">
|
||||
<string>Locals</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabParameterVariables">
|
||||
<attribute name="title">
|
||||
<string>Parameters</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DisassemblyWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>pcsx2-qt/Debugger/DisassemblyWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RegisterWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>pcsx2-qt/Debugger/RegisterWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MemoryViewWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>pcsx2-qt/Debugger/MemoryViewWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MemorySearchWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>pcsx2-qt/Debugger/MemorySearchWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
58
pcsx2-qt/Debugger/DebuggerEvents.h
Normal file
58
pcsx2-qt/Debugger/DebuggerEvents.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common/Pcsx2Types.h>
|
||||
|
||||
#include <QtCore/qttranslation.h>
|
||||
|
||||
namespace DebuggerEvents
|
||||
{
|
||||
struct Event
|
||||
{
|
||||
virtual ~Event() = default;
|
||||
};
|
||||
|
||||
// Sent when a debugger widget is first created, and subsequently broadcast
|
||||
// to all debugger widgets at regular intervals.
|
||||
struct Refresh : Event
|
||||
{
|
||||
};
|
||||
|
||||
// Go to the address in a disassembly or memory view and switch to that tab.
|
||||
struct GoToAddress : Event
|
||||
{
|
||||
enum Filter
|
||||
{
|
||||
NONE,
|
||||
DISASSEMBLER,
|
||||
MEMORY_VIEW
|
||||
};
|
||||
|
||||
u32 address = 0;
|
||||
|
||||
// Prevent the memory view from handling events for jumping to functions
|
||||
// and vice versa.
|
||||
Filter filter = NONE;
|
||||
|
||||
bool switch_to_tab = true;
|
||||
|
||||
static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Go to in");
|
||||
};
|
||||
|
||||
// The state of the VM has changed and widgets should be updated to reflect
|
||||
// the new state (e.g. the VM has been paused).
|
||||
struct VMUpdate : Event
|
||||
{
|
||||
};
|
||||
|
||||
// Add the address to the saved addresses list and switch to that tab.
|
||||
struct AddToSavedAddresses : Event
|
||||
{
|
||||
u32 address = 0;
|
||||
bool switch_to_tab = true;
|
||||
|
||||
static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Add to");
|
||||
};
|
||||
} // namespace DebuggerEvents
|
||||
@@ -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<std::mutex> lock(writeLock);
|
||||
QJsonObject loadedSettings = loadGameSettingsJSON();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "Models/BreakpointModel.h"
|
||||
#include "Models/SavedAddressesModel.h"
|
||||
#include "Breakpoints/BreakpointModel.h"
|
||||
#include "Memory/SavedAddressesModel.h"
|
||||
|
||||
class DebuggerSettingsManager final
|
||||
{
|
||||
|
||||
312
pcsx2-qt/Debugger/DebuggerWidget.cpp
Normal file
312
pcsx2-qt/Debugger/DebuggerWidget.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DebuggerWidget.h"
|
||||
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
|
||||
DebuggerWidget::DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags)
|
||||
: QWidget(parameters.parent)
|
||||
, m_unique_name(parameters.unique_name)
|
||||
, m_cpu(parameters.cpu)
|
||||
, m_cpu_override(parameters.cpu_override)
|
||||
, m_flags(flags)
|
||||
{
|
||||
updateStyleSheet();
|
||||
}
|
||||
|
||||
DebugInterface& DebuggerWidget::cpu() const
|
||||
{
|
||||
if (m_cpu_override.has_value())
|
||||
return DebugInterface::get(*m_cpu_override);
|
||||
|
||||
pxAssertRel(m_cpu, "DebuggerWidget::cpu called on object with null cpu.");
|
||||
return *m_cpu;
|
||||
}
|
||||
|
||||
QString DebuggerWidget::uniqueName() const
|
||||
{
|
||||
return m_unique_name;
|
||||
}
|
||||
|
||||
QString DebuggerWidget::displayName() const
|
||||
{
|
||||
QString name = displayNameWithoutSuffix();
|
||||
|
||||
// If there are multiple debugger widgets of the same name, append a number
|
||||
// to the display name.
|
||||
if (m_display_name_suffix_number.has_value())
|
||||
name = tr("%1 #%2").arg(name).arg(*m_display_name_suffix_number);
|
||||
|
||||
if (m_cpu_override)
|
||||
name = tr("%1 (%2)").arg(name).arg(DebugInterface::cpuName(*m_cpu_override));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
QString DebuggerWidget::displayNameWithoutSuffix() const
|
||||
{
|
||||
return m_translated_display_name;
|
||||
}
|
||||
|
||||
QString DebuggerWidget::customDisplayName() const
|
||||
{
|
||||
return m_custom_display_name;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::setCustomDisplayName(QString display_name)
|
||||
{
|
||||
if (display_name.size() > DockUtils::MAX_DOCK_WIDGET_NAME_SIZE)
|
||||
return false;
|
||||
|
||||
m_custom_display_name = display_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::isPrimary() const
|
||||
{
|
||||
return m_is_primary;
|
||||
}
|
||||
|
||||
void DebuggerWidget::setPrimary(bool is_primary)
|
||||
{
|
||||
m_is_primary = is_primary;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::setCpu(DebugInterface& new_cpu)
|
||||
{
|
||||
BreakPointCpu before = cpu().getCpuType();
|
||||
m_cpu = &new_cpu;
|
||||
BreakPointCpu after = cpu().getCpuType();
|
||||
return before == after;
|
||||
}
|
||||
|
||||
std::optional<BreakPointCpu> DebuggerWidget::cpuOverride() const
|
||||
{
|
||||
return m_cpu_override;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::setCpuOverride(std::optional<BreakPointCpu> new_cpu)
|
||||
{
|
||||
BreakPointCpu before = cpu().getCpuType();
|
||||
m_cpu_override = new_cpu;
|
||||
BreakPointCpu after = cpu().getCpuType();
|
||||
return before == after;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::handleEvent(const DebuggerEvents::Event& event)
|
||||
{
|
||||
auto [begin, end] = m_event_handlers.equal_range(typeid(event).name());
|
||||
for (auto handler = begin; handler != end; handler++)
|
||||
if (handler->second(event))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DebuggerWidget::acceptsEventType(const char* event_type)
|
||||
{
|
||||
auto [begin, end] = m_event_handlers.equal_range(event_type);
|
||||
return begin != end;
|
||||
}
|
||||
|
||||
|
||||
void DebuggerWidget::toJson(JsonValueWrapper& json)
|
||||
{
|
||||
std::string custom_display_name_str = m_custom_display_name.toStdString();
|
||||
rapidjson::Value custom_display_name;
|
||||
custom_display_name.SetString(custom_display_name_str.c_str(), custom_display_name_str.size(), json.allocator());
|
||||
json.value().AddMember("customDisplayName", custom_display_name, json.allocator());
|
||||
|
||||
json.value().AddMember("isPrimary", m_is_primary, json.allocator());
|
||||
}
|
||||
|
||||
bool DebuggerWidget::fromJson(const JsonValueWrapper& json)
|
||||
{
|
||||
auto custom_display_name = json.value().FindMember("customDisplayName");
|
||||
if (custom_display_name != json.value().MemberEnd() && custom_display_name->value.IsString())
|
||||
{
|
||||
m_custom_display_name = QString(custom_display_name->value.GetString());
|
||||
m_custom_display_name.truncate(DockUtils::MAX_DOCK_WIDGET_NAME_SIZE);
|
||||
}
|
||||
|
||||
auto is_primary = json.value().FindMember("isPrimary");
|
||||
if (is_primary != json.value().MemberEnd() && is_primary->value.IsBool())
|
||||
m_is_primary = is_primary->value.GetBool();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebuggerWidget::switchToThisTab()
|
||||
{
|
||||
g_debugger_window->dockManager().switchToDebuggerWidget(this);
|
||||
}
|
||||
|
||||
bool DebuggerWidget::supportsMultipleInstances()
|
||||
{
|
||||
return !(m_flags & DISALLOW_MULTIPLE_INSTANCES);
|
||||
}
|
||||
|
||||
void DebuggerWidget::retranslateDisplayName()
|
||||
{
|
||||
if (!m_custom_display_name.isEmpty())
|
||||
{
|
||||
m_translated_display_name = m_custom_display_name;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto description = DockTables::DEBUGGER_WIDGETS.find(metaObject()->className());
|
||||
if (description != DockTables::DEBUGGER_WIDGETS.end())
|
||||
m_translated_display_name = QCoreApplication::translate("DebuggerWidget", description->second.display_name);
|
||||
else
|
||||
m_translated_display_name = QString();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> DebuggerWidget::displayNameSuffixNumber() const
|
||||
{
|
||||
return m_display_name_suffix_number;
|
||||
}
|
||||
|
||||
void DebuggerWidget::setDisplayNameSuffixNumber(std::optional<int> suffix_number)
|
||||
{
|
||||
m_display_name_suffix_number = suffix_number;
|
||||
}
|
||||
|
||||
void DebuggerWidget::updateStyleSheet()
|
||||
{
|
||||
QString stylesheet;
|
||||
|
||||
if (m_flags & MONOSPACE_FONT)
|
||||
{
|
||||
// Easiest way to handle cross platform monospace fonts
|
||||
// There are issues related to TabWidget -> Children font inheritance otherwise
|
||||
#if defined(WIN32)
|
||||
stylesheet += QStringLiteral("font-family: 'Lucida Console';");
|
||||
#elif defined(__APPLE__)
|
||||
stylesheet += QStringLiteral("font-family: 'Monaco';");
|
||||
#else
|
||||
stylesheet += QStringLiteral("font-family: 'Monospace';");
|
||||
#endif
|
||||
}
|
||||
|
||||
// HACK: Make the font size smaller without applying a stylesheet to the
|
||||
// whole window (which would impact performance).
|
||||
if (g_debugger_window)
|
||||
stylesheet += QString("font-size: %1pt;").arg(g_debugger_window->fontSize());
|
||||
|
||||
setStyleSheet(stylesheet);
|
||||
}
|
||||
|
||||
void DebuggerWidget::goToInDisassembler(u32 address, bool switch_to_tab)
|
||||
{
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = address;
|
||||
event.filter = DebuggerEvents::GoToAddress::DISASSEMBLER;
|
||||
event.switch_to_tab = switch_to_tab;
|
||||
DebuggerWidget::sendEvent(std::move(event));
|
||||
}
|
||||
|
||||
void DebuggerWidget::goToInMemoryView(u32 address, bool switch_to_tab)
|
||||
{
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = address;
|
||||
event.filter = DebuggerEvents::GoToAddress::MEMORY_VIEW;
|
||||
event.switch_to_tab = switch_to_tab;
|
||||
DebuggerWidget::sendEvent(std::move(event));
|
||||
}
|
||||
|
||||
void DebuggerWidget::sendEventImplementation(const DebuggerEvents::Event& event)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
|
||||
if (widget->isPrimary() && widget->handleEvent(event))
|
||||
return;
|
||||
|
||||
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
|
||||
if (!widget->isPrimary() && widget->handleEvent(event))
|
||||
return;
|
||||
}
|
||||
|
||||
void DebuggerWidget::broadcastEventImplementation(const DebuggerEvents::Event& event)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
|
||||
widget->handleEvent(event);
|
||||
}
|
||||
|
||||
std::vector<QAction*> DebuggerWidget::createEventActionsImplementation(
|
||||
QMenu* menu,
|
||||
u32 max_top_level_actions,
|
||||
bool skip_self,
|
||||
const char* event_type,
|
||||
const char* action_prefix,
|
||||
std::function<const DebuggerEvents::Event*()> event_func)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return {};
|
||||
|
||||
std::vector<DebuggerWidget*> receivers;
|
||||
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
|
||||
if ((!skip_self || widget != this) && widget->acceptsEventType(event_type))
|
||||
receivers.emplace_back(widget);
|
||||
|
||||
std::sort(receivers.begin(), receivers.end(), [&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) {
|
||||
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
|
||||
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
|
||||
|
||||
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
|
||||
});
|
||||
|
||||
QMenu* submenu = nullptr;
|
||||
if (receivers.size() > max_top_level_actions)
|
||||
{
|
||||
QString title_format = QCoreApplication::translate("DebuggerEvent", "%1...");
|
||||
submenu = new QMenu(title_format.arg(QCoreApplication::translate("DebuggerEvent", action_prefix)), menu);
|
||||
}
|
||||
|
||||
std::vector<QAction*> actions;
|
||||
for (size_t i = 0; i < receivers.size(); i++)
|
||||
{
|
||||
DebuggerWidget* receiver = receivers[i];
|
||||
|
||||
QAction* action;
|
||||
if (!submenu || i + 1 < max_top_level_actions)
|
||||
{
|
||||
QString title_format = QCoreApplication::translate("DebuggerEvent", "%1 %2");
|
||||
QString event_title = QCoreApplication::translate("DebuggerEvent", action_prefix);
|
||||
QString title = title_format.arg(event_title).arg(receiver->displayName());
|
||||
action = new QAction(title, menu);
|
||||
menu->addAction(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
action = new QAction(receiver->displayName(), submenu);
|
||||
submenu->addAction(action);
|
||||
}
|
||||
|
||||
connect(action, &QAction::triggered, receiver, [receiver, event_func]() {
|
||||
const DebuggerEvents::Event* event = event_func();
|
||||
if (event)
|
||||
receiver->handleEvent(*event);
|
||||
});
|
||||
|
||||
actions.emplace_back(action);
|
||||
}
|
||||
|
||||
if (submenu)
|
||||
menu->addMenu(submenu);
|
||||
|
||||
return actions;
|
||||
}
|
||||
198
pcsx2-qt/Debugger/DebuggerWidget.h
Normal file
198
pcsx2-qt/Debugger/DebuggerWidget.h
Normal file
@@ -0,0 +1,198 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QtHost.h"
|
||||
#include "Debugger/DebuggerEvents.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class JsonValueWrapper;
|
||||
|
||||
// Container for variables to be passed to the constructor of DebuggerWidget.
|
||||
struct DebuggerWidgetParameters
|
||||
{
|
||||
QString unique_name;
|
||||
DebugInterface* cpu = nullptr;
|
||||
std::optional<BreakPointCpu> cpu_override;
|
||||
QWidget* parent = nullptr;
|
||||
};
|
||||
|
||||
// The base class for the contents of the dock widgets in the debugger.
|
||||
class DebuggerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QString uniqueName() const;
|
||||
|
||||
// Get the translated name that should be displayed for this widget.
|
||||
QString displayName() const;
|
||||
QString displayNameWithoutSuffix() const;
|
||||
|
||||
QString customDisplayName() const;
|
||||
bool setCustomDisplayName(QString display_name);
|
||||
|
||||
bool isPrimary() const;
|
||||
void setPrimary(bool is_primary);
|
||||
|
||||
// Get the effective debug interface associated with this particular widget
|
||||
// if it's set, otherwise return the one associated with the layout that
|
||||
// contains this widget.
|
||||
DebugInterface& cpu() const;
|
||||
|
||||
// Set the debug interface associated with the layout. If false is returned,
|
||||
// we have to recreate the object.
|
||||
bool setCpu(DebugInterface& new_cpu);
|
||||
|
||||
// Get the CPU associated with this particular widget.
|
||||
std::optional<BreakPointCpu> cpuOverride() const;
|
||||
|
||||
// Set the CPU associated with the individual dock widget. If false is
|
||||
// returned, we have to recreate the object.
|
||||
bool setCpuOverride(std::optional<BreakPointCpu> new_cpu);
|
||||
|
||||
// Send each open debugger widget an event in turn, until one handles it.
|
||||
template <typename Event>
|
||||
static void sendEvent(Event event)
|
||||
{
|
||||
if (!QtHost::IsOnUIThread())
|
||||
{
|
||||
QtHost::RunOnUIThread([event = std::move(event)]() {
|
||||
DebuggerWidget::sendEventImplementation(event);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sendEventImplementation(event);
|
||||
}
|
||||
|
||||
// Send all open debugger widgets an event.
|
||||
template <typename Event>
|
||||
static void broadcastEvent(Event event)
|
||||
{
|
||||
if (!QtHost::IsOnUIThread())
|
||||
{
|
||||
QtHost::RunOnUIThread([event = std::move(event)]() {
|
||||
DebuggerWidget::broadcastEventImplementation(event);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
broadcastEventImplementation(event);
|
||||
}
|
||||
|
||||
// Register a handler callback for the specified type of event.
|
||||
template <typename Event>
|
||||
void receiveEvent(std::function<bool(const Event&)> callback)
|
||||
{
|
||||
m_event_handlers.emplace(
|
||||
typeid(Event).name(),
|
||||
[callback](const DebuggerEvents::Event& event) -> bool {
|
||||
return callback(static_cast<const Event&>(event));
|
||||
});
|
||||
}
|
||||
|
||||
// Register a handler member function for the specified type of event.
|
||||
template <typename Event, typename SubClass>
|
||||
void receiveEvent(bool (SubClass::*function)(const Event& event))
|
||||
{
|
||||
m_event_handlers.emplace(
|
||||
typeid(Event).name(),
|
||||
[this, function](const DebuggerEvents::Event& event) -> bool {
|
||||
return (*static_cast<SubClass*>(this).*function)(static_cast<const Event&>(event));
|
||||
});
|
||||
}
|
||||
|
||||
// Call the handler callback for the specified event.
|
||||
bool handleEvent(const DebuggerEvents::Event& event);
|
||||
|
||||
// Check if this debugger widget can receive the specified type of event.
|
||||
bool acceptsEventType(const char* event_type);
|
||||
|
||||
// Generates context menu actions to send an event to each debugger widget
|
||||
// that can receive it. A submenu is generated if the number of possible
|
||||
// receivers exceeds max_top_level_actions. If skip_self is true, actions
|
||||
// are only generated if the sender and receiver aren't the same object.
|
||||
template <typename Event>
|
||||
std::vector<QAction*> createEventActions(
|
||||
QMenu* menu,
|
||||
std::function<std::optional<Event>()> event_func,
|
||||
bool skip_self = true,
|
||||
u32 max_top_level_actions = 5)
|
||||
{
|
||||
return createEventActionsImplementation(
|
||||
menu, max_top_level_actions, skip_self, typeid(Event).name(), Event::ACTION_PREFIX,
|
||||
[event_func]() -> DebuggerEvents::Event* {
|
||||
static std::optional<Event> event;
|
||||
event = event_func();
|
||||
if (!event.has_value())
|
||||
return nullptr;
|
||||
|
||||
return static_cast<DebuggerEvents::Event*>(&(*event));
|
||||
});
|
||||
}
|
||||
|
||||
virtual void toJson(JsonValueWrapper& json);
|
||||
virtual bool fromJson(const JsonValueWrapper& json);
|
||||
|
||||
void switchToThisTab();
|
||||
|
||||
bool supportsMultipleInstances();
|
||||
|
||||
void retranslateDisplayName();
|
||||
|
||||
std::optional<int> displayNameSuffixNumber() const;
|
||||
void setDisplayNameSuffixNumber(std::optional<int> suffix_number);
|
||||
|
||||
void updateStyleSheet();
|
||||
|
||||
static void goToInDisassembler(u32 address, bool switch_to_tab);
|
||||
static void goToInMemoryView(u32 address, bool switch_to_tab);
|
||||
|
||||
protected:
|
||||
enum Flags
|
||||
{
|
||||
NO_DEBUGGER_FLAGS = 0,
|
||||
// Prevent the user from opening multiple dock widgets of this type.
|
||||
DISALLOW_MULTIPLE_INSTANCES = 1 << 0,
|
||||
// Apply a stylesheet that gives all the text a monospace font.
|
||||
MONOSPACE_FONT = 1 << 1
|
||||
};
|
||||
|
||||
DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags);
|
||||
|
||||
private:
|
||||
static void sendEventImplementation(const DebuggerEvents::Event& event);
|
||||
static void broadcastEventImplementation(const DebuggerEvents::Event& event);
|
||||
|
||||
std::vector<QAction*> createEventActionsImplementation(
|
||||
QMenu* menu,
|
||||
u32 max_top_level_actions,
|
||||
bool skip_self,
|
||||
const char* event_type,
|
||||
const char* action_prefix,
|
||||
std::function<const DebuggerEvents::Event*()> event_func);
|
||||
|
||||
QString m_unique_name;
|
||||
|
||||
// A user-defined name, or an empty string if no name was specified so that
|
||||
// the default names can be retranslated on the fly.
|
||||
QString m_custom_display_name;
|
||||
|
||||
QString m_translated_display_name;
|
||||
std::optional<int> m_display_name_suffix_number;
|
||||
|
||||
// Primary debugger widgets will be chosen to handle events first. For
|
||||
// example, clicking on an address to go to it in the primary memory view.
|
||||
bool m_is_primary = false;
|
||||
|
||||
DebugInterface* m_cpu;
|
||||
std::optional<BreakPointCpu> m_cpu_override;
|
||||
u32 m_flags;
|
||||
|
||||
std::multimap<std::string, std::function<bool(const DebuggerEvents::Event&)>> m_event_handlers;
|
||||
};
|
||||
@@ -3,109 +3,366 @@
|
||||
|
||||
#include "DebuggerWindow.h"
|
||||
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/Breakpoints.h"
|
||||
#include "DebugTools/MIPSAnalyst.h"
|
||||
#include "DebugTools/MipsStackWalk.h"
|
||||
#include "DebugTools/SymbolImporter.h"
|
||||
#include "VMManager.h"
|
||||
#include "QtHost.h"
|
||||
#include "MainWindow.h"
|
||||
#include "AnalysisOptionsDialog.h"
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
DebuggerWindow* g_debugger_window = nullptr;
|
||||
|
||||
DebuggerWindow::DebuggerWindow(QWidget* parent)
|
||||
: QMainWindow(parent)
|
||||
: KDDockWidgets::QtWidgets::MainWindow(QStringLiteral("DebuggerWindow"), {}, parent)
|
||||
, m_dock_manager(new DockManager(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
|
||||
g_debugger_window = this;
|
||||
|
||||
setupDefaultToolBarState();
|
||||
setupFonts();
|
||||
restoreWindowGeometry();
|
||||
|
||||
m_dock_manager->loadLayouts();
|
||||
|
||||
connect(m_ui.actionAnalyse, &QAction::triggered, this, &DebuggerWindow::onAnalyse);
|
||||
connect(m_ui.actionSettings, &QAction::triggered, this, &DebuggerWindow::onSettings);
|
||||
connect(m_ui.actionGameSettings, &QAction::triggered, this, &DebuggerWindow::onGameSettings);
|
||||
connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close);
|
||||
|
||||
connect(m_ui.actionOnTop, &QAction::triggered, this, [this](bool checked) {
|
||||
if (checked)
|
||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
else
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
|
||||
show();
|
||||
});
|
||||
|
||||
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);
|
||||
connect(m_ui.actionStepOut, &QAction::triggered, this, &DebuggerWindow::onStepOut);
|
||||
connect(m_ui.actionAnalyse, &QAction::triggered, this, &DebuggerWindow::onAnalyse);
|
||||
connect(m_ui.actionOnTop, &QAction::triggered, [this] { this->setWindowFlags(this->windowFlags() ^ Qt::WindowStaysOnTopHint); this->show(); });
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMStateChanged);
|
||||
connect(g_emu_thread, &EmuThread::onVMResumed, this, &DebuggerWindow::onVMStateChanged);
|
||||
connect(m_ui.actionShutDown, &QAction::triggered, [this]() {
|
||||
if (currentCPU() && currentCPU()->isAlive())
|
||||
g_emu_thread->shutdownVM(false);
|
||||
});
|
||||
|
||||
onVMStateChanged(); // If we missed a state change while we weren't loaded
|
||||
connect(m_ui.actionReset, &QAction::triggered, [this]() {
|
||||
if (currentCPU() && currentCPU()->isAlive())
|
||||
g_emu_thread->resetVM();
|
||||
});
|
||||
|
||||
// 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);
|
||||
connect(m_ui.menuTools, &QMenu::aboutToShow, this, [this]() {
|
||||
m_dock_manager->createToolsMenu(m_ui.menuTools);
|
||||
});
|
||||
|
||||
m_cpuWidget_r5900 = new CpuWidget(this, r5900Debug);
|
||||
m_cpuWidget_r3000 = new CpuWidget(this, r3000Debug);
|
||||
connect(m_ui.menuWindows, &QMenu::aboutToShow, this, [this]() {
|
||||
m_dock_manager->createWindowsMenu(m_ui.menuWindows);
|
||||
});
|
||||
|
||||
m_ui.cpuTabs->addTab(m_cpuWidget_r5900, "R5900");
|
||||
m_ui.cpuTabs->addTab(m_cpuWidget_r3000, "R3000");
|
||||
}
|
||||
connect(m_ui.actionResetAllLayouts, &QAction::triggered, this, [this]() {
|
||||
QString text = tr("Are you sure you want to reset all layouts?");
|
||||
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
DebuggerWindow::~DebuggerWindow() = default;
|
||||
m_dock_manager->resetAllLayouts();
|
||||
});
|
||||
|
||||
// There is no straightforward way to set the tab text to bold in Qt
|
||||
// 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());
|
||||
}
|
||||
connect(m_ui.actionResetDefaultLayouts, &QAction::triggered, this, [this]() {
|
||||
QString text = tr("Are you sure you want to reset the default layouts?");
|
||||
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
void DebuggerWindow::onVMStateChanged()
|
||||
{
|
||||
if (!QtHost::IsVMPaused())
|
||||
m_dock_manager->resetDefaultLayouts();
|
||||
});
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onVMPaused, this, []() {
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
});
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onVMStarting, this, &DebuggerWindow::onVMStarting);
|
||||
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMPaused);
|
||||
connect(g_emu_thread, &EmuThread::onVMResumed, this, &DebuggerWindow::onVMResumed);
|
||||
connect(g_emu_thread, &EmuThread::onVMStopped, this, &DebuggerWindow::onVMStopped);
|
||||
|
||||
if (QtHost::IsVMValid())
|
||||
{
|
||||
m_ui.actionRun->setText(tr("Pause"));
|
||||
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("pause-line")));
|
||||
m_ui.actionStepInto->setEnabled(false);
|
||||
m_ui.actionStepOver->setEnabled(false);
|
||||
m_ui.actionStepOut->setEnabled(false);
|
||||
setTabActiveStyle(BREAKPOINT_IOP_AND_EE);
|
||||
onVMStarting();
|
||||
|
||||
if (QtHost::IsVMPaused())
|
||||
onVMPaused();
|
||||
else
|
||||
onVMResumed();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.actionRun->setText(tr("Run"));
|
||||
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("play-line")));
|
||||
m_ui.actionStepInto->setEnabled(true);
|
||||
m_ui.actionStepOver->setEnabled(true);
|
||||
m_ui.actionStepOut->setEnabled(true);
|
||||
// Switch to the CPU tab that triggered the breakpoint
|
||||
// Also bold the tab text to indicate that a breakpoint was triggered
|
||||
if (CBreakPoints::GetBreakpointTriggered())
|
||||
{
|
||||
const BreakPointCpu triggeredCpu = CBreakPoints::GetBreakpointTriggeredCpu();
|
||||
setTabActiveStyle(triggeredCpu);
|
||||
switch (triggeredCpu)
|
||||
{
|
||||
case BREAKPOINT_EE:
|
||||
m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r5900);
|
||||
break;
|
||||
case BREAKPOINT_IOP:
|
||||
m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r3000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Host::RunOnCPUThread([] {
|
||||
CBreakPoints::ClearTemporaryBreakPoints();
|
||||
CBreakPoints::SetBreakpointTriggered(false, BREAKPOINT_IOP_AND_EE);
|
||||
// Our current PC is on a breakpoint.
|
||||
// When we run the core again, we want to skip this breakpoint and run
|
||||
CBreakPoints::SetSkipFirst(BREAKPOINT_EE, r5900Debug.getPC());
|
||||
CBreakPoints::SetSkipFirst(BREAKPOINT_IOP, r3000Debug.getPC());
|
||||
});
|
||||
}
|
||||
onVMStopped();
|
||||
}
|
||||
return;
|
||||
|
||||
m_dock_manager->switchToLayout(0);
|
||||
|
||||
QMenuBar* menu_bar = menuBar();
|
||||
|
||||
setMenuWidget(m_dock_manager->createLayoutSwitcher(menu_bar));
|
||||
|
||||
Host::RunOnCPUThread([]() {
|
||||
R5900SymbolImporter.OnDebuggerOpened();
|
||||
});
|
||||
|
||||
QTimer* refresh_timer = new QTimer(this);
|
||||
connect(refresh_timer, &QTimer::timeout, this, []() {
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::Refresh());
|
||||
});
|
||||
refresh_timer->start(1000);
|
||||
}
|
||||
|
||||
DebuggerWindow* DebuggerWindow::getInstance()
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
createInstance();
|
||||
|
||||
return g_debugger_window;
|
||||
}
|
||||
|
||||
DebuggerWindow* DebuggerWindow::createInstance()
|
||||
{
|
||||
// Setup KDDockWidgets.
|
||||
DockManager::configureDockingSystem();
|
||||
|
||||
if (g_debugger_window)
|
||||
destroyInstance();
|
||||
|
||||
return new DebuggerWindow(nullptr);
|
||||
}
|
||||
|
||||
void DebuggerWindow::destroyInstance()
|
||||
{
|
||||
if (g_debugger_window)
|
||||
g_debugger_window->close();
|
||||
}
|
||||
|
||||
bool DebuggerWindow::shouldShowOnStartup()
|
||||
{
|
||||
return Host::GetBaseBoolSettingValue("Debugger/UserInterface", "ShowOnStartup", false);
|
||||
}
|
||||
|
||||
DockManager& DebuggerWindow::dockManager()
|
||||
{
|
||||
return *m_dock_manager;
|
||||
}
|
||||
|
||||
void DebuggerWindow::setupDefaultToolBarState()
|
||||
{
|
||||
// Hiding all the toolbars lets us save the default state of the window with
|
||||
// all the toolbars hidden. The DockManager will show the appropriate ones
|
||||
// later anyway.
|
||||
for (QToolBar* toolbar : findChildren<QToolBar*>())
|
||||
toolbar->hide();
|
||||
|
||||
m_default_toolbar_state = saveState();
|
||||
|
||||
for (QToolBar* toolbar : findChildren<QToolBar*>())
|
||||
connect(toolbar, &QToolBar::topLevelChanged, m_dock_manager, &DockManager::updateToolBarLockState);
|
||||
}
|
||||
|
||||
void DebuggerWindow::clearToolBarState()
|
||||
{
|
||||
restoreState(m_default_toolbar_state);
|
||||
}
|
||||
|
||||
void DebuggerWindow::setupFonts()
|
||||
{
|
||||
m_font_size = Host::GetBaseIntSettingValue("Debugger/UserInterface", "FontSize", DEFAULT_FONT_SIZE);
|
||||
if (m_font_size < MINIMUM_FONT_SIZE || m_font_size > MAXIMUM_FONT_SIZE)
|
||||
m_font_size = DEFAULT_FONT_SIZE;
|
||||
|
||||
m_ui.actionIncreaseFontSize->setShortcuts(QKeySequence::ZoomIn);
|
||||
connect(m_ui.actionIncreaseFontSize, &QAction::triggered, this, [this]() {
|
||||
if (m_font_size >= MAXIMUM_FONT_SIZE)
|
||||
return;
|
||||
|
||||
m_font_size++;
|
||||
|
||||
updateFontActions();
|
||||
updateStyleSheets();
|
||||
saveFontSize();
|
||||
});
|
||||
|
||||
m_ui.actionDecreaseFontSize->setShortcut(QKeySequence::ZoomOut);
|
||||
connect(m_ui.actionDecreaseFontSize, &QAction::triggered, this, [this]() {
|
||||
if (m_font_size <= MINIMUM_FONT_SIZE)
|
||||
return;
|
||||
|
||||
m_font_size--;
|
||||
|
||||
updateFontActions();
|
||||
updateStyleSheets();
|
||||
saveFontSize();
|
||||
});
|
||||
|
||||
connect(m_ui.actionResetFontSize, &QAction::triggered, this, [this]() {
|
||||
m_font_size = DEFAULT_FONT_SIZE;
|
||||
|
||||
updateFontActions();
|
||||
updateStyleSheets();
|
||||
saveFontSize();
|
||||
});
|
||||
|
||||
updateFontActions();
|
||||
updateStyleSheets();
|
||||
}
|
||||
|
||||
void DebuggerWindow::updateFontActions()
|
||||
{
|
||||
m_ui.actionIncreaseFontSize->setEnabled(m_font_size < MAXIMUM_FONT_SIZE);
|
||||
m_ui.actionDecreaseFontSize->setEnabled(m_font_size > MINIMUM_FONT_SIZE);
|
||||
m_ui.actionResetFontSize->setEnabled(m_font_size != DEFAULT_FONT_SIZE);
|
||||
}
|
||||
|
||||
void DebuggerWindow::saveFontSize()
|
||||
{
|
||||
Host::SetBaseIntSettingValue("Debugger/UserInterface", "FontSize", m_font_size);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
|
||||
int DebuggerWindow::fontSize()
|
||||
{
|
||||
return m_font_size;
|
||||
}
|
||||
|
||||
void DebuggerWindow::updateStyleSheets()
|
||||
{
|
||||
// TODO: Migrate away from stylesheets to improve performance.
|
||||
if (m_font_size != DEFAULT_FONT_SIZE)
|
||||
{
|
||||
int size = m_font_size + QApplication::font().pointSize() - DEFAULT_FONT_SIZE;
|
||||
setStyleSheet(QString("* { font-size: %1pt; } QTabBar { font-size: %2pt; }").arg(size).arg(size + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
setStyleSheet(QString());
|
||||
}
|
||||
|
||||
dockManager().updateStyleSheets();
|
||||
}
|
||||
|
||||
void DebuggerWindow::saveWindowGeometry()
|
||||
{
|
||||
std::string old_geometry = Host::GetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry");
|
||||
|
||||
std::string geometry;
|
||||
if (shouldSaveWindowGeometry())
|
||||
geometry = saveGeometry().toBase64().toStdString();
|
||||
|
||||
if (geometry != old_geometry)
|
||||
{
|
||||
Host::SetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry", geometry.c_str());
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow::restoreWindowGeometry()
|
||||
{
|
||||
if (!shouldSaveWindowGeometry())
|
||||
return;
|
||||
|
||||
std::string geometry = Host::GetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry");
|
||||
restoreGeometry(QByteArray::fromBase64(QByteArray::fromStdString(geometry)));
|
||||
}
|
||||
|
||||
bool DebuggerWindow::shouldSaveWindowGeometry()
|
||||
{
|
||||
return Host::GetBaseBoolSettingValue("Debugger/UserInterface", "SaveWindowGeometry", true);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onVMStarting()
|
||||
{
|
||||
m_ui.actionRun->setEnabled(true);
|
||||
m_ui.actionStepInto->setEnabled(true);
|
||||
m_ui.actionStepOver->setEnabled(true);
|
||||
m_ui.actionStepOut->setEnabled(true);
|
||||
|
||||
m_ui.actionAnalyse->setEnabled(true);
|
||||
m_ui.actionGameSettings->setEnabled(true);
|
||||
|
||||
m_ui.actionShutDown->setEnabled(true);
|
||||
m_ui.actionReset->setEnabled(true);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onVMPaused()
|
||||
{
|
||||
m_ui.actionRun->setText(tr("Run"));
|
||||
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("play-line")));
|
||||
m_ui.actionStepInto->setEnabled(true);
|
||||
m_ui.actionStepOver->setEnabled(true);
|
||||
m_ui.actionStepOut->setEnabled(true);
|
||||
|
||||
// Switch to the CPU tab that triggered the breakpoint.
|
||||
// Also blink the tab text to indicate that a breakpoint was triggered.
|
||||
if (CBreakPoints::GetBreakpointTriggered())
|
||||
{
|
||||
const BreakPointCpu triggeredCpu = CBreakPoints::GetBreakpointTriggeredCpu();
|
||||
m_dock_manager->switchToLayoutWithCPU(triggeredCpu, true);
|
||||
|
||||
Host::RunOnCPUThread([] {
|
||||
CBreakPoints::ClearTemporaryBreakPoints();
|
||||
CBreakPoints::SetBreakpointTriggered(false, BREAKPOINT_IOP_AND_EE);
|
||||
|
||||
// Our current PC is on a breakpoint.
|
||||
// When we run the core again, we want to skip this breakpoint and run.
|
||||
CBreakPoints::SetSkipFirst(BREAKPOINT_EE, r5900Debug.getPC());
|
||||
CBreakPoints::SetSkipFirst(BREAKPOINT_IOP, r3000Debug.getPC());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow::onVMResumed()
|
||||
{
|
||||
m_ui.actionRun->setText(tr("Pause"));
|
||||
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("pause-line")));
|
||||
m_ui.actionStepInto->setEnabled(false);
|
||||
m_ui.actionStepOver->setEnabled(false);
|
||||
m_ui.actionStepOut->setEnabled(false);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onVMStopped()
|
||||
{
|
||||
m_ui.actionRun->setEnabled(false);
|
||||
m_ui.actionStepInto->setEnabled(false);
|
||||
m_ui.actionStepOver->setEnabled(false);
|
||||
m_ui.actionStepOut->setEnabled(false);
|
||||
|
||||
m_ui.actionAnalyse->setEnabled(false);
|
||||
m_ui.actionGameSettings->setEnabled(false);
|
||||
|
||||
m_ui.actionShutDown->setEnabled(false);
|
||||
m_ui.actionReset->setEnabled(false);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onAnalyse()
|
||||
{
|
||||
AnalysisOptionsDialog* dialog = new AnalysisOptionsDialog(this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
void DebuggerWindow::onSettings()
|
||||
{
|
||||
g_main_window->doSettings("Debug");
|
||||
}
|
||||
|
||||
void DebuggerWindow::onGameSettings()
|
||||
{
|
||||
g_main_window->doGameSettings("Debug");
|
||||
}
|
||||
|
||||
void DebuggerWindow::onRunPause()
|
||||
@@ -115,40 +372,162 @@ void DebuggerWindow::onRunPause()
|
||||
|
||||
void DebuggerWindow::onStepInto()
|
||||
{
|
||||
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
|
||||
currentCpu->onStepInto();
|
||||
DebugInterface* cpu = currentCPU();
|
||||
if (!cpu)
|
||||
return;
|
||||
|
||||
if (!cpu->isAlive() || !cpu->isCpuPaused())
|
||||
return;
|
||||
|
||||
// Allow the cpu to skip this pc if it is a breakpoint
|
||||
CBreakPoints::SetSkipFirst(cpu->getCpuType(), cpu->getPC());
|
||||
|
||||
const u32 pc = cpu->getPC();
|
||||
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(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, bpAddr] {
|
||||
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
|
||||
cpu->resumeCpu();
|
||||
});
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DebuggerWindow::onStepOver()
|
||||
{
|
||||
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
|
||||
currentCpu->onStepOver();
|
||||
DebugInterface* cpu = currentCPU();
|
||||
if (!cpu)
|
||||
return;
|
||||
|
||||
if (!cpu->isAlive() || !cpu->isCpuPaused())
|
||||
return;
|
||||
|
||||
const u32 pc = cpu->getPC();
|
||||
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(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, bpAddr] {
|
||||
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
|
||||
cpu->resumeCpu();
|
||||
});
|
||||
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void DebuggerWindow::onStepOut()
|
||||
{
|
||||
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
|
||||
currentCpu->onStepOut();
|
||||
}
|
||||
DebugInterface* cpu = currentCPU();
|
||||
if (!cpu)
|
||||
return;
|
||||
|
||||
void DebuggerWindow::onAnalyse()
|
||||
{
|
||||
AnalysisOptionsDialog* dialog = new AnalysisOptionsDialog(this);
|
||||
dialog->show();
|
||||
}
|
||||
if (!cpu->isAlive() || !cpu->isCpuPaused())
|
||||
return;
|
||||
|
||||
void DebuggerWindow::showEvent(QShowEvent* event)
|
||||
{
|
||||
Host::RunOnCPUThread([]() {
|
||||
R5900SymbolImporter.OnDebuggerOpened();
|
||||
// Allow the cpu to skip this pc if it is a breakpoint
|
||||
CBreakPoints::SetSkipFirst(cpu->getCpuType(), cpu->getPC());
|
||||
|
||||
std::vector<MipsStackWalk::StackFrame> stack_frames;
|
||||
for (const auto& thread : cpu->GetThreadList())
|
||||
{
|
||||
if (thread->Status() == ThreadStatus::THS_RUN)
|
||||
{
|
||||
stack_frames = MipsStackWalk::Walk(
|
||||
cpu,
|
||||
cpu->getPC(),
|
||||
cpu->getRegister(0, 31),
|
||||
cpu->getRegister(0, 29),
|
||||
thread->EntryPoint(),
|
||||
thread->StackTop());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stack_frames.size() < 2)
|
||||
return;
|
||||
|
||||
u32 breakpoint_pc = stack_frames.at(1).pc;
|
||||
|
||||
Host::RunOnCPUThread([cpu, breakpoint_pc] {
|
||||
CBreakPoints::AddBreakPoint(cpu->getCpuType(), breakpoint_pc, true);
|
||||
cpu->resumeCpu();
|
||||
});
|
||||
QMainWindow::showEvent(event);
|
||||
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void DebuggerWindow::hideEvent(QHideEvent* event)
|
||||
void DebuggerWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
dockManager().saveCurrentLayout();
|
||||
saveWindowGeometry();
|
||||
|
||||
Host::RunOnCPUThread([]() {
|
||||
R5900SymbolImporter.OnDebuggerClosed();
|
||||
});
|
||||
QMainWindow::hideEvent(event);
|
||||
|
||||
KDDockWidgets::QtWidgets::MainWindow::closeEvent(event);
|
||||
|
||||
g_debugger_window = nullptr;
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
DebugInterface* DebuggerWindow::currentCPU()
|
||||
{
|
||||
std::optional<BreakPointCpu> maybe_cpu = m_dock_manager->cpu();
|
||||
if (!maybe_cpu.has_value())
|
||||
return nullptr;
|
||||
|
||||
return &DebugInterface::get(*maybe_cpu);
|
||||
}
|
||||
|
||||
@@ -3,39 +3,70 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CpuWidget.h"
|
||||
|
||||
#include "ui_DebuggerWindow.h"
|
||||
|
||||
class DebuggerWindow : public QMainWindow
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
|
||||
class DockManager;
|
||||
|
||||
class DebuggerWindow : public KDDockWidgets::QtWidgets::MainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DebuggerWindow(QWidget* parent);
|
||||
~DebuggerWindow();
|
||||
|
||||
static DebuggerWindow* getInstance();
|
||||
static DebuggerWindow* createInstance();
|
||||
static void destroyInstance();
|
||||
static bool shouldShowOnStartup();
|
||||
|
||||
DockManager& dockManager();
|
||||
|
||||
void setupDefaultToolBarState();
|
||||
void clearToolBarState();
|
||||
void setupFonts();
|
||||
void updateFontActions();
|
||||
void saveFontSize();
|
||||
int fontSize();
|
||||
void updateStyleSheets();
|
||||
|
||||
void saveWindowGeometry();
|
||||
void restoreWindowGeometry();
|
||||
bool shouldSaveWindowGeometry();
|
||||
|
||||
public slots:
|
||||
void onVMStateChanged();
|
||||
void onVMStarting();
|
||||
void onVMPaused();
|
||||
void onVMResumed();
|
||||
void onVMStopped();
|
||||
|
||||
void onAnalyse();
|
||||
void onSettings();
|
||||
void onGameSettings();
|
||||
void onRunPause();
|
||||
void onStepInto();
|
||||
void onStepOver();
|
||||
void onStepOut();
|
||||
void onAnalyse();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event);
|
||||
void hideEvent(QHideEvent *event);
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
private:
|
||||
DebugInterface* currentCPU();
|
||||
|
||||
Ui::DebuggerWindow m_ui;
|
||||
QAction* m_actionRunPause;
|
||||
QAction* m_actionStepInto;
|
||||
QAction* m_actionStepOver;
|
||||
QAction* m_actionStepOut;
|
||||
|
||||
CpuWidget* m_cpuWidget_r5900;
|
||||
CpuWidget* m_cpuWidget_r3000;
|
||||
DockManager* m_dock_manager;
|
||||
|
||||
void setTabActiveStyle(BreakPointCpu toggledCPU);
|
||||
QByteArray m_default_toolbar_state;
|
||||
|
||||
int m_font_size;
|
||||
static const constexpr int DEFAULT_FONT_SIZE = 10;
|
||||
static const constexpr int MINIMUM_FONT_SIZE = 5;
|
||||
static const constexpr int MAXIMUM_FONT_SIZE = 30;
|
||||
};
|
||||
|
||||
extern DebuggerWindow* g_debugger_window;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
<width>1000</width>
|
||||
<height>750</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -18,31 +18,74 @@
|
||||
<normalon>:/icons/AppIcon64.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="cpuTabs"/>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1000</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionAnalyse"/>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="actionGameSettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionClose"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuDebug">
|
||||
<property name="title">
|
||||
<string>Debug</string>
|
||||
</property>
|
||||
<addaction name="actionRun"/>
|
||||
<addaction name="actionStepInto"/>
|
||||
<addaction name="actionStepOver"/>
|
||||
<addaction name="actionStepOut"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuWindows">
|
||||
<property name="title">
|
||||
<string>Windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuView">
|
||||
<property name="title">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<addaction name="actionOnTop"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionIncreaseFontSize"/>
|
||||
<addaction name="actionDecreaseFontSize"/>
|
||||
<addaction name="actionResetFontSize"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuLayouts">
|
||||
<property name="title">
|
||||
<string>Layouts</string>
|
||||
</property>
|
||||
<addaction name="actionResetAllLayouts"/>
|
||||
<addaction name="actionResetDefaultLayouts"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTools">
|
||||
<property name="title">
|
||||
<string>Tools</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuView"/>
|
||||
<addaction name="menuDebug"/>
|
||||
<addaction name="menuTools"/>
|
||||
<addaction name="menuWindows"/>
|
||||
<addaction name="menuLayouts"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
|
||||
</property>
|
||||
<property name="movable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
<widget class="QToolBar" name="toolBarDebug">
|
||||
<property name="windowTitle">
|
||||
<string>Debug</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
@@ -54,8 +97,57 @@
|
||||
<addaction name="actionStepInto"/>
|
||||
<addaction name="actionStepOver"/>
|
||||
<addaction name="actionStepOut"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBarFile">
|
||||
<property name="windowTitle">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAnalyse"/>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="actionGameSettings"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBarSystem">
|
||||
<property name="windowTitle">
|
||||
<string>System</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionShutDown"/>
|
||||
<addaction name="actionReset"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBarView">
|
||||
<property name="windowTitle">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionOnTop"/>
|
||||
<addaction name="actionIncreaseFontSize"/>
|
||||
<addaction name="actionDecreaseFontSize"/>
|
||||
<addaction name="actionResetFontSize"/>
|
||||
</widget>
|
||||
<action name="actionRun">
|
||||
<property name="icon">
|
||||
@@ -120,6 +212,112 @@
|
||||
<string>Analyze</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetAllLayouts">
|
||||
<property name="text">
|
||||
<string>Reset All Layouts</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetDefaultLayouts">
|
||||
<property name="text">
|
||||
<string>Reset Default Layouts</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetSplitterPositions">
|
||||
<property name="text">
|
||||
<string>Reset Splitter Positions</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionShutDown">
|
||||
<property name="icon">
|
||||
<iconset theme="shut-down-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shut Down</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReset">
|
||||
<property name="icon">
|
||||
<iconset theme="restart-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClose">
|
||||
<property name="icon">
|
||||
<iconset theme="close-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionIncreaseFontSize">
|
||||
<property name="icon">
|
||||
<iconset theme="zoom-in-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Increase Font Size</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDecreaseFontSize">
|
||||
<property name="icon">
|
||||
<iconset theme="zoom-out-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Decrease Font Size</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+-</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetFontSize">
|
||||
<property name="icon">
|
||||
<iconset theme="refresh-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset Font Size</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="settings-3-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGameSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="file-settings-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Game Settings</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include "DisassemblyWidget.h"
|
||||
|
||||
#include "Debugger/Breakpoints/BreakpointModel.h"
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/DisassemblyManager.h"
|
||||
#include "DebugTools/Breakpoints.h"
|
||||
@@ -10,6 +13,7 @@
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include "QtHost.h"
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtGui/QClipboard>
|
||||
@@ -19,16 +23,72 @@
|
||||
|
||||
using namespace QtUtils;
|
||||
|
||||
DisassemblyWidget::DisassemblyWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
DisassemblyWidget::DisassemblyWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, MONOSPACE_FONT)
|
||||
{
|
||||
ui.setupUi(this);
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::customMenuRequested);
|
||||
m_disassemblyManager.setCpu(&cpu());
|
||||
|
||||
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::openContextMenu);
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DisassemblyWidget::gotoProgramCounterOnPause);
|
||||
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
update();
|
||||
return true;
|
||||
});
|
||||
|
||||
receiveEvent<DebuggerEvents::GoToAddress>([this](const DebuggerEvents::GoToAddress& event) -> bool {
|
||||
if (event.filter != DebuggerEvents::GoToAddress::NONE &&
|
||||
event.filter != DebuggerEvents::GoToAddress::DISASSEMBLER)
|
||||
return false;
|
||||
|
||||
gotoAddress(event.address, event.switch_to_tab);
|
||||
|
||||
if (event.switch_to_tab)
|
||||
switchToThisTab();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
DisassemblyWidget::~DisassemblyWidget() = default;
|
||||
|
||||
void DisassemblyWidget::toJson(JsonValueWrapper& json)
|
||||
{
|
||||
DebuggerWidget::toJson(json);
|
||||
|
||||
json.value().AddMember("startAddress", m_visibleStart, json.allocator());
|
||||
json.value().AddMember("goToPCOnPause", m_goToProgramCounterOnPause, json.allocator());
|
||||
json.value().AddMember("showInstructionBytes", m_showInstructionBytes, json.allocator());
|
||||
}
|
||||
|
||||
bool DisassemblyWidget::fromJson(const JsonValueWrapper& json)
|
||||
{
|
||||
if (!DebuggerWidget::fromJson(json))
|
||||
return false;
|
||||
|
||||
auto start_address = json.value().FindMember("startAddress");
|
||||
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
|
||||
m_visibleStart = start_address->value.GetUint() & ~3;
|
||||
|
||||
auto go_to_pc_on_pause = json.value().FindMember("goToPCOnPause");
|
||||
if (go_to_pc_on_pause != json.value().MemberEnd() && go_to_pc_on_pause->value.IsBool())
|
||||
m_goToProgramCounterOnPause = go_to_pc_on_pause->value.GetBool();
|
||||
|
||||
auto show_instruction_bytes = json.value().FindMember("showInstructionBytes");
|
||||
if (show_instruction_bytes != json.value().MemberEnd() && show_instruction_bytes->value.IsBool())
|
||||
m_showInstructionBytes = show_instruction_bytes->value.GetBool();
|
||||
|
||||
repaint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisassemblyWidget::contextCopyAddress()
|
||||
{
|
||||
QGuiApplication::clipboard()->setText(FetchSelectionInfo(SelectionInfo::ADDRESS));
|
||||
@@ -46,7 +106,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 +123,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,32 +132,32 @@ 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)});
|
||||
cpu->write32(i, val);
|
||||
}
|
||||
emit VMUpdate();
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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)});
|
||||
cpu->write32(i, 0x00);
|
||||
}
|
||||
emit VMUpdate();
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
});
|
||||
}
|
||||
|
||||
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())
|
||||
@@ -106,14 +166,14 @@ void DisassemblyWidget::contextRestoreInstruction()
|
||||
this->m_nopedInstructions.erase(i);
|
||||
}
|
||||
}
|
||||
emit VMUpdate();
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
});
|
||||
}
|
||||
|
||||
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,28 +181,13 @@ void DisassemblyWidget::contextRunToCursor()
|
||||
|
||||
void DisassemblyWidget::contextJumpToCursor()
|
||||
{
|
||||
m_cpu->setPc(m_selectedAddressStart);
|
||||
cpu().setPc(m_selectedAddressStart);
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void DisassemblyWidget::contextToggleBreakpoint()
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
return;
|
||||
|
||||
const u32 selectedAddressStart = m_selectedAddressStart;
|
||||
const BreakPointCpu cpuType = m_cpu->getCpuType();
|
||||
if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddressStart))
|
||||
{
|
||||
Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddressStart); });
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::AddBreakPoint(cpuType, selectedAddressStart); });
|
||||
}
|
||||
|
||||
breakpointsChanged();
|
||||
this->repaint();
|
||||
toggleBreakpoint(m_selectedAddressStart);
|
||||
}
|
||||
|
||||
void DisassemblyWidget::contextFollowBranch()
|
||||
@@ -171,7 +216,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 +227,8 @@ void DisassemblyWidget::contextGoToAddress()
|
||||
|
||||
void DisassemblyWidget::contextAddFunction()
|
||||
{
|
||||
NewFunctionDialog* dialog = new NewFunctionDialog(*m_cpu, this);
|
||||
NewFunctionDialog* dialog = new NewFunctionDialog(cpu(), this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->setName(QString("func_%1").arg(m_selectedAddressStart, 8, 16, QChar('0')));
|
||||
dialog->setAddress(m_selectedAddressStart);
|
||||
if (m_selectedAddressEnd != m_selectedAddressStart)
|
||||
@@ -193,13 +239,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 +261,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,28 +282,28 @@ 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
|
||||
emit VMUpdate();
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
});
|
||||
}
|
||||
|
||||
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,12 +312,12 @@ 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);
|
||||
this->m_stubbedFunctions.erase(address);
|
||||
emit VMUpdate();
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -280,18 +326,12 @@ void DisassemblyWidget::contextRestoreFunction()
|
||||
}
|
||||
}
|
||||
|
||||
void DisassemblyWidget::contextShowOpcode()
|
||||
void DisassemblyWidget::contextShowInstructionBytes()
|
||||
{
|
||||
m_showInstructionOpcode = !m_showInstructionOpcode;
|
||||
m_showInstructionBytes = !m_showInstructionBytes;
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void DisassemblyWidget::SetCpu(DebugInterface* cpu)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
m_disassemblyManager.setCpu(cpu);
|
||||
}
|
||||
|
||||
QString DisassemblyWidget::GetLineDisasm(u32 address)
|
||||
{
|
||||
DisassemblyLineInfo lineInfo;
|
||||
@@ -322,7 +362,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 +387,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)
|
||||
{
|
||||
@@ -371,7 +411,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event)
|
||||
s32 branchCount = 0;
|
||||
for (const auto& branchLine : branchLines)
|
||||
{
|
||||
if (branchCount == (m_showInstructionOpcode ? 3 : 5))
|
||||
if (branchCount == (m_showInstructionBytes ? 3 : 5))
|
||||
break;
|
||||
const int winBottom = this->height();
|
||||
|
||||
@@ -506,21 +546,7 @@ void DisassemblyWidget::mousePressEvent(QMouseEvent* event)
|
||||
|
||||
void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
return;
|
||||
|
||||
const u32 selectedAddress = (static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart;
|
||||
const BreakPointCpu cpuType = m_cpu->getCpuType();
|
||||
if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddress))
|
||||
{
|
||||
Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddress); });
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::AddBreakPoint(cpuType, selectedAddress); });
|
||||
}
|
||||
breakpointsChanged();
|
||||
this->repaint();
|
||||
toggleBreakpoint((static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart);
|
||||
}
|
||||
|
||||
void DisassemblyWidget::wheelEvent(QWheelEvent* event)
|
||||
@@ -598,115 +624,139 @@ 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;
|
||||
case Qt::Key_I:
|
||||
m_showInstructionBytes = !m_showInstructionBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void DisassemblyWidget::customMenuRequested(QPoint pos)
|
||||
void DisassemblyWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
QMenu* contextMenu = new QMenu(this);
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* action = 0;
|
||||
contextMenu->addAction(action = new QAction(tr("Copy Address"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress);
|
||||
contextMenu->addAction(action = new QAction(tr("Copy Instruction Hex"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex);
|
||||
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))
|
||||
QAction* copy_address_action = menu->addAction(tr("Copy Address"));
|
||||
connect(copy_address_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress);
|
||||
|
||||
QAction* copy_instruction_hex_action = menu->addAction(tr("Copy Instruction Hex"));
|
||||
connect(copy_instruction_hex_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex);
|
||||
|
||||
QAction* copy_instruction_text_action = menu->addAction(tr("&Copy Instruction Text"));
|
||||
copy_instruction_text_action->setShortcut(QKeySequence(Qt::Key_C));
|
||||
connect(copy_instruction_text_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText);
|
||||
|
||||
if (cpu().GetSymbolGuardian().FunctionExistsWithStartingAddress(m_selectedAddressStart))
|
||||
{
|
||||
contextMenu->addAction(action = new QAction(tr("Copy Function Name"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName);
|
||||
QAction* copy_function_name_action = menu->addAction(tr("Copy Function Name"));
|
||||
connect(copy_function_name_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName);
|
||||
}
|
||||
contextMenu->addSeparator();
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
if (AddressCanRestore(m_selectedAddressStart, m_selectedAddressEnd))
|
||||
{
|
||||
contextMenu->addAction(action = new QAction(tr("Restore Instruction(s)"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction);
|
||||
QAction* restore_instruction_action = menu->addAction(tr("Restore Instruction(s)"));
|
||||
connect(restore_instruction_action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction);
|
||||
}
|
||||
contextMenu->addAction(action = new QAction(tr("Asse&mble new Instruction(s)"), this));
|
||||
action->setShortcut(QKeySequence(Qt::Key_M));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction);
|
||||
contextMenu->addAction(action = new QAction(tr("NOP Instruction(s)"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction);
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(action = new QAction(tr("Run to Cursor"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor);
|
||||
contextMenu->addAction(action = new QAction(tr("&Jump to Cursor"), this));
|
||||
action->setShortcut(QKeySequence(Qt::Key_J));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor);
|
||||
contextMenu->addAction(action = new QAction(tr("Toggle &Breakpoint"), this));
|
||||
action->setShortcut(QKeySequence(Qt::Key_B));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint);
|
||||
contextMenu->addAction(action = new QAction(tr("Follow Branch"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch);
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(action = new QAction(tr("&Go to Address"), this));
|
||||
action->setShortcut(QKeySequence(Qt::Key_G));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress);
|
||||
contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this));
|
||||
connect(action, &QAction::triggered, this, [this]() { gotoInMemory(m_selectedAddressStart); });
|
||||
|
||||
contextMenu->addAction(action = new QAction(tr("Go to PC on Pause"), this));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(m_goToProgramCounterOnPause);
|
||||
connect(action, &QAction::triggered, this, [this](bool value) { m_goToProgramCounterOnPause = value; });
|
||||
QAction* assemble_new_instruction = menu->addAction(tr("Asse&mble new Instruction(s)"));
|
||||
assemble_new_instruction->setShortcut(QKeySequence(Qt::Key_M));
|
||||
connect(assemble_new_instruction, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction);
|
||||
|
||||
QAction* nop_instruction_action = menu->addAction(tr("NOP Instruction(s)"));
|
||||
connect(nop_instruction_action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* run_to_cursor_action = menu->addAction(tr("Run to Cursor"));
|
||||
connect(run_to_cursor_action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor);
|
||||
|
||||
QAction* jump_to_cursor_action = menu->addAction(tr("&Jump to Cursor"));
|
||||
jump_to_cursor_action->setShortcut(QKeySequence(Qt::Key_J));
|
||||
connect(jump_to_cursor_action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor);
|
||||
|
||||
QAction* toggle_breakpoint_action = menu->addAction(tr("Toggle &Breakpoint"));
|
||||
toggle_breakpoint_action->setShortcut(QKeySequence(Qt::Key_B));
|
||||
connect(toggle_breakpoint_action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint);
|
||||
|
||||
QAction* follow_branch_action = menu->addAction(tr("Follow Branch"));
|
||||
connect(follow_branch_action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* go_to_address_action = menu->addAction(tr("&Go to Address"));
|
||||
go_to_address_action->setShortcut(QKeySequence(Qt::Key_G));
|
||||
connect(go_to_address_action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress);
|
||||
|
||||
createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = m_selectedAddressStart;
|
||||
return std::optional(event);
|
||||
});
|
||||
|
||||
QAction* go_to_pc_on_pause = menu->addAction(tr("Go to PC on Pause"));
|
||||
go_to_pc_on_pause->setCheckable(true);
|
||||
go_to_pc_on_pause->setChecked(m_goToProgramCounterOnPause);
|
||||
connect(go_to_pc_on_pause, &QAction::triggered, this,
|
||||
[this](bool value) { m_goToProgramCounterOnPause = value; });
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* add_function_action = menu->addAction(tr("Add Function"));
|
||||
connect(add_function_action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction);
|
||||
|
||||
QAction* rename_function_action = menu->addAction(tr("Rename Function"));
|
||||
connect(rename_function_action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction);
|
||||
|
||||
QAction* remove_function_action = menu->addAction(tr("Remove Function"));
|
||||
menu->addAction(remove_function_action);
|
||||
connect(remove_function_action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction);
|
||||
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(action = new QAction(tr("Add Function"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction);
|
||||
contextMenu->addAction(action = new QAction(tr("Rename Function"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction);
|
||||
contextMenu->addAction(action = new QAction(tr("Remove Function"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction);
|
||||
if (FunctionCanRestore(m_selectedAddressStart))
|
||||
{
|
||||
contextMenu->addAction(action = new QAction(tr("Restore Function"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction);
|
||||
QAction* restore_action = menu->addAction(tr("Restore Function"));
|
||||
connect(restore_action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction);
|
||||
}
|
||||
else
|
||||
{
|
||||
contextMenu->addAction(action = new QAction(tr("Stub (NOP) Function"), this));
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction);
|
||||
QAction* stub_action = menu->addAction(tr("Stub (NOP) Function"));
|
||||
connect(stub_action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction);
|
||||
}
|
||||
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(action = new QAction(tr("Show &Opcode"), this));
|
||||
action->setShortcut(QKeySequence(Qt::Key_O));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(m_showInstructionOpcode);
|
||||
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextShowOpcode);
|
||||
menu->addSeparator();
|
||||
|
||||
contextMenu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
contextMenu->popup(this->mapToGlobal(pos));
|
||||
QAction* show_instruction_bytes_action = menu->addAction(tr("Show &Instruction Bytes"));
|
||||
show_instruction_bytes_action->setShortcut(QKeySequence(Qt::Key_I));
|
||||
show_instruction_bytes_action->setCheckable(true);
|
||||
show_instruction_bytes_action->setChecked(m_showInstructionBytes);
|
||||
connect(show_instruction_bytes_action, &QAction::triggered, this, &DisassemblyWidget::contextShowInstructionBytes);
|
||||
|
||||
menu->popup(this->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected)
|
||||
{
|
||||
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_showInstructionBytes && cpu().isAlive();
|
||||
|
||||
QString lineString;
|
||||
if (showOpcode)
|
||||
@@ -739,7 +789,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));
|
||||
}
|
||||
|
||||
@@ -754,16 +804,8 @@ inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFon
|
||||
|
||||
QColor DisassemblyWidget::GetAddressFunctionColor(u32 address)
|
||||
{
|
||||
// This is an attempt to figure out if the current palette is dark or light
|
||||
// We calculate the luminance of the alternateBase colour
|
||||
// and swap between our darker and lighter function colours
|
||||
|
||||
std::array<QColor, 6> colors;
|
||||
const QColor base = this->palette().alternateBase().color();
|
||||
|
||||
const auto Y = (base.redF() * 0.33) + (0.5 * base.greenF()) + (0.16 * base.blueF());
|
||||
|
||||
if (Y > 0.5)
|
||||
if (QtUtils::IsLightTheme(palette()))
|
||||
{
|
||||
colors = {
|
||||
QColor::fromRgba(0xFFFA3434),
|
||||
@@ -789,7 +831,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 +859,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 +873,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)
|
||||
@@ -847,6 +889,27 @@ void DisassemblyWidget::gotoAddress(u32 address, bool should_set_focus)
|
||||
this->setFocus();
|
||||
}
|
||||
|
||||
void DisassemblyWidget::toggleBreakpoint(u32 address)
|
||||
{
|
||||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
QPointer<DisassemblyWidget> disassembly_widget(this);
|
||||
|
||||
Host::RunOnCPUThread([cpu = &cpu(), address, disassembly_widget] {
|
||||
if (!CBreakPoints::IsAddressBreakPoint(cpu->getCpuType(), address))
|
||||
CBreakPoints::AddBreakPoint(cpu->getCpuType(), address);
|
||||
else
|
||||
CBreakPoints::RemoveBreakPoint(cpu->getCpuType(), address);
|
||||
|
||||
QtHost::RunOnUIThread([cpu, disassembly_widget]() {
|
||||
BreakpointModel::getInstance(*cpu)->refreshData();
|
||||
if (disassembly_widget)
|
||||
disassembly_widget->repaint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool DisassemblyWidget::AddressCanRestore(u32 start, u32 end)
|
||||
{
|
||||
for (u32 i = start; i <= end; i += 4)
|
||||
@@ -861,7 +924,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;
|
||||
|
||||
|
||||
@@ -5,36 +5,36 @@
|
||||
|
||||
#include "ui_DisassemblyWidget.h"
|
||||
|
||||
#include "pcsx2/DebugTools/DebugInterface.h"
|
||||
#include "DebuggerWidget.h"
|
||||
|
||||
#include "pcsx2/DebugTools/DisassemblyManager.h"
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
class DisassemblyWidget final : public QWidget
|
||||
class DisassemblyWidget final : public DebuggerWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisassemblyWidget(QWidget* parent);
|
||||
DisassemblyWidget(const DebuggerWidgetParameters& parameters);
|
||||
~DisassemblyWidget();
|
||||
|
||||
// Required because our constructor needs to take no extra arguments.
|
||||
void SetCpu(DebugInterface* cpu);
|
||||
void toJson(JsonValueWrapper& json) override;
|
||||
bool fromJson(const JsonValueWrapper& json) override;
|
||||
|
||||
// Required for the breakpoint list (ugh wtf)
|
||||
QString GetLineDisasm(u32 address);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseDoubleClickEvent(QMouseEvent* event);
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void customMenuRequested(QPoint pos);
|
||||
void openContextMenu(QPoint pos);
|
||||
|
||||
// Context menu actions
|
||||
// When called, m_selectedAddressStart will be the 'selected' instruction
|
||||
@@ -56,23 +56,18 @@ public slots:
|
||||
void contextRemoveFunction();
|
||||
void contextStubFunction();
|
||||
void contextRestoreFunction();
|
||||
void contextShowOpcode();
|
||||
void contextShowInstructionBytes();
|
||||
|
||||
void gotoAddressAndSetFocus(u32 address);
|
||||
void gotoProgramCounterOnPause();
|
||||
void gotoAddress(u32 address, bool should_set_focus);
|
||||
|
||||
void setDemangle(bool demangle) { m_demangleFunctions = demangle; };
|
||||
signals:
|
||||
void gotoInMemory(u32 address);
|
||||
void breakpointsChanged();
|
||||
void VMUpdate();
|
||||
void toggleBreakpoint(u32 address);
|
||||
|
||||
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_visibleStart = 0x100000; // The address of the first instruction shown.
|
||||
u32 m_visibleRows;
|
||||
u32 m_selectedAddressStart = 0;
|
||||
u32 m_selectedAddressEnd = 0;
|
||||
@@ -81,8 +76,7 @@ private:
|
||||
std::map<u32, u32> m_nopedInstructions;
|
||||
std::map<u32, std::tuple<u32, u32>> m_stubbedFunctions;
|
||||
|
||||
bool m_demangleFunctions = true;
|
||||
bool m_showInstructionOpcode = true;
|
||||
bool m_showInstructionBytes = true;
|
||||
bool m_goToProgramCounterOnPause = true;
|
||||
DisassemblyManager m_disassemblyManager;
|
||||
|
||||
|
||||
918
pcsx2-qt/Debugger/Docking/DockLayout.cpp
Normal file
918
pcsx2-qt/Debugger/Docking/DockLayout.cpp
Normal file
@@ -0,0 +1,918 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockLayout.h"
|
||||
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
#include <kddockwidgets/LayoutSaver.h>
|
||||
#include <kddockwidgets/core/DockRegistry.h>
|
||||
#include <kddockwidgets/core/DockWidget.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/core/Layout.h>
|
||||
#include <kddockwidgets/core/ViewFactory.h>
|
||||
#include <kddockwidgets/qtwidgets/Group.h>
|
||||
#include <kddockwidgets/qtwidgets/MainWindow.h>
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
|
||||
const char* DEBUGGER_LAYOUT_FILE_FORMAT = "PCSX2 Debugger User Interface Layout";
|
||||
|
||||
// Increment this whenever there is a breaking change to the JSON format.
|
||||
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR = 1;
|
||||
|
||||
// Increment this whenever there is a non-breaking change to the JSON format.
|
||||
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR = 0;
|
||||
|
||||
DockLayout::DockLayout(
|
||||
QString name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const std::string& base_name,
|
||||
DockLayout::Index index)
|
||||
: m_name(name)
|
||||
, m_cpu(cpu)
|
||||
, m_is_default(is_default)
|
||||
, m_base_layout(base_name)
|
||||
{
|
||||
reset();
|
||||
save(index);
|
||||
}
|
||||
|
||||
DockLayout::DockLayout(
|
||||
QString name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
DockLayout::Index index)
|
||||
: m_name(name)
|
||||
, m_cpu(cpu)
|
||||
, m_is_default(is_default)
|
||||
{
|
||||
save(index);
|
||||
}
|
||||
|
||||
DockLayout::DockLayout(
|
||||
QString name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const DockLayout& layout_to_clone,
|
||||
DockLayout::Index index)
|
||||
: m_name(name)
|
||||
, m_cpu(cpu)
|
||||
, m_is_default(is_default)
|
||||
, m_next_unique_name(layout_to_clone.m_next_unique_name)
|
||||
, m_base_layout(layout_to_clone.m_base_layout)
|
||||
, m_toolbars(layout_to_clone.m_toolbars)
|
||||
, m_geometry(layout_to_clone.m_geometry)
|
||||
{
|
||||
for (const auto& [unique_name, widget_to_clone] : layout_to_clone.m_widgets)
|
||||
{
|
||||
auto widget_description = DockTables::DEBUGGER_WIDGETS.find(widget_to_clone->metaObject()->className());
|
||||
if (widget_description == DockTables::DEBUGGER_WIDGETS.end())
|
||||
continue;
|
||||
|
||||
DebuggerWidgetParameters parameters;
|
||||
parameters.unique_name = unique_name;
|
||||
parameters.cpu = &DebugInterface::get(cpu);
|
||||
parameters.cpu_override = widget_to_clone->cpuOverride();
|
||||
|
||||
DebuggerWidget* new_widget = widget_description->second.create_widget(parameters);
|
||||
new_widget->setCustomDisplayName(widget_to_clone->customDisplayName());
|
||||
new_widget->setPrimary(widget_to_clone->isPrimary());
|
||||
m_widgets.emplace(unique_name, new_widget);
|
||||
}
|
||||
|
||||
save(index);
|
||||
}
|
||||
|
||||
DockLayout::DockLayout(
|
||||
const std::string& path,
|
||||
DockLayout::LoadResult& result,
|
||||
DockLayout::Index& index_last_session,
|
||||
DockLayout::Index index)
|
||||
{
|
||||
load(path, result, index_last_session);
|
||||
}
|
||||
|
||||
DockLayout::~DockLayout()
|
||||
{
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
delete widget;
|
||||
}
|
||||
}
|
||||
|
||||
const QString& DockLayout::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void DockLayout::setName(QString name)
|
||||
{
|
||||
m_name = std::move(name);
|
||||
}
|
||||
|
||||
BreakPointCpu DockLayout::cpu() const
|
||||
{
|
||||
return m_cpu;
|
||||
}
|
||||
|
||||
bool DockLayout::isDefault() const
|
||||
{
|
||||
return m_is_default;
|
||||
}
|
||||
|
||||
void DockLayout::setCpu(BreakPointCpu cpu)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
if (!widget->setCpu(DebugInterface::get(cpu)))
|
||||
recreateDebuggerWidget(unique_name);
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::freeze()
|
||||
{
|
||||
pxAssert(m_is_active);
|
||||
m_is_active = false;
|
||||
|
||||
if (g_debugger_window)
|
||||
m_toolbars = g_debugger_window->saveState();
|
||||
|
||||
// Store the geometry of all the dock widgets as JSON.
|
||||
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
||||
m_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.
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock->view());
|
||||
view->setWidget(new QWidget());
|
||||
|
||||
delete dock;
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::thaw()
|
||||
{
|
||||
pxAssert(!m_is_active);
|
||||
m_is_active = true;
|
||||
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
// Restore the state of the toolbars.
|
||||
if (m_toolbars.isEmpty())
|
||||
{
|
||||
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
|
||||
if (base_layout)
|
||||
{
|
||||
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
|
||||
if (base_layout->toolbars.contains(toolbar->objectName().toStdString()))
|
||||
toolbar->show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_debugger_window->restoreState(m_toolbars);
|
||||
}
|
||||
|
||||
if (m_geometry.isEmpty())
|
||||
{
|
||||
// This is a newly created layout with no geometry information.
|
||||
setupDefaultLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create all the dock widgets.
|
||||
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
||||
if (!saver.restoreLayout(m_geometry))
|
||||
{
|
||||
// We've failed to restore the geometry, so just tear down whatever
|
||||
// dock widgets may exist and then setup the default layout.
|
||||
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
|
||||
{
|
||||
// Make sure the dock widget releases ownership of its content.
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock->view());
|
||||
view->setWidget(new QWidget());
|
||||
|
||||
delete dock;
|
||||
}
|
||||
|
||||
setupDefaultLayout();
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all the dock widgets have been restored correctly.
|
||||
std::vector<QString> orphaned_debugger_widgets;
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
if (!controller || !view)
|
||||
{
|
||||
Console.Error("Debugger: Failed to restore dock widget '%s'.", unique_name.toStdString().c_str());
|
||||
orphaned_debugger_widgets.emplace_back(unique_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any debugger widgets that haven't been restored correctly.
|
||||
for (const QString& unique_name : orphaned_debugger_widgets)
|
||||
{
|
||||
auto widget_iterator = m_widgets.find(unique_name);
|
||||
pxAssert(widget_iterator != m_widgets.end());
|
||||
|
||||
setPrimaryDebuggerWidget(widget_iterator->second.get(), false);
|
||||
delete widget_iterator->second.get();
|
||||
m_widgets.erase(widget_iterator);
|
||||
}
|
||||
|
||||
updateDockWidgetTitles();
|
||||
}
|
||||
|
||||
bool DockLayout::canReset()
|
||||
{
|
||||
return DockTables::defaultLayout(m_base_layout) != nullptr;
|
||||
}
|
||||
|
||||
void DockLayout::reset()
|
||||
{
|
||||
pxAssert(!m_is_active);
|
||||
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
delete widget;
|
||||
}
|
||||
|
||||
m_next_unique_name = 0;
|
||||
m_toolbars.clear();
|
||||
m_widgets.clear();
|
||||
m_geometry.clear();
|
||||
|
||||
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
|
||||
if (!base_layout)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < base_layout->widgets.size(); i++)
|
||||
{
|
||||
auto iterator = DockTables::DEBUGGER_WIDGETS.find(base_layout->widgets[i].type);
|
||||
pxAssertRel(iterator != DockTables::DEBUGGER_WIDGETS.end(), "Invalid default layout.");
|
||||
const DockTables::DebuggerWidgetDescription& dock_description = iterator->second;
|
||||
|
||||
DebuggerWidgetParameters parameters;
|
||||
parameters.unique_name = generateNewUniqueName(base_layout->widgets[i].type.c_str());
|
||||
parameters.cpu = &DebugInterface::get(m_cpu);
|
||||
|
||||
if (parameters.unique_name.isEmpty())
|
||||
continue;
|
||||
|
||||
DebuggerWidget* widget = dock_description.create_widget(parameters);
|
||||
widget->setPrimary(true);
|
||||
m_widgets.emplace(parameters.unique_name, widget);
|
||||
}
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& name)
|
||||
{
|
||||
pxAssert(m_is_active);
|
||||
pxAssert(KDDockWidgets::LayoutSaver::restoreInProgress());
|
||||
|
||||
auto widget_iterator = m_widgets.find(name);
|
||||
if (widget_iterator == m_widgets.end())
|
||||
return nullptr;
|
||||
|
||||
DebuggerWidget* widget = widget_iterator->second;
|
||||
if (!widget)
|
||||
return nullptr;
|
||||
|
||||
pxAssert(widget->uniqueName() == name);
|
||||
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
||||
KDDockWidgets::Config::self().viewFactory()->createDockWidget(name));
|
||||
view->setWidget(widget);
|
||||
|
||||
return view->asController<KDDockWidgets::Core::DockWidget>();
|
||||
}
|
||||
|
||||
void DockLayout::updateDockWidgetTitles()
|
||||
{
|
||||
if (!m_is_active)
|
||||
return;
|
||||
|
||||
// Translate default debugger widget names.
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
widget->retranslateDisplayName();
|
||||
|
||||
// Determine if any widgets have duplicate display names.
|
||||
std::map<QString, std::vector<DebuggerWidget*>> display_name_to_widgets;
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
display_name_to_widgets[widget->displayNameWithoutSuffix()].emplace_back(widget.get());
|
||||
|
||||
for (auto& [display_name, widgets] : display_name_to_widgets)
|
||||
{
|
||||
std::sort(widgets.begin(), widgets.end(),
|
||||
[&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) {
|
||||
return lhs->uniqueName() < rhs->uniqueName();
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < widgets.size(); i++)
|
||||
{
|
||||
std::optional<int> suffix_number;
|
||||
if (widgets.size() != 1)
|
||||
suffix_number = static_cast<int>(i + 1);
|
||||
|
||||
widgets[i]->setDisplayNameSuffixNumber(suffix_number);
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate the new names from the debugger widgets to the dock widgets.
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(widget->uniqueName());
|
||||
if (!controller)
|
||||
continue;
|
||||
|
||||
controller->setTitle(widget->displayName());
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<QString, QPointer<DebuggerWidget>>& DockLayout::debuggerWidgets()
|
||||
{
|
||||
return m_widgets;
|
||||
}
|
||||
|
||||
bool DockLayout::hasDebuggerWidget(const QString& unique_name)
|
||||
{
|
||||
return m_widgets.find(unique_name) != m_widgets.end();
|
||||
}
|
||||
|
||||
size_t DockLayout::countDebuggerWidgetsOfType(const char* type)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (const auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
if (strcmp(widget->metaObject()->className(), type) == 0)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void DockLayout::createDebuggerWidget(const std::string& type)
|
||||
{
|
||||
pxAssert(m_is_active);
|
||||
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(type);
|
||||
pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end());
|
||||
|
||||
const DockTables::DebuggerWidgetDescription& description = description_iterator->second;
|
||||
|
||||
DebuggerWidgetParameters parameters;
|
||||
parameters.unique_name = generateNewUniqueName(type.c_str());
|
||||
parameters.cpu = &DebugInterface::get(m_cpu);
|
||||
|
||||
if (parameters.unique_name.isEmpty())
|
||||
return;
|
||||
|
||||
DebuggerWidget* widget = description.create_widget(parameters);
|
||||
m_widgets.emplace(parameters.unique_name, widget);
|
||||
|
||||
setPrimaryDebuggerWidget(widget, countDebuggerWidgetsOfType(type.c_str()) == 0);
|
||||
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
||||
KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName()));
|
||||
view->setWidget(widget);
|
||||
|
||||
KDDockWidgets::Core::DockWidget* controller = view->asController<KDDockWidgets::Core::DockWidget>();
|
||||
pxAssert(controller);
|
||||
|
||||
DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, g_debugger_window);
|
||||
updateDockWidgetTitles();
|
||||
}
|
||||
|
||||
void DockLayout::recreateDebuggerWidget(const QString& unique_name)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto debugger_widget_iterator = m_widgets.find(unique_name);
|
||||
pxAssert(debugger_widget_iterator != m_widgets.end());
|
||||
|
||||
DebuggerWidget* old_debugger_widget = debugger_widget_iterator->second;
|
||||
|
||||
auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(old_debugger_widget->metaObject()->className());
|
||||
pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end());
|
||||
|
||||
const DockTables::DebuggerWidgetDescription& description = description_iterator->second;
|
||||
|
||||
DebuggerWidgetParameters parameters;
|
||||
parameters.unique_name = old_debugger_widget->uniqueName();
|
||||
parameters.cpu = &DebugInterface::get(m_cpu);
|
||||
parameters.cpu_override = old_debugger_widget->cpuOverride();
|
||||
|
||||
DebuggerWidget* new_debugger_widget = description.create_widget(parameters);
|
||||
new_debugger_widget->setCustomDisplayName(old_debugger_widget->customDisplayName());
|
||||
new_debugger_widget->setPrimary(old_debugger_widget->isPrimary());
|
||||
debugger_widget_iterator->second = new_debugger_widget;
|
||||
|
||||
if (m_is_active)
|
||||
{
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
if (view)
|
||||
view->setWidget(new_debugger_widget);
|
||||
}
|
||||
|
||||
delete old_debugger_widget;
|
||||
}
|
||||
|
||||
void DockLayout::destroyDebuggerWidget(const QString& unique_name)
|
||||
{
|
||||
pxAssert(m_is_active);
|
||||
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto debugger_widget_iterator = m_widgets.find(unique_name);
|
||||
if (debugger_widget_iterator == m_widgets.end())
|
||||
return;
|
||||
|
||||
setPrimaryDebuggerWidget(debugger_widget_iterator->second.get(), false);
|
||||
delete debugger_widget_iterator->second.get();
|
||||
m_widgets.erase(debugger_widget_iterator);
|
||||
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
if (!controller)
|
||||
return;
|
||||
|
||||
controller->deleteLater();
|
||||
|
||||
updateDockWidgetTitles();
|
||||
}
|
||||
|
||||
void DockLayout::setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary)
|
||||
{
|
||||
bool present = false;
|
||||
for (auto& [unique_name, test_widget] : m_widgets)
|
||||
{
|
||||
if (test_widget.get() == widget)
|
||||
{
|
||||
present = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!present)
|
||||
return;
|
||||
|
||||
if (is_primary)
|
||||
{
|
||||
// Set the passed widget as the primary widget.
|
||||
for (auto& [unique_name, test_widget] : m_widgets)
|
||||
{
|
||||
if (strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0)
|
||||
{
|
||||
test_widget->setPrimary(test_widget.get() == widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (widget->isPrimary())
|
||||
{
|
||||
// Set an arbitrary widget as the primary widget.
|
||||
bool next = true;
|
||||
for (auto& [unique_name, test_widget] : m_widgets)
|
||||
{
|
||||
if (test_widget != widget &&
|
||||
strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0)
|
||||
{
|
||||
test_widget->setPrimary(next);
|
||||
next = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't set another widget as the primary one we can't make
|
||||
// this one not the primary one.
|
||||
if (!next)
|
||||
widget->setPrimary(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::deleteFile()
|
||||
{
|
||||
if (m_layout_file_path.empty())
|
||||
return;
|
||||
|
||||
if (!FileSystem::DeleteFilePath(m_layout_file_path.c_str()))
|
||||
Console.Error("Debugger: Failed to delete layout file '%s'.", m_layout_file_path.c_str());
|
||||
}
|
||||
|
||||
bool DockLayout::save(DockLayout::Index layout_index)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return false;
|
||||
|
||||
if (m_is_active)
|
||||
{
|
||||
m_toolbars = g_debugger_window->saveState();
|
||||
|
||||
// Store the geometry of all the dock widgets as JSON.
|
||||
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
|
||||
m_geometry = saver.serializeLayout();
|
||||
}
|
||||
|
||||
// Serialize the layout as JSON.
|
||||
rapidjson::Document json(rapidjson::kObjectType);
|
||||
rapidjson::Document geometry;
|
||||
|
||||
const char* cpu_name = DebugInterface::cpuName(m_cpu);
|
||||
const std::string& default_layouts_hash = DockTables::hashDefaultLayouts();
|
||||
|
||||
rapidjson::Value format;
|
||||
format.SetString(DEBUGGER_LAYOUT_FILE_FORMAT, strlen(DEBUGGER_LAYOUT_FILE_FORMAT));
|
||||
json.AddMember("format", format, json.GetAllocator());
|
||||
|
||||
json.AddMember("version_major", DEBUGGER_LAYOUT_FILE_VERSION_MAJOR, json.GetAllocator());
|
||||
json.AddMember("version_minor", DEBUGGER_LAYOUT_FILE_VERSION_MINOR, json.GetAllocator());
|
||||
rapidjson::Value version_hash;
|
||||
version_hash.SetString(default_layouts_hash.c_str(), default_layouts_hash.size());
|
||||
json.AddMember("version_hash", version_hash, json.GetAllocator());
|
||||
|
||||
std::string name_str = m_name.toStdString();
|
||||
json.AddMember("name", rapidjson::Value().SetString(name_str.c_str(), name_str.size()), json.GetAllocator());
|
||||
json.AddMember("target", rapidjson::Value().SetString(cpu_name, strlen(cpu_name)), json.GetAllocator());
|
||||
json.AddMember("index", static_cast<int>(layout_index), json.GetAllocator());
|
||||
json.AddMember("isDefault", m_is_default, json.GetAllocator());
|
||||
json.AddMember("nextUniqueName", m_next_unique_name, json.GetAllocator());
|
||||
|
||||
if (!m_base_layout.empty())
|
||||
{
|
||||
rapidjson::Value base_layout;
|
||||
base_layout.SetString(m_base_layout.c_str(), m_base_layout.size());
|
||||
json.AddMember("baseLayout", base_layout, json.GetAllocator());
|
||||
}
|
||||
|
||||
if (!m_toolbars.isEmpty())
|
||||
{
|
||||
std::string toolbars_str = m_toolbars.toBase64().toStdString();
|
||||
rapidjson::Value toolbars;
|
||||
toolbars.SetString(toolbars_str.data(), toolbars_str.size(), json.GetAllocator());
|
||||
json.AddMember("toolbars", toolbars, json.GetAllocator());
|
||||
}
|
||||
|
||||
rapidjson::Value widgets(rapidjson::kArrayType);
|
||||
for (auto& [unique_name, widget] : m_widgets)
|
||||
{
|
||||
pxAssert(widget.get());
|
||||
|
||||
rapidjson::Value object(rapidjson::kObjectType);
|
||||
|
||||
std::string name_str = unique_name.toStdString();
|
||||
rapidjson::Value name;
|
||||
name.SetString(name_str.c_str(), name_str.size(), json.GetAllocator());
|
||||
object.AddMember("uniqueName", name, json.GetAllocator());
|
||||
|
||||
const char* type_str = widget->metaObject()->className();
|
||||
rapidjson::Value type;
|
||||
type.SetString(type_str, strlen(type_str), json.GetAllocator());
|
||||
object.AddMember("type", type, json.GetAllocator());
|
||||
|
||||
if (widget->cpuOverride().has_value())
|
||||
{
|
||||
const char* cpu_name = DebugInterface::cpuName(*widget->cpuOverride());
|
||||
|
||||
rapidjson::Value target;
|
||||
target.SetString(cpu_name, strlen(cpu_name));
|
||||
object.AddMember("target", target, json.GetAllocator());
|
||||
}
|
||||
|
||||
JsonValueWrapper wrapper(object, json.GetAllocator());
|
||||
widget->toJson(wrapper);
|
||||
|
||||
widgets.PushBack(object, json.GetAllocator());
|
||||
}
|
||||
json.AddMember("widgets", widgets, json.GetAllocator());
|
||||
|
||||
if (!m_geometry.isEmpty() && !geometry.Parse(m_geometry).HasParseError())
|
||||
json.AddMember("geometry", geometry, json.GetAllocator());
|
||||
|
||||
rapidjson::StringBuffer string_buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(string_buffer);
|
||||
json.Accept(writer);
|
||||
|
||||
std::string safe_name = Path::SanitizeFileName(m_name.toStdString());
|
||||
|
||||
// Create a temporary file first so that we don't corrupt an existing file
|
||||
// in the case that we succeed in opening the file but fail to write our
|
||||
// data to it.
|
||||
std::string temp_file_path = Path::Combine(EmuFolders::DebuggerLayouts, safe_name + ".tmp");
|
||||
|
||||
if (!FileSystem::WriteStringToFile(temp_file_path.c_str(), string_buffer.GetString()))
|
||||
{
|
||||
Console.Error("Debugger: Failed to save temporary layout file '%s'.", temp_file_path.c_str());
|
||||
FileSystem::DeleteFilePath(temp_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now move the layout to its final location.
|
||||
std::string file_path = Path::Combine(EmuFolders::DebuggerLayouts, safe_name + ".json");
|
||||
|
||||
if (!FileSystem::RenamePath(temp_file_path.c_str(), file_path.c_str()))
|
||||
{
|
||||
Console.Error("Debugger: Failed to move layout file to '%s'.", file_path.c_str());
|
||||
FileSystem::DeleteFilePath(temp_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the layout has been renamed we need to delete the old file.
|
||||
if (file_path != m_layout_file_path)
|
||||
deleteFile();
|
||||
|
||||
m_layout_file_path = std::move(file_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DockLayout::load(
|
||||
const std::string& path,
|
||||
LoadResult& result,
|
||||
DockLayout::Index& index_last_session)
|
||||
{
|
||||
pxAssert(!m_is_active);
|
||||
|
||||
result = SUCCESS;
|
||||
|
||||
std::optional<std::string> text = FileSystem::ReadFileToString(path.c_str());
|
||||
if (!text.has_value())
|
||||
{
|
||||
Console.Error("Debugger: Failed to open layout file '%s'.", path.c_str());
|
||||
result = FILE_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document json;
|
||||
if (json.Parse(text->c_str()).HasParseError() || !json.IsObject())
|
||||
{
|
||||
Console.Error("Debugger: Failed to parse layout file '%s' as JSON.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
auto format = json.FindMember("format");
|
||||
if (format == json.MemberEnd() ||
|
||||
!format->value.IsString() ||
|
||||
strcmp(format->value.GetString(), DEBUGGER_LAYOUT_FILE_FORMAT) != 0)
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'format' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
auto version_major = json.FindMember("version_major");
|
||||
if (version_major == json.MemberEnd() || !version_major->value.IsInt())
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_major' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (version_major->value.GetInt() != DEBUGGER_LAYOUT_FILE_VERSION_MAJOR)
|
||||
{
|
||||
result = MAJOR_VERSION_MISMATCH;
|
||||
return;
|
||||
}
|
||||
|
||||
auto version_minor = json.FindMember("version_minor");
|
||||
if (version_minor == json.MemberEnd() || !version_minor->value.IsInt())
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_minor' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
auto version_hash = json.FindMember("version_hash");
|
||||
if (version_hash == json.MemberEnd() || !version_hash->value.IsString())
|
||||
{
|
||||
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_hash' property.", path.c_str());
|
||||
result = INVALID_FORMAT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(version_hash->value.GetString(), DockTables::hashDefaultLayouts().c_str()) != 0)
|
||||
result = DEFAULT_LAYOUT_HASH_MISMATCH;
|
||||
|
||||
auto name = json.FindMember("name");
|
||||
if (name != json.MemberEnd() && name->value.IsString())
|
||||
m_name = name->value.GetString();
|
||||
else
|
||||
m_name = QCoreApplication::translate("DockLayout", "Unnamed");
|
||||
|
||||
m_name.truncate(DockUtils::MAX_LAYOUT_NAME_SIZE);
|
||||
|
||||
auto target = json.FindMember("target");
|
||||
m_cpu = BREAKPOINT_EE;
|
||||
if (target != json.MemberEnd() && target->value.IsString())
|
||||
{
|
||||
for (BreakPointCpu cpu : DEBUG_CPUS)
|
||||
if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0)
|
||||
m_cpu = cpu;
|
||||
}
|
||||
|
||||
auto index = json.FindMember("index");
|
||||
if (index != json.MemberEnd() && index->value.IsInt())
|
||||
index_last_session = index->value.GetInt();
|
||||
|
||||
auto is_default = json.FindMember("isDefault");
|
||||
if (is_default != json.MemberEnd() && is_default->value.IsBool())
|
||||
m_is_default = is_default->value.GetBool();
|
||||
|
||||
auto next_unique_name = json.FindMember("nextUniqueName");
|
||||
if (next_unique_name != json.MemberBegin() && next_unique_name->value.IsInt())
|
||||
m_next_unique_name = next_unique_name->value.GetInt();
|
||||
|
||||
auto base_layout = json.FindMember("baseLayout");
|
||||
if (base_layout != json.MemberEnd() && base_layout->value.IsString())
|
||||
m_base_layout = base_layout->value.GetString();
|
||||
|
||||
auto toolbars = json.FindMember("toolbars");
|
||||
if (toolbars != json.MemberEnd() && toolbars->value.IsString())
|
||||
m_toolbars = QByteArray::fromBase64(toolbars->value.GetString());
|
||||
|
||||
auto widgets = json.FindMember("widgets");
|
||||
if (widgets != json.MemberEnd() && widgets->value.IsArray())
|
||||
{
|
||||
for (rapidjson::Value& object : widgets->value.GetArray())
|
||||
{
|
||||
auto unique_name = object.FindMember("uniqueName");
|
||||
if (unique_name == object.MemberEnd() || !unique_name->value.IsString())
|
||||
continue;
|
||||
|
||||
auto widgets_iterator = m_widgets.find(unique_name->value.GetString());
|
||||
if (widgets_iterator != m_widgets.end())
|
||||
continue;
|
||||
|
||||
auto type = object.FindMember("type");
|
||||
if (type == object.MemberEnd() || !type->value.IsString())
|
||||
continue;
|
||||
|
||||
auto description = DockTables::DEBUGGER_WIDGETS.find(type->value.GetString());
|
||||
if (description == DockTables::DEBUGGER_WIDGETS.end())
|
||||
continue;
|
||||
|
||||
std::optional<BreakPointCpu> cpu_override;
|
||||
|
||||
auto target = object.FindMember("target");
|
||||
if (target != object.MemberEnd() && target->value.IsString())
|
||||
{
|
||||
for (BreakPointCpu cpu : DEBUG_CPUS)
|
||||
if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0)
|
||||
cpu_override = cpu;
|
||||
}
|
||||
|
||||
DebuggerWidgetParameters parameters;
|
||||
parameters.unique_name = unique_name->value.GetString();
|
||||
parameters.cpu = &DebugInterface::get(m_cpu);
|
||||
parameters.cpu_override = cpu_override;
|
||||
|
||||
DebuggerWidget* widget = description->second.create_widget(parameters);
|
||||
|
||||
JsonValueWrapper wrapper(object, json.GetAllocator());
|
||||
if (!widget->fromJson(wrapper))
|
||||
{
|
||||
delete widget;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_widgets.emplace(unique_name->value.GetString(), widget);
|
||||
}
|
||||
}
|
||||
|
||||
auto geometry = json.FindMember("geometry");
|
||||
if (geometry != json.MemberEnd() && geometry->value.IsObject())
|
||||
{
|
||||
rapidjson::StringBuffer string_buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(string_buffer);
|
||||
geometry->value.Accept(writer);
|
||||
|
||||
m_geometry = QByteArray(string_buffer.GetString(), string_buffer.GetSize());
|
||||
}
|
||||
|
||||
m_layout_file_path = path;
|
||||
|
||||
validatePrimaryDebuggerWidgets();
|
||||
}
|
||||
|
||||
void DockLayout::validatePrimaryDebuggerWidgets()
|
||||
{
|
||||
std::map<std::string, std::vector<DebuggerWidget*>> type_to_widgets;
|
||||
for (const auto& [unique_name, widget] : m_widgets)
|
||||
type_to_widgets[widget->metaObject()->className()].emplace_back(widget.get());
|
||||
|
||||
for (auto& [type, widgets] : type_to_widgets)
|
||||
{
|
||||
u32 primary_widgets = 0;
|
||||
|
||||
// Make sure at most one widget is marked as primary.
|
||||
for (DebuggerWidget* widget : widgets)
|
||||
{
|
||||
if (widget->isPrimary())
|
||||
{
|
||||
if (primary_widgets != 0)
|
||||
widget->setPrimary(false);
|
||||
|
||||
primary_widgets++;
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the widgets were marked as primary, just set the first one
|
||||
// as the primary one.
|
||||
if (primary_widgets == 0)
|
||||
widgets[0]->setPrimary(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DockLayout::setupDefaultLayout()
|
||||
{
|
||||
pxAssert(m_is_active);
|
||||
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
|
||||
if (!base_layout)
|
||||
return;
|
||||
|
||||
std::vector<KDDockWidgets::QtWidgets::DockWidget*> groups(base_layout->groups.size(), nullptr);
|
||||
|
||||
for (const DockTables::DefaultDockWidgetDescription& dock_description : base_layout->widgets)
|
||||
{
|
||||
const DockTables::DefaultDockGroupDescription& group =
|
||||
base_layout->groups[static_cast<u32>(dock_description.group)];
|
||||
|
||||
DebuggerWidget* widget = nullptr;
|
||||
for (auto& [unique_name, test_widget] : m_widgets)
|
||||
if (test_widget->metaObject()->className() == dock_description.type)
|
||||
widget = test_widget;
|
||||
|
||||
if (!widget)
|
||||
continue;
|
||||
|
||||
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
|
||||
KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName()));
|
||||
view->setWidget(widget);
|
||||
|
||||
if (!groups[static_cast<u32>(dock_description.group)])
|
||||
{
|
||||
KDDockWidgets::QtWidgets::DockWidget* parent = nullptr;
|
||||
if (group.parent != DockTables::DefaultDockGroup::ROOT)
|
||||
parent = groups[static_cast<u32>(group.parent)];
|
||||
|
||||
g_debugger_window->addDockWidget(view, group.location, parent);
|
||||
|
||||
groups[static_cast<u32>(dock_description.group)] = view;
|
||||
}
|
||||
else
|
||||
{
|
||||
groups[static_cast<u32>(dock_description.group)]->addDockWidgetAsTab(view);
|
||||
}
|
||||
}
|
||||
|
||||
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
||||
group->setCurrentTabIndex(0);
|
||||
}
|
||||
|
||||
QString DockLayout::generateNewUniqueName(const char* type)
|
||||
{
|
||||
QString name;
|
||||
do
|
||||
{
|
||||
if (m_next_unique_name == INT_MAX)
|
||||
return QString();
|
||||
|
||||
// Produce unique names that will lexicographically sort in the order
|
||||
// they were allocated. This ensures the #1, #2, etc suffixes for dock
|
||||
// widgets with conflicting names will be assigned in the correct order.
|
||||
name = QStringLiteral("%1-%2").arg(m_next_unique_name, 16, 10, QLatin1Char('0')).arg(type);
|
||||
m_next_unique_name++;
|
||||
} while (hasDebuggerWidget(name));
|
||||
return name;
|
||||
}
|
||||
163
pcsx2-qt/Debugger/Docking/DockLayout.h
Normal file
163
pcsx2-qt/Debugger/Docking/DockLayout.h
Normal file
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
class DebuggerWidget;
|
||||
class DebuggerWindow;
|
||||
|
||||
extern const char* DEBUGGER_LAYOUT_FILE_FORMAT;
|
||||
|
||||
// Increment this whenever there is a breaking change to the JSON format.
|
||||
extern const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR;
|
||||
|
||||
// Increment this whenever there is a non-breaking change to the JSON format.
|
||||
extern const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR;
|
||||
|
||||
class DockLayout
|
||||
{
|
||||
public:
|
||||
using Index = size_t;
|
||||
static const constexpr Index INVALID_INDEX = SIZE_MAX;
|
||||
|
||||
enum LoadResult
|
||||
{
|
||||
SUCCESS,
|
||||
FILE_NOT_FOUND,
|
||||
INVALID_FORMAT,
|
||||
MAJOR_VERSION_MISMATCH,
|
||||
DEFAULT_LAYOUT_HASH_MISMATCH,
|
||||
CONFLICTING_NAME
|
||||
};
|
||||
|
||||
// Create a layout based on a default layout.
|
||||
DockLayout(
|
||||
QString name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const std::string& base_name,
|
||||
DockLayout::Index index);
|
||||
|
||||
// Create a new blank layout.
|
||||
DockLayout(
|
||||
QString name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
DockLayout::Index index);
|
||||
|
||||
// Clone an existing layout.
|
||||
DockLayout(
|
||||
QString name,
|
||||
BreakPointCpu cpu,
|
||||
bool is_default,
|
||||
const DockLayout& layout_to_clone,
|
||||
DockLayout::Index index);
|
||||
|
||||
// Load a layout from a file.
|
||||
DockLayout(
|
||||
const std::string& path,
|
||||
LoadResult& result,
|
||||
DockLayout::Index& index_last_session,
|
||||
DockLayout::Index index);
|
||||
|
||||
~DockLayout();
|
||||
|
||||
DockLayout(const DockLayout& rhs) = delete;
|
||||
DockLayout& operator=(const DockLayout& rhs) = delete;
|
||||
|
||||
DockLayout(DockLayout&& rhs) = default;
|
||||
DockLayout& operator=(DockLayout&&) = default;
|
||||
|
||||
const QString& name() const;
|
||||
void setName(QString name);
|
||||
|
||||
BreakPointCpu cpu() const;
|
||||
void setCpu(BreakPointCpu cpu);
|
||||
|
||||
bool isDefault() const;
|
||||
|
||||
// Tear down and save the state of all the dock widgets from this layout.
|
||||
void freeze();
|
||||
|
||||
// Restore the state of all the dock widgets from this layout.
|
||||
void thaw();
|
||||
|
||||
bool canReset();
|
||||
void reset();
|
||||
|
||||
KDDockWidgets::Core::DockWidget* createDockWidget(const QString& name);
|
||||
void updateDockWidgetTitles();
|
||||
|
||||
const std::map<QString, QPointer<DebuggerWidget>>& debuggerWidgets();
|
||||
bool hasDebuggerWidget(const QString& unique_name);
|
||||
size_t countDebuggerWidgetsOfType(const char* type);
|
||||
void createDebuggerWidget(const std::string& type);
|
||||
void recreateDebuggerWidget(const QString& unique_name);
|
||||
void destroyDebuggerWidget(const QString& unique_name);
|
||||
void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary);
|
||||
|
||||
void deleteFile();
|
||||
|
||||
bool save(DockLayout::Index layout_index);
|
||||
|
||||
private:
|
||||
void load(
|
||||
const std::string& path,
|
||||
DockLayout::LoadResult& result,
|
||||
DockLayout::Index& index_last_session);
|
||||
|
||||
// Make sure there is only a single primary debugger widget of each type.
|
||||
void validatePrimaryDebuggerWidgets();
|
||||
|
||||
void setupDefaultLayout();
|
||||
|
||||
QString generateNewUniqueName(const char* type);
|
||||
|
||||
// The name displayed in the user interface. Also used to determine the
|
||||
// file name for the layout file.
|
||||
QString m_name;
|
||||
|
||||
// The default target for dock widgets in this layout. This can be
|
||||
// overriden on a per-widget basis.
|
||||
BreakPointCpu m_cpu;
|
||||
|
||||
// Is this one of the default layouts?
|
||||
bool m_is_default = false;
|
||||
|
||||
// A counter used to generate new unique names for dock widgets.
|
||||
int m_next_unique_name = 0;
|
||||
|
||||
// The name of the default layout which this layout was based on. This will
|
||||
// be used if the m_geometry variable above is empty.
|
||||
std::string m_base_layout;
|
||||
|
||||
// The state of all the toolbars, saved and restored using
|
||||
// QMainWindow::saveState and QMainWindow::restoreState respectively.
|
||||
QByteArray m_toolbars;
|
||||
|
||||
// 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::map<QString, QPointer<DebuggerWidget>> m_widgets;
|
||||
|
||||
// The geometry of all the dock widgets, converted to JSON by the
|
||||
// LayoutSaver class from KDDockWidgets.
|
||||
QByteArray m_geometry;
|
||||
|
||||
// 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 m_layout_file_path;
|
||||
|
||||
// If this layout is the currently selected layout this will be true,
|
||||
// otherwise it will be false.
|
||||
bool m_is_active = false;
|
||||
};
|
||||
967
pcsx2-qt/Debugger/Docking/DockManager.cpp
Normal file
967
pcsx2-qt/Debugger/Docking/DockManager.cpp
Normal file
@@ -0,0 +1,967 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockManager.h"
|
||||
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
#include "Debugger/Docking/DockViews.h"
|
||||
#include "Debugger/Docking/DropIndicators.h"
|
||||
#include "Debugger/Docking/LayoutEditorDialog.h"
|
||||
#include "Debugger/Docking/NoLayoutsWidget.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Path.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/core/Stack.h>
|
||||
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
|
||||
#include <kddockwidgets/qtwidgets/Stack.h>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QtTranslation>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
DockManager::DockManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
QTimer* autosave_timer = new QTimer(this);
|
||||
connect(autosave_timer, &QTimer::timeout, this, &DockManager::saveCurrentLayout);
|
||||
autosave_timer->start(60 * 1000);
|
||||
|
||||
m_blink_timer = new QTimer(this);
|
||||
connect(m_blink_timer, &QTimer::timeout, this, &DockManager::layoutSwitcherUpdateBlink);
|
||||
}
|
||||
|
||||
void DockManager::configureDockingSystem()
|
||||
{
|
||||
std::string indicator_style = Host::GetBaseStringSettingValue(
|
||||
"Debugger/UserInterface", "DropIndicatorStyle", "Classic");
|
||||
|
||||
if (indicator_style == "Segmented" || indicator_style == "Minimalistic")
|
||||
{
|
||||
KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Segmented;
|
||||
DockSegmentedDropIndicatorOverlay::s_indicator_style = indicator_style;
|
||||
}
|
||||
else
|
||||
{
|
||||
KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Classic;
|
||||
}
|
||||
|
||||
static bool done = false;
|
||||
if (done)
|
||||
return;
|
||||
|
||||
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
|
||||
|
||||
KDDockWidgets::Config& config = KDDockWidgets::Config::self();
|
||||
|
||||
config.setFlags(
|
||||
KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible |
|
||||
KDDockWidgets::Config::Flag_AlwaysShowTabs |
|
||||
KDDockWidgets::Config::Flag_AllowReorderTabs |
|
||||
KDDockWidgets::Config::Flag_TitleBarIsFocusable);
|
||||
|
||||
// We set this flag regardless of whether or not the windowing system
|
||||
// supports compositing since it's only used by the built-in docking
|
||||
// indicator, and we only fall back to that if compositing is disabled.
|
||||
config.setInternalFlags(KDDockWidgets::Config::InternalFlag_DisableTranslucency);
|
||||
|
||||
config.setDockWidgetFactoryFunc(&DockManager::dockWidgetFactory);
|
||||
config.setViewFactory(new DockViewFactory());
|
||||
config.setDragAboutToStartFunc(&DockManager::dragAboutToStart);
|
||||
config.setStartDragDistance(std::max(QApplication::startDragDistance(), 32));
|
||||
|
||||
done = true;
|
||||
}
|
||||
|
||||
bool DockManager::deleteLayout(DockLayout::Index layout_index)
|
||||
{
|
||||
pxAssertRel(layout_index != DockLayout::INVALID_INDEX,
|
||||
"DockManager::deleteLayout called with INVALID_INDEX.");
|
||||
|
||||
if (layout_index == m_current_layout)
|
||||
{
|
||||
DockLayout::Index other_layout = DockLayout::INVALID_INDEX;
|
||||
if (layout_index + 1 < m_layouts.size())
|
||||
other_layout = layout_index + 1;
|
||||
else if (layout_index > 0)
|
||||
other_layout = layout_index - 1;
|
||||
|
||||
switchToLayout(other_layout);
|
||||
}
|
||||
|
||||
m_layouts.at(layout_index).deleteFile();
|
||||
m_layouts.erase(m_layouts.begin() + layout_index);
|
||||
|
||||
// All the layouts after the one being deleted have been shifted over by
|
||||
// one, so adjust the current layout index accordingly.
|
||||
if (m_current_layout > layout_index && m_current_layout != DockLayout::INVALID_INDEX)
|
||||
m_current_layout--;
|
||||
|
||||
if (m_layouts.empty() && g_debugger_window)
|
||||
{
|
||||
NoLayoutsWidget* widget = new NoLayoutsWidget;
|
||||
connect(widget->createDefaultLayoutsButton(), &QPushButton::clicked, this, &DockManager::resetAllLayouts);
|
||||
|
||||
KDDockWidgets::QtWidgets::DockWidget* dock = new KDDockWidgets::QtWidgets::DockWidget("placeholder");
|
||||
dock->setTitle(tr("No Layouts"));
|
||||
dock->setWidget(widget);
|
||||
g_debugger_window->addDockWidget(dock, KDDockWidgets::Location_OnTop);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DockManager::switchToLayout(DockLayout::Index layout_index, bool blink_tab)
|
||||
{
|
||||
if (layout_index != m_current_layout)
|
||||
{
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX)
|
||||
{
|
||||
DockLayout& layout = m_layouts.at(m_current_layout);
|
||||
layout.freeze();
|
||||
layout.save(m_current_layout);
|
||||
}
|
||||
|
||||
// Clear out the existing positions of toolbars so they don't affect
|
||||
// where new toolbars appear for other layouts.
|
||||
if (g_debugger_window)
|
||||
g_debugger_window->clearToolBarState();
|
||||
|
||||
updateToolBarLockState();
|
||||
|
||||
m_current_layout = layout_index;
|
||||
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX)
|
||||
{
|
||||
DockLayout& layout = m_layouts.at(m_current_layout);
|
||||
layout.thaw();
|
||||
|
||||
int tab_index = static_cast<int>(layout_index);
|
||||
if (m_switcher && tab_index >= 0 && tab_index < m_plus_tab_index)
|
||||
{
|
||||
m_ignore_current_tab_changed = true;
|
||||
m_switcher->setCurrentIndex(tab_index);
|
||||
m_ignore_current_tab_changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blink_tab)
|
||||
layoutSwitcherStartBlink();
|
||||
}
|
||||
|
||||
bool DockManager::switchToLayoutWithCPU(BreakPointCpu cpu, bool blink_tab)
|
||||
{
|
||||
// Don't interrupt the user if the current layout already has the right CPU.
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX && m_layouts.at(m_current_layout).cpu() == cpu)
|
||||
{
|
||||
switchToLayout(m_current_layout, blink_tab);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
||||
{
|
||||
if (m_layouts[i].cpu() == cpu)
|
||||
{
|
||||
switchToLayout(i, blink_tab);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
bool needs_reset = false;
|
||||
bool order_changed = false;
|
||||
std::vector<DockLayout::Index> indices_last_session;
|
||||
|
||||
for (const FILESYSTEM_FIND_DATA& ffd : files)
|
||||
{
|
||||
DockLayout::LoadResult result;
|
||||
DockLayout::Index index_last_session = DockLayout::INVALID_INDEX;
|
||||
DockLayout::Index index =
|
||||
createLayout(ffd.FileName, result, index_last_session);
|
||||
|
||||
DockLayout& layout = m_layouts.at(index);
|
||||
|
||||
// Try to make sure the layout has a unique name.
|
||||
const QString& name = layout.name();
|
||||
QString new_name = name;
|
||||
if (result == DockLayout::SUCCESS || result == DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
|
||||
{
|
||||
for (int i = 2; hasNameConflict(new_name, index) && i < 100; i++)
|
||||
{
|
||||
if (i == 99)
|
||||
{
|
||||
result = DockLayout::CONFLICTING_NAME;
|
||||
break;
|
||||
}
|
||||
|
||||
new_name = QString("%1 #%2").arg(name).arg(i);
|
||||
}
|
||||
}
|
||||
|
||||
needs_reset |= result != DockLayout::SUCCESS;
|
||||
|
||||
if (result != DockLayout::SUCCESS && result != DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
|
||||
{
|
||||
deleteLayout(index);
|
||||
|
||||
// Only delete the file if we've identified that it's actually a
|
||||
// layout file.
|
||||
if (result == DockLayout::MAJOR_VERSION_MISMATCH || result == DockLayout::CONFLICTING_NAME)
|
||||
FileSystem::DeleteFilePath(ffd.FileName.c_str());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (new_name != name)
|
||||
{
|
||||
layout.setName(new_name);
|
||||
layout.save(index);
|
||||
}
|
||||
|
||||
if (index_last_session != index)
|
||||
order_changed = true;
|
||||
|
||||
indices_last_session.emplace_back(index_last_session);
|
||||
}
|
||||
|
||||
// Make sure the layouts remain in the same order they were in previously.
|
||||
std::vector<DockLayout*> layout_pointers;
|
||||
for (DockLayout& layout : m_layouts)
|
||||
layout_pointers.emplace_back(&layout);
|
||||
|
||||
std::sort(layout_pointers.begin(), layout_pointers.end(),
|
||||
[this, &indices_last_session](const DockLayout* lhs, const DockLayout* rhs) {
|
||||
size_t lhs_index = lhs - m_layouts.data();
|
||||
size_t rhs_index = rhs - m_layouts.data();
|
||||
DockLayout::Index lhs_index_last_session = indices_last_session.at(lhs_index);
|
||||
DockLayout::Index rhs_index_last_session = indices_last_session.at(rhs_index);
|
||||
return lhs_index_last_session < rhs_index_last_session;
|
||||
});
|
||||
|
||||
std::vector<DockLayout> sorted_layouts;
|
||||
for (size_t i = 0; i < layout_pointers.size(); i++)
|
||||
sorted_layouts.emplace_back(std::move(*layout_pointers[i]));
|
||||
|
||||
m_layouts = std::move(sorted_layouts);
|
||||
|
||||
if (m_layouts.empty() || needs_reset)
|
||||
resetDefaultLayouts();
|
||||
else
|
||||
updateLayoutSwitcher();
|
||||
|
||||
// Make sure the indices in the existing layout files match up with the
|
||||
// indices of any new layouts.
|
||||
if (order_changed)
|
||||
saveLayouts();
|
||||
}
|
||||
|
||||
bool DockManager::saveLayouts()
|
||||
{
|
||||
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
||||
if (!m_layouts[i].save(i))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DockManager::saveCurrentLayout()
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return true;
|
||||
|
||||
return m_layouts.at(m_current_layout).save(m_current_layout);
|
||||
}
|
||||
|
||||
void DockManager::resetAllLayouts()
|
||||
{
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
for (DockLayout& layout : m_layouts)
|
||||
layout.deleteFile();
|
||||
|
||||
m_layouts.clear();
|
||||
|
||||
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
||||
createLayout(tr(layout.name.c_str()), layout.cpu, true, layout.name);
|
||||
|
||||
switchToLayout(0);
|
||||
updateLayoutSwitcher();
|
||||
saveLayouts();
|
||||
}
|
||||
|
||||
void DockManager::resetDefaultLayouts()
|
||||
{
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
std::vector<DockLayout> old_layouts = std::move(m_layouts);
|
||||
m_layouts = std::vector<DockLayout>();
|
||||
|
||||
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
||||
createLayout(tr(layout.name.c_str()), layout.cpu, true, layout.name);
|
||||
|
||||
for (DockLayout& layout : old_layouts)
|
||||
if (!layout.isDefault())
|
||||
m_layouts.emplace_back(std::move(layout));
|
||||
else
|
||||
layout.deleteFile();
|
||||
|
||||
switchToLayout(0);
|
||||
updateLayoutSwitcher();
|
||||
saveLayouts();
|
||||
}
|
||||
|
||||
void DockManager::createToolsMenu(QMenu* menu)
|
||||
{
|
||||
menu->clear();
|
||||
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX || !g_debugger_window)
|
||||
return;
|
||||
|
||||
for (QToolBar* widget : g_debugger_window->findChildren<QToolBar*>())
|
||||
{
|
||||
QAction* action = menu->addAction(widget->windowTitle());
|
||||
action->setText(widget->windowTitle());
|
||||
action->setCheckable(true);
|
||||
action->setChecked(widget->isVisible());
|
||||
connect(action, &QAction::triggered, this, [widget]() {
|
||||
widget->setVisible(!widget->isVisible());
|
||||
});
|
||||
menu->addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
void DockManager::createWindowsMenu(QMenu* menu)
|
||||
{
|
||||
menu->clear();
|
||||
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts.at(m_current_layout);
|
||||
|
||||
// Create a menu that allows for multiple dock widgets of the same type to
|
||||
// be opened.
|
||||
QMenu* add_another_menu = menu->addMenu(tr("Add Another..."));
|
||||
|
||||
std::vector<DebuggerWidget*> add_another_widgets;
|
||||
std::set<std::string> add_another_types;
|
||||
for (const auto& [unique_name, widget] : layout.debuggerWidgets())
|
||||
{
|
||||
std::string type = widget->metaObject()->className();
|
||||
|
||||
if (widget->supportsMultipleInstances() && !add_another_types.contains(type))
|
||||
{
|
||||
add_another_widgets.emplace_back(widget);
|
||||
add_another_types.emplace(type);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(add_another_widgets.begin(), add_another_widgets.end(),
|
||||
[](const DebuggerWidget* lhs, const DebuggerWidget* rhs) {
|
||||
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
|
||||
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
|
||||
|
||||
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
|
||||
});
|
||||
|
||||
for (DebuggerWidget* widget : add_another_widgets)
|
||||
{
|
||||
const char* type = widget->metaObject()->className();
|
||||
|
||||
const auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(type);
|
||||
pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end());
|
||||
|
||||
QAction* action = add_another_menu->addAction(description_iterator->second.display_name);
|
||||
connect(action, &QAction::triggered, this, [this, type]() {
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).createDebuggerWidget(type);
|
||||
});
|
||||
}
|
||||
|
||||
if (add_another_widgets.empty())
|
||||
add_another_menu->setDisabled(true);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
struct DebuggerWidgetToggle
|
||||
{
|
||||
QString display_name;
|
||||
std::optional<int> suffix_number;
|
||||
QAction* action;
|
||||
};
|
||||
|
||||
std::vector<DebuggerWidgetToggle> toggles;
|
||||
std::set<std::string> toggle_types;
|
||||
|
||||
// Create a menu item for each open debugger widget.
|
||||
for (const auto& [unique_name, widget] : layout.debuggerWidgets())
|
||||
{
|
||||
QAction* action = new QAction(menu);
|
||||
action->setText(widget->displayName());
|
||||
action->setCheckable(true);
|
||||
action->setChecked(true);
|
||||
connect(action, &QAction::triggered, this, [this, unique_name]() {
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).destroyDebuggerWidget(unique_name);
|
||||
});
|
||||
|
||||
DebuggerWidgetToggle& toggle = toggles.emplace_back();
|
||||
toggle.display_name = widget->displayNameWithoutSuffix();
|
||||
toggle.suffix_number = widget->displayNameSuffixNumber();
|
||||
toggle.action = action;
|
||||
|
||||
toggle_types.emplace(widget->metaObject()->className());
|
||||
}
|
||||
|
||||
// Create menu items to open debugger widgets without any open instances.
|
||||
for (const auto& [type, desc] : DockTables::DEBUGGER_WIDGETS)
|
||||
{
|
||||
if (!toggle_types.contains(type))
|
||||
{
|
||||
QString display_name = QCoreApplication::translate("DebuggerWidget", desc.display_name);
|
||||
|
||||
QAction* action = new QAction(menu);
|
||||
action->setText(display_name);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(false);
|
||||
connect(action, &QAction::triggered, this, [this, type]() {
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).createDebuggerWidget(type);
|
||||
});
|
||||
|
||||
DebuggerWidgetToggle& toggle = toggles.emplace_back();
|
||||
toggle.display_name = display_name;
|
||||
toggle.suffix_number = std::nullopt;
|
||||
toggle.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(toggles.begin(), toggles.end(),
|
||||
[](const DebuggerWidgetToggle& lhs, const DebuggerWidgetToggle& rhs) {
|
||||
if (lhs.display_name == rhs.display_name)
|
||||
return lhs.suffix_number < rhs.suffix_number;
|
||||
|
||||
return lhs.display_name < rhs.display_name;
|
||||
});
|
||||
|
||||
for (const DebuggerWidgetToggle& toggle : toggles)
|
||||
menu->addAction(toggle.action);
|
||||
}
|
||||
|
||||
QWidget* DockManager::createLayoutSwitcher(QWidget* menu_bar)
|
||||
{
|
||||
QWidget* container = new QWidget;
|
||||
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, 2, 2, 0);
|
||||
container->setLayout(layout);
|
||||
|
||||
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);
|
||||
|
||||
bool layout_locked = Host::GetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", true);
|
||||
|
||||
QPushButton* lock_layout_toggle = new QPushButton;
|
||||
lock_layout_toggle->setCheckable(true);
|
||||
lock_layout_toggle->setChecked(layout_locked);
|
||||
lock_layout_toggle->setFlat(true);
|
||||
connect(lock_layout_toggle, &QPushButton::toggled, this, [this, lock_layout_toggle](bool checked) {
|
||||
setLayoutLocked(checked, lock_layout_toggle, true);
|
||||
});
|
||||
layout->addWidget(lock_layout_toggle);
|
||||
|
||||
setLayoutLocked(layout_locked, lock_layout_toggle, false);
|
||||
|
||||
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 (DockLayout& layout : m_layouts)
|
||||
{
|
||||
const char* cpu_name = DebugInterface::cpuName(layout.cpu());
|
||||
QString tab_name = QString("%1 (%2)").arg(layout.name()).arg(cpu_name);
|
||||
m_switcher->addTab(tab_name);
|
||||
}
|
||||
|
||||
m_plus_tab_index = m_switcher->addTab("+");
|
||||
m_current_tab_index = m_current_layout;
|
||||
|
||||
if (m_current_layout != DockLayout::INVALID_INDEX)
|
||||
m_switcher->setCurrentIndex(m_current_layout);
|
||||
|
||||
// If we don't have any layouts, the currently selected tab will never be
|
||||
// changed, so we respond to all clicks instead.
|
||||
if (!m_layouts.empty())
|
||||
m_tab_connection = connect(m_switcher, &QTabBar::currentChanged, this, &DockManager::layoutSwitcherTabChanged);
|
||||
else
|
||||
m_tab_connection = connect(m_switcher, &QTabBar::tabBarClicked, this, &DockManager::layoutSwitcherTabChanged);
|
||||
|
||||
layoutSwitcherStopBlink();
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherTabChanged(int index)
|
||||
{
|
||||
// Prevent recursion.
|
||||
if (m_ignore_current_tab_changed)
|
||||
return;
|
||||
|
||||
if (index == m_plus_tab_index)
|
||||
{
|
||||
if (m_current_tab_index >= 0 && m_current_tab_index < m_plus_tab_index)
|
||||
{
|
||||
m_ignore_current_tab_changed = true;
|
||||
m_switcher->setCurrentIndex(m_current_tab_index);
|
||||
m_ignore_current_tab_changed = false;
|
||||
}
|
||||
|
||||
auto name_validator = [this](const QString& name) {
|
||||
return !hasNameConflict(name, DockLayout::INVALID_INDEX);
|
||||
};
|
||||
|
||||
bool can_clone_current_layout = m_current_layout != DockLayout::INVALID_INDEX;
|
||||
|
||||
QPointer<LayoutEditorDialog> dialog = new LayoutEditorDialog(
|
||||
name_validator, can_clone_current_layout, g_debugger_window);
|
||||
|
||||
if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
|
||||
{
|
||||
DockLayout::Index new_layout = DockLayout::INVALID_INDEX;
|
||||
|
||||
const auto [mode, index] = dialog->initialState();
|
||||
switch (mode)
|
||||
{
|
||||
case LayoutEditorDialog::DEFAULT_LAYOUT:
|
||||
{
|
||||
const DockTables::DefaultDockLayout& default_layout = DockTables::DEFAULT_DOCK_LAYOUTS.at(index);
|
||||
new_layout = createLayout(dialog->name(), dialog->cpu(), false, default_layout.name);
|
||||
break;
|
||||
}
|
||||
case LayoutEditorDialog::BLANK_LAYOUT:
|
||||
{
|
||||
new_layout = createLayout(dialog->name(), dialog->cpu(), false);
|
||||
break;
|
||||
}
|
||||
case LayoutEditorDialog::CLONE_LAYOUT:
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
DockLayout::Index old_layout = m_current_layout;
|
||||
|
||||
// Freeze the current layout so we can copy the geometry.
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
new_layout = createLayout(dialog->name(), dialog->cpu(), false, m_layouts.at(old_layout));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateLayoutSwitcher();
|
||||
switchToLayout(new_layout);
|
||||
}
|
||||
|
||||
delete dialog.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
DockLayout::Index layout_index = static_cast<DockLayout::Index>(index);
|
||||
if (layout_index < 0 || layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
switchToLayout(layout_index);
|
||||
m_current_tab_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherTabMoved(int from, int to)
|
||||
{
|
||||
DockLayout::Index from_index = static_cast<DockLayout::Index>(from);
|
||||
DockLayout::Index to_index = static_cast<DockLayout::Index>(to);
|
||||
|
||||
if (from_index >= m_layouts.size() || to_index >= m_layouts.size())
|
||||
{
|
||||
// This happens when the user tries to move a layout to the right of the
|
||||
// plus button.
|
||||
updateLayoutSwitcher();
|
||||
return;
|
||||
}
|
||||
|
||||
DockLayout& from_layout = m_layouts[from_index];
|
||||
DockLayout& to_layout = m_layouts[to_index];
|
||||
|
||||
std::swap(from_layout, to_layout);
|
||||
|
||||
from_layout.save(from_index);
|
||||
to_layout.save(to_index);
|
||||
|
||||
if (from_index == m_current_layout)
|
||||
m_current_layout = to_index;
|
||||
else if (to_index == m_current_layout)
|
||||
m_current_layout = from_index;
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherContextMenu(QPoint pos)
|
||||
{
|
||||
DockLayout::Index layout_index = static_cast<DockLayout::Index>(m_switcher->tabAt(pos));
|
||||
if (layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts[layout_index];
|
||||
|
||||
QMenu* menu = new QMenu(m_switcher);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* edit_action = menu->addAction(tr("Edit Layout"));
|
||||
connect(edit_action, &QAction::triggered, [this, layout_index]() {
|
||||
if (layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts[layout_index];
|
||||
|
||||
auto name_validator = [this, layout_index](const QString& name) {
|
||||
return !hasNameConflict(name, layout_index);
|
||||
};
|
||||
|
||||
QPointer<LayoutEditorDialog> dialog = new LayoutEditorDialog(
|
||||
layout.name(), layout.cpu(), name_validator, g_debugger_window);
|
||||
|
||||
if (dialog->exec() != QDialog::Accepted || !name_validator(dialog->name()))
|
||||
return;
|
||||
|
||||
layout.setName(dialog->name());
|
||||
layout.setCpu(dialog->cpu());
|
||||
|
||||
layout.save(layout_index);
|
||||
|
||||
delete dialog.get();
|
||||
|
||||
updateLayoutSwitcher();
|
||||
});
|
||||
|
||||
QAction* reset_action = menu->addAction(tr("Reset Layout"));
|
||||
reset_action->setEnabled(layout.canReset());
|
||||
reset_action->connect(reset_action, &QAction::triggered, [this, layout_index]() {
|
||||
if (layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts[layout_index];
|
||||
if (!layout.canReset())
|
||||
return;
|
||||
|
||||
QString text = tr("Are you sure you want to reset layout '%1'?").arg(layout.name());
|
||||
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
bool current_layout = layout_index == m_current_layout;
|
||||
|
||||
if (current_layout)
|
||||
switchToLayout(DockLayout::INVALID_INDEX);
|
||||
|
||||
layout.reset();
|
||||
layout.save(layout_index);
|
||||
|
||||
if (current_layout)
|
||||
switchToLayout(layout_index);
|
||||
});
|
||||
|
||||
QAction* delete_action = menu->addAction(tr("Delete Layout"));
|
||||
connect(delete_action, &QAction::triggered, [this, layout_index]() {
|
||||
if (layout_index >= m_layouts.size())
|
||||
return;
|
||||
|
||||
DockLayout& layout = m_layouts[layout_index];
|
||||
|
||||
QString text = tr("Are you sure you want to delete layout '%1'?").arg(layout.name());
|
||||
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
deleteLayout(layout_index);
|
||||
updateLayoutSwitcher();
|
||||
});
|
||||
|
||||
menu->popup(m_switcher->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherStartBlink()
|
||||
{
|
||||
if (!m_switcher)
|
||||
return;
|
||||
|
||||
layoutSwitcherStopBlink();
|
||||
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_blink_tab = m_current_layout;
|
||||
m_blink_stage = 0;
|
||||
m_blink_timer->start(500);
|
||||
|
||||
layoutSwitcherUpdateBlink();
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherUpdateBlink()
|
||||
{
|
||||
if (!m_switcher)
|
||||
return;
|
||||
|
||||
if (m_blink_tab < m_switcher->count())
|
||||
{
|
||||
if (m_blink_stage % 2 == 0)
|
||||
m_switcher->setTabTextColor(m_blink_tab, Qt::red);
|
||||
else
|
||||
m_switcher->setTabTextColor(m_blink_tab, m_switcher->palette().text().color());
|
||||
}
|
||||
|
||||
m_blink_stage++;
|
||||
|
||||
if (m_blink_stage > 7)
|
||||
m_blink_timer->stop();
|
||||
}
|
||||
|
||||
void DockManager::layoutSwitcherStopBlink()
|
||||
{
|
||||
if (m_blink_timer->isActive())
|
||||
{
|
||||
if (m_blink_tab < m_switcher->count())
|
||||
m_switcher->setTabTextColor(m_blink_tab, m_switcher->palette().text().color());
|
||||
|
||||
m_blink_timer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool DockManager::hasNameConflict(const QString& name, DockLayout::Index layout_index)
|
||||
{
|
||||
std::string safe_name = Path::SanitizeFileName(name.toStdString());
|
||||
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
|
||||
{
|
||||
std::string other_safe_name = Path::SanitizeFileName(m_layouts[i].name().toStdString());
|
||||
if (i != layout_index && StringUtil::compareNoCase(other_safe_name, safe_name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DockManager::updateDockWidgetTitles()
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).updateDockWidgetTitles();
|
||||
}
|
||||
|
||||
const std::map<QString, QPointer<DebuggerWidget>>& DockManager::debuggerWidgets()
|
||||
{
|
||||
static std::map<QString, QPointer<DebuggerWidget>> dummy;
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return dummy;
|
||||
|
||||
return m_layouts.at(m_current_layout).debuggerWidgets();
|
||||
}
|
||||
|
||||
size_t DockManager::countDebuggerWidgetsOfType(const char* type)
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return 0;
|
||||
|
||||
return m_layouts.at(m_current_layout).countDebuggerWidgetsOfType(type);
|
||||
}
|
||||
|
||||
void DockManager::recreateDebuggerWidget(const QString& unique_name)
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).recreateDebuggerWidget(unique_name);
|
||||
}
|
||||
|
||||
void DockManager::destroyDebuggerWidget(const QString& unique_name)
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).destroyDebuggerWidget(unique_name);
|
||||
}
|
||||
|
||||
void DockManager::setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary)
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_layouts.at(m_current_layout).setPrimaryDebuggerWidget(widget, is_primary);
|
||||
}
|
||||
|
||||
void DockManager::switchToDebuggerWidget(DebuggerWidget* widget)
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
for (const auto& [unique_name, test_widget] : m_layouts.at(m_current_layout).debuggerWidgets())
|
||||
{
|
||||
if (widget == test_widget)
|
||||
{
|
||||
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
|
||||
controller->setAsCurrentTab();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DockManager::updateStyleSheets()
|
||||
{
|
||||
for (DockLayout& layout : m_layouts)
|
||||
for (const auto& [unique_name, widget] : layout.debuggerWidgets())
|
||||
widget->updateStyleSheet();
|
||||
}
|
||||
|
||||
bool DockManager::isLayoutLocked()
|
||||
{
|
||||
return m_layout_locked;
|
||||
}
|
||||
|
||||
void DockManager::setLayoutLocked(bool locked, QPushButton* lock_layout_toggle, bool write_back)
|
||||
{
|
||||
m_layout_locked = locked;
|
||||
|
||||
if (lock_layout_toggle)
|
||||
{
|
||||
if (m_layout_locked)
|
||||
{
|
||||
lock_layout_toggle->setText(tr("Layout Locked"));
|
||||
lock_layout_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-lock")));
|
||||
}
|
||||
else
|
||||
{
|
||||
lock_layout_toggle->setText(tr("Layout Unlocked"));
|
||||
lock_layout_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-unlock")));
|
||||
}
|
||||
}
|
||||
|
||||
updateToolBarLockState();
|
||||
|
||||
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
|
||||
{
|
||||
auto stack = static_cast<KDDockWidgets::QtWidgets::Stack*>(group->stack()->view());
|
||||
stack->setTabsClosable(!m_layout_locked);
|
||||
|
||||
// HACK: Make sure the sizes of the tabs get updated.
|
||||
if (stack->tabBar()->count() > 0)
|
||||
stack->tabBar()->setTabText(0, stack->tabBar()->tabText(0));
|
||||
}
|
||||
|
||||
if (write_back)
|
||||
{
|
||||
Host::SetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", m_layout_locked);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
}
|
||||
|
||||
void DockManager::updateToolBarLockState()
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
|
||||
toolbar->setMovable(!m_layout_locked || toolbar->isFloating());
|
||||
}
|
||||
|
||||
std::optional<BreakPointCpu> DockManager::cpu()
|
||||
{
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return std::nullopt;
|
||||
|
||||
return m_layouts.at(m_current_layout).cpu();
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return nullptr;
|
||||
|
||||
DockManager& manager = g_debugger_window->dockManager();
|
||||
if (manager.m_current_layout == DockLayout::INVALID_INDEX)
|
||||
return nullptr;
|
||||
|
||||
return manager.m_layouts.at(manager.m_current_layout).createDockWidget(name);
|
||||
}
|
||||
|
||||
bool DockManager::dragAboutToStart(KDDockWidgets::Core::Draggable* draggable)
|
||||
{
|
||||
bool locked = true;
|
||||
if (g_debugger_window)
|
||||
locked = g_debugger_window->dockManager().isLayoutLocked();
|
||||
|
||||
KDDockWidgets::Config::self().setDropIndicatorsInhibited(locked);
|
||||
|
||||
if (draggable->isInProgrammaticDrag())
|
||||
return true;
|
||||
|
||||
// Allow floating windows to be dragged around even if the layout is locked.
|
||||
if (draggable->isWindow())
|
||||
return true;
|
||||
|
||||
if (!g_debugger_window)
|
||||
return false;
|
||||
|
||||
return !locked;
|
||||
}
|
||||
118
pcsx2-qt/Debugger/Docking/DockManager.h
Normal file
118
pcsx2-qt/Debugger/Docking/DockManager.h
Normal file
@@ -0,0 +1,118 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Debugger/Docking/DockLayout.h"
|
||||
|
||||
#include <kddockwidgets/MainWindow.h>
|
||||
#include <kddockwidgets/DockWidget.h>
|
||||
#include <kddockwidgets/core/DockRegistry.h>
|
||||
#include <kddockwidgets/core/DockWidget.h>
|
||||
#include <kddockwidgets/core/Draggable_p.h>
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QTabBar>
|
||||
|
||||
class DockManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockManager(QObject* parent = nullptr);
|
||||
|
||||
DockManager(const DockManager& rhs) = delete;
|
||||
DockManager& operator=(const DockManager& rhs) = delete;
|
||||
|
||||
DockManager(DockManager&& rhs) = delete;
|
||||
DockManager& operator=(DockManager&&) = delete;
|
||||
|
||||
// This needs to be called before any KDDockWidgets objects are created
|
||||
// including the debugger window itself.
|
||||
static void configureDockingSystem();
|
||||
|
||||
template <typename... Args>
|
||||
DockLayout::Index createLayout(Args&&... args)
|
||||
{
|
||||
DockLayout::Index layout_index = m_layouts.size();
|
||||
|
||||
if (m_layouts.empty())
|
||||
{
|
||||
// Delete the placeholder created in DockManager::deleteLayout.
|
||||
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
|
||||
delete dock;
|
||||
}
|
||||
|
||||
m_layouts.emplace_back(std::forward<Args>(args)..., layout_index);
|
||||
|
||||
return layout_index;
|
||||
}
|
||||
|
||||
bool deleteLayout(DockLayout::Index layout_index);
|
||||
|
||||
void switchToLayout(DockLayout::Index layout_index, bool blink_tab = false);
|
||||
bool switchToLayoutWithCPU(BreakPointCpu cpu, bool blink_tab = false);
|
||||
|
||||
void loadLayouts();
|
||||
bool saveLayouts();
|
||||
bool saveCurrentLayout();
|
||||
|
||||
QString currentLayoutName();
|
||||
bool canResetCurrentLayout();
|
||||
|
||||
void resetCurrentLayout();
|
||||
void resetDefaultLayouts();
|
||||
void resetAllLayouts();
|
||||
|
||||
void createToolsMenu(QMenu* menu);
|
||||
void createWindowsMenu(QMenu* menu);
|
||||
|
||||
QWidget* createLayoutSwitcher(QWidget* menu_bar);
|
||||
void updateLayoutSwitcher();
|
||||
void layoutSwitcherTabChanged(int index);
|
||||
void layoutSwitcherTabMoved(int from, int to);
|
||||
void layoutSwitcherContextMenu(QPoint pos);
|
||||
void layoutSwitcherStartBlink();
|
||||
void layoutSwitcherUpdateBlink();
|
||||
void layoutSwitcherStopBlink();
|
||||
|
||||
bool hasNameConflict(const QString& name, DockLayout::Index layout_index);
|
||||
|
||||
void updateDockWidgetTitles();
|
||||
|
||||
const std::map<QString, QPointer<DebuggerWidget>>& debuggerWidgets();
|
||||
size_t countDebuggerWidgetsOfType(const char* type);
|
||||
void recreateDebuggerWidget(const QString& unique_name);
|
||||
void destroyDebuggerWidget(const QString& unique_name);
|
||||
void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary);
|
||||
void switchToDebuggerWidget(DebuggerWidget* widget);
|
||||
|
||||
void updateStyleSheets();
|
||||
|
||||
bool isLayoutLocked();
|
||||
void setLayoutLocked(bool locked, QPushButton* lock_layout_toggle, bool write_back);
|
||||
void updateToolBarLockState();
|
||||
|
||||
std::optional<BreakPointCpu> cpu();
|
||||
|
||||
private:
|
||||
static KDDockWidgets::Core::DockWidget* dockWidgetFactory(const QString& name);
|
||||
static bool dragAboutToStart(KDDockWidgets::Core::Draggable* draggable);
|
||||
|
||||
std::vector<DockLayout> m_layouts;
|
||||
DockLayout::Index m_current_layout = DockLayout::INVALID_INDEX;
|
||||
|
||||
QTabBar* m_switcher = nullptr;
|
||||
int m_plus_tab_index = -1;
|
||||
int m_current_tab_index = -1;
|
||||
bool m_ignore_current_tab_changed = false;
|
||||
|
||||
QMetaObject::Connection m_tab_connection;
|
||||
|
||||
bool m_layout_locked = true;
|
||||
|
||||
QTimer* m_blink_timer = nullptr;
|
||||
int m_blink_tab = 0;
|
||||
int m_blink_stage = 0;
|
||||
};
|
||||
226
pcsx2-qt/Debugger/Docking/DockTables.cpp
Normal file
226
pcsx2-qt/Debugger/Docking/DockTables.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockTables.h"
|
||||
|
||||
#include "Debugger/DebuggerEvents.h"
|
||||
#include "Debugger/DisassemblyWidget.h"
|
||||
#include "Debugger/RegisterWidget.h"
|
||||
#include "Debugger/StackWidget.h"
|
||||
#include "Debugger/ThreadWidget.h"
|
||||
#include "Debugger/Breakpoints/BreakpointWidget.h"
|
||||
#include "Debugger/Memory/MemorySearchWidget.h"
|
||||
#include "Debugger/Memory/MemoryViewWidget.h"
|
||||
#include "Debugger/Memory/SavedAddressesWidget.h"
|
||||
#include "Debugger/SymbolTree/SymbolTreeWidgets.h"
|
||||
|
||||
#include "common/MD5Digest.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
using namespace DockUtils;
|
||||
|
||||
#define DEBUGGER_WIDGET(type, display_name, preferred_location) \
|
||||
{ \
|
||||
#type, \
|
||||
{ \
|
||||
[](const DebuggerWidgetParameters& parameters) -> DebuggerWidget* { \
|
||||
DebuggerWidget* widget = new type(parameters); \
|
||||
widget->handleEvent(DebuggerEvents::Refresh()); \
|
||||
return widget; \
|
||||
}, \
|
||||
display_name, \
|
||||
preferred_location \
|
||||
} \
|
||||
}
|
||||
|
||||
const std::map<std::string, DockTables::DebuggerWidgetDescription> DockTables::DEBUGGER_WIDGETS = {
|
||||
DEBUGGER_WIDGET(BreakpointWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Breakpoints"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(DisassemblyWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Disassembly"), TOP_RIGHT),
|
||||
DEBUGGER_WIDGET(FunctionTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Functions"), TOP_LEFT),
|
||||
DEBUGGER_WIDGET(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Globals"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Locals"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(MemorySearchWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory Search"), TOP_LEFT),
|
||||
DEBUGGER_WIDGET(MemoryViewWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Parameters"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(RegisterWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Registers"), TOP_LEFT),
|
||||
DEBUGGER_WIDGET(SavedAddressesWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Saved Addresses"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(StackWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Stack"), BOTTOM_MIDDLE),
|
||||
DEBUGGER_WIDGET(ThreadWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Threads"), BOTTOM_MIDDLE),
|
||||
};
|
||||
|
||||
#undef DEBUGGER_WIDGET
|
||||
|
||||
const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUTS = {
|
||||
{
|
||||
.name = QT_TRANSLATE_NOOP("DebuggerLayout", "R5900"),
|
||||
.cpu = BREAKPOINT_EE,
|
||||
.groups = {
|
||||
/* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT},
|
||||
/* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT},
|
||||
/* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT},
|
||||
},
|
||||
.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},
|
||||
},
|
||||
.toolbars = {
|
||||
"toolBarDebug",
|
||||
"toolBarFile",
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = QT_TRANSLATE_NOOP("DebuggerLayout", "R3000"),
|
||||
.cpu = BREAKPOINT_IOP,
|
||||
.groups = {
|
||||
/* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT},
|
||||
/* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT},
|
||||
/* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT},
|
||||
},
|
||||
.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},
|
||||
},
|
||||
.toolbars = {
|
||||
"toolBarDebug",
|
||||
"toolBarFile",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const DockTables::DefaultDockLayout* DockTables::defaultLayout(const std::string& name)
|
||||
{
|
||||
for (const DockTables::DefaultDockLayout& default_layout : DockTables::DEFAULT_DOCK_LAYOUTS)
|
||||
if (default_layout.name == name)
|
||||
return &default_layout;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string& DockTables::hashDefaultLayouts()
|
||||
{
|
||||
static std::string hash;
|
||||
if (!hash.empty())
|
||||
return hash;
|
||||
|
||||
MD5Digest md5;
|
||||
|
||||
u32 hash_version = 1;
|
||||
md5.Update(&hash_version, sizeof(hash_version));
|
||||
|
||||
u32 layout_count = static_cast<u32>(DEFAULT_DOCK_LAYOUTS.size());
|
||||
md5.Update(&layout_count, sizeof(layout_count));
|
||||
|
||||
for (const DefaultDockLayout& layout : DEFAULT_DOCK_LAYOUTS)
|
||||
hashDefaultLayout(layout, md5);
|
||||
|
||||
u8 digest[16];
|
||||
md5.Final(digest);
|
||||
hash = fmt::format(
|
||||
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void DockTables::hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5)
|
||||
{
|
||||
u32 layout_name_size = static_cast<u32>(layout.name.size());
|
||||
md5.Update(&layout_name_size, sizeof(layout_name_size));
|
||||
md5.Update(layout.name.data(), layout_name_size);
|
||||
|
||||
const char* cpu_name = DebugInterface::cpuName(layout.cpu);
|
||||
u32 cpu_name_size = static_cast<u32>(strlen(cpu_name));
|
||||
md5.Update(&cpu_name_size, sizeof(cpu_name_size));
|
||||
md5.Update(cpu_name, cpu_name_size);
|
||||
|
||||
u32 group_count = static_cast<u32>(layout.groups.size());
|
||||
md5.Update(&group_count, sizeof(group_count));
|
||||
|
||||
for (const DefaultDockGroupDescription& group : layout.groups)
|
||||
hashDefaultGroup(group, md5);
|
||||
|
||||
u32 widget_count = static_cast<u32>(layout.widgets.size());
|
||||
md5.Update(&widget_count, sizeof(widget_count));
|
||||
|
||||
for (const DefaultDockWidgetDescription& widget : layout.widgets)
|
||||
hashDefaultDockWidget(widget, md5);
|
||||
|
||||
u32 toolbar_count = static_cast<u32>(layout.toolbars.size());
|
||||
md5.Update(&toolbar_count, sizeof(toolbar_count));
|
||||
for (const std::string& toolbar : layout.toolbars)
|
||||
{
|
||||
u32 toolbar_size = toolbar.size();
|
||||
md5.Update(&toolbar_size, sizeof(toolbar_size));
|
||||
md5.Update(toolbar.data(), toolbar.size());
|
||||
}
|
||||
}
|
||||
|
||||
void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5)
|
||||
{
|
||||
// This is inline here so that it's obvious that changing it will affect the
|
||||
// result of the hash.
|
||||
const char* location = "";
|
||||
switch (group.location)
|
||||
{
|
||||
case KDDockWidgets::Location_None:
|
||||
location = "none";
|
||||
break;
|
||||
case KDDockWidgets::Location_OnLeft:
|
||||
location = "left";
|
||||
break;
|
||||
case KDDockWidgets::Location_OnTop:
|
||||
location = "top";
|
||||
break;
|
||||
case KDDockWidgets::Location_OnRight:
|
||||
location = "right";
|
||||
break;
|
||||
case KDDockWidgets::Location_OnBottom:
|
||||
location = "bottom";
|
||||
break;
|
||||
}
|
||||
|
||||
u32 location_size = static_cast<u32>(strlen(location));
|
||||
md5.Update(&location_size, sizeof(location_size));
|
||||
md5.Update(location, location_size);
|
||||
|
||||
u32 parent = static_cast<u32>(group.parent);
|
||||
md5.Update(&parent, sizeof(parent));
|
||||
}
|
||||
|
||||
void DockTables::hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5)
|
||||
{
|
||||
u32 type_size = static_cast<u32>(widget.type.size());
|
||||
md5.Update(&type_size, sizeof(type_size));
|
||||
md5.Update(widget.type.data(), type_size);
|
||||
|
||||
u32 group = static_cast<u32>(widget.group);
|
||||
md5.Update(&group, sizeof(group));
|
||||
}
|
||||
75
pcsx2-qt/Debugger/Docking/DockTables.h
Normal file
75
pcsx2-qt/Debugger/Docking/DockTables.h
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DockUtils.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/KDDockWidgets.h>
|
||||
|
||||
class MD5Digest;
|
||||
|
||||
class DebuggerWidget;
|
||||
struct DebuggerWidgetParameters;
|
||||
|
||||
namespace DockTables
|
||||
{
|
||||
struct DebuggerWidgetDescription
|
||||
{
|
||||
DebuggerWidget* (*create_widget)(const DebuggerWidgetParameters& parameters);
|
||||
|
||||
// The untranslated string displayed as the dock widget tab text.
|
||||
const char* display_name;
|
||||
|
||||
// This is used to determine which group dock widgets of this type are
|
||||
// added to when they're opened from the Windows menu.
|
||||
DockUtils::PreferredLocation preferred_location;
|
||||
};
|
||||
|
||||
extern const std::map<std::string, DebuggerWidgetDescription> DEBUGGER_WIDGETS;
|
||||
|
||||
enum class DefaultDockGroup
|
||||
{
|
||||
ROOT = -1,
|
||||
TOP_RIGHT = 0,
|
||||
BOTTOM = 1,
|
||||
TOP_LEFT = 2
|
||||
};
|
||||
|
||||
struct DefaultDockGroupDescription
|
||||
{
|
||||
KDDockWidgets::Location location;
|
||||
DefaultDockGroup parent;
|
||||
};
|
||||
|
||||
extern const std::vector<DefaultDockGroupDescription> DEFAULT_DOCK_GROUPS;
|
||||
|
||||
struct DefaultDockWidgetDescription
|
||||
{
|
||||
std::string type;
|
||||
DefaultDockGroup group;
|
||||
};
|
||||
|
||||
struct DefaultDockLayout
|
||||
{
|
||||
std::string name;
|
||||
BreakPointCpu cpu;
|
||||
std::vector<DefaultDockGroupDescription> groups;
|
||||
std::vector<DefaultDockWidgetDescription> widgets;
|
||||
std::set<std::string> toolbars;
|
||||
};
|
||||
|
||||
extern const std::vector<DefaultDockLayout> DEFAULT_DOCK_LAYOUTS;
|
||||
|
||||
const DefaultDockLayout* defaultLayout(const std::string& name);
|
||||
|
||||
// This is used to determine if the user has updated and we need to recreate
|
||||
// the default layouts.
|
||||
const std::string& hashDefaultLayouts();
|
||||
|
||||
void hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5);
|
||||
void hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5);
|
||||
void hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5);
|
||||
} // namespace DockTables
|
||||
98
pcsx2-qt/Debugger/Docking/DockUtils.cpp
Normal file
98
pcsx2-qt/Debugger/Docking/DockUtils.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockUtils.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/core/DockRegistry.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/qtwidgets/DockWidget.h>
|
||||
#include <kddockwidgets/qtwidgets/Group.h>
|
||||
|
||||
DockUtils::DockWidgetPair DockUtils::dockWidgetFromName(const QString& unique_name)
|
||||
{
|
||||
KDDockWidgets::Vector<QString> names{unique_name};
|
||||
KDDockWidgets::Vector<KDDockWidgets::Core::DockWidget*> dock_widgets =
|
||||
KDDockWidgets::DockRegistry::self()->dockWidgets(names);
|
||||
if (dock_widgets.size() != 1 || !dock_widgets[0])
|
||||
return {};
|
||||
|
||||
return {dock_widgets[0], static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_widgets[0]->view())};
|
||||
}
|
||||
|
||||
void DockUtils::insertDockWidgetAtPreferredLocation(
|
||||
KDDockWidgets::Core::DockWidget* dock_widget,
|
||||
PreferredLocation location,
|
||||
KDDockWidgets::QtWidgets::MainWindow* window)
|
||||
{
|
||||
int width = window->width();
|
||||
int height = window->height();
|
||||
int half_width = width / 2;
|
||||
int half_height = height / 2;
|
||||
|
||||
QPoint preferred_location;
|
||||
switch (location)
|
||||
{
|
||||
case DockUtils::TOP_LEFT:
|
||||
preferred_location = {0, 0};
|
||||
break;
|
||||
case DockUtils::TOP_MIDDLE:
|
||||
preferred_location = {half_width, 0};
|
||||
break;
|
||||
case DockUtils::TOP_RIGHT:
|
||||
preferred_location = {width, 0};
|
||||
break;
|
||||
case DockUtils::MIDDLE_LEFT:
|
||||
preferred_location = {0, half_height};
|
||||
break;
|
||||
case DockUtils::MIDDLE_MIDDLE:
|
||||
preferred_location = {half_width, half_height};
|
||||
break;
|
||||
case DockUtils::MIDDLE_RIGHT:
|
||||
preferred_location = {width, half_height};
|
||||
break;
|
||||
case DockUtils::BOTTOM_LEFT:
|
||||
preferred_location = {0, height};
|
||||
break;
|
||||
case DockUtils::BOTTOM_MIDDLE:
|
||||
preferred_location = {half_width, height};
|
||||
break;
|
||||
case DockUtils::BOTTOM_RIGHT:
|
||||
preferred_location = {width, height};
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the dock group which is closest to the preferred location.
|
||||
KDDockWidgets::Core::Group* best_group = nullptr;
|
||||
int best_distance_squared = 0;
|
||||
|
||||
for (KDDockWidgets::Core::Group* group_controller : KDDockWidgets::DockRegistry::self()->groups())
|
||||
{
|
||||
if (group_controller->isFloating())
|
||||
continue;
|
||||
|
||||
auto group = static_cast<KDDockWidgets::QtWidgets::Group*>(group_controller->view());
|
||||
|
||||
QPoint local_midpoint = group->pos() + QPoint(group->width() / 2, group->height() / 2);
|
||||
QPoint midpoint = group->mapTo(window, local_midpoint);
|
||||
QPoint delta = midpoint - preferred_location;
|
||||
int distance_squared = delta.x() * delta.x() + delta.y() * delta.y();
|
||||
|
||||
if (!best_group || distance_squared < best_distance_squared)
|
||||
{
|
||||
best_group = group_controller;
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_group && best_group->dockWidgetCount() > 0)
|
||||
{
|
||||
KDDockWidgets::Core::DockWidget* other_dock_widget = best_group->dockWidgetAt(0);
|
||||
other_dock_widget->addDockWidgetAsTab(dock_widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dock_view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_widget->view());
|
||||
window->addDockWidget(dock_view, KDDockWidgets::Location_OnTop);
|
||||
}
|
||||
}
|
||||
40
pcsx2-qt/Debugger/Docking/DockUtils.h
Normal file
40
pcsx2-qt/Debugger/Docking/DockUtils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kddockwidgets/KDDockWidgets.h>
|
||||
#include <kddockwidgets/core/DockWidget.h>
|
||||
#include <kddockwidgets/qtwidgets/MainWindow.h>
|
||||
|
||||
namespace DockUtils
|
||||
{
|
||||
inline const constexpr int MAX_LAYOUT_NAME_SIZE = 40;
|
||||
inline const constexpr int MAX_DOCK_WIDGET_NAME_SIZE = 40;
|
||||
|
||||
struct DockWidgetPair
|
||||
{
|
||||
KDDockWidgets::Core::DockWidget* controller = nullptr;
|
||||
KDDockWidgets::QtWidgets::DockWidget* view = nullptr;
|
||||
};
|
||||
|
||||
DockWidgetPair dockWidgetFromName(const QString& unique_name);
|
||||
|
||||
enum PreferredLocation
|
||||
{
|
||||
TOP_LEFT,
|
||||
TOP_MIDDLE,
|
||||
TOP_RIGHT,
|
||||
MIDDLE_LEFT,
|
||||
MIDDLE_MIDDLE,
|
||||
MIDDLE_RIGHT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_MIDDLE,
|
||||
BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
void insertDockWidgetAtPreferredLocation(
|
||||
KDDockWidgets::Core::DockWidget* dock_widget,
|
||||
PreferredLocation location,
|
||||
KDDockWidgets::QtWidgets::MainWindow* window);
|
||||
} // namespace DockUtils
|
||||
303
pcsx2-qt/Debugger/Docking/DockViews.cpp
Normal file
303
pcsx2-qt/Debugger/Docking/DockViews.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DockViews.h"
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
#include "Debugger/Docking/DropIndicators.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/core/TabBar.h>
|
||||
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
|
||||
|
||||
#include <QtGui/QActionGroup>
|
||||
#include <QtGui/QPalette>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createDockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options,
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options,
|
||||
Qt::WindowFlags window_flags) const
|
||||
{
|
||||
return new DockWidget(unique_name, options, layout_saver_options, window_flags);
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createTitleBar(
|
||||
KDDockWidgets::Core::TitleBar* controller,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockTitleBar(controller, parent);
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createStack(
|
||||
KDDockWidgets::Core::Stack* controller,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockStack(controller, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createTabBar(
|
||||
KDDockWidgets::Core::TabBar* tabBar,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockTabBar(tabBar, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockViewFactory::createClassicIndicatorWindow(
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockDropIndicatorProxy(classic_indicators);
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockViewFactory::createFallbackClassicIndicatorWindow(
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return KDDockWidgets::QtWidgets::ViewFactory::createClassicIndicatorWindow(classic_indicators, parent);
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::View* DockViewFactory::createSegmentedDropIndicatorOverlayView(
|
||||
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller,
|
||||
KDDockWidgets::Core::View* parent) const
|
||||
{
|
||||
return new DockSegmentedDropIndicatorOverlay(controller, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockWidget::DockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options,
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options,
|
||||
Qt::WindowFlags window_flags)
|
||||
: KDDockWidgets::QtWidgets::DockWidget(unique_name, options, layout_saver_options, window_flags)
|
||||
{
|
||||
connect(this, &DockWidget::isOpenChanged, this, &DockWidget::openStateChanged);
|
||||
}
|
||||
|
||||
void DockWidget::openStateChanged(bool open)
|
||||
{
|
||||
// The LayoutSaver class will close a bunch of dock widgets. We only want to
|
||||
// delete the dock widgets when they're being closed by the user.
|
||||
if (KDDockWidgets::LayoutSaver::restoreInProgress())
|
||||
return;
|
||||
|
||||
if (!open && g_debugger_window)
|
||||
g_debugger_window->dockManager().destroyDebuggerWidget(uniqueName());
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockTitleBar::DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent)
|
||||
: KDDockWidgets::QtWidgets::TitleBar(controller, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DockTitleBar::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
|
||||
KDDockWidgets::QtWidgets::TitleBar::mouseDoubleClickEvent(event);
|
||||
else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockStack::DockStack(KDDockWidgets::Core::Stack* controller, QWidget* parent)
|
||||
: KDDockWidgets::QtWidgets::Stack(controller, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DockStack::init()
|
||||
{
|
||||
KDDockWidgets::QtWidgets::Stack::init();
|
||||
|
||||
if (g_debugger_window)
|
||||
{
|
||||
bool locked = g_debugger_window->dockManager().isLayoutLocked();
|
||||
setTabsClosable(!locked);
|
||||
}
|
||||
}
|
||||
|
||||
void DockStack::mouseDoubleClickEvent(QMouseEvent* ev)
|
||||
{
|
||||
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
|
||||
KDDockWidgets::QtWidgets::Stack::mouseDoubleClickEvent(ev);
|
||||
else
|
||||
ev->ignore();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
|
||||
: KDDockWidgets::QtWidgets::TabBar(controller, parent)
|
||||
{
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::openContextMenu);
|
||||
}
|
||||
|
||||
void DockTabBar::openContextMenu(QPoint pos)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
int tab_index = tabAt(pos);
|
||||
|
||||
// Filter out the placeholder widget displayed when there are no layouts.
|
||||
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
size_t dock_widgets_of_type = g_debugger_window->dockManager().countDebuggerWidgetsOfType(
|
||||
widget->metaObject()->className());
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* rename_action = menu->addAction(tr("Rename"));
|
||||
connect(rename_action, &QAction::triggered, this, [this, tab_index]() {
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
bool ok;
|
||||
QString new_name = QInputDialog::getText(
|
||||
this, tr("Rename Window"), tr("New name:"), QLineEdit::Normal, widget->displayNameWithoutSuffix(), &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
if (!widget->setCustomDisplayName(new_name))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid Name"), tr("The specified name is too long."));
|
||||
return;
|
||||
}
|
||||
|
||||
g_debugger_window->dockManager().updateDockWidgetTitles();
|
||||
});
|
||||
|
||||
QAction* reset_name_action = menu->addAction(tr("Reset Name"));
|
||||
reset_name_action->setEnabled(!widget->customDisplayName().isEmpty());
|
||||
connect(reset_name_action, &QAction::triggered, this, [this, tab_index] {
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
widget->setCustomDisplayName(QString());
|
||||
g_debugger_window->dockManager().updateDockWidgetTitles();
|
||||
});
|
||||
|
||||
QAction* primary_action = menu->addAction(tr("Primary"));
|
||||
primary_action->setCheckable(true);
|
||||
primary_action->setChecked(widget->isPrimary());
|
||||
primary_action->setEnabled(dock_widgets_of_type > 1);
|
||||
connect(primary_action, &QAction::triggered, this, [this, tab_index](bool checked) {
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
g_debugger_window->dockManager().setPrimaryDebuggerWidget(widget, checked);
|
||||
});
|
||||
|
||||
QMenu* set_target_menu = menu->addMenu(tr("Set Target"));
|
||||
QActionGroup* set_target_group = new QActionGroup(menu);
|
||||
set_target_group->setExclusive(true);
|
||||
|
||||
for (BreakPointCpu cpu : DEBUG_CPUS)
|
||||
{
|
||||
const char* long_cpu_name = DebugInterface::longCpuName(cpu);
|
||||
const char* cpu_name = DebugInterface::cpuName(cpu);
|
||||
QString text = QString("%1 (%2)").arg(long_cpu_name).arg(cpu_name);
|
||||
|
||||
QAction* cpu_action = set_target_menu->addAction(text);
|
||||
cpu_action->setCheckable(true);
|
||||
cpu_action->setChecked(widget->cpuOverride().has_value() && *widget->cpuOverride() == cpu);
|
||||
connect(cpu_action, &QAction::triggered, this, [this, tab_index, cpu]() {
|
||||
setCpuOverrideForTab(tab_index, cpu);
|
||||
});
|
||||
set_target_group->addAction(cpu_action);
|
||||
}
|
||||
|
||||
set_target_menu->addSeparator();
|
||||
|
||||
QAction* inherit_action = set_target_menu->addAction(tr("Inherit From Layout"));
|
||||
inherit_action->setCheckable(true);
|
||||
inherit_action->setChecked(!widget->cpuOverride().has_value());
|
||||
connect(inherit_action, &QAction::triggered, this, [this, tab_index]() {
|
||||
setCpuOverrideForTab(tab_index, std::nullopt);
|
||||
});
|
||||
set_target_group->addAction(inherit_action);
|
||||
|
||||
QAction* close_action = menu->addAction(tr("Close"));
|
||||
connect(close_action, &QAction::triggered, this, [this, tab_index]() {
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
g_debugger_window->dockManager().destroyDebuggerWidget(widget->uniqueName());
|
||||
});
|
||||
|
||||
menu->popup(mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void DockTabBar::setCpuOverrideForTab(int tab_index, std::optional<BreakPointCpu> cpu_override)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
return;
|
||||
|
||||
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
if (!widget->setCpuOverride(cpu_override))
|
||||
g_debugger_window->dockManager().recreateDebuggerWidget(view->uniqueName());
|
||||
|
||||
g_debugger_window->dockManager().updateDockWidgetTitles();
|
||||
}
|
||||
|
||||
DockTabBar::WidgetsFromTabIndexResult DockTabBar::widgetsFromTabIndex(int tab_index)
|
||||
{
|
||||
KDDockWidgets::Core::TabBar* tab_bar_controller = asController<KDDockWidgets::Core::TabBar>();
|
||||
if (!tab_bar_controller)
|
||||
return {};
|
||||
|
||||
KDDockWidgets::Core::DockWidget* dock_controller = tab_bar_controller->dockWidgetAt(tab_index);
|
||||
if (!dock_controller)
|
||||
return {};
|
||||
|
||||
auto dock_view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_controller->view());
|
||||
|
||||
DebuggerWidget* widget = qobject_cast<DebuggerWidget*>(dock_view->widget());
|
||||
if (!widget)
|
||||
return {};
|
||||
|
||||
return {widget, dock_controller, dock_view};
|
||||
}
|
||||
|
||||
void DockTabBar::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
|
||||
KDDockWidgets::QtWidgets::TabBar::mouseDoubleClickEvent(event);
|
||||
else
|
||||
event->ignore();
|
||||
}
|
||||
113
pcsx2-qt/Debugger/Docking/DockViews.h
Normal file
113
pcsx2-qt/Debugger/Docking/DockViews.h
Normal file
@@ -0,0 +1,113 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <kddockwidgets/qtwidgets/ViewFactory.h>
|
||||
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
|
||||
#include <kddockwidgets/qtwidgets/views/Stack.h>
|
||||
#include <kddockwidgets/qtwidgets/views/TitleBar.h>
|
||||
#include <kddockwidgets/qtwidgets/views/TabBar.h>
|
||||
|
||||
class DebuggerWidget;
|
||||
class DockManager;
|
||||
|
||||
class DockViewFactory : public KDDockWidgets::QtWidgets::ViewFactory
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
KDDockWidgets::Core::View* createDockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options = {},
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options = {},
|
||||
Qt::WindowFlags window_flags = {}) const override;
|
||||
|
||||
KDDockWidgets::Core::View* createTitleBar(
|
||||
KDDockWidgets::Core::TitleBar* controller,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
|
||||
KDDockWidgets::Core::View* createStack(
|
||||
KDDockWidgets::Core::Stack* controller,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
|
||||
KDDockWidgets::Core::View* createTabBar(
|
||||
KDDockWidgets::Core::TabBar* tabBar,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* createClassicIndicatorWindow(
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* createFallbackClassicIndicatorWindow(
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
|
||||
KDDockWidgets::Core::View* parent) const;
|
||||
|
||||
KDDockWidgets::Core::View* createSegmentedDropIndicatorOverlayView(
|
||||
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller,
|
||||
KDDockWidgets::Core::View* parent) const override;
|
||||
};
|
||||
|
||||
class DockWidget : public KDDockWidgets::QtWidgets::DockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockWidget(
|
||||
const QString& unique_name,
|
||||
KDDockWidgets::DockWidgetOptions options,
|
||||
KDDockWidgets::LayoutSaverOptions layout_saver_options,
|
||||
Qt::WindowFlags window_flags);
|
||||
|
||||
protected:
|
||||
void openStateChanged(bool open);
|
||||
};
|
||||
|
||||
class DockTitleBar : public KDDockWidgets::QtWidgets::TitleBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
};
|
||||
|
||||
class DockStack : public KDDockWidgets::QtWidgets::Stack
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockStack(KDDockWidgets::Core::Stack* controller, QWidget* parent = nullptr);
|
||||
|
||||
void init() override;
|
||||
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
};
|
||||
|
||||
class DockTabBar : public KDDockWidgets::QtWidgets::TabBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void openContextMenu(QPoint pos);
|
||||
|
||||
struct WidgetsFromTabIndexResult
|
||||
{
|
||||
DebuggerWidget* debugger_widget = nullptr;
|
||||
KDDockWidgets::Core::DockWidget* controller = nullptr;
|
||||
KDDockWidgets::QtWidgets::DockWidget* view = nullptr;
|
||||
};
|
||||
|
||||
void setCpuOverrideForTab(int tab_index, std::optional<BreakPointCpu> cpu_override);
|
||||
WidgetsFromTabIndexResult widgetsFromTabIndex(int tab_index);
|
||||
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
};
|
||||
571
pcsx2-qt/Debugger/Docking/DropIndicators.cpp
Normal file
571
pcsx2-qt/Debugger/Docking/DropIndicators.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "DropIndicators.h"
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include "Debugger/Docking/DockViews.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <kddockwidgets/Config.h>
|
||||
#include <kddockwidgets/core/Group.h>
|
||||
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
|
||||
#include <kddockwidgets/qtwidgets/ViewFactory.h>
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
static std::pair<QColor, QColor> pickNiceColours(const QPalette& palette, bool hovered)
|
||||
{
|
||||
QColor fill = palette.highlight().color();
|
||||
QColor outline = palette.highlight().color();
|
||||
|
||||
if (QtUtils::IsLightTheme(palette))
|
||||
{
|
||||
fill = fill.darker(200);
|
||||
outline = outline.darker(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
fill = fill.lighter(200);
|
||||
outline = outline.lighter(200);
|
||||
}
|
||||
|
||||
fill.setAlpha(200);
|
||||
outline.setAlpha(255);
|
||||
|
||||
if (!hovered)
|
||||
{
|
||||
fill.setAlpha(fill.alpha() / 2);
|
||||
outline.setAlpha(outline.alpha() / 2);
|
||||
}
|
||||
|
||||
return {fill, outline};
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockDropIndicatorProxy::DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
|
||||
: m_classic_indicators(classic_indicators)
|
||||
{
|
||||
recreateWindowIfNecessary();
|
||||
}
|
||||
|
||||
DockDropIndicatorProxy::~DockDropIndicatorProxy()
|
||||
{
|
||||
delete m_window;
|
||||
delete m_fallback_window;
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::setObjectName(const QString& name)
|
||||
{
|
||||
window()->setObjectName(name);
|
||||
}
|
||||
|
||||
KDDockWidgets::DropLocation DockDropIndicatorProxy::hover(QPoint globalPos)
|
||||
{
|
||||
return window()->hover(globalPos);
|
||||
}
|
||||
|
||||
QPoint DockDropIndicatorProxy::posForIndicator(KDDockWidgets::DropLocation loc) const
|
||||
{
|
||||
return window()->posForIndicator(loc);
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::updatePositions()
|
||||
{
|
||||
// Check if a compositor is running whenever a drag starts.
|
||||
recreateWindowIfNecessary();
|
||||
|
||||
window()->updatePositions();
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::raise()
|
||||
{
|
||||
window()->raise();
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::setVisible(bool visible)
|
||||
{
|
||||
window()->setVisible(visible);
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::resize(QSize size)
|
||||
{
|
||||
window()->resize(size);
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::setGeometry(QRect rect)
|
||||
{
|
||||
window()->setGeometry(rect);
|
||||
}
|
||||
|
||||
bool DockDropIndicatorProxy::isWindow() const
|
||||
{
|
||||
return window()->isWindow();
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::updateIndicatorVisibility()
|
||||
{
|
||||
window()->updateIndicatorVisibility();
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window()
|
||||
{
|
||||
if (!m_supports_compositing)
|
||||
{
|
||||
pxAssert(m_fallback_window);
|
||||
return m_fallback_window;
|
||||
}
|
||||
|
||||
pxAssert(m_window);
|
||||
return m_window;
|
||||
}
|
||||
|
||||
const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window() const
|
||||
{
|
||||
if (!m_supports_compositing)
|
||||
{
|
||||
pxAssert(m_fallback_window);
|
||||
return m_fallback_window;
|
||||
}
|
||||
|
||||
pxAssert(m_window);
|
||||
return m_window;
|
||||
}
|
||||
|
||||
void DockDropIndicatorProxy::recreateWindowIfNecessary()
|
||||
{
|
||||
bool supports_compositing = QtUtils::IsCompositorManagerRunning();
|
||||
if (supports_compositing == m_supports_compositing && (m_window || m_fallback_window))
|
||||
return;
|
||||
|
||||
m_supports_compositing = supports_compositing;
|
||||
|
||||
DockViewFactory* factory = static_cast<DockViewFactory*>(KDDockWidgets::Config::self().viewFactory());
|
||||
|
||||
if (supports_compositing)
|
||||
{
|
||||
if (!m_window)
|
||||
m_window = new DockDropIndicatorWindow(m_classic_indicators);
|
||||
|
||||
QWidget* old_window = dynamic_cast<QWidget*>(m_fallback_window);
|
||||
if (old_window)
|
||||
{
|
||||
m_window->setObjectName(old_window->objectName());
|
||||
m_window->setVisible(old_window->isVisible());
|
||||
m_window->setGeometry(old_window->geometry());
|
||||
}
|
||||
|
||||
delete m_fallback_window;
|
||||
m_fallback_window = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_fallback_window)
|
||||
m_fallback_window = factory->createFallbackClassicIndicatorWindow(m_classic_indicators, nullptr);
|
||||
|
||||
QWidget* old_window = dynamic_cast<QWidget*>(m_window);
|
||||
if (old_window)
|
||||
{
|
||||
m_window->setObjectName(old_window->objectName());
|
||||
m_window->setVisible(old_window->isVisible());
|
||||
m_window->setGeometry(old_window->geometry());
|
||||
}
|
||||
|
||||
delete m_window;
|
||||
m_window = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
static const constexpr int IND_LEFT = 0;
|
||||
static const constexpr int IND_TOP = 1;
|
||||
static const constexpr int IND_RIGHT = 2;
|
||||
static const constexpr int IND_BOTTOM = 3;
|
||||
static const constexpr int IND_CENTER = 4;
|
||||
static const constexpr int IND_OUTER_LEFT = 5;
|
||||
static const constexpr int IND_OUTER_TOP = 6;
|
||||
static const constexpr int IND_OUTER_RIGHT = 7;
|
||||
static const constexpr int IND_OUTER_BOTTOM = 8;
|
||||
|
||||
static const constexpr int INDICATOR_SIZE = 40;
|
||||
static const constexpr int INDICATOR_MARGIN = 10;
|
||||
|
||||
static bool isWayland()
|
||||
{
|
||||
return KDDockWidgets::Core::Platform::instance()->displayType() ==
|
||||
KDDockWidgets::Core::Platform::DisplayType::Wayland;
|
||||
}
|
||||
|
||||
static QWidget* parentForIndicatorWindow(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
|
||||
{
|
||||
if (isWayland())
|
||||
return KDDockWidgets::QtCommon::View_qt::asQWidget(classic_indicators->view());
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Qt::WindowFlags flagsForIndicatorWindow()
|
||||
{
|
||||
if (isWayland())
|
||||
return Qt::Widget;
|
||||
|
||||
return Qt::Tool | Qt::BypassWindowManagerHint;
|
||||
}
|
||||
|
||||
DockDropIndicatorWindow::DockDropIndicatorWindow(
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
|
||||
: QWidget(parentForIndicatorWindow(classic_indicators), flagsForIndicatorWindow())
|
||||
, m_classic_indicators(classic_indicators)
|
||||
, m_indicators({
|
||||
/* [IND_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Left, this),
|
||||
/* [IND_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Top, this),
|
||||
/* [IND_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Right, this),
|
||||
/* [IND_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Bottom, this),
|
||||
/* [IND_CENTER] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Center, this),
|
||||
/* [IND_OUTER_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterLeft, this),
|
||||
/* [IND_OUTER_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterTop, this),
|
||||
/* [IND_OUTER_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterRight, this),
|
||||
/* [IND_OUTER_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterBottom, this),
|
||||
})
|
||||
{
|
||||
setWindowFlag(Qt::FramelessWindowHint, true);
|
||||
|
||||
if (KDDockWidgets::Config::self().flags() & KDDockWidgets::Config::Flag_KeepAboveIfNotUtilityWindow)
|
||||
setWindowFlag(Qt::WindowStaysOnTopHint, true);
|
||||
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::setObjectName(const QString& name)
|
||||
{
|
||||
QWidget::setObjectName(name);
|
||||
}
|
||||
|
||||
KDDockWidgets::DropLocation DockDropIndicatorWindow::hover(QPoint globalPos)
|
||||
{
|
||||
KDDockWidgets::DropLocation result = KDDockWidgets::DropLocation_None;
|
||||
|
||||
for (DockDropIndicator* indicator : m_indicators)
|
||||
{
|
||||
if (indicator->isVisible())
|
||||
{
|
||||
bool hovered = indicator->rect().contains(indicator->mapFromGlobal(globalPos));
|
||||
if (hovered != indicator->hovered)
|
||||
{
|
||||
indicator->hovered = hovered;
|
||||
indicator->update();
|
||||
}
|
||||
|
||||
if (hovered)
|
||||
result = indicator->location;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QPoint DockDropIndicatorWindow::posForIndicator(KDDockWidgets::DropLocation loc) const
|
||||
{
|
||||
for (DockDropIndicator* indicator : m_indicators)
|
||||
if (indicator->location == loc)
|
||||
return indicator->mapToGlobal(indicator->rect().center());
|
||||
|
||||
return QPoint();
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::updatePositions()
|
||||
{
|
||||
DockDropIndicator* left = m_indicators[IND_LEFT];
|
||||
DockDropIndicator* top = m_indicators[IND_TOP];
|
||||
DockDropIndicator* right = m_indicators[IND_RIGHT];
|
||||
DockDropIndicator* bottom = m_indicators[IND_BOTTOM];
|
||||
DockDropIndicator* center = m_indicators[IND_CENTER];
|
||||
DockDropIndicator* outer_left = m_indicators[IND_OUTER_LEFT];
|
||||
DockDropIndicator* outer_top = m_indicators[IND_OUTER_TOP];
|
||||
DockDropIndicator* outer_right = m_indicators[IND_OUTER_RIGHT];
|
||||
DockDropIndicator* outer_bottom = m_indicators[IND_OUTER_BOTTOM];
|
||||
|
||||
QRect r = rect();
|
||||
int half_indicator_width = INDICATOR_SIZE / 2;
|
||||
|
||||
outer_left->move(r.x() + INDICATOR_MARGIN, r.center().y() - half_indicator_width);
|
||||
outer_bottom->move(r.center().x() - half_indicator_width, r.y() + height() - INDICATOR_SIZE - INDICATOR_MARGIN);
|
||||
outer_top->move(r.center().x() - half_indicator_width, r.y() + INDICATOR_MARGIN);
|
||||
outer_right->move(r.x() + width() - INDICATOR_SIZE - INDICATOR_MARGIN, r.center().y() - half_indicator_width);
|
||||
|
||||
KDDockWidgets::Core::Group* hovered_group = m_classic_indicators->hoveredGroup();
|
||||
if (hovered_group)
|
||||
{
|
||||
QRect hoveredRect = hovered_group->view()->geometry();
|
||||
center->move(r.topLeft() + hoveredRect.center() - QPoint(half_indicator_width, half_indicator_width));
|
||||
top->move(center->pos() - QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN));
|
||||
right->move(center->pos() + QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0));
|
||||
bottom->move(center->pos() + QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN));
|
||||
left->move(center->pos() - QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::raise()
|
||||
{
|
||||
QWidget::raise();
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::setVisible(bool is)
|
||||
{
|
||||
QWidget::setVisible(is);
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::resize(QSize size)
|
||||
{
|
||||
QWidget::resize(size);
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::setGeometry(QRect rect)
|
||||
{
|
||||
QWidget::setGeometry(rect);
|
||||
}
|
||||
|
||||
bool DockDropIndicatorWindow::isWindow() const
|
||||
{
|
||||
return QWidget::isWindow();
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::updateIndicatorVisibility()
|
||||
{
|
||||
for (DockDropIndicator* indicator : m_indicators)
|
||||
indicator->setVisible(m_classic_indicators->dropIndicatorVisible(indicator->location));
|
||||
}
|
||||
|
||||
void DockDropIndicatorWindow::resizeEvent(QResizeEvent* ev)
|
||||
{
|
||||
QWidget::resizeEvent(ev);
|
||||
updatePositions();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockDropIndicator::DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, location(loc)
|
||||
{
|
||||
setFixedSize(INDICATOR_SIZE, INDICATOR_SIZE);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
void DockDropIndicator::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
auto [fill, outline] = pickNiceColours(palette(), hovered);
|
||||
|
||||
painter.setBrush(fill);
|
||||
|
||||
QPen pen;
|
||||
pen.setColor(outline);
|
||||
pen.setWidth(2);
|
||||
painter.setPen(pen);
|
||||
|
||||
painter.drawRect(rect());
|
||||
|
||||
QRectF rf = rect().toRectF();
|
||||
|
||||
QRectF outer = rf.marginsRemoved(QMarginsF(4.f, 4.f, 4.f, 4.f));
|
||||
QPointF icon_position;
|
||||
switch (location)
|
||||
{
|
||||
case KDDockWidgets::DropLocation_Left:
|
||||
case KDDockWidgets::DropLocation_OutterLeft:
|
||||
outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, outer.width() / 2.f, 0.f));
|
||||
icon_position = rf.marginsRemoved(QMarginsF(rf.width() / 2.f, 0.f, 0.f, 0.f)).center();
|
||||
break;
|
||||
case KDDockWidgets::DropLocation_Top:
|
||||
case KDDockWidgets::DropLocation_OutterTop:
|
||||
outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, outer.width() / 2.f));
|
||||
icon_position = rf.marginsRemoved(QMarginsF(0.f, rf.width() / 2.f, 0.f, 0.f)).center();
|
||||
break;
|
||||
case KDDockWidgets::DropLocation_Right:
|
||||
case KDDockWidgets::DropLocation_OutterRight:
|
||||
outer = outer.marginsRemoved(QMarginsF(outer.width() / 2.f, 0.f, 0.f, 0.f));
|
||||
icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, rf.width() / 2.f, 0.f)).center();
|
||||
break;
|
||||
case KDDockWidgets::DropLocation_Bottom:
|
||||
case KDDockWidgets::DropLocation_OutterBottom:
|
||||
outer = outer.marginsRemoved(QMarginsF(0.f, outer.width() / 2.f, 0.f, 0.f));
|
||||
icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, rf.width() / 2.f)).center();
|
||||
break;
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
painter.drawRect(outer);
|
||||
|
||||
float arrow_size = INDICATOR_SIZE / 10.f;
|
||||
|
||||
QPolygonF arrow;
|
||||
switch (location)
|
||||
{
|
||||
case KDDockWidgets::DropLocation_Left:
|
||||
arrow = {
|
||||
icon_position + QPointF(-arrow_size, 0.f),
|
||||
icon_position + QPointF(arrow_size, arrow_size * 2.f),
|
||||
icon_position + QPointF(arrow_size, -arrow_size * 2.f),
|
||||
};
|
||||
break;
|
||||
case KDDockWidgets::DropLocation_Top:
|
||||
arrow = {
|
||||
icon_position + QPointF(0.f, -arrow_size),
|
||||
icon_position + QPointF(arrow_size * 2.f, arrow_size),
|
||||
icon_position + QPointF(-arrow_size * 2.f, arrow_size),
|
||||
};
|
||||
break;
|
||||
case KDDockWidgets::DropLocation_Right:
|
||||
arrow = {
|
||||
icon_position + QPointF(arrow_size, 0.f),
|
||||
icon_position + QPointF(-arrow_size, arrow_size * 2.f),
|
||||
icon_position + QPointF(-arrow_size, -arrow_size * 2.f),
|
||||
};
|
||||
break;
|
||||
case KDDockWidgets::DropLocation_Bottom:
|
||||
arrow = {
|
||||
icon_position + QPointF(0.f, arrow_size),
|
||||
icon_position + QPointF(arrow_size * 2.f, -arrow_size),
|
||||
icon_position + QPointF(-arrow_size * 2.f, -arrow_size),
|
||||
};
|
||||
break;
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
painter.drawPolygon(arrow);
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
std::string DockSegmentedDropIndicatorOverlay::s_indicator_style;
|
||||
|
||||
DockSegmentedDropIndicatorOverlay::DockSegmentedDropIndicatorOverlay(
|
||||
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent)
|
||||
: KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay(controller, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DockSegmentedDropIndicatorOverlay::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
if (s_indicator_style == "Minimalistic")
|
||||
drawMinimalistic();
|
||||
else
|
||||
drawSegmented();
|
||||
}
|
||||
|
||||
void DockSegmentedDropIndicatorOverlay::drawSegmented()
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller =
|
||||
asController<KDDockWidgets::Core::SegmentedDropIndicatorOverlay>();
|
||||
|
||||
const std::unordered_map<KDDockWidgets::DropLocation, QPolygon>& segments = controller->segments();
|
||||
|
||||
for (KDDockWidgets::DropLocation location :
|
||||
{KDDockWidgets::DropLocation_Left,
|
||||
KDDockWidgets::DropLocation_Top,
|
||||
KDDockWidgets::DropLocation_Right,
|
||||
KDDockWidgets::DropLocation_Bottom,
|
||||
KDDockWidgets::DropLocation_Center,
|
||||
KDDockWidgets::DropLocation_OutterLeft,
|
||||
KDDockWidgets::DropLocation_OutterTop,
|
||||
KDDockWidgets::DropLocation_OutterRight,
|
||||
KDDockWidgets::DropLocation_OutterBottom})
|
||||
{
|
||||
auto segment = segments.find(location);
|
||||
if (segment == segments.end() || segment->second.size() < 2)
|
||||
continue;
|
||||
|
||||
bool hovered = segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill);
|
||||
auto [fill, outline] = pickNiceColours(palette(), hovered);
|
||||
|
||||
painter.setBrush(fill);
|
||||
|
||||
QPen pen(outline);
|
||||
pen.setWidth(1);
|
||||
painter.setPen(pen);
|
||||
|
||||
int margin = KDDockWidgets::Core::SegmentedDropIndicatorOverlay::s_segmentGirth * 2;
|
||||
|
||||
// Make sure the rectangles don't intersect with each other.
|
||||
QRect rect;
|
||||
switch (location)
|
||||
{
|
||||
case KDDockWidgets::DropLocation_Top:
|
||||
case KDDockWidgets::DropLocation_Bottom:
|
||||
case KDDockWidgets::DropLocation_OutterTop:
|
||||
case KDDockWidgets::DropLocation_OutterBottom:
|
||||
{
|
||||
rect = segment->second.boundingRect().marginsRemoved(QMargins(margin, 4, margin, 4));
|
||||
break;
|
||||
}
|
||||
case KDDockWidgets::DropLocation_Left:
|
||||
case KDDockWidgets::DropLocation_Right:
|
||||
case KDDockWidgets::DropLocation_OutterLeft:
|
||||
case KDDockWidgets::DropLocation_OutterRight:
|
||||
{
|
||||
rect = segment->second.boundingRect().marginsRemoved(QMargins(4, margin, 4, margin));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
rect = segment->second.boundingRect().marginsRemoved(QMargins(4, 4, 4, 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
painter.drawRect(rect);
|
||||
}
|
||||
}
|
||||
|
||||
void DockSegmentedDropIndicatorOverlay::drawMinimalistic()
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller =
|
||||
asController<KDDockWidgets::Core::SegmentedDropIndicatorOverlay>();
|
||||
|
||||
const std::unordered_map<KDDockWidgets::DropLocation, QPolygon>& segments = controller->segments();
|
||||
|
||||
for (KDDockWidgets::DropLocation location :
|
||||
{KDDockWidgets::DropLocation_Left,
|
||||
KDDockWidgets::DropLocation_Top,
|
||||
KDDockWidgets::DropLocation_Right,
|
||||
KDDockWidgets::DropLocation_Bottom,
|
||||
KDDockWidgets::DropLocation_Center,
|
||||
KDDockWidgets::DropLocation_OutterLeft,
|
||||
KDDockWidgets::DropLocation_OutterTop,
|
||||
KDDockWidgets::DropLocation_OutterRight,
|
||||
KDDockWidgets::DropLocation_OutterBottom})
|
||||
{
|
||||
auto segment = segments.find(location);
|
||||
if (segment == segments.end() || segment->second.size() < 2)
|
||||
continue;
|
||||
|
||||
if (!segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill))
|
||||
continue;
|
||||
|
||||
auto [fill, outline] = pickNiceColours(palette(), true);
|
||||
|
||||
painter.setBrush(fill);
|
||||
|
||||
QPen pen(outline);
|
||||
pen.setWidth(1);
|
||||
painter.setPen(pen);
|
||||
|
||||
painter.drawRect(segment->second.boundingRect());
|
||||
}
|
||||
}
|
||||
108
pcsx2-qt/Debugger/Docking/DropIndicators.h
Normal file
108
pcsx2-qt/Debugger/Docking/DropIndicators.h
Normal file
@@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kddockwidgets/core/indicators/ClassicDropIndicatorOverlay.h>
|
||||
#include <kddockwidgets/core/views/ClassicIndicatorWindowViewInterface.h>
|
||||
#include <kddockwidgets/qtwidgets/views/SegmentedDropIndicatorOverlay.h>
|
||||
|
||||
class DockDropIndicator;
|
||||
|
||||
// This switches between our custom drop indicators and KDDockWidget's built-in
|
||||
// ones on the fly depending on whether or not we have a windowing system that
|
||||
// supports compositing.
|
||||
class DockDropIndicatorProxy : public KDDockWidgets::Core::ClassicIndicatorWindowViewInterface
|
||||
{
|
||||
public:
|
||||
DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators);
|
||||
~DockDropIndicatorProxy();
|
||||
|
||||
void setObjectName(const QString&) override;
|
||||
KDDockWidgets::DropLocation hover(QPoint globalPos) override;
|
||||
QPoint posForIndicator(KDDockWidgets::DropLocation) const override;
|
||||
void updatePositions() override;
|
||||
void raise() override;
|
||||
void setVisible(bool visible) override;
|
||||
void resize(QSize size) override;
|
||||
void setGeometry(QRect rect) override;
|
||||
bool isWindow() const override;
|
||||
void updateIndicatorVisibility() override;
|
||||
|
||||
private:
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* window();
|
||||
const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* window() const;
|
||||
|
||||
void recreateWindowIfNecessary();
|
||||
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* m_window = nullptr;
|
||||
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* m_fallback_window = nullptr;
|
||||
|
||||
bool m_supports_compositing = true;
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* m_classic_indicators = nullptr;
|
||||
};
|
||||
|
||||
// Our default custom drop indicator implementation. This fits in with PCSX2's
|
||||
// themes a lot better, but doesn't support windowing systems where compositing
|
||||
// is disabled (it would show a black screen).
|
||||
class DockDropIndicatorWindow : public QWidget, public KDDockWidgets::Core::ClassicIndicatorWindowViewInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockDropIndicatorWindow(
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators);
|
||||
|
||||
void setObjectName(const QString& name) override;
|
||||
KDDockWidgets::DropLocation hover(QPoint globalPos) override;
|
||||
QPoint posForIndicator(KDDockWidgets::DropLocation loc) const override;
|
||||
void updatePositions() override;
|
||||
void raise() override;
|
||||
void setVisible(bool visible) override;
|
||||
void resize(QSize size) override;
|
||||
void setGeometry(QRect rect) override;
|
||||
bool isWindow() const override;
|
||||
void updateIndicatorVisibility() override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* ev) override;
|
||||
|
||||
private:
|
||||
KDDockWidgets::Core::ClassicDropIndicatorOverlay* m_classic_indicators;
|
||||
std::vector<DockDropIndicator*> m_indicators;
|
||||
};
|
||||
|
||||
class DockDropIndicator : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent = nullptr);
|
||||
|
||||
KDDockWidgets::DropLocation location;
|
||||
bool hovered = false;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
};
|
||||
|
||||
// An alternative drop indicator design that can be enabled from the settings
|
||||
// menu. For this one we don't need to worry about whether compositing is
|
||||
// supported since it doesn't create its own window.
|
||||
class DockSegmentedDropIndicatorOverlay : public KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DockSegmentedDropIndicatorOverlay(
|
||||
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent = nullptr);
|
||||
|
||||
static std::string s_indicator_style;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
void drawSegmented();
|
||||
void drawMinimalistic();
|
||||
};
|
||||
107
pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp
Normal file
107
pcsx2-qt/Debugger/Docking/LayoutEditorDialog.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "LayoutEditorDialog.h"
|
||||
|
||||
#include "Debugger/Docking/DockTables.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
Q_DECLARE_METATYPE(LayoutEditorDialog::InitialState);
|
||||
|
||||
LayoutEditorDialog::LayoutEditorDialog(NameValidator name_validator, bool can_clone_current_layout, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_name_validator(name_validator)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setWindowTitle(tr("New Layout"));
|
||||
|
||||
setupInputWidgets(BREAKPOINT_EE, can_clone_current_layout);
|
||||
|
||||
onNameChanged();
|
||||
}
|
||||
|
||||
LayoutEditorDialog::LayoutEditorDialog(
|
||||
const QString& name, BreakPointCpu cpu, NameValidator name_validator, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_name_validator(name_validator)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setWindowTitle(tr("Edit Layout"));
|
||||
|
||||
m_ui.nameEditor->setText(name);
|
||||
|
||||
setupInputWidgets(cpu, {});
|
||||
|
||||
m_ui.initialStateLabel->hide();
|
||||
m_ui.initialStateEditor->hide();
|
||||
|
||||
onNameChanged();
|
||||
}
|
||||
|
||||
QString LayoutEditorDialog::name()
|
||||
{
|
||||
return m_ui.nameEditor->text();
|
||||
}
|
||||
|
||||
BreakPointCpu LayoutEditorDialog::cpu()
|
||||
{
|
||||
return static_cast<BreakPointCpu>(m_ui.cpuEditor->currentData().toInt());
|
||||
}
|
||||
|
||||
LayoutEditorDialog::InitialState LayoutEditorDialog::initialState()
|
||||
{
|
||||
return m_ui.initialStateEditor->currentData().value<InitialState>();
|
||||
}
|
||||
|
||||
void LayoutEditorDialog::setupInputWidgets(BreakPointCpu cpu, bool can_clone_current_layout)
|
||||
{
|
||||
connect(m_ui.nameEditor, &QLineEdit::textChanged, this, &LayoutEditorDialog::onNameChanged);
|
||||
|
||||
for (BreakPointCpu cpu : DEBUG_CPUS)
|
||||
{
|
||||
const char* long_cpu_name = DebugInterface::longCpuName(cpu);
|
||||
const char* cpu_name = DebugInterface::cpuName(cpu);
|
||||
QString text = QString("%1 (%2)").arg(long_cpu_name).arg(cpu_name);
|
||||
m_ui.cpuEditor->addItem(text, cpu);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_ui.cpuEditor->count(); i++)
|
||||
if (m_ui.cpuEditor->itemData(i).toInt() == cpu)
|
||||
m_ui.cpuEditor->setCurrentIndex(i);
|
||||
|
||||
for (size_t i = 0; i < DockTables::DEFAULT_DOCK_LAYOUTS.size(); i++)
|
||||
m_ui.initialStateEditor->addItem(
|
||||
tr("Create Default \"%1\" Layout").arg(tr(DockTables::DEFAULT_DOCK_LAYOUTS[i].name.c_str())),
|
||||
QVariant::fromValue(InitialState(DEFAULT_LAYOUT, i)));
|
||||
|
||||
m_ui.initialStateEditor->addItem(tr("Create Blank Layout"), QVariant::fromValue(InitialState(BLANK_LAYOUT, 0)));
|
||||
|
||||
if (can_clone_current_layout)
|
||||
m_ui.initialStateEditor->addItem(tr("Clone Current Layout"), QVariant::fromValue(InitialState(CLONE_LAYOUT, 0)));
|
||||
|
||||
m_ui.initialStateEditor->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void LayoutEditorDialog::onNameChanged()
|
||||
{
|
||||
QString error_message;
|
||||
|
||||
if (m_ui.nameEditor->text().isEmpty())
|
||||
{
|
||||
error_message = tr("Name is empty.");
|
||||
}
|
||||
else if (m_ui.nameEditor->text().size() > DockUtils::MAX_LAYOUT_NAME_SIZE)
|
||||
{
|
||||
error_message = tr("Name too long.");
|
||||
}
|
||||
else if (!m_name_validator(m_ui.nameEditor->text()))
|
||||
{
|
||||
error_message = tr("A layout with that name already exists.");
|
||||
}
|
||||
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(error_message.isEmpty());
|
||||
m_ui.errorMessage->setText(error_message);
|
||||
}
|
||||
45
pcsx2-qt/Debugger/Docking/LayoutEditorDialog.h
Normal file
45
pcsx2-qt/Debugger/Docking/LayoutEditorDialog.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_LayoutEditorDialog.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class LayoutEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using NameValidator = std::function<bool(const QString&)>;
|
||||
|
||||
enum CreationMode
|
||||
{
|
||||
DEFAULT_LAYOUT,
|
||||
BLANK_LAYOUT,
|
||||
CLONE_LAYOUT,
|
||||
};
|
||||
|
||||
// Bundles together a creation mode and inital state.
|
||||
using InitialState = std::pair<CreationMode, size_t>;
|
||||
|
||||
// Create a "New Layout" dialog.
|
||||
LayoutEditorDialog(NameValidator name_validator, bool can_clone_current_layout, QWidget* parent = nullptr);
|
||||
|
||||
// Create a "Edit Layout" dialog.
|
||||
LayoutEditorDialog(const QString& name, BreakPointCpu cpu, NameValidator name_validator, QWidget* parent = nullptr);
|
||||
|
||||
QString name();
|
||||
BreakPointCpu cpu();
|
||||
InitialState initialState();
|
||||
|
||||
private:
|
||||
void setupInputWidgets(BreakPointCpu cpu, bool can_clone_current_layout);
|
||||
void onNameChanged();
|
||||
|
||||
Ui::LayoutEditorDialog m_ui;
|
||||
NameValidator m_name_validator;
|
||||
};
|
||||
115
pcsx2-qt/Debugger/Docking/LayoutEditorDialog.ui
Normal file
115
pcsx2-qt/Debugger/Docking/LayoutEditorDialog.ui
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LayoutEditorDialog</class>
|
||||
<widget class="QDialog" name="LayoutEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="cpuLabel">
|
||||
<property name="text">
|
||||
<string>Target</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="initialStateLabel">
|
||||
<property name="text">
|
||||
<string>Initial State</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cpuEditor"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nameEditor"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="initialStateEditor"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="errorMessage">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: red</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>LayoutEditorDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>LayoutEditorDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
15
pcsx2-qt/Debugger/Docking/NoLayoutsWidget.cpp
Normal file
15
pcsx2-qt/Debugger/Docking/NoLayoutsWidget.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "NoLayoutsWidget.h"
|
||||
|
||||
NoLayoutsWidget::NoLayoutsWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
}
|
||||
|
||||
QPushButton* NoLayoutsWidget::createDefaultLayoutsButton()
|
||||
{
|
||||
return m_ui.createDefaultLayoutsButton;
|
||||
}
|
||||
21
pcsx2-qt/Debugger/Docking/NoLayoutsWidget.h
Normal file
21
pcsx2-qt/Debugger/Docking/NoLayoutsWidget.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_NoLayoutsWidget.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
class NoLayoutsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NoLayoutsWidget(QWidget* parent = nullptr);
|
||||
|
||||
QPushButton* createDefaultLayoutsButton();
|
||||
|
||||
private:
|
||||
Ui::NoLayoutsWidget m_ui;
|
||||
};
|
||||
97
pcsx2-qt/Debugger/Docking/NoLayoutsWidget.ui
Normal file
97
pcsx2-qt/Debugger/Docking/NoLayoutsWidget.ui
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NoLayoutsWidget</class>
|
||||
<widget class="QWidget" name="NoLayoutsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="topSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>There are no layouts.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="leftSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="createDefaultLayoutsButton">
|
||||
<property name="text">
|
||||
<string>Create Default Layouts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="rightSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="bottomSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
39
pcsx2-qt/Debugger/JsonValueWrapper.h
Normal file
39
pcsx2-qt/Debugger/JsonValueWrapper.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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<rapidjson::CrtAllocator>& allocator)
|
||||
: m_value(value)
|
||||
, m_allocator(allocator)
|
||||
{
|
||||
}
|
||||
|
||||
rapidjson::Value& value()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
const rapidjson::Value& value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& allocator()
|
||||
{
|
||||
return m_allocator;
|
||||
}
|
||||
|
||||
private:
|
||||
rapidjson::Value& m_value;
|
||||
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& m_allocator;
|
||||
};
|
||||
@@ -23,8 +23,8 @@ using SearchResult = MemorySearchWidget::SearchResult;
|
||||
|
||||
using namespace QtUtils;
|
||||
|
||||
MemorySearchWidget::MemorySearchWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
MemorySearchWidget::MemorySearchWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, MONOSPACE_FONT)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
this->repaint();
|
||||
@@ -32,10 +32,8 @@ 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)
|
||||
{
|
||||
emit switchToMemoryViewTab();
|
||||
emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16));
|
||||
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [](QListWidgetItem* item) {
|
||||
goToInMemoryView(item->text().toUInt(nullptr, 16), true);
|
||||
});
|
||||
connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &MemorySearchWidget::onSearchResultsListScroll);
|
||||
connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &MemorySearchWidget::onListSearchResultsContextMenu);
|
||||
@@ -46,21 +44,11 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent)
|
||||
m_resultsLoadTimer.setInterval(100);
|
||||
m_resultsLoadTimer.setSingleShot(true);
|
||||
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();
|
||||
if (!selModel->hasSelection())
|
||||
return;
|
||||
|
||||
u32 selectedAddress = m_ui.listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt();
|
||||
emit goToAddressInDisassemblyView(selectedAddress);
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
update();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void MemorySearchWidget::contextRemoveSearchResult()
|
||||
@@ -92,44 +80,47 @@ void MemorySearchWidget::contextCopySearchResultAddress()
|
||||
|
||||
void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos)
|
||||
{
|
||||
QMenu* contextMenu = new QMenu(tr("Search Results List Context Menu"), m_ui.listSearchResults);
|
||||
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
|
||||
const auto listSearchResults = m_ui.listSearchResults;
|
||||
const QItemSelectionModel* selection_model = m_ui.listSearchResults->selectionModel();
|
||||
const QListWidget* list_search_results = m_ui.listSearchResults;
|
||||
|
||||
if (selModel->hasSelection())
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (selection_model->hasSelection())
|
||||
{
|
||||
QAction* copyAddressAction = new QAction(tr("Copy Address"), m_ui.listSearchResults);
|
||||
connect(copyAddressAction, &QAction::triggered, this, &MemorySearchWidget::contextCopySearchResultAddress);
|
||||
contextMenu->addAction(copyAddressAction);
|
||||
connect(menu->addAction(tr("Copy Address")), &QAction::triggered,
|
||||
this, &MemorySearchWidget::contextCopySearchResultAddress);
|
||||
|
||||
QAction* goToDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.listSearchResults);
|
||||
connect(goToDisassemblyAction, &QAction::triggered, this, &MemorySearchWidget::contextSearchResultGoToDisassembly);
|
||||
contextMenu->addAction(goToDisassemblyAction);
|
||||
|
||||
QAction* addToSavedAddressesAction = new QAction(tr("Add to Saved Memory Addresses"), m_ui.listSearchResults);
|
||||
connect(addToSavedAddressesAction, &QAction::triggered, this, [this, listSearchResults]() {
|
||||
u32 selectedAddress = listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt();
|
||||
emit addAddressToSavedAddressesList(selectedAddress);
|
||||
createEventActions<DebuggerEvents::GoToAddress>(menu, [list_search_results]() {
|
||||
u32 selected_address = list_search_results->selectedItems().first()->data(Qt::UserRole).toUInt();
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = selected_address;
|
||||
return std::optional(event);
|
||||
});
|
||||
contextMenu->addAction(addToSavedAddressesAction);
|
||||
|
||||
QAction* removeResultAction = new QAction(tr("Remove Result"), m_ui.listSearchResults);
|
||||
connect(removeResultAction, &QAction::triggered, this, &MemorySearchWidget::contextRemoveSearchResult);
|
||||
contextMenu->addAction(removeResultAction);
|
||||
createEventActions<DebuggerEvents::AddToSavedAddresses>(menu, [list_search_results]() {
|
||||
u32 selected_address = list_search_results->selectedItems().first()->data(Qt::UserRole).toUInt();
|
||||
DebuggerEvents::AddToSavedAddresses event;
|
||||
event.address = selected_address;
|
||||
return std::optional(event);
|
||||
});
|
||||
|
||||
connect(menu->addAction(tr("Remove Result")), &QAction::triggered,
|
||||
this, &MemorySearchWidget::contextRemoveSearchResult);
|
||||
}
|
||||
|
||||
contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
|
||||
menu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template <typename T>
|
||||
T readValueAtAddress(DebugInterface* cpu, u32 addr);
|
||||
template<>
|
||||
template <>
|
||||
float readValueAtAddress<float>(DebugInterface* cpu, u32 addr)
|
||||
{
|
||||
return std::bit_cast<float>(cpu->read32(addr));
|
||||
}
|
||||
|
||||
template<>
|
||||
template <>
|
||||
double readValueAtAddress<double>(DebugInterface* cpu, u32 addr)
|
||||
{
|
||||
return std::bit_cast<double>(cpu->read64(addr));
|
||||
@@ -230,7 +221,7 @@ template <typename T>
|
||||
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:
|
||||
@@ -302,7 +293,7 @@ void searchWorker(DebugInterface* cpu, std::vector<SearchResult>& searchResults,
|
||||
{
|
||||
if (!cpu->isValidAddress(addr))
|
||||
continue;
|
||||
|
||||
|
||||
T readValue = readValueAtAddress<T>(cpu, addr);
|
||||
if (handleSearchComparison(searchComparison, addr, nullptr, searchValue, readValue))
|
||||
{
|
||||
@@ -315,7 +306,7 @@ void searchWorker(DebugInterface* cpu, std::vector<SearchResult>& 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<T>(cpu, addr);
|
||||
|
||||
@@ -415,7 +406,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;
|
||||
@@ -453,7 +444,7 @@ std::vector<SearchResult> startWorker(DebugInterface* cpu, const SearchType type
|
||||
isSigned ? searchWorker<s32>(cpu, searchResults, type, comparison, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchResults, type, comparison, start, end, value.toUInt(nullptr, base));
|
||||
break;
|
||||
case SearchType::Int64Type:
|
||||
isSigned ? searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toULongLong(nullptr, base));
|
||||
isSigned ? searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toLongLong(nullptr, base)) : searchWorker<u64>(cpu, searchResults, type, comparison, start, end, value.toULongLong(nullptr, base));
|
||||
break;
|
||||
case SearchType::FloatType:
|
||||
searchWorker<float>(cpu, searchResults, type, comparison, start, end, value.toFloat());
|
||||
@@ -476,7 +467,7 @@ std::vector<SearchResult> startWorker(DebugInterface* cpu, const SearchType type
|
||||
|
||||
void MemorySearchWidget::onSearchButtonClicked()
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
const SearchType searchType = getCurrentSearchType();
|
||||
@@ -510,9 +501,9 @@ void MemorySearchWidget::onSearchButtonClicked()
|
||||
const bool isFilterSearch = sender() == m_ui.btnFilterSearch;
|
||||
unsigned long long value;
|
||||
|
||||
if(searchComparison != SearchComparison::UnknownValue)
|
||||
if (searchComparison != SearchComparison::UnknownValue)
|
||||
{
|
||||
if(doesSearchComparisonTakeInput(searchComparison))
|
||||
if (doesSearchComparisonTakeInput(searchComparison))
|
||||
{
|
||||
switch (searchType)
|
||||
{
|
||||
@@ -565,16 +556,32 @@ void MemorySearchWidget::onSearchButtonClicked()
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QFutureWatcher<std::vector<SearchResult>>* workerWatcher = new QFutureWatcher<std::vector<SearchResult>>();
|
||||
auto onSearchFinished = [this, workerWatcher] {
|
||||
m_ui.btnSearch->setDisabled(false);
|
||||
@@ -597,7 +604,7 @@ void MemorySearchWidget::onSearchButtonClicked()
|
||||
m_searchResults.clear();
|
||||
}
|
||||
|
||||
QFuture<std::vector<SearchResult>> workerFuture = QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, std::move(m_searchResults), searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
|
||||
QFuture<std::vector<SearchResult>> workerFuture = QtConcurrent::run(startWorker, &cpu(), searchType, searchComparison, std::move(m_searchResults), searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
|
||||
workerWatcher->setFuture(workerFuture);
|
||||
connect(workerWatcher, &QFutureWatcher<std::vector<SearchResult>>::finished, onSearchFinished);
|
||||
m_searchResults.clear();
|
||||
@@ -649,7 +656,8 @@ SearchComparison MemorySearchWidget::getCurrentSearchComparison()
|
||||
|
||||
bool MemorySearchWidget::doesSearchComparisonTakeInput(const SearchComparison comparison)
|
||||
{
|
||||
switch (comparison) {
|
||||
switch (comparison)
|
||||
{
|
||||
case SearchComparison::Equals:
|
||||
case SearchComparison::NotEquals:
|
||||
case SearchComparison::GreaterThan:
|
||||
@@ -708,7 +716,7 @@ void MemorySearchWidget::updateSearchComparisonSelections()
|
||||
std::vector<SearchComparison> MemorySearchWidget::getValidSearchComparisonsForState(SearchType type, std::vector<SearchResult>& existingResults)
|
||||
{
|
||||
const bool hasResults = existingResults.size() > 0;
|
||||
std::vector<SearchComparison> comparisons = { SearchComparison::Equals };
|
||||
std::vector<SearchComparison> comparisons = {SearchComparison::Equals};
|
||||
|
||||
if (type == SearchType::ArrayType || type == SearchType::StringType)
|
||||
{
|
||||
@@ -736,8 +744,8 @@ std::vector<SearchComparison> MemorySearchWidget::getValidSearchComparisonsForSt
|
||||
comparisons.push_back(SearchComparison::ChangedBy);
|
||||
comparisons.push_back(SearchComparison::NotChanged);
|
||||
}
|
||||
|
||||
if(!hasResults)
|
||||
|
||||
if (!hasResults)
|
||||
{
|
||||
comparisons.push_back(SearchComparison::UnknownValue);
|
||||
}
|
||||
@@ -5,22 +5,23 @@
|
||||
|
||||
#include "ui_MemorySearchWidget.h"
|
||||
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QMap>
|
||||
|
||||
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(const DebuggerWidgetParameters& parameters);
|
||||
~MemorySearchWidget() = default;
|
||||
|
||||
enum class SearchType
|
||||
enum class SearchType
|
||||
{
|
||||
ByteType,
|
||||
Int16Type,
|
||||
@@ -77,9 +78,11 @@ public:
|
||||
{
|
||||
return labelToEnumMap.value(comparisonLabel, SearchComparison::Invalid);
|
||||
}
|
||||
QString enumToLabel(SearchComparison comparison) {
|
||||
QString enumToLabel(SearchComparison comparison)
|
||||
{
|
||||
return enumToLabelMap.value(comparison, "");
|
||||
}
|
||||
|
||||
private:
|
||||
QMap<SearchComparison, QString> enumToLabelMap;
|
||||
QMap<QString, SearchComparison> labelToEnumMap;
|
||||
@@ -100,7 +103,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; }
|
||||
@@ -111,7 +116,7 @@ public:
|
||||
SearchType getType() const { return type; }
|
||||
QByteArray getArrayValue() const { return isArrayValue() ? value.toByteArray() : QByteArray(); }
|
||||
|
||||
template<typename T>
|
||||
template <typename T>
|
||||
T getValue() const
|
||||
{
|
||||
return value.value<T>();
|
||||
@@ -124,29 +129,21 @@ public slots:
|
||||
void onSearchTypeChanged(int newIndex);
|
||||
void onSearchComparisonChanged(int newIndex);
|
||||
void loadSearchResults();
|
||||
void contextSearchResultGoToDisassembly();
|
||||
void contextRemoveSearchResult();
|
||||
void contextCopySearchResultAddress();
|
||||
void onListSearchResultsContextMenu(QPoint pos);
|
||||
|
||||
signals:
|
||||
void addAddressToSavedAddressesList(u32 address);
|
||||
void goToAddressInDisassemblyView(u32 address);
|
||||
void goToAddressInMemoryView(u32 address);
|
||||
void switchToMemoryViewTab();
|
||||
|
||||
private:
|
||||
std::vector<SearchResult> 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<SearchComparison> getValidSearchComparisonsForState(SearchType type, std::vector<SearchResult> &existingResults);
|
||||
std::vector<SearchComparison> getValidSearchComparisonsForState(SearchType type, std::vector<SearchResult>& existingResults);
|
||||
SearchType getCurrentSearchType();
|
||||
SearchComparison getCurrentSearchComparison();
|
||||
bool doesSearchComparisonTakeInput(SearchComparison comparison);
|
||||
@@ -10,11 +10,6 @@
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
@@ -2,15 +2,17 @@
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "MemoryViewWidget.h"
|
||||
#include "common/Console.h"
|
||||
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
|
||||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtGui/QActionGroup>
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
using namespace QtUtils;
|
||||
|
||||
@@ -41,7 +43,7 @@ void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height)
|
||||
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu)
|
||||
{
|
||||
rowHeight = painter.fontMetrics().height() + 2;
|
||||
const s32 charWidth = painter.fontMetrics().averageCharWidth();
|
||||
@@ -106,7 +108,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
||||
{
|
||||
case MemoryViewType::BYTE:
|
||||
{
|
||||
const u8 val = static_cast<u8>(m_cpu->read8(thisSegmentsStart, valid));
|
||||
const u8 val = static_cast<u8>(cpu.read8(thisSegmentsStart, valid));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "??");
|
||||
@@ -114,7 +116,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
||||
}
|
||||
case MemoryViewType::BYTEHW:
|
||||
{
|
||||
const u16 val = convertEndian<u16>(static_cast<u16>(m_cpu->read16(thisSegmentsStart, valid)));
|
||||
const u16 val = convertEndian<u16>(static_cast<u16>(cpu.read16(thisSegmentsStart, valid)));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????");
|
||||
@@ -122,7 +124,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
||||
}
|
||||
case MemoryViewType::WORD:
|
||||
{
|
||||
const u32 val = convertEndian<u32>(m_cpu->read32(thisSegmentsStart, valid));
|
||||
const u32 val = convertEndian<u32>(cpu.read32(thisSegmentsStart, valid));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????");
|
||||
@@ -130,7 +132,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
||||
}
|
||||
case MemoryViewType::DWORD:
|
||||
{
|
||||
const u64 val = convertEndian<u64>(m_cpu->read64(thisSegmentsStart, valid));
|
||||
const u64 val = convertEndian<u64>(cpu.read64(thisSegmentsStart, valid));
|
||||
if (penDefault && val == 0)
|
||||
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
|
||||
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????????????");
|
||||
@@ -153,7 +155,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
|
||||
painter.setPen(palette.text().color());
|
||||
|
||||
bool valid;
|
||||
const u8 value = m_cpu->read8(currentRowAddress + j, valid);
|
||||
const u8 value = cpu.read8(currentRowAddress + j, valid);
|
||||
if (valid)
|
||||
{
|
||||
QChar curChar = QChar::fromLatin1(value);
|
||||
@@ -173,6 +175,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];
|
||||
@@ -212,54 +218,54 @@ void MemoryViewTable::SelectAt(QPoint pos)
|
||||
}
|
||||
}
|
||||
|
||||
u128 MemoryViewTable::GetSelectedSegment()
|
||||
u128 MemoryViewTable::GetSelectedSegment(DebugInterface& cpu)
|
||||
{
|
||||
u128 val;
|
||||
switch (displayType)
|
||||
{
|
||||
case MemoryViewType::BYTE:
|
||||
val.lo = m_cpu->read8(selectedAddress);
|
||||
val.lo = cpu.read8(selectedAddress);
|
||||
break;
|
||||
case MemoryViewType::BYTEHW:
|
||||
val.lo = convertEndian(static_cast<u16>(m_cpu->read16(selectedAddress & ~1)));
|
||||
val.lo = convertEndian(static_cast<u16>(cpu.read16(selectedAddress & ~1)));
|
||||
break;
|
||||
case MemoryViewType::WORD:
|
||||
val.lo = convertEndian(m_cpu->read32(selectedAddress & ~3));
|
||||
val.lo = convertEndian(cpu.read32(selectedAddress & ~3));
|
||||
break;
|
||||
case MemoryViewType::DWORD:
|
||||
val._u64[0] = convertEndian(m_cpu->read64(selectedAddress & ~7));
|
||||
val._u64[0] = convertEndian(cpu.read64(selectedAddress & ~7));
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void MemoryViewTable::InsertIntoSelectedHexView(u8 value)
|
||||
void MemoryViewTable::InsertIntoSelectedHexView(u8 value, DebugInterface& cpu)
|
||||
{
|
||||
const u8 mask = selectedNibbleHI ? 0x0f : 0xf0;
|
||||
u8 curVal = m_cpu->read8(selectedAddress) & mask;
|
||||
u8 curVal = cpu.read8(selectedAddress) & mask;
|
||||
u8 newVal = value << (selectedNibbleHI ? 4 : 0);
|
||||
curVal |= newVal;
|
||||
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, val = curVal] {
|
||||
cpu->write8(address, val);
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, val = curVal] {
|
||||
cpu.write8(address, val);
|
||||
QtHost::RunOnUIThread([this] { parent->update(); });
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryViewTable::InsertAtCurrentSelection(const QString& text)
|
||||
void MemoryViewTable::InsertAtCurrentSelection(const QString& text, DebugInterface& cpu)
|
||||
{
|
||||
if (!m_cpu->isValidAddress(selectedAddress))
|
||||
if (!cpu.isValidAddress(selectedAddress))
|
||||
return;
|
||||
|
||||
// If pasting into the hex view, also decode the input as hex bytes.
|
||||
// This approach prevents one from pasting on a nibble boundary, but that is almost always
|
||||
// user error, and we don't have an undo function in this view, so best to stay conservative.
|
||||
QByteArray input = selectedText ? text.toUtf8() : QByteArray::fromHex(text.toUtf8());
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, inBytes = input] {
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, inBytes = input] {
|
||||
u32 currAddr = address;
|
||||
for (int i = 0; i < inBytes.size(); i++)
|
||||
{
|
||||
cpu->write8(currAddr, inBytes[i]);
|
||||
cpu.write8(currAddr, inBytes[i]);
|
||||
currAddr = nextAddress(currAddr);
|
||||
QtHost::RunOnUIThread([this] { parent->update(); });
|
||||
}
|
||||
@@ -339,9 +345,9 @@ void MemoryViewTable::BackwardSelection()
|
||||
|
||||
|
||||
// We need both key and keychar because `key` is easy to use, but is case insensitive
|
||||
bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
bool MemoryViewTable::KeyPress(int key, QChar keychar, DebugInterface& cpu)
|
||||
{
|
||||
if (!m_cpu->isValidAddress(selectedAddress))
|
||||
if (!cpu.isValidAddress(selectedAddress))
|
||||
return false;
|
||||
|
||||
bool pressHandled = false;
|
||||
@@ -352,8 +358,8 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
{
|
||||
if (keyCharIsText || (!keychar.isNonCharacter() && keychar.category() != QChar::Other_Control))
|
||||
{
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, val = keychar.toLatin1()] {
|
||||
cpu->write8(address, val);
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, val = keychar.toLatin1()] {
|
||||
cpu.write8(address, val);
|
||||
QtHost::RunOnUIThread([this] { UpdateSelectedAddress(selectedAddress + 1); parent->update(); });
|
||||
});
|
||||
pressHandled = true;
|
||||
@@ -363,8 +369,8 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
{
|
||||
case Qt::Key::Key_Backspace:
|
||||
case Qt::Key::Key_Escape:
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu] {
|
||||
cpu->write8(address, 0);
|
||||
Host::RunOnCPUThread([this, address = selectedAddress, &cpu] {
|
||||
cpu.write8(address, 0);
|
||||
QtHost::RunOnUIThread([this] {BackwardSelection(); parent->update(); });
|
||||
});
|
||||
pressHandled = true;
|
||||
@@ -391,7 +397,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
const u8 keyPressed = static_cast<u8>(QString(QChar(key)).toInt(&pressHandled, 16));
|
||||
if (pressHandled)
|
||||
{
|
||||
InsertIntoSelectedHexView(keyPressed);
|
||||
InsertIntoSelectedHexView(keyPressed, cpu);
|
||||
ForwardSelection();
|
||||
}
|
||||
}
|
||||
@@ -400,7 +406,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
{
|
||||
case Qt::Key::Key_Backspace:
|
||||
case Qt::Key::Key_Escape:
|
||||
InsertIntoSelectedHexView(0);
|
||||
InsertIntoSelectedHexView(0, cpu);
|
||||
BackwardSelection();
|
||||
pressHandled = true;
|
||||
break;
|
||||
@@ -447,123 +453,174 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
|
||||
/*
|
||||
MemoryViewWidget
|
||||
*/
|
||||
MemoryViewWidget::MemoryViewWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
MemoryViewWidget::MemoryViewWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, MONOSPACE_FONT)
|
||||
, m_table(this)
|
||||
{
|
||||
ui.setupUi(this);
|
||||
this->setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::customMenuRequested);
|
||||
|
||||
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::openContextMenu);
|
||||
|
||||
m_table.UpdateStartAddress(0x100000);
|
||||
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
update();
|
||||
return true;
|
||||
});
|
||||
|
||||
receiveEvent<DebuggerEvents::GoToAddress>([this](const DebuggerEvents::GoToAddress& event) -> bool {
|
||||
if (event.filter != DebuggerEvents::GoToAddress::NONE &&
|
||||
event.filter != DebuggerEvents::GoToAddress::MEMORY_VIEW)
|
||||
return false;
|
||||
|
||||
gotoAddress(event.address);
|
||||
|
||||
if (event.switch_to_tab)
|
||||
switchToThisTab();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
MemoryViewWidget::~MemoryViewWidget() = default;
|
||||
|
||||
void MemoryViewWidget::SetCpu(DebugInterface* cpu)
|
||||
void MemoryViewWidget::toJson(JsonValueWrapper& json)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
m_table.SetCpu(cpu);
|
||||
m_table.UpdateStartAddress(0x480000);
|
||||
DebuggerWidget::toJson(json);
|
||||
|
||||
json.value().AddMember("startAddress", m_table.startAddress, json.allocator());
|
||||
json.value().AddMember("viewType", static_cast<int>(m_table.GetViewType()), json.allocator());
|
||||
json.value().AddMember("littleEndian", m_table.GetLittleEndian(), json.allocator());
|
||||
}
|
||||
|
||||
bool MemoryViewWidget::fromJson(const JsonValueWrapper& json)
|
||||
{
|
||||
if (!DebuggerWidget::fromJson(json))
|
||||
return false;
|
||||
|
||||
auto start_address = json.value().FindMember("startAddress");
|
||||
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
|
||||
m_table.UpdateStartAddress(start_address->value.GetUint());
|
||||
|
||||
auto view_type = json.value().FindMember("viewType");
|
||||
if (view_type != json.value().MemberEnd() && view_type->value.IsInt())
|
||||
{
|
||||
MemoryViewType type = static_cast<MemoryViewType>(view_type->value.GetInt());
|
||||
if (type == MemoryViewType::BYTE ||
|
||||
type == MemoryViewType::BYTEHW ||
|
||||
type == MemoryViewType::WORD ||
|
||||
type == MemoryViewType::DWORD)
|
||||
m_table.SetViewType(type);
|
||||
}
|
||||
|
||||
auto little_endian = json.value().FindMember("littleEndian");
|
||||
if (little_endian != json.value().MemberEnd() && little_endian->value.IsBool())
|
||||
m_table.SetLittleEndian(little_endian->value.GetBool());
|
||||
|
||||
repaint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MemoryViewWidget::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
return;
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
m_table.DrawTable(painter, this->palette(), this->height());
|
||||
painter.fillRect(rect(), palette().window());
|
||||
|
||||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
m_table.DrawTable(painter, this->palette(), this->height(), cpu());
|
||||
}
|
||||
|
||||
void MemoryViewWidget::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
m_table.SelectAt(event->pos());
|
||||
repaint();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::customMenuRequested(QPoint pos)
|
||||
void MemoryViewWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
if (!m_cpu->isAlive())
|
||||
if (!cpu().isAlive())
|
||||
return;
|
||||
|
||||
if (!m_contextMenu)
|
||||
{
|
||||
m_contextMenu = new QMenu(this);
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* action = new QAction(tr("Copy Address"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper()); });
|
||||
QAction* copy_action = menu->addAction(tr("Copy Address"));
|
||||
connect(copy_action, &QAction::triggered, this, [this]() {
|
||||
QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper());
|
||||
});
|
||||
|
||||
action = new QAction(tr("Go to in Disassembly"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { emit gotoInDisasm(m_table.selectedAddress); });
|
||||
createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = m_table.selectedAddress;
|
||||
return std::optional(event);
|
||||
});
|
||||
|
||||
action = new QAction(tr("Go to address"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { contextGoToAddress(); });
|
||||
QAction* go_to_address_action = menu->addAction(tr("Go to address"));
|
||||
connect(go_to_address_action, &QAction::triggered, this, [this]() { contextGoToAddress(); });
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
menu->addSeparator();
|
||||
|
||||
m_actionLittleEndian = new QAction(tr("Show as Little Endian"));
|
||||
m_actionLittleEndian->setCheckable(true);
|
||||
m_contextMenu->addAction(m_actionLittleEndian);
|
||||
connect(m_actionLittleEndian, &QAction::triggered, this, [this]() { m_table.SetLittleEndian(m_actionLittleEndian->isChecked()); });
|
||||
QAction* endian_action = menu->addAction(tr("Show as Little Endian"));
|
||||
endian_action->setCheckable(true);
|
||||
endian_action->setChecked(m_table.GetLittleEndian());
|
||||
connect(endian_action, &QAction::triggered, this, [this, endian_action]() {
|
||||
m_table.SetLittleEndian(endian_action->isChecked());
|
||||
});
|
||||
|
||||
// View Types
|
||||
m_actionBYTE = new QAction(tr("Show as 1 byte"));
|
||||
m_actionBYTE->setCheckable(true);
|
||||
m_contextMenu->addAction(m_actionBYTE);
|
||||
connect(m_actionBYTE, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); });
|
||||
const MemoryViewType current_view_type = m_table.GetViewType();
|
||||
|
||||
m_actionBYTEHW = new QAction(tr("Show as 2 bytes"));
|
||||
m_actionBYTEHW->setCheckable(true);
|
||||
m_contextMenu->addAction(m_actionBYTEHW);
|
||||
connect(m_actionBYTEHW, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); });
|
||||
// View Types
|
||||
QActionGroup* view_type_group = new QActionGroup(menu);
|
||||
view_type_group->setExclusive(true);
|
||||
|
||||
m_actionWORD = new QAction(tr("Show as 4 bytes"));
|
||||
m_actionWORD->setCheckable(true);
|
||||
m_contextMenu->addAction(m_actionWORD);
|
||||
connect(m_actionWORD, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); });
|
||||
QAction* byte_action = menu->addAction(tr("Show as 1 byte"));
|
||||
byte_action->setCheckable(true);
|
||||
byte_action->setChecked(current_view_type == MemoryViewType::BYTE);
|
||||
connect(byte_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); });
|
||||
view_type_group->addAction(byte_action);
|
||||
|
||||
m_actionDWORD = new QAction(tr("Show as 8 bytes"));
|
||||
m_actionDWORD->setCheckable(true);
|
||||
m_contextMenu->addAction(m_actionDWORD);
|
||||
connect(m_actionDWORD, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); });
|
||||
QAction* bytehw_action = menu->addAction(tr("Show as 2 bytes"));
|
||||
bytehw_action->setCheckable(true);
|
||||
bytehw_action->setChecked(current_view_type == MemoryViewType::BYTEHW);
|
||||
connect(bytehw_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); });
|
||||
view_type_group->addAction(bytehw_action);
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
QAction* word_action = menu->addAction(tr("Show as 4 bytes"));
|
||||
word_action->setCheckable(true);
|
||||
word_action->setChecked(current_view_type == MemoryViewType::WORD);
|
||||
connect(word_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); });
|
||||
view_type_group->addAction(word_action);
|
||||
|
||||
action = new QAction((tr("Add to Saved Memory Addresses")));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { emit addToSavedAddresses(m_table.selectedAddress); });
|
||||
QAction* dword_action = menu->addAction(tr("Show as 8 bytes"));
|
||||
dword_action->setCheckable(true);
|
||||
dword_action->setChecked(current_view_type == MemoryViewType::DWORD);
|
||||
connect(dword_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); });
|
||||
view_type_group->addAction(dword_action);
|
||||
|
||||
action = new QAction(tr("Copy Byte"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { contextCopyByte(); });
|
||||
menu->addSeparator();
|
||||
|
||||
action = new QAction(tr("Copy Segment"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { contextCopySegment(); });
|
||||
createEventActions<DebuggerEvents::AddToSavedAddresses>(menu, [this]() {
|
||||
DebuggerEvents::AddToSavedAddresses event;
|
||||
event.address = m_table.selectedAddress;
|
||||
return std::optional(event);
|
||||
});
|
||||
|
||||
action = new QAction(tr("Copy Character"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { contextCopyCharacter(); });
|
||||
connect(menu->addAction(tr("Copy Byte")), &QAction::triggered, this, &MemoryViewWidget::contextCopyByte);
|
||||
connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &MemoryViewWidget::contextCopySegment);
|
||||
connect(menu->addAction(tr("Copy Character")), &QAction::triggered, this, &MemoryViewWidget::contextCopyCharacter);
|
||||
connect(menu->addAction(tr("Paste")), &QAction::triggered, this, &MemoryViewWidget::contextPaste);
|
||||
|
||||
action = new QAction(tr("Paste"));
|
||||
m_contextMenu->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this]() { contextPaste(); });
|
||||
}
|
||||
m_actionLittleEndian->setChecked(m_table.GetLittleEndian());
|
||||
|
||||
const MemoryViewType currentViewType = m_table.GetViewType();
|
||||
|
||||
m_actionBYTE->setChecked(currentViewType == MemoryViewType::BYTE);
|
||||
m_actionBYTEHW->setChecked(currentViewType == MemoryViewType::BYTEHW);
|
||||
m_actionWORD->setChecked(currentViewType == MemoryViewType::WORD);
|
||||
m_actionDWORD->setChecked(currentViewType == MemoryViewType::DWORD);
|
||||
m_contextMenu->popup(this->mapToGlobal(pos));
|
||||
menu->popup(this->mapToGlobal(pos));
|
||||
|
||||
this->repaint();
|
||||
return;
|
||||
@@ -571,22 +628,22 @@ 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()
|
||||
{
|
||||
QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment().lo, 16).toUpper());
|
||||
QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment(cpu()).lo, 16).toUpper());
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
m_table.InsertAtCurrentSelection(QApplication::clipboard()->text());
|
||||
m_table.InsertAtCurrentSelection(QApplication::clipboard()->text(), cpu());
|
||||
}
|
||||
|
||||
void MemoryViewWidget::contextGoToAddress()
|
||||
@@ -600,7 +657,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;
|
||||
@@ -628,7 +685,7 @@ void MemoryViewWidget::wheelEvent(QWheelEvent* event)
|
||||
|
||||
void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0'))
|
||||
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0', cpu()))
|
||||
{
|
||||
switch (event->key())
|
||||
{
|
||||
@@ -644,7 +701,7 @@ void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
|
||||
}
|
||||
}
|
||||
this->repaint();
|
||||
VMUpdate();
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
}
|
||||
|
||||
void MemoryViewWidget::gotoAddress(u32 address)
|
||||
@@ -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"
|
||||
@@ -27,7 +29,6 @@ enum class MemoryViewType
|
||||
class MemoryViewTable
|
||||
{
|
||||
QWidget* parent;
|
||||
DebugInterface* m_cpu;
|
||||
MemoryViewType displayType = MemoryViewType::BYTE;
|
||||
bool littleEndian = true;
|
||||
u32 rowCount;
|
||||
@@ -44,7 +45,7 @@ class MemoryViewTable
|
||||
|
||||
bool selectedNibbleHI = false;
|
||||
|
||||
void InsertIntoSelectedHexView(u8 value);
|
||||
void InsertIntoSelectedHexView(u8 value, DebugInterface& cpu);
|
||||
|
||||
template <class T>
|
||||
T convertEndian(T in)
|
||||
@@ -64,24 +65,23 @@ class MemoryViewTable
|
||||
|
||||
public:
|
||||
MemoryViewTable(QWidget* parent)
|
||||
: parent(parent){};
|
||||
: parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
u32 startAddress;
|
||||
u32 selectedAddress;
|
||||
|
||||
void SetCpu(DebugInterface* cpu)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
}
|
||||
void UpdateStartAddress(u32 start);
|
||||
void UpdateSelectedAddress(u32 selected, bool page = false);
|
||||
void DrawTable(QPainter& painter, const QPalette& palette, s32 height);
|
||||
void DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu);
|
||||
void SelectAt(QPoint pos);
|
||||
u128 GetSelectedSegment();
|
||||
void InsertAtCurrentSelection(const QString& text);
|
||||
u128 GetSelectedSegment(DebugInterface& cpu);
|
||||
void InsertAtCurrentSelection(const QString& text, DebugInterface& cpu);
|
||||
void ForwardSelection();
|
||||
void BackwardSelection();
|
||||
// Returns true if the keypress was handled
|
||||
bool KeyPress(int key, QChar keychar);
|
||||
bool KeyPress(int key, QChar keychar, DebugInterface& cpu);
|
||||
|
||||
MemoryViewType GetViewType()
|
||||
{
|
||||
@@ -104,26 +104,26 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class MemoryViewWidget final : public QWidget
|
||||
class MemoryViewWidget final : public DebuggerWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MemoryViewWidget(QWidget* parent);
|
||||
MemoryViewWidget(const DebuggerWidgetParameters& parameters);
|
||||
~MemoryViewWidget();
|
||||
|
||||
void SetCpu(DebugInterface* cpu);
|
||||
void toJson(JsonValueWrapper& json) override;
|
||||
bool fromJson(const JsonValueWrapper& json) override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseDoubleClickEvent(QMouseEvent* event);
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void customMenuRequested(QPoint pos);
|
||||
void openContextMenu(QPoint pos);
|
||||
|
||||
void contextGoToAddress();
|
||||
void contextCopyByte();
|
||||
@@ -132,21 +132,8 @@ public slots:
|
||||
void contextPaste();
|
||||
void gotoAddress(u32 address);
|
||||
|
||||
signals:
|
||||
void gotoInDisasm(u32 address, bool should_set_focus = true);
|
||||
void addToSavedAddresses(u32 address);
|
||||
void VMUpdate();
|
||||
|
||||
private:
|
||||
Ui::RegisterWidget ui;
|
||||
Ui::MemoryViewWidget ui;
|
||||
|
||||
QMenu* m_contextMenu = 0x0;
|
||||
QAction* m_actionLittleEndian;
|
||||
QAction* m_actionBYTE;
|
||||
QAction* m_actionBYTEHW;
|
||||
QAction* m_actionWORD;
|
||||
QAction* m_actionDWORD;
|
||||
|
||||
DebugInterface* m_cpu;
|
||||
MemoryViewTable m_table;
|
||||
};
|
||||
@@ -6,93 +6,112 @@
|
||||
|
||||
#include "common/Console.h"
|
||||
|
||||
std::map<BreakPointCpu, SavedAddressesModel*> SavedAddressesModel::s_instances;
|
||||
|
||||
SavedAddressesModel::SavedAddressesModel(DebugInterface& cpu, QObject* parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_cpu(cpu)
|
||||
{
|
||||
}
|
||||
|
||||
SavedAddressesModel* SavedAddressesModel::getInstance(DebugInterface& cpu)
|
||||
{
|
||||
auto iterator = s_instances.find(cpu.getCpuType());
|
||||
if (iterator == s_instances.end())
|
||||
iterator = s_instances.emplace(cpu.getCpuType(), new SavedAddressesModel(cpu)).first;
|
||||
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
QVariant SavedAddressesModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
size_t row = static_cast<size_t>(index.row());
|
||||
if (!index.isValid() || row >= m_savedAddresses.size())
|
||||
return false;
|
||||
|
||||
const SavedAddress& entry = m_savedAddresses[row];
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
{
|
||||
SavedAddress savedAddress = m_savedAddresses.at(index.row());
|
||||
switch (index.column())
|
||||
{
|
||||
case HeaderColumns::ADDRESS:
|
||||
return QString::number(savedAddress.address, 16).toUpper();
|
||||
return QString::number(entry.address, 16).toUpper();
|
||||
case HeaderColumns::LABEL:
|
||||
return savedAddress.label;
|
||||
return entry.label;
|
||||
case HeaderColumns::DESCRIPTION:
|
||||
return savedAddress.description;
|
||||
return entry.description;
|
||||
}
|
||||
}
|
||||
if (role == Qt::UserRole)
|
||||
{
|
||||
SavedAddress savedAddress = m_savedAddresses.at(index.row());
|
||||
switch (index.column())
|
||||
{
|
||||
case HeaderColumns::ADDRESS:
|
||||
return savedAddress.address;
|
||||
return entry.address;
|
||||
case HeaderColumns::LABEL:
|
||||
return savedAddress.label;
|
||||
return entry.label;
|
||||
case HeaderColumns::DESCRIPTION:
|
||||
return savedAddress.description;
|
||||
return entry.description;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool SavedAddressesModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
size_t row = static_cast<size_t>(index.row());
|
||||
if (!index.isValid() || row >= m_savedAddresses.size())
|
||||
return false;
|
||||
|
||||
SavedAddress& entry = m_savedAddresses[row];
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == Qt::EditRole)
|
||||
{
|
||||
SavedAddress addressToEdit = m_savedAddresses.at(index.row());
|
||||
if (index.column() == HeaderColumns::ADDRESS)
|
||||
{
|
||||
bool ok = false;
|
||||
const u32 address = value.toString().toUInt(&ok, 16);
|
||||
if (ok)
|
||||
addressToEdit.address = address;
|
||||
entry.address = address;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index.column() == HeaderColumns::DESCRIPTION)
|
||||
addressToEdit.description = value.toString();
|
||||
entry.description = value.toString();
|
||||
|
||||
if (index.column() == HeaderColumns::LABEL)
|
||||
addressToEdit.label = value.toString();
|
||||
m_savedAddresses.at(index.row()) = addressToEdit;
|
||||
entry.label = value.toString();
|
||||
|
||||
emit dataChanged(index, index, QList<int>(role));
|
||||
return true;
|
||||
}
|
||||
else if (role == Qt::UserRole)
|
||||
{
|
||||
SavedAddress addressToEdit = m_savedAddresses.at(index.row());
|
||||
if (index.column() == HeaderColumns::ADDRESS)
|
||||
{
|
||||
const u32 address = value.toUInt();
|
||||
addressToEdit.address = address;
|
||||
entry.address = address;
|
||||
}
|
||||
|
||||
if (index.column() == HeaderColumns::DESCRIPTION)
|
||||
addressToEdit.description = value.toString();
|
||||
entry.description = value.toString();
|
||||
|
||||
if (index.column() == HeaderColumns::LABEL)
|
||||
addressToEdit.label = value.toString();
|
||||
m_savedAddresses.at(index.row()) = addressToEdit;
|
||||
entry.label = value.toString();
|
||||
|
||||
emit dataChanged(index, index, QList<int>(role));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -155,6 +174,7 @@ bool SavedAddressesModel::removeRows(int row, int count, const QModelIndex& pare
|
||||
{
|
||||
if (row < 0 || count < 1 || static_cast<size_t>(row + count) > m_savedAddresses.size())
|
||||
return false;
|
||||
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
m_savedAddresses.erase(m_savedAddresses.begin() + row, m_savedAddresses.begin() + row + count);
|
||||
endRemoveRows();
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
QString description;
|
||||
};
|
||||
|
||||
enum HeaderColumns: int
|
||||
enum HeaderColumns : int
|
||||
{
|
||||
ADDRESS = 0,
|
||||
LABEL,
|
||||
@@ -28,14 +28,14 @@ public:
|
||||
COLUMN_COUNT
|
||||
};
|
||||
|
||||
static constexpr QHeaderView::ResizeMode HeaderResizeModes[HeaderColumns::COLUMN_COUNT] =
|
||||
{
|
||||
static constexpr QHeaderView::ResizeMode HeaderResizeModes[HeaderColumns::COLUMN_COUNT] = {
|
||||
QHeaderView::ResizeMode::ResizeToContents,
|
||||
QHeaderView::ResizeMode::ResizeToContents,
|
||||
QHeaderView::ResizeMode::Stretch,
|
||||
};
|
||||
|
||||
explicit SavedAddressesModel(DebugInterface& cpu, QObject* parent = nullptr);
|
||||
static SavedAddressesModel* getInstance(DebugInterface& cpu);
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
void clear();
|
||||
|
||||
private:
|
||||
SavedAddressesModel(DebugInterface& cpu, QObject* parent = nullptr);
|
||||
|
||||
DebugInterface& m_cpu;
|
||||
std::vector<SavedAddress> m_savedAddresses;
|
||||
|
||||
static std::map<BreakPointCpu, SavedAddressesModel*> s_instances;
|
||||
};
|
||||
166
pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp
Normal file
166
pcsx2-qt/Debugger/Memory/SavedAddressesWidget.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "SavedAddressesWidget.h"
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include "Debugger/DebuggerSettingsManager.h"
|
||||
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
SavedAddressesWidget::SavedAddressesWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, DISALLOW_MULTIPLE_INSTANCES)
|
||||
, m_model(SavedAddressesModel::getInstance(cpu()))
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.savedAddressesList->setModel(m_model);
|
||||
|
||||
m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested,
|
||||
this, &SavedAddressesWidget::openContextMenu);
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) {
|
||||
if (title.isEmpty())
|
||||
return;
|
||||
|
||||
if (m_model->rowCount() == 0)
|
||||
DebuggerSettingsManager::loadGameSettings(m_model);
|
||||
});
|
||||
|
||||
DebuggerSettingsManager::loadGameSettings(m_model);
|
||||
|
||||
for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes)
|
||||
{
|
||||
m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode);
|
||||
}
|
||||
|
||||
QTableView* savedAddressesTableView = m_ui.savedAddressesList;
|
||||
connect(m_model, &QAbstractItemModel::dataChanged, this, [savedAddressesTableView](const QModelIndex& topLeft) {
|
||||
savedAddressesTableView->resizeColumnToContents(topLeft.column());
|
||||
});
|
||||
|
||||
receiveEvent<DebuggerEvents::AddToSavedAddresses>([this](const DebuggerEvents::AddToSavedAddresses& event) {
|
||||
addAddress(event.address);
|
||||
|
||||
if (event.switch_to_tab)
|
||||
switchToThisTab();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void SavedAddressesWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* new_action = menu->addAction(tr("New"));
|
||||
connect(new_action, &QAction::triggered, this, &SavedAddressesWidget::contextNew);
|
||||
|
||||
const QModelIndex index_at_pos = m_ui.savedAddressesList->indexAt(pos);
|
||||
const bool is_index_valid = index_at_pos.isValid();
|
||||
bool is_cpu_alive = cpu().isAlive();
|
||||
|
||||
std::vector<QAction*> go_to_actions = createEventActions<DebuggerEvents::GoToAddress>(
|
||||
menu, [this, index_at_pos]() {
|
||||
const QModelIndex rowAddressIndex = m_model->index(index_at_pos.row(), 0, QModelIndex());
|
||||
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = m_model->data(rowAddressIndex, Qt::UserRole).toUInt();
|
||||
return std::optional(event);
|
||||
});
|
||||
|
||||
for (QAction* go_to_action : go_to_actions)
|
||||
go_to_action->setEnabled(is_index_valid);
|
||||
|
||||
QAction* copy_action = menu->addAction(index_at_pos.column() == 0 ? tr("Copy Address") : tr("Copy Text"));
|
||||
copy_action->setEnabled(is_index_valid);
|
||||
connect(copy_action, &QAction::triggered, [this, index_at_pos]() {
|
||||
QGuiApplication::clipboard()->setText(
|
||||
m_model->data(index_at_pos, Qt::DisplayRole).toString());
|
||||
});
|
||||
|
||||
if (m_model->rowCount() > 0)
|
||||
{
|
||||
QAction* copy_all_as_csv_action = menu->addAction(tr("Copy all as CSV"));
|
||||
connect(copy_all_as_csv_action, &QAction::triggered, [this]() {
|
||||
QGuiApplication::clipboard()->setText(
|
||||
QtUtils::AbstractItemModelToCSV(m_ui.savedAddressesList->model(), Qt::DisplayRole, true));
|
||||
});
|
||||
}
|
||||
|
||||
QAction* paste_from_csv_action = menu->addAction(tr("Paste from CSV"));
|
||||
connect(paste_from_csv_action, &QAction::triggered, this, &SavedAddressesWidget::contextPasteCSV);
|
||||
|
||||
QAction* load_action = menu->addAction(tr("Load from Settings"));
|
||||
load_action->setEnabled(is_cpu_alive);
|
||||
connect(load_action, &QAction::triggered, [this]() {
|
||||
m_model->clear();
|
||||
DebuggerSettingsManager::loadGameSettings(m_model);
|
||||
});
|
||||
|
||||
QAction* save_action = menu->addAction(tr("Save to Settings"));
|
||||
save_action->setEnabled(is_cpu_alive);
|
||||
connect(save_action, &QAction::triggered, this, &SavedAddressesWidget::saveToDebuggerSettings);
|
||||
|
||||
QAction* delete_action = menu->addAction(tr("Delete"));
|
||||
connect(delete_action, &QAction::triggered, this, [this, index_at_pos]() {
|
||||
m_model->removeRows(index_at_pos.row(), 1);
|
||||
});
|
||||
delete_action->setEnabled(is_index_valid);
|
||||
|
||||
menu->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 each_quote_pair(R"("([^"]|\\.)*")");
|
||||
QRegularExpressionMatchIterator it = each_quote_pair.globalMatch(line);
|
||||
while (it.hasNext())
|
||||
{
|
||||
QRegularExpressionMatch match = it.next();
|
||||
QString matched_value = match.captured(0);
|
||||
fields << matched_value.mid(1, matched_value.length() - 2);
|
||||
}
|
||||
|
||||
m_model->loadSavedAddressFromFieldList(fields);
|
||||
}
|
||||
}
|
||||
|
||||
void SavedAddressesWidget::contextNew()
|
||||
{
|
||||
m_model->addRow();
|
||||
const u32 row_count = m_model->rowCount();
|
||||
m_ui.savedAddressesList->edit(m_model->index(row_count - 1, 0));
|
||||
}
|
||||
|
||||
void SavedAddressesWidget::addAddress(u32 address)
|
||||
{
|
||||
m_model->addRow();
|
||||
|
||||
u32 row_count = m_model->rowCount();
|
||||
|
||||
QModelIndex address_index = m_model->index(row_count - 1, SavedAddressesModel::ADDRESS);
|
||||
m_model->setData(address_index, address, Qt::UserRole);
|
||||
|
||||
QModelIndex label_index = m_model->index(row_count - 1, SavedAddressesModel::LABEL);
|
||||
if (label_index.isValid())
|
||||
m_ui.savedAddressesList->edit(label_index);
|
||||
}
|
||||
|
||||
void SavedAddressesWidget::saveToDebuggerSettings()
|
||||
{
|
||||
DebuggerSettingsManager::saveGameSettings(m_model);
|
||||
}
|
||||
29
pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h
Normal file
29
pcsx2-qt/Debugger/Memory/SavedAddressesWidget.h
Normal file
@@ -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(const DebuggerWidgetParameters& parameters);
|
||||
|
||||
void openContextMenu(QPoint pos);
|
||||
void contextPasteCSV();
|
||||
void contextNew();
|
||||
void addAddress(u32 address);
|
||||
void saveToDebuggerSettings();
|
||||
|
||||
private:
|
||||
Ui::SavedAddressesWidget m_ui;
|
||||
|
||||
SavedAddressesModel* m_model;
|
||||
};
|
||||
39
pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui
Normal file
39
pcsx2-qt/Debugger/Memory/SavedAddressesWidget.ui
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SavedAddressesWidget</class>
|
||||
<widget class="QWidget" name="SavedAddressesWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Saved Addresses</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="savedAddressesList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "RegisterWidget.h"
|
||||
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtWidgets/QTabBar>
|
||||
@@ -13,15 +15,14 @@
|
||||
#include <QtWidgets/QProxyStyle>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
|
||||
#define CAT_SHOW_FLOAT (categoryIndex == EECAT_FPR && m_showFPRFloat) || (categoryIndex == EECAT_VU0F && m_showVU0FFloat)
|
||||
|
||||
using namespace QtUtils;
|
||||
|
||||
RegisterWidget::RegisterWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
RegisterWidget::RegisterWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, MONOSPACE_FONT)
|
||||
{
|
||||
this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||
|
||||
@@ -30,21 +31,48 @@ RegisterWidget::RegisterWidget(QWidget* parent)
|
||||
|
||||
connect(this, &RegisterWidget::customContextMenuRequested, this, &RegisterWidget::customMenuRequested);
|
||||
connect(ui.registerTabs, &QTabBar::currentChanged, this, &RegisterWidget::tabCurrentChanged);
|
||||
};
|
||||
|
||||
for (int i = 0; i < cpu().getRegisterCategoryCount(); i++)
|
||||
{
|
||||
ui.registerTabs->addTab(cpu().getRegisterCategoryName(i));
|
||||
}
|
||||
|
||||
connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); });
|
||||
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
update();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
RegisterWidget::~RegisterWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void RegisterWidget::SetCpu(DebugInterface* cpu)
|
||||
void RegisterWidget::toJson(JsonValueWrapper& json)
|
||||
{
|
||||
m_cpu = cpu;
|
||||
for (int i = 0; i < m_cpu->getRegisterCategoryCount(); i++)
|
||||
{
|
||||
ui.registerTabs->addTab(m_cpu->getRegisterCategoryName(i));
|
||||
}
|
||||
DebuggerWidget::toJson(json);
|
||||
|
||||
connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); });
|
||||
json.value().AddMember("showVU0FFloat", m_showVU0FFloat, json.allocator());
|
||||
json.value().AddMember("showFPRFloat", m_showFPRFloat, json.allocator());
|
||||
}
|
||||
|
||||
bool RegisterWidget::fromJson(const JsonValueWrapper& json)
|
||||
{
|
||||
if (!DebuggerWidget::fromJson(json))
|
||||
return false;
|
||||
|
||||
auto show_vu0f_float = json.value().FindMember("showVU0FFloat");
|
||||
if (show_vu0f_float != json.value().MemberEnd() && show_vu0f_float->value.IsBool())
|
||||
m_showVU0FFloat = show_vu0f_float->value.GetBool();
|
||||
|
||||
auto show_fpr_float = json.value().FindMember("showFPRFloat");
|
||||
if (show_fpr_float != json.value().MemberEnd() && show_fpr_float->value.IsBool())
|
||||
m_showFPRFloat = show_fpr_float->value.GetBool();
|
||||
|
||||
repaint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegisterWidget::tabCurrentChanged(int cur)
|
||||
@@ -54,9 +82,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 +119,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 +135,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 +145,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 +161,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<float>(m_cpu->getRegister(categoryIndex, registerIndex)._u32[regIndex])), Qt::ElideRight, m_fieldWidth - painter.fontMetrics().averageCharWidth()));
|
||||
painter.fontMetrics().elidedText(QString::number(std::bit_cast<float>(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 +178,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<float>(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0]))).toUpper());
|
||||
else if (m_cpu->getRegisterSize(categoryIndex) == 64)
|
||||
QString("%1").arg(QString::number(std::bit_cast<float>(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 +196,7 @@ void RegisterWidget::mousePressEvent(QMouseEvent* event)
|
||||
m_selectedRow = static_cast<int>(((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 +215,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 +229,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,85 +242,85 @@ 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)
|
||||
return;
|
||||
|
||||
// Unlike the disassembly widget, we need to create a new context menu every time
|
||||
// we show it. Because some register groups are special
|
||||
if (!m_contextMenu)
|
||||
m_contextMenu = new QMenu(this);
|
||||
else
|
||||
m_contextMenu->clear();
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
const int categoryIndex = ui.registerTabs->currentIndex();
|
||||
|
||||
QAction* action = 0;
|
||||
|
||||
if (categoryIndex == EECAT_FPR)
|
||||
{
|
||||
m_contextMenu->addAction(action = new QAction(m_showFPRFloat ? tr("View as hex") : tr("View as float")));
|
||||
connect(action, &QAction::triggered, this, [this]() { m_showFPRFloat = !m_showFPRFloat; });
|
||||
m_contextMenu->addSeparator();
|
||||
QAction* action = menu->addAction(tr("Show as Float"));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(m_showFPRFloat);
|
||||
connect(action, &QAction::triggered, this, [this]() {
|
||||
m_showFPRFloat = !m_showFPRFloat;
|
||||
repaint();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
if (categoryIndex == EECAT_VU0F)
|
||||
{
|
||||
m_contextMenu->addAction(action = new QAction(m_showVU0FFloat ? tr("View as hex") : tr("View as float")));
|
||||
connect(action, &QAction::triggered, this, [this]() { m_showVU0FFloat = !m_showVU0FFloat; });
|
||||
m_contextMenu->addSeparator();
|
||||
QAction* action = menu->addAction(tr("Show as Float"));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(m_showVU0FFloat);
|
||||
connect(action, &QAction::triggered, this, [this]() {
|
||||
m_showVU0FFloat = !m_showVU0FFloat;
|
||||
repaint();
|
||||
});
|
||||
|
||||
menu->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);
|
||||
m_contextMenu->addAction(action = new QAction(tr("Copy Bottom Half"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyBottom);
|
||||
m_contextMenu->addAction(action = new QAction(tr("Copy Segment"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopySegment);
|
||||
connect(menu->addAction(tr("Copy Top Half")), &QAction::triggered, this, &RegisterWidget::contextCopyTop);
|
||||
connect(menu->addAction(tr("Copy Bottom Half")), &QAction::triggered, this, &RegisterWidget::contextCopyBottom);
|
||||
connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &RegisterWidget::contextCopySegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_contextMenu->addAction(action = new QAction(tr("Copy Value"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyValue);
|
||||
connect(menu->addAction(tr("Copy Value")), &QAction::triggered, this, &RegisterWidget::contextCopyValue);
|
||||
}
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
menu->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);
|
||||
m_contextMenu->addAction(action = new QAction(tr("Change Bottom Half"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeBottom);
|
||||
m_contextMenu->addAction(action = new QAction(tr("Change Segment"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeSegment);
|
||||
connect(menu->addAction(tr("Change Top Half")), &QAction::triggered,
|
||||
this, &RegisterWidget::contextChangeTop);
|
||||
connect(menu->addAction(tr("Change Bottom Half")), &QAction::triggered,
|
||||
this, &RegisterWidget::contextChangeBottom);
|
||||
connect(menu->addAction(tr("Change Segment")), &QAction::triggered,
|
||||
this, &RegisterWidget::contextChangeSegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_contextMenu->addAction(action = new QAction(tr("Change Value"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeValue);
|
||||
connect(menu->addAction(tr("Change Value")), &QAction::triggered,
|
||||
this, &RegisterWidget::contextChangeValue);
|
||||
}
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
menu->addSeparator();
|
||||
|
||||
m_contextMenu->addAction(action = new QAction(tr("Go to in Disassembly"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoDisasm);
|
||||
createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
|
||||
return contextCreateGotoEvent();
|
||||
});
|
||||
|
||||
m_contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this));
|
||||
connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoMemory);
|
||||
|
||||
m_contextMenu->popup(this->mapToGlobal(pos));
|
||||
menu->popup(this->mapToGlobal(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<float>(val._u32[0])).toUpper(), 16));
|
||||
else
|
||||
@@ -305,21 +330,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<float>(val._u32[3 - m_selected128Field]), 10));
|
||||
else
|
||||
@@ -330,7 +355,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 +366,7 @@ bool RegisterWidget::contextFetchNewValue(u64& out, u64 currentValue, bool segme
|
||||
existingValue = existingValue.arg(std::bit_cast<float>((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,76 +398,70 @@ 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));
|
||||
VMUpdate();
|
||||
cpu().setRegister(categoryIndex, m_selectedRow, u128::From64(newVal));
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
VMUpdate();
|
||||
cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
VMUpdate();
|
||||
cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
VMUpdate();
|
||||
cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
|
||||
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterWidget::contextGotoDisasm()
|
||||
std::optional<DebuggerEvents::GoToAddress> RegisterWidget::contextCreateGotoEvent()
|
||||
{
|
||||
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))
|
||||
gotoInDisasm(addr);
|
||||
else
|
||||
QMessageBox::warning(this, tr("Invalid target address"), ("This register holds an invalid address."));
|
||||
}
|
||||
|
||||
void RegisterWidget::contextGotoMemory()
|
||||
{
|
||||
const int categoryIndex = ui.registerTabs->currentIndex();
|
||||
u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow);
|
||||
u32 addr = 0;
|
||||
|
||||
if (m_cpu->getRegisterSize(categoryIndex) == 128)
|
||||
addr = regVal._u32[3 - m_selected128Field];
|
||||
else
|
||||
addr = regVal._u32[0];
|
||||
|
||||
gotoInMemory(addr);
|
||||
if (!cpu().isValidAddress(addr))
|
||||
{
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
tr("Invalid target address"),
|
||||
tr("This register holds an invalid address."));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = addr;
|
||||
return event;
|
||||
}
|
||||
|
||||
@@ -5,29 +5,31 @@
|
||||
|
||||
#include "ui_RegisterWidget.h"
|
||||
|
||||
#include "DebuggerWidget.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/DisassemblyManager.h"
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QTabBar>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
class RegisterWidget final : public QWidget
|
||||
class RegisterWidget final : public DebuggerWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RegisterWidget(QWidget* parent);
|
||||
RegisterWidget(const DebuggerWidgetParameters& parameters);
|
||||
~RegisterWidget();
|
||||
|
||||
void SetCpu(DebugInterface* cpu);
|
||||
void toJson(JsonValueWrapper& json) override;
|
||||
bool fromJson(const JsonValueWrapper& json) override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseDoubleClickEvent(QMouseEvent* event);
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void customMenuRequested(QPoint pos);
|
||||
@@ -40,41 +42,30 @@ public slots:
|
||||
void contextChangeBottom();
|
||||
void contextChangeSegment();
|
||||
|
||||
void contextGotoDisasm();
|
||||
void contextGotoMemory();
|
||||
std::optional<DebuggerEvents::GoToAddress> contextCreateGotoEvent();
|
||||
|
||||
void tabCurrentChanged(int cur);
|
||||
|
||||
signals:
|
||||
void gotoInDisasm(u32 address, bool should_set_focus = true);
|
||||
void gotoInMemory(u32 address);
|
||||
void VMUpdate();
|
||||
|
||||
private:
|
||||
Ui::RegisterWidget ui;
|
||||
|
||||
QMenu* m_contextMenu = 0x0;
|
||||
|
||||
// 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;
|
||||
|
||||
s32 m_rowStart = 0; // Index, 0 -> VF00, 1 -> VF01 etc
|
||||
s32 m_rowEnd; // Index, what register is the last one drawn
|
||||
s32 m_rowHeight; // The height of each register row
|
||||
s32 m_rowStart = 0; // Index, 0 -> VF00, 1 -> VF01 etc
|
||||
s32 m_rowEnd; // Index, what register is the last one drawn
|
||||
s32 m_rowHeight; // The height of each register row
|
||||
// Used for mouse clicks
|
||||
s32 m_fieldStartX[4]; // Where the register segments start
|
||||
s32 m_fieldWidth; // How wide the register segments are
|
||||
s32 m_fieldStartX[4]; // Where the register segments start
|
||||
s32 m_fieldWidth; // How wide the register segments are
|
||||
|
||||
s32 m_selectedRow = 0; // Index
|
||||
s32 m_selectedRow = 0; // Index
|
||||
s32 m_selected128Field = 0; // Values are from 0 to 3
|
||||
|
||||
// TODO: Save this configuration ??
|
||||
bool m_showVU0FFloat = false;
|
||||
bool m_showFPRFloat = false;
|
||||
};
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
<height>316</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@@ -25,31 +25,46 @@
|
||||
<property name="windowTitle">
|
||||
<string>Register View</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>411</width>
|
||||
<height>301</height>
|
||||
</rect>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignLeft|Qt::AlignTop">
|
||||
<widget class="QTabBar" name="registerTabs" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabBar" name="registerTabs" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>289</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -24,16 +24,20 @@ int StackModel::columnCount(const QModelIndex&) const
|
||||
|
||||
QVariant StackModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
size_t row = static_cast<size_t>(index.row());
|
||||
if (row >= m_stackFrames.size())
|
||||
return QVariant();
|
||||
|
||||
const auto& stackFrame = m_stackFrames[row];
|
||||
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
const auto& stackFrame = m_stackFrames.at(index.row());
|
||||
|
||||
switch (index.column())
|
||||
{
|
||||
case StackModel::ENTRY:
|
||||
return QtUtils::FilledQStringFromValue(stackFrame.entry, 16);
|
||||
case StackModel::ENTRY_LABEL:
|
||||
return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(stackFrame.entry).name);
|
||||
return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(stackFrame.entry).name);
|
||||
case StackModel::PC:
|
||||
return QtUtils::FilledQStringFromValue(stackFrame.pc, 16);
|
||||
case StackModel::PC_OPCODE:
|
||||
@@ -63,6 +67,7 @@ QVariant StackModel::data(const QModelIndex& index, int role) const
|
||||
return stackFrame.stackSize;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
84
pcsx2-qt/Debugger/StackWidget.cpp
Normal file
84
pcsx2-qt/Debugger/StackWidget.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "StackWidget.h"
|
||||
|
||||
#include "QtUtils.h"
|
||||
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
StackWidget::StackWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, NO_DEBUGGER_FLAGS)
|
||||
, m_model(new StackModel(cpu()))
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.stackList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &StackWidget::openContextMenu);
|
||||
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++;
|
||||
}
|
||||
|
||||
receiveEvent<DebuggerEvents::VMUpdate>([this](const DebuggerEvents::VMUpdate& event) -> bool {
|
||||
m_model->refreshData();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void StackWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
if (!m_ui.stackList->selectionModel()->hasSelection())
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(m_ui.stackList);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* copy_action = menu->addAction(tr("Copy"));
|
||||
connect(copy_action, &QAction::triggered, [this]() {
|
||||
const auto* selection_model = m_ui.stackList->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return;
|
||||
|
||||
QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString());
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* copy_all_as_csv_action = menu->addAction(tr("Copy all as CSV"));
|
||||
connect(copy_all_as_csv_action, &QAction::triggered, [this]() {
|
||||
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model()));
|
||||
});
|
||||
|
||||
menu->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:
|
||||
{
|
||||
QModelIndex entry_index = m_model->index(index.row(), StackModel::StackColumns::ENTRY);
|
||||
goToInDisassembler(m_model->data(entry_index, Qt::UserRole).toUInt(), true);
|
||||
break;
|
||||
}
|
||||
case StackModel::StackModel::SP:
|
||||
{
|
||||
goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true);
|
||||
break;
|
||||
}
|
||||
default: // Default to PC
|
||||
{
|
||||
QModelIndex pc_index = m_model->index(index.row(), StackModel::StackColumns::PC);
|
||||
goToInDisassembler(m_model->data(pc_index, Qt::UserRole).toUInt(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
pcsx2-qt/Debugger/StackWidget.h
Normal file
26
pcsx2-qt/Debugger/StackWidget.h
Normal file
@@ -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(const DebuggerWidgetParameters& parameters);
|
||||
|
||||
void openContextMenu(QPoint pos);
|
||||
void onDoubleClick(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
Ui::StackWidget m_ui;
|
||||
|
||||
StackModel* m_model;
|
||||
};
|
||||
39
pcsx2-qt/Debugger/StackWidget.ui
Normal file
39
pcsx2-qt/Debugger/StackWidget.ui
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>StackWidget</class>
|
||||
<widget class="QWidget" name="StackWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Stack</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="stackList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -6,12 +6,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>419</width>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string>Symbol Tree</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="vertical_layout">
|
||||
<property name="spacing">
|
||||
|
||||
@@ -3,30 +3,32 @@
|
||||
|
||||
#include "SymbolTreeWidgets.h"
|
||||
|
||||
#include "Debugger/JsonValueWrapper.h"
|
||||
#include "Debugger/SymbolTree/NewSymbolDialogs.h"
|
||||
#include "Debugger/SymbolTree/SymbolTreeDelegates.h"
|
||||
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
|
||||
#include "NewSymbolDialogs.h"
|
||||
#include "SymbolTreeDelegates.h"
|
||||
|
||||
static bool testName(const QString& name, const QString& filter);
|
||||
|
||||
SymbolTreeWidget::SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_cpu(cpu)
|
||||
SymbolTreeWidget::SymbolTreeWidget(
|
||||
u32 flags,
|
||||
s32 symbol_address_alignment,
|
||||
const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, MONOSPACE_FONT)
|
||||
, m_flags(flags)
|
||||
, m_symbol_address_alignment(symbol_address_alignment)
|
||||
, m_group_by_module(cpu().getCpuType() == BREAKPOINT_IOP)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setupMenu();
|
||||
|
||||
connect(m_ui.refreshButton, &QPushButton::clicked, this, [&]() {
|
||||
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
m_cpu.GetSymbolGuardian().UpdateFunctionHashes(database, m_cpu);
|
||||
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
cpu().GetSymbolGuardian().UpdateFunctionHashes(database, cpu());
|
||||
});
|
||||
|
||||
reset();
|
||||
@@ -42,11 +44,16 @@ SymbolTreeWidget::SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, Debu
|
||||
});
|
||||
|
||||
m_ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openMenu);
|
||||
connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openContextMenu);
|
||||
|
||||
connect(m_ui.treeView, &QTreeView::expanded, this, [&]() {
|
||||
updateVisibleNodes(true);
|
||||
});
|
||||
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
updateModel();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
SymbolTreeWidget::~SymbolTreeWidget() = default;
|
||||
@@ -58,6 +65,78 @@ void SymbolTreeWidget::resizeEvent(QResizeEvent* event)
|
||||
updateVisibleNodes(false);
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::toJson(JsonValueWrapper& json)
|
||||
{
|
||||
DebuggerWidget::toJson(json);
|
||||
|
||||
json.value().AddMember("showSizeColumn", m_show_size_column, json.allocator());
|
||||
if (m_flags & ALLOW_GROUPING)
|
||||
{
|
||||
json.value().AddMember("groupByModule", m_group_by_module, json.allocator());
|
||||
json.value().AddMember("groupBySection", m_group_by_section, json.allocator());
|
||||
json.value().AddMember("groupBySourceFile", m_group_by_source_file, json.allocator());
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
|
||||
{
|
||||
json.value().AddMember("sortByIfTypeIsKnown", m_sort_by_if_type_is_known, json.allocator());
|
||||
}
|
||||
}
|
||||
|
||||
bool SymbolTreeWidget::fromJson(const JsonValueWrapper& json)
|
||||
{
|
||||
if (!DebuggerWidget::fromJson(json))
|
||||
return false;
|
||||
|
||||
bool needs_reset = false;
|
||||
|
||||
auto show_size_column = json.value().FindMember("showSizeColumn");
|
||||
if (show_size_column != json.value().MemberEnd() && show_size_column->value.IsBool())
|
||||
{
|
||||
needs_reset |= show_size_column->value.GetBool() != m_show_size_column;
|
||||
m_show_size_column = show_size_column->value.GetBool();
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_GROUPING)
|
||||
{
|
||||
auto group_by_module = json.value().FindMember("groupByModule");
|
||||
if (group_by_module != json.value().MemberEnd() && group_by_module->value.IsBool())
|
||||
{
|
||||
needs_reset |= group_by_module->value.GetBool() != m_group_by_module;
|
||||
m_group_by_module = group_by_module->value.GetBool();
|
||||
}
|
||||
|
||||
auto group_by_section = json.value().FindMember("groupBySection");
|
||||
if (group_by_section != json.value().MemberEnd() && group_by_section->value.IsBool())
|
||||
{
|
||||
needs_reset |= group_by_section->value.GetBool() != m_group_by_section;
|
||||
m_group_by_section = group_by_section->value.GetBool();
|
||||
}
|
||||
|
||||
auto group_by_source_file = json.value().FindMember("groupBySourceFile");
|
||||
if (group_by_source_file != json.value().MemberEnd() && group_by_source_file->value.IsBool())
|
||||
{
|
||||
needs_reset |= group_by_source_file->value.GetBool() != m_group_by_source_file;
|
||||
m_group_by_source_file = group_by_source_file->value.GetBool();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
|
||||
{
|
||||
auto sort_by_if_type_is_known = json.value().FindMember("sortByIfTypeIsKnown");
|
||||
if (sort_by_if_type_is_known != json.value().MemberEnd() && sort_by_if_type_is_known->value.IsBool())
|
||||
{
|
||||
needs_reset |= sort_by_if_type_is_known->value.GetBool() != m_sort_by_if_type_is_known;
|
||||
m_sort_by_if_type_is_known = sort_by_if_type_is_known->value.GetBool();
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_reset)
|
||||
reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::updateModel()
|
||||
{
|
||||
if (needsReset())
|
||||
@@ -71,28 +150,22 @@ void SymbolTreeWidget::reset()
|
||||
if (!m_model)
|
||||
setupTree();
|
||||
|
||||
m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column || !m_show_size_column->isChecked());
|
||||
m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column);
|
||||
|
||||
SymbolFilters filters;
|
||||
std::unique_ptr<SymbolTreeNode> root;
|
||||
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
||||
filters.group_by_module = m_group_by_module && m_group_by_module->isChecked();
|
||||
filters.group_by_section = m_group_by_section && m_group_by_section->isChecked();
|
||||
filters.group_by_source_file = m_group_by_source_file && m_group_by_source_file->isChecked();
|
||||
filters.string = m_ui.filterBox->text();
|
||||
|
||||
root = buildTree(filters, database);
|
||||
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
||||
root = buildTree(database);
|
||||
});
|
||||
|
||||
if (root)
|
||||
{
|
||||
root->sortChildrenRecursively(m_sort_by_if_type_is_known && m_sort_by_if_type_is_known->isChecked());
|
||||
root->sortChildrenRecursively(m_sort_by_if_type_is_known);
|
||||
m_model->reset(std::move(root));
|
||||
|
||||
// Read the initial values for visible nodes.
|
||||
updateVisibleNodes(true);
|
||||
|
||||
if (!filters.string.isEmpty())
|
||||
if (!m_ui.filterBox->text().isEmpty())
|
||||
expandGroups(QModelIndex());
|
||||
}
|
||||
}
|
||||
@@ -114,8 +187,8 @@ void SymbolTreeWidget::updateVisibleNodes(bool update_hashes)
|
||||
// Hash functions for symbols with visible nodes.
|
||||
if (update_hashes)
|
||||
{
|
||||
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
SymbolTreeNode::updateSymbolHashes(nodes, m_cpu, database);
|
||||
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
SymbolTreeNode::updateSymbolHashes(nodes, cpu(), database);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,16 +220,16 @@ void SymbolTreeWidget::expandGroups(QModelIndex index)
|
||||
|
||||
void SymbolTreeWidget::setupTree()
|
||||
{
|
||||
m_model = new SymbolTreeModel(m_cpu, this);
|
||||
m_model = new SymbolTreeModel(cpu(), this);
|
||||
m_ui.treeView->setModel(m_model);
|
||||
|
||||
auto location_delegate = new SymbolTreeLocationDelegate(m_cpu, m_symbol_address_alignment, this);
|
||||
auto location_delegate = new SymbolTreeLocationDelegate(cpu(), m_symbol_address_alignment, this);
|
||||
m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::LOCATION, location_delegate);
|
||||
|
||||
auto type_delegate = new SymbolTreeTypeDelegate(m_cpu, this);
|
||||
auto type_delegate = new SymbolTreeTypeDelegate(cpu(), this);
|
||||
m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::TYPE, type_delegate);
|
||||
|
||||
auto value_delegate = new SymbolTreeValueDelegate(m_cpu, this);
|
||||
auto value_delegate = new SymbolTreeValueDelegate(cpu(), this);
|
||||
m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::VALUE, value_delegate);
|
||||
|
||||
m_ui.treeView->setAlternatingRowColors(true);
|
||||
@@ -167,9 +240,9 @@ void SymbolTreeWidget::setupTree()
|
||||
connect(m_ui.treeView, &QTreeView::pressed, this, &SymbolTreeWidget::onTreeViewClicked);
|
||||
}
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database)
|
||||
std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const ccc::SymbolDatabase& database)
|
||||
{
|
||||
std::vector<SymbolWork> symbols = getSymbols(filters.string, database);
|
||||
std::vector<SymbolWork> symbols = getSymbols(m_ui.filterBox->text(), database);
|
||||
|
||||
auto source_file_comparator = [](const SymbolWork& lhs, const SymbolWork& rhs) -> bool {
|
||||
if (lhs.source_file)
|
||||
@@ -194,13 +267,13 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const SymbolFilters&
|
||||
|
||||
// Sort all of the symbols so that we can iterate over them in order and
|
||||
// build a tree.
|
||||
if (filters.group_by_source_file)
|
||||
if (m_group_by_source_file)
|
||||
std::stable_sort(symbols.begin(), symbols.end(), source_file_comparator);
|
||||
|
||||
if (filters.group_by_section)
|
||||
if (m_group_by_section)
|
||||
std::stable_sort(symbols.begin(), symbols.end(), section_comparator);
|
||||
|
||||
if (filters.group_by_module)
|
||||
if (m_group_by_module)
|
||||
std::stable_sort(symbols.begin(), symbols.end(), module_comparator);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> root = std::make_unique<SymbolTreeNode>();
|
||||
@@ -221,23 +294,23 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const SymbolFilters&
|
||||
{
|
||||
std::unique_ptr<SymbolTreeNode> node = buildNode(work, database);
|
||||
|
||||
if (filters.group_by_source_file)
|
||||
if (m_group_by_source_file)
|
||||
{
|
||||
node = groupBySourceFile(std::move(node), work, source_file_node, source_file_work, filters);
|
||||
node = groupBySourceFile(std::move(node), work, source_file_node, source_file_work);
|
||||
if (!node)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filters.group_by_section)
|
||||
if (m_group_by_section)
|
||||
{
|
||||
node = groupBySection(std::move(node), work, section_node, section_work, filters);
|
||||
node = groupBySection(std::move(node), work, section_node, section_work);
|
||||
if (!node)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filters.group_by_module)
|
||||
if (m_group_by_module)
|
||||
{
|
||||
node = groupByModule(std::move(node), work, module_node, module_work, filters);
|
||||
node = groupByModule(std::move(node), work, module_node, module_work);
|
||||
if (!node)
|
||||
continue;
|
||||
}
|
||||
@@ -252,14 +325,13 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupBySourceFile(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work,
|
||||
const SymbolFilters& filters)
|
||||
const SymbolWork*& prev_work)
|
||||
{
|
||||
bool group_exists =
|
||||
prev_group &&
|
||||
child_work.source_file == prev_work->source_file &&
|
||||
(!filters.group_by_section || child_work.section == prev_work->section) &&
|
||||
(!filters.group_by_module || child_work.module_symbol == prev_work->module_symbol);
|
||||
(!m_group_by_section || child_work.section == prev_work->section) &&
|
||||
(!m_group_by_module || child_work.module_symbol == prev_work->module_symbol);
|
||||
if (group_exists)
|
||||
{
|
||||
prev_group->emplaceChild(std::move(child));
|
||||
@@ -296,13 +368,12 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupBySection(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work,
|
||||
const SymbolFilters& filters)
|
||||
const SymbolWork*& prev_work)
|
||||
{
|
||||
bool group_exists =
|
||||
prev_group &&
|
||||
child_work.section == prev_work->section &&
|
||||
(!filters.group_by_module || child_work.module_symbol == prev_work->module_symbol);
|
||||
(!m_group_by_module || child_work.module_symbol == prev_work->module_symbol);
|
||||
if (group_exists)
|
||||
{
|
||||
prev_group->emplaceChild(std::move(child));
|
||||
@@ -336,8 +407,7 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupByModule(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work,
|
||||
const SymbolFilters& filters)
|
||||
const SymbolWork*& prev_work)
|
||||
{
|
||||
bool group_exists =
|
||||
prev_group &&
|
||||
@@ -378,95 +448,7 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupByModule(
|
||||
return child;
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::setupMenu()
|
||||
{
|
||||
m_context_menu = new QMenu(this);
|
||||
|
||||
QAction* copy_name = new QAction(tr("Copy Name"), this);
|
||||
connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName);
|
||||
m_context_menu->addAction(copy_name);
|
||||
|
||||
if (m_flags & ALLOW_MANGLED_NAME_ACTIONS)
|
||||
{
|
||||
QAction* copy_mangled_name = new QAction(tr("Copy Mangled Name"), this);
|
||||
connect(copy_mangled_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyMangledName);
|
||||
m_context_menu->addAction(copy_mangled_name);
|
||||
}
|
||||
|
||||
QAction* copy_location = new QAction(tr("Copy Location"), this);
|
||||
connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation);
|
||||
m_context_menu->addAction(copy_location);
|
||||
|
||||
m_context_menu->addSeparator();
|
||||
|
||||
m_rename_symbol = new QAction(tr("Rename Symbol"), this);
|
||||
connect(m_rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol);
|
||||
m_context_menu->addAction(m_rename_symbol);
|
||||
|
||||
m_context_menu->addSeparator();
|
||||
|
||||
m_go_to_in_disassembly = new QAction(tr("Go to in Disassembly"), this);
|
||||
connect(m_go_to_in_disassembly, &QAction::triggered, this, &SymbolTreeWidget::onGoToInDisassembly);
|
||||
m_context_menu->addAction(m_go_to_in_disassembly);
|
||||
|
||||
m_m_go_to_in_memory_view = new QAction(tr("Go to in Memory View"), this);
|
||||
connect(m_m_go_to_in_memory_view, &QAction::triggered, this, &SymbolTreeWidget::onGoToInMemoryView);
|
||||
m_context_menu->addAction(m_m_go_to_in_memory_view);
|
||||
|
||||
m_show_size_column = new QAction(tr("Show Size Column"), this);
|
||||
m_show_size_column->setCheckable(true);
|
||||
connect(m_show_size_column, &QAction::triggered, this, &SymbolTreeWidget::reset);
|
||||
m_context_menu->addAction(m_show_size_column);
|
||||
|
||||
if (m_flags & ALLOW_GROUPING)
|
||||
{
|
||||
m_context_menu->addSeparator();
|
||||
|
||||
m_group_by_module = new QAction(tr("Group by Module"), this);
|
||||
m_group_by_module->setCheckable(true);
|
||||
if (m_cpu.getCpuType() == BREAKPOINT_IOP)
|
||||
m_group_by_module->setChecked(true);
|
||||
connect(m_group_by_module, &QAction::toggled, this, &SymbolTreeWidget::reset);
|
||||
m_context_menu->addAction(m_group_by_module);
|
||||
|
||||
m_group_by_section = new QAction(tr("Group by Section"), this);
|
||||
m_group_by_section->setCheckable(true);
|
||||
connect(m_group_by_section, &QAction::toggled, this, &SymbolTreeWidget::reset);
|
||||
m_context_menu->addAction(m_group_by_section);
|
||||
|
||||
m_group_by_source_file = new QAction(tr("Group by Source File"), this);
|
||||
m_group_by_source_file->setCheckable(true);
|
||||
connect(m_group_by_source_file, &QAction::toggled, this, &SymbolTreeWidget::reset);
|
||||
m_context_menu->addAction(m_group_by_source_file);
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
|
||||
{
|
||||
m_context_menu->addSeparator();
|
||||
|
||||
m_sort_by_if_type_is_known = new QAction(tr("Sort by if type is known"), this);
|
||||
m_sort_by_if_type_is_known->setCheckable(true);
|
||||
m_context_menu->addAction(m_sort_by_if_type_is_known);
|
||||
|
||||
connect(m_sort_by_if_type_is_known, &QAction::toggled, this, &SymbolTreeWidget::reset);
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_TYPE_ACTIONS)
|
||||
{
|
||||
m_context_menu->addSeparator();
|
||||
|
||||
m_reset_children = new QAction(tr("Reset children"), this);
|
||||
m_context_menu->addAction(m_reset_children);
|
||||
|
||||
m_change_type_temporarily = new QAction(tr("Change type temporarily"), this);
|
||||
m_context_menu->addAction(m_change_type_temporarily);
|
||||
|
||||
connect(m_reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren);
|
||||
connect(m_change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily);
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::openMenu(QPoint pos)
|
||||
void SymbolTreeWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
SymbolTreeNode* node = currentNode();
|
||||
if (!node)
|
||||
@@ -476,17 +458,107 @@ void SymbolTreeWidget::openMenu(QPoint pos)
|
||||
bool node_is_symbol = node->symbol.valid();
|
||||
bool node_is_memory = node->location.type == SymbolTreeLocation::MEMORY;
|
||||
|
||||
m_rename_symbol->setEnabled(node_is_symbol);
|
||||
m_go_to_in_disassembly->setEnabled(node_is_memory);
|
||||
m_m_go_to_in_memory_view->setEnabled(node_is_memory);
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (m_reset_children)
|
||||
m_reset_children->setEnabled(node_is_object);
|
||||
QAction* copy_name = menu->addAction(tr("Copy Name"));
|
||||
connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName);
|
||||
|
||||
if (m_change_type_temporarily)
|
||||
m_change_type_temporarily->setEnabled(node_is_object);
|
||||
if (m_flags & ALLOW_MANGLED_NAME_ACTIONS)
|
||||
{
|
||||
QAction* copy_mangled_name = menu->addAction(tr("Copy Mangled Name"));
|
||||
connect(copy_mangled_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyMangledName);
|
||||
}
|
||||
|
||||
m_context_menu->exec(m_ui.treeView->viewport()->mapToGlobal(pos));
|
||||
QAction* copy_location = menu->addAction(tr("Copy Location"));
|
||||
connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* rename_symbol = menu->addAction(tr("Rename Symbol"));
|
||||
rename_symbol->setEnabled(node_is_symbol);
|
||||
connect(rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
std::vector<QAction*> go_to_actions = createEventActions<DebuggerEvents::GoToAddress>(
|
||||
menu, [this]() -> std::optional<DebuggerEvents::GoToAddress> {
|
||||
SymbolTreeNode* node = currentNode();
|
||||
if (!node)
|
||||
return std::nullopt;
|
||||
|
||||
DebuggerEvents::GoToAddress event;
|
||||
event.address = node->location.address;
|
||||
return event;
|
||||
});
|
||||
|
||||
for (QAction* action : go_to_actions)
|
||||
action->setEnabled(node_is_memory);
|
||||
|
||||
QAction* show_size_column = menu->addAction(tr("Show Size Column"));
|
||||
show_size_column->setCheckable(true);
|
||||
show_size_column->setChecked(m_show_size_column);
|
||||
connect(show_size_column, &QAction::triggered, this, [this](bool checked) {
|
||||
m_show_size_column = checked;
|
||||
m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column);
|
||||
});
|
||||
|
||||
if (m_flags & ALLOW_GROUPING)
|
||||
{
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* group_by_module = menu->addAction(tr("Group by Module"));
|
||||
group_by_module->setCheckable(true);
|
||||
group_by_module->setChecked(m_group_by_module);
|
||||
connect(group_by_module, &QAction::toggled, this, [this](bool checked) {
|
||||
m_group_by_module = checked;
|
||||
reset();
|
||||
});
|
||||
|
||||
QAction* group_by_section = menu->addAction(tr("Group by Section"));
|
||||
group_by_section->setCheckable(true);
|
||||
group_by_section->setChecked(m_group_by_section);
|
||||
connect(group_by_section, &QAction::toggled, this, [this](bool checked) {
|
||||
m_group_by_section = checked;
|
||||
reset();
|
||||
});
|
||||
|
||||
QAction* group_by_source_file = menu->addAction(tr("Group by Source File"));
|
||||
group_by_source_file->setCheckable(true);
|
||||
group_by_source_file->setChecked(m_group_by_source_file);
|
||||
connect(group_by_source_file, &QAction::toggled, this, [this](bool checked) {
|
||||
m_group_by_source_file = checked;
|
||||
reset();
|
||||
});
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
|
||||
{
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* sort_by_if_type_is_known = menu->addAction(tr("Sort by if type is known"));
|
||||
sort_by_if_type_is_known->setCheckable(true);
|
||||
sort_by_if_type_is_known->setChecked(m_sort_by_if_type_is_known);
|
||||
connect(sort_by_if_type_is_known, &QAction::toggled, this, [this](bool checked) {
|
||||
m_sort_by_if_type_is_known = checked;
|
||||
reset();
|
||||
});
|
||||
}
|
||||
|
||||
if (m_flags & ALLOW_TYPE_ACTIONS)
|
||||
{
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* reset_children = menu->addAction(tr("Reset Children"));
|
||||
reset_children->setEnabled(node_is_object);
|
||||
connect(reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren);
|
||||
|
||||
QAction* change_type_temporarily = menu->addAction(tr("Change Type Temporarily"));
|
||||
change_type_temporarily->setEnabled(node_is_object);
|
||||
connect(change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily);
|
||||
}
|
||||
|
||||
menu->popup(m_ui.treeView->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
bool SymbolTreeWidget::needsReset() const
|
||||
@@ -506,7 +578,7 @@ void SymbolTreeWidget::onDeleteButtonPressed()
|
||||
if (QMessageBox::question(this, tr("Confirm Deletion"), tr("Delete '%1'?").arg(node->name)) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
node->symbol.destroy_symbol(database, true);
|
||||
});
|
||||
|
||||
@@ -540,7 +612,7 @@ void SymbolTreeWidget::onCopyLocation()
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
QApplication::clipboard()->setText(node->location.toString(m_cpu));
|
||||
QApplication::clipboard()->setText(node->location.toString(cpu()));
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::onRenameSymbol()
|
||||
@@ -553,7 +625,7 @@ void SymbolTreeWidget::onRenameSymbol()
|
||||
QString label = tr("Name:");
|
||||
|
||||
QString text;
|
||||
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
||||
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
||||
const ccc::Symbol* symbol = node->symbol.lookup_symbol(database);
|
||||
if (!symbol || !symbol->address().valid())
|
||||
return;
|
||||
@@ -566,29 +638,11 @@ void SymbolTreeWidget::onRenameSymbol()
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
|
||||
node->symbol.rename_symbol(name, database);
|
||||
});
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::onGoToInDisassembly()
|
||||
{
|
||||
SymbolTreeNode* node = currentNode();
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
emit goToInDisassembly(node->location.address);
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::onGoToInMemoryView()
|
||||
{
|
||||
SymbolTreeNode* node = currentNode();
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
emit goToInMemoryView(node->location.address);
|
||||
}
|
||||
|
||||
void SymbolTreeWidget::onResetChildren()
|
||||
{
|
||||
if (!m_model)
|
||||
@@ -634,19 +688,17 @@ void SymbolTreeWidget::onTreeViewClicked(const QModelIndex& index)
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
SymbolTreeNode* node = m_model->nodeFromIndex(index);
|
||||
if (!node)
|
||||
if ((m_flags & CLICK_TO_GO_TO_IN_DISASSEMBLER) == 0)
|
||||
return;
|
||||
|
||||
switch (index.column())
|
||||
{
|
||||
case SymbolTreeModel::NAME:
|
||||
emit nameColumnClicked(node->location.address);
|
||||
break;
|
||||
case SymbolTreeModel::LOCATION:
|
||||
emit locationColumnClicked(node->location.address);
|
||||
break;
|
||||
}
|
||||
if ((QGuiApplication::mouseButtons() & Qt::LeftButton) == 0)
|
||||
return;
|
||||
|
||||
SymbolTreeNode* node = m_model->nodeFromIndex(index);
|
||||
if (!node || node->location.type != SymbolTreeLocation::MEMORY)
|
||||
return;
|
||||
|
||||
goToInDisassembler(node->location.address, false);
|
||||
}
|
||||
|
||||
SymbolTreeNode* SymbolTreeWidget::currentNode()
|
||||
@@ -660,8 +712,11 @@ SymbolTreeNode* SymbolTreeWidget::currentNode()
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
FunctionTreeWidget::FunctionTreeWidget(DebugInterface& cpu, QWidget* parent)
|
||||
: SymbolTreeWidget(ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS, 4, cpu, parent)
|
||||
FunctionTreeWidget::FunctionTreeWidget(const DebuggerWidgetParameters& parameters)
|
||||
: SymbolTreeWidget(
|
||||
ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS | CLICK_TO_GO_TO_IN_DISASSEMBLER,
|
||||
4,
|
||||
parameters)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -737,15 +792,19 @@ void FunctionTreeWidget::configureColumns()
|
||||
|
||||
void FunctionTreeWidget::onNewButtonPressed()
|
||||
{
|
||||
NewFunctionDialog* dialog = new NewFunctionDialog(m_cpu, this);
|
||||
NewFunctionDialog* dialog = new NewFunctionDialog(cpu(), this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
if (dialog->exec() == QDialog::Accepted)
|
||||
reset();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
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)
|
||||
GlobalVariableTreeWidget::GlobalVariableTreeWidget(const DebuggerWidgetParameters& parameters)
|
||||
: SymbolTreeWidget(
|
||||
ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS | ALLOW_MANGLED_NAME_ACTIONS,
|
||||
1,
|
||||
parameters)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -876,15 +935,19 @@ void GlobalVariableTreeWidget::configureColumns()
|
||||
|
||||
void GlobalVariableTreeWidget::onNewButtonPressed()
|
||||
{
|
||||
NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(m_cpu, this);
|
||||
NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(cpu(), this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
if (dialog->exec() == QDialog::Accepted)
|
||||
reset();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
LocalVariableTreeWidget::LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent)
|
||||
: SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent)
|
||||
LocalVariableTreeWidget::LocalVariableTreeWidget(const DebuggerWidgetParameters& parameters)
|
||||
: SymbolTreeWidget(
|
||||
ALLOW_TYPE_ACTIONS,
|
||||
1,
|
||||
parameters)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -895,10 +958,10 @@ bool LocalVariableTreeWidget::needsReset() const
|
||||
if (!m_function.valid())
|
||||
return true;
|
||||
|
||||
u32 program_counter = m_cpu.getPC();
|
||||
u32 program_counter = cpu().getPC();
|
||||
|
||||
bool left_function = true;
|
||||
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
||||
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
||||
const ccc::Function* function = database.functions.symbol_from_handle(m_function);
|
||||
if (!function || !function->address().valid())
|
||||
return;
|
||||
@@ -918,7 +981,7 @@ bool LocalVariableTreeWidget::needsReset() const
|
||||
std::vector<SymbolTreeWidget::SymbolWork> LocalVariableTreeWidget::getSymbols(
|
||||
const QString& filter, const ccc::SymbolDatabase& database)
|
||||
{
|
||||
u32 program_counter = m_cpu.getPC();
|
||||
u32 program_counter = cpu().getPC();
|
||||
const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter);
|
||||
if (!function || !function->local_variables().has_value())
|
||||
{
|
||||
@@ -927,7 +990,7 @@ std::vector<SymbolTreeWidget::SymbolWork> LocalVariableTreeWidget::getSymbols(
|
||||
}
|
||||
|
||||
m_function = function->handle();
|
||||
m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function);
|
||||
m_caller_stack_pointer = cpu().getCallerStackPointer(*function);
|
||||
|
||||
std::vector<SymbolTreeWidget::SymbolWork> symbols;
|
||||
|
||||
@@ -973,7 +1036,7 @@ std::unique_ptr<SymbolTreeNode> LocalVariableTreeWidget::buildNode(
|
||||
node->live_range = local_variable.live_range;
|
||||
node->symbol = ccc::MultiSymbolHandle(local_variable);
|
||||
|
||||
if (const ccc::GlobalStorage* storage = std::get_if<ccc::GlobalStorage>(&local_variable.storage))
|
||||
if (std::get_if<ccc::GlobalStorage>(&local_variable.storage))
|
||||
node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, local_variable.address().value);
|
||||
else if (const ccc::RegisterStorage* storage = std::get_if<ccc::RegisterStorage>(&local_variable.storage))
|
||||
node->location = SymbolTreeLocation(SymbolTreeLocation::REGISTER, storage->dbx_register_number);
|
||||
@@ -1001,15 +1064,19 @@ void LocalVariableTreeWidget::configureColumns()
|
||||
|
||||
void LocalVariableTreeWidget::onNewButtonPressed()
|
||||
{
|
||||
NewLocalVariableDialog* dialog = new NewLocalVariableDialog(m_cpu, this);
|
||||
NewLocalVariableDialog* dialog = new NewLocalVariableDialog(cpu(), this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
if (dialog->exec() == QDialog::Accepted)
|
||||
reset();
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
ParameterVariableTreeWidget::ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent)
|
||||
: SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent)
|
||||
ParameterVariableTreeWidget::ParameterVariableTreeWidget(const DebuggerWidgetParameters& parameters)
|
||||
: SymbolTreeWidget(
|
||||
ALLOW_TYPE_ACTIONS,
|
||||
1,
|
||||
parameters)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1020,10 +1087,10 @@ bool ParameterVariableTreeWidget::needsReset() const
|
||||
if (!m_function.valid())
|
||||
return true;
|
||||
|
||||
u32 program_counter = m_cpu.getPC();
|
||||
u32 program_counter = cpu().getPC();
|
||||
|
||||
bool left_function = true;
|
||||
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
||||
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
||||
const ccc::Function* function = database.functions.symbol_from_handle(m_function);
|
||||
if (!function || !function->address().valid())
|
||||
return;
|
||||
@@ -1045,7 +1112,7 @@ std::vector<SymbolTreeWidget::SymbolWork> ParameterVariableTreeWidget::getSymbol
|
||||
{
|
||||
std::vector<SymbolTreeWidget::SymbolWork> symbols;
|
||||
|
||||
u32 program_counter = m_cpu.getPC();
|
||||
u32 program_counter = cpu().getPC();
|
||||
const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter);
|
||||
if (!function || !function->parameter_variables().has_value())
|
||||
{
|
||||
@@ -1054,7 +1121,7 @@ std::vector<SymbolTreeWidget::SymbolWork> ParameterVariableTreeWidget::getSymbol
|
||||
}
|
||||
|
||||
m_function = function->handle();
|
||||
m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function);
|
||||
m_caller_stack_pointer = cpu().getCallerStackPointer(*function);
|
||||
|
||||
for (const ccc::ParameterVariableHandle parameter_variable_handle : *function->parameter_variables())
|
||||
{
|
||||
@@ -1124,7 +1191,8 @@ void ParameterVariableTreeWidget::configureColumns()
|
||||
|
||||
void ParameterVariableTreeWidget::onNewButtonPressed()
|
||||
{
|
||||
NewParameterVariableDialog* dialog = new NewParameterVariableDialog(m_cpu, this);
|
||||
NewParameterVariableDialog* dialog = new NewParameterVariableDialog(cpu(), this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
if (dialog->exec() == QDialog::Accepted)
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include "SymbolTreeModel.h"
|
||||
|
||||
#include "ui_SymbolTreeWidget.h"
|
||||
|
||||
struct SymbolFilters;
|
||||
#include "Debugger/DebuggerWidget.h"
|
||||
#include "Debugger/SymbolTree/SymbolTreeModel.h"
|
||||
|
||||
// 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
|
||||
|
||||
@@ -24,12 +22,6 @@ public:
|
||||
void updateVisibleNodes(bool update_hashes);
|
||||
void expandGroups(QModelIndex index);
|
||||
|
||||
signals:
|
||||
void goToInDisassembly(u32 address);
|
||||
void goToInMemoryView(u32 address);
|
||||
void nameColumnClicked(u32 address);
|
||||
void locationColumnClicked(u32 address);
|
||||
|
||||
protected:
|
||||
struct SymbolWork
|
||||
{
|
||||
@@ -41,36 +33,38 @@ 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,
|
||||
const DebuggerWidgetParameters& parameters);
|
||||
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
void toJson(JsonValueWrapper& json) override;
|
||||
bool fromJson(const JsonValueWrapper& json) override;
|
||||
|
||||
void setupTree();
|
||||
std::unique_ptr<SymbolTreeNode> buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database);
|
||||
std::unique_ptr<SymbolTreeNode> buildTree(const ccc::SymbolDatabase& database);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> groupBySourceFile(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work,
|
||||
const SymbolFilters& filters);
|
||||
const SymbolWork*& prev_work);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> groupBySection(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work,
|
||||
const SymbolFilters& filters);
|
||||
const SymbolWork*& prev_work);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> groupByModule(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work,
|
||||
const SymbolFilters& filters);
|
||||
const SymbolWork*& prev_work);
|
||||
|
||||
void setupMenu();
|
||||
void openMenu(QPoint pos);
|
||||
void openContextMenu(QPoint pos);
|
||||
|
||||
virtual bool needsReset() const;
|
||||
|
||||
@@ -89,8 +83,6 @@ protected:
|
||||
void onCopyMangledName();
|
||||
void onCopyLocation();
|
||||
void onRenameSymbol();
|
||||
void onGoToInDisassembly();
|
||||
void onGoToInMemoryView();
|
||||
void onResetChildren();
|
||||
void onChangeTypeTemporarily();
|
||||
|
||||
@@ -100,39 +92,33 @@ protected:
|
||||
|
||||
Ui::SymbolTreeWidget m_ui;
|
||||
|
||||
DebugInterface& m_cpu;
|
||||
SymbolTreeModel* m_model = nullptr;
|
||||
|
||||
QMenu* m_context_menu = nullptr;
|
||||
QAction* m_rename_symbol = nullptr;
|
||||
QAction* m_go_to_in_disassembly = nullptr;
|
||||
QAction* m_m_go_to_in_memory_view = nullptr;
|
||||
QAction* m_show_size_column = nullptr;
|
||||
QAction* m_group_by_module = nullptr;
|
||||
QAction* m_group_by_section = nullptr;
|
||||
QAction* m_group_by_source_file = nullptr;
|
||||
QAction* m_sort_by_if_type_is_known = nullptr;
|
||||
QAction* m_reset_children = nullptr;
|
||||
QAction* m_change_type_temporarily = nullptr;
|
||||
|
||||
enum Flags
|
||||
{
|
||||
NO_SYMBOL_TREE_FLAGS = 0,
|
||||
ALLOW_GROUPING = 1 << 0,
|
||||
ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN = 1 << 1,
|
||||
ALLOW_TYPE_ACTIONS = 1 << 2,
|
||||
ALLOW_MANGLED_NAME_ACTIONS = 1 << 3
|
||||
ALLOW_MANGLED_NAME_ACTIONS = 1 << 3,
|
||||
CLICK_TO_GO_TO_IN_DISASSEMBLER = 1 << 4
|
||||
};
|
||||
|
||||
u32 m_flags;
|
||||
u32 m_symbol_address_alignment;
|
||||
|
||||
bool m_show_size_column = false;
|
||||
bool m_group_by_module = false;
|
||||
bool m_group_by_section = false;
|
||||
bool m_group_by_source_file = false;
|
||||
bool m_sort_by_if_type_is_known = false;
|
||||
};
|
||||
|
||||
class FunctionTreeWidget : public SymbolTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FunctionTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
|
||||
explicit FunctionTreeWidget(const DebuggerWidgetParameters& parameters);
|
||||
virtual ~FunctionTreeWidget();
|
||||
|
||||
protected:
|
||||
@@ -151,7 +137,7 @@ class GlobalVariableTreeWidget : public SymbolTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
|
||||
explicit GlobalVariableTreeWidget(const DebuggerWidgetParameters& parameters);
|
||||
virtual ~GlobalVariableTreeWidget();
|
||||
|
||||
protected:
|
||||
@@ -170,7 +156,7 @@ class LocalVariableTreeWidget : public SymbolTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
|
||||
explicit LocalVariableTreeWidget(const DebuggerWidgetParameters& parameters);
|
||||
virtual ~LocalVariableTreeWidget();
|
||||
|
||||
protected:
|
||||
@@ -194,7 +180,7 @@ class ParameterVariableTreeWidget : public SymbolTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
|
||||
explicit ParameterVariableTreeWidget(const DebuggerWidgetParameters& parameters);
|
||||
virtual ~ParameterVariableTreeWidget();
|
||||
|
||||
protected:
|
||||
@@ -213,11 +199,3 @@ protected:
|
||||
ccc::FunctionHandle m_function;
|
||||
std::optional<u32> m_caller_stack_pointer;
|
||||
};
|
||||
|
||||
struct SymbolFilters
|
||||
{
|
||||
bool group_by_module = false;
|
||||
bool group_by_section = false;
|
||||
bool group_by_source_file = false;
|
||||
QString string;
|
||||
};
|
||||
|
||||
@@ -23,8 +23,13 @@ int ThreadModel::columnCount(const QModelIndex&) const
|
||||
|
||||
QVariant ThreadModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
const auto threads = m_cpu.GetThreadList();
|
||||
auto* const thread = threads.at(index.row()).get();
|
||||
const std::vector<std::unique_ptr<BiosThread>> threads = m_cpu.GetThreadList();
|
||||
|
||||
size_t row = static_cast<size_t>(index.row());
|
||||
if (row >= threads.size())
|
||||
return QVariant();
|
||||
|
||||
const BiosThread* thread = threads[row].get();
|
||||
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
82
pcsx2-qt/Debugger/ThreadWidget.cpp
Normal file
82
pcsx2-qt/Debugger/ThreadWidget.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "ThreadWidget.h"
|
||||
|
||||
#include "QtUtils.h"
|
||||
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
ThreadWidget::ThreadWidget(const DebuggerWidgetParameters& parameters)
|
||||
: DebuggerWidget(parameters, NO_DEBUGGER_FLAGS)
|
||||
, m_model(new ThreadModel(cpu()))
|
||||
, m_proxy_model(new QSortFilterProxyModel())
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.threadList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &ThreadWidget::openContextMenu);
|
||||
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++;
|
||||
}
|
||||
|
||||
receiveEvent<DebuggerEvents::VMUpdate>([this](const DebuggerEvents::VMUpdate& event) -> bool {
|
||||
m_model->refreshData();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void ThreadWidget::openContextMenu(QPoint pos)
|
||||
{
|
||||
if (!m_ui.threadList->selectionModel()->hasSelection())
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(m_ui.threadList);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction* copy = menu->addAction(tr("Copy"));
|
||||
connect(copy, &QAction::triggered, [this]() {
|
||||
const QItemSelectionModel* selection_model = m_ui.threadList->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return;
|
||||
|
||||
QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString());
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QAction* copy_all_as_csv = menu->addAction(tr("Copy all as CSV"));
|
||||
connect(copy_all_as_csv, &QAction::triggered, [this]() {
|
||||
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model()));
|
||||
});
|
||||
|
||||
menu->popup(m_ui.threadList->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void ThreadWidget::onDoubleClick(const QModelIndex& index)
|
||||
{
|
||||
switch (index.column())
|
||||
{
|
||||
case ThreadModel::ThreadColumns::ENTRY:
|
||||
{
|
||||
goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true);
|
||||
break;
|
||||
}
|
||||
default: // Default to PC
|
||||
{
|
||||
QModelIndex pc_index = m_model->index(index.row(), ThreadModel::ThreadColumns::PC);
|
||||
goToInDisassembler(m_model->data(pc_index, Qt::UserRole).toUInt(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
pcsx2-qt/Debugger/ThreadWidget.h
Normal file
28
pcsx2-qt/Debugger/ThreadWidget.h
Normal file
@@ -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 <QtCore/QSortFilterProxyModel>
|
||||
|
||||
class ThreadWidget final : public DebuggerWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ThreadWidget(const DebuggerWidgetParameters& parameters);
|
||||
|
||||
void openContextMenu(QPoint pos);
|
||||
void onDoubleClick(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
Ui::ThreadWidget m_ui;
|
||||
|
||||
ThreadModel* m_model;
|
||||
QSortFilterProxyModel* m_proxy_model;
|
||||
};
|
||||
39
pcsx2-qt/Debugger/ThreadWidget.ui
Normal file
39
pcsx2-qt/Debugger/ThreadWidget.ui
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ThreadWidget</class>
|
||||
<widget class="QWidget" name="ThreadWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Threads</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="threadList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
#include "Debugger/Docking/DockManager.h"
|
||||
#include "Settings/AchievementLoginDialog.h"
|
||||
#include "Settings/ControllerSettingsWindow.h"
|
||||
#include "Settings/GameListSettingsWidget.h"
|
||||
@@ -99,11 +100,23 @@ static QString s_current_disc_serial;
|
||||
static quint32 s_current_disc_crc;
|
||||
static quint32 s_current_running_crc;
|
||||
|
||||
static bool s_record_on_start = false;
|
||||
static QString s_path_to_recording_for_record_on_start;
|
||||
|
||||
MainWindow::MainWindow()
|
||||
{
|
||||
pxAssert(!g_main_window);
|
||||
g_main_window = this;
|
||||
|
||||
// Native window rendering is broken in wayland.
|
||||
// Let's work around it by disabling it for every widget besides
|
||||
// DisplayWidget.
|
||||
// Additionally, alien widget rendering is much more performant, so we
|
||||
// should have a nice responsiveness boost in our UI :)
|
||||
// QTBUG-133919, reported upstream by govanify
|
||||
QGuiApplication::setAttribute(Qt::AA_NativeWindows, false);
|
||||
QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
||||
#endif
|
||||
@@ -608,12 +621,7 @@ void MainWindow::quit()
|
||||
|
||||
void MainWindow::destroySubWindows()
|
||||
{
|
||||
if (m_debugger_window)
|
||||
{
|
||||
m_debugger_window->close();
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
}
|
||||
DebuggerWindow::destroyInstance();
|
||||
|
||||
if (m_controller_settings_window)
|
||||
{
|
||||
@@ -729,7 +737,48 @@ void MainWindow::updateAdvancedSettingsVisibility()
|
||||
void MainWindow::onVideoCaptureToggled(bool checked)
|
||||
{
|
||||
if (!s_vm_valid)
|
||||
{
|
||||
if (!s_record_on_start)
|
||||
{
|
||||
QMessageBox msgbox(this);
|
||||
msgbox.setIcon(QMessageBox::Question);
|
||||
msgbox.setWindowIcon(QtHost::GetAppIcon());
|
||||
msgbox.setWindowTitle(tr("Record On Boot"));
|
||||
msgbox.setWindowModality(Qt::WindowModal);
|
||||
msgbox.setText(tr("Did you want to start recording on boot?"));
|
||||
msgbox.addButton(QMessageBox::Yes);
|
||||
msgbox.addButton(QMessageBox::No);
|
||||
msgbox.setDefaultButton(QMessageBox::Yes);
|
||||
if (msgbox.exec() == QMessageBox::Yes)
|
||||
{
|
||||
const QString container(QString::fromStdString(
|
||||
Host::GetStringSettingValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER)));
|
||||
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
|
||||
|
||||
QString temp(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseVideoFilename())).arg(container));
|
||||
temp = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), temp, filter));
|
||||
s_path_to_recording_for_record_on_start = temp;
|
||||
if (s_path_to_recording_for_record_on_start.isEmpty())
|
||||
return;
|
||||
s_record_on_start = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox msgbox(this);
|
||||
msgbox.setIcon(QMessageBox::Question);
|
||||
msgbox.setWindowIcon(QtHost::GetAppIcon());
|
||||
msgbox.setWindowTitle(tr("Record On Boot"));
|
||||
msgbox.setWindowModality(Qt::WindowModal);
|
||||
msgbox.setText(tr("Did you want to cancel recording on boot?"));
|
||||
msgbox.addButton(QMessageBox::Yes);
|
||||
msgbox.addButton(QMessageBox::No);
|
||||
msgbox.setDefaultButton(QMessageBox::Yes);
|
||||
if (msgbox.exec() == QMessageBox::Yes)
|
||||
s_record_on_start = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the checked state, we'll get updated by the GS thread.
|
||||
QSignalBlocker sb(m_ui.actionVideoCapture);
|
||||
@@ -741,16 +790,26 @@ void MainWindow::onVideoCaptureToggled(bool checked)
|
||||
return;
|
||||
}
|
||||
|
||||
const QString container(QString::fromStdString(
|
||||
Host::GetStringSettingValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER)));
|
||||
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
|
||||
if (s_record_on_start && !s_path_to_recording_for_record_on_start.isEmpty())
|
||||
{
|
||||
// We can't start recording immediately, this is called before full GS init (specifically the fps amount)
|
||||
// and GSCapture ends up unhappy.
|
||||
// TODO: Pass some sort of flag or callback to the GS thread to start recording on frame 0.
|
||||
Host::AddOSDMessage(tr("Recording will start in a moment").toStdString(), 3.0f);
|
||||
QTimer::singleShot(2000, []() { g_emu_thread->beginCapture(s_path_to_recording_for_record_on_start); });
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString container(QString::fromStdString(
|
||||
Host::GetStringSettingValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER)));
|
||||
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
|
||||
|
||||
QString path(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseVideoFilename())).arg(container));
|
||||
path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), path, filter));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
g_emu_thread->beginCapture(path);
|
||||
QString path(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseVideoFilename())).arg(container));
|
||||
path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), path, filter));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
g_emu_thread->beginCapture(path);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onCaptureStarted(const QString& filename)
|
||||
@@ -787,12 +846,8 @@ void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
|
||||
{
|
||||
// If PauseOnEntry is enabled, we prompt the user to disable Hardcore Mode
|
||||
// or cancel the action later, so we should keep the debugger around
|
||||
if (m_debugger_window && !DebugInterface::getPauseOnEntry())
|
||||
{
|
||||
m_debugger_window->close();
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
}
|
||||
if (g_debugger_window && !DebugInterface::getPauseOnEntry())
|
||||
DebuggerWindow::destroyInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,8 +950,6 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool stoppi
|
||||
m_ui.actionToolbarSaveState->setEnabled(running);
|
||||
|
||||
m_ui.actionViewGameProperties->setEnabled(running);
|
||||
|
||||
m_ui.actionVideoCapture->setEnabled(running);
|
||||
if (!running && m_ui.actionVideoCapture->isChecked())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.actionVideoCapture);
|
||||
@@ -1084,7 +1137,7 @@ bool MainWindow::shouldMouseLock() const
|
||||
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
|
||||
return false;
|
||||
|
||||
bool windowsHidden = (!m_debugger_window || m_debugger_window->isHidden()) &&
|
||||
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
|
||||
(!m_controller_settings_window || m_controller_settings_window->isHidden()) &&
|
||||
(!m_settings_window || m_settings_window->isHidden());
|
||||
|
||||
@@ -1160,6 +1213,11 @@ void MainWindow::cancelGameListRefresh()
|
||||
m_game_list_widget->cancelRefresh();
|
||||
}
|
||||
|
||||
void MainWindow::reportInfo(const QString& title, const QString& message)
|
||||
{
|
||||
QMessageBox::information(this, title, message);
|
||||
}
|
||||
|
||||
void MainWindow::reportError(const QString& title, const QString& message)
|
||||
{
|
||||
QMessageBox::critical(this, title, message);
|
||||
@@ -1369,7 +1427,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
connect(action, &QAction::triggered, [entry]() {
|
||||
SettingsWindow::openGamePropertiesDialog(entry,
|
||||
entry->title, entry->serial, entry->crc, entry->type == GameList::EntryType::ELF);
|
||||
entry->title, entry->serial, entry->crc, entry->type == GameList::EntryType::ELF, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1415,7 +1473,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
connect(action, &QAction::triggered, [this, entry]() {
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
startGameListEntry(entry);
|
||||
getDebuggerWindow()->show();
|
||||
DebuggerWindow::getInstance()->show();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1573,41 +1631,7 @@ void MainWindow::onViewSystemDisplayTriggered()
|
||||
|
||||
void MainWindow::onViewGamePropertiesActionTriggered()
|
||||
{
|
||||
if (!s_vm_valid)
|
||||
return;
|
||||
|
||||
// prefer to use a game list entry, if we have one, that way the summary is populated
|
||||
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
|
||||
if (entry)
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// open properties for the current running file (isn't in the game list)
|
||||
if (s_current_disc_crc == 0)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Game Properties"), tr("Game properties is unavailable for the current game."));
|
||||
return;
|
||||
}
|
||||
|
||||
// can't use serial for ELFs, because they might have a disc set
|
||||
if (s_current_elf_override.isEmpty())
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
nullptr, s_current_title.toStdString(), s_current_disc_serial.toStdString(), s_current_disc_crc, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
nullptr, s_current_title.toStdString(), std::string(), s_current_disc_crc, true);
|
||||
}
|
||||
doGameSettings(nullptr);
|
||||
}
|
||||
|
||||
void MainWindow::onGitHubRepositoryActionTriggered()
|
||||
@@ -1753,36 +1777,11 @@ void MainWindow::onCreateMemoryCardOpenRequested()
|
||||
|
||||
void MainWindow::updateTheme()
|
||||
{
|
||||
// The debugger hates theme changes.
|
||||
// We have unfortunately to destroy it and recreate it.
|
||||
const bool debugger_is_open = m_debugger_window ? m_debugger_window->isVisible() : false;
|
||||
const QSize debugger_size = m_debugger_window ? m_debugger_window->size() : QSize();
|
||||
const QPoint debugger_pos = m_debugger_window ? m_debugger_window->pos() : QPoint();
|
||||
if (m_debugger_window)
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Theme Change"),
|
||||
tr("Changing the theme will close the debugger window. Any unsaved data will be lost. Do you want to continue?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QtHost::UpdateApplicationTheme();
|
||||
reloadThemeSpecificImages();
|
||||
|
||||
if (m_debugger_window)
|
||||
{
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
getDebuggerWindow(); // populates m_debugger_window
|
||||
m_debugger_window->resize(debugger_size);
|
||||
m_debugger_window->move(debugger_pos);
|
||||
if (debugger_is_open)
|
||||
{
|
||||
m_debugger_window->show();
|
||||
}
|
||||
}
|
||||
if (g_debugger_window)
|
||||
g_debugger_window->updateStyleSheets();
|
||||
}
|
||||
|
||||
void MainWindow::reloadThemeSpecificImages()
|
||||
@@ -1982,6 +1981,11 @@ void MainWindow::onVMStarted()
|
||||
updateWindowTitle();
|
||||
updateStatusBarWidgetVisibility();
|
||||
updateInputRecordingActions(true);
|
||||
if (s_record_on_start)
|
||||
{
|
||||
m_ui.actionVideoCapture->setChecked(true);
|
||||
s_record_on_start = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onVMPaused()
|
||||
@@ -2592,13 +2596,12 @@ QWidget* MainWindow::getDisplayContainer() const
|
||||
|
||||
void MainWindow::setupMouseMoveHandler()
|
||||
{
|
||||
auto mouse_cb_fn = [](int x, int y)
|
||||
{
|
||||
if(g_main_window)
|
||||
auto mouse_cb_fn = [](int x, int y) {
|
||||
if (g_main_window)
|
||||
g_main_window->checkMousePosition(x, y);
|
||||
};
|
||||
|
||||
if(!Common::AttachMousePositionCb(mouse_cb_fn))
|
||||
|
||||
if (!Common::AttachMousePositionCb(mouse_cb_fn))
|
||||
{
|
||||
Console.Warning("Unable to setup mouse position cb!");
|
||||
}
|
||||
@@ -2708,18 +2711,48 @@ void MainWindow::doSettings(const char* category /* = nullptr */)
|
||||
dlg->setCategory(category);
|
||||
}
|
||||
|
||||
DebuggerWindow* MainWindow::getDebuggerWindow()
|
||||
void MainWindow::doGameSettings(const char* category)
|
||||
{
|
||||
if (!m_debugger_window)
|
||||
// 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);
|
||||
if (!s_vm_valid)
|
||||
return;
|
||||
|
||||
return m_debugger_window;
|
||||
// prefer to use a game list entry, if we have one, that way the summary is populated
|
||||
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
|
||||
if (entry)
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty(), category);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// open properties for the current running file (isn't in the game list)
|
||||
if (s_current_disc_crc == 0)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Game Properties"), tr("Game properties is unavailable for the current game."));
|
||||
return;
|
||||
}
|
||||
|
||||
// can't use serial for ELFs, because they might have a disc set
|
||||
if (s_current_elf_override.isEmpty())
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
nullptr, s_current_title.toStdString(), s_current_disc_serial.toStdString(), s_current_disc_crc, false, category);
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
nullptr, s_current_title.toStdString(), std::string(), s_current_disc_crc, true, category);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openDebugger()
|
||||
{
|
||||
DebuggerWindow* dwnd = getDebuggerWindow();
|
||||
DebuggerWindow* dwnd = DebuggerWindow::getInstance();
|
||||
dwnd->isVisible() ? dwnd->activateWindow() : dwnd->show();
|
||||
}
|
||||
|
||||
|
||||
@@ -103,12 +103,16 @@ public:
|
||||
/// Rescans a single file. NOTE: Happens on UI thread.
|
||||
void rescanFile(const std::string& path);
|
||||
|
||||
void doSettings(const char* category = nullptr);
|
||||
void doGameSettings(const char* category = nullptr);
|
||||
|
||||
void openDebugger();
|
||||
void checkMousePosition(int x, int y);
|
||||
public Q_SLOTS:
|
||||
void checkForUpdates(bool display_message, bool force_check);
|
||||
void refreshGameList(bool invalidate_cache);
|
||||
void cancelGameListRefresh();
|
||||
void reportInfo(const QString& title, const QString& message);
|
||||
void reportError(const QString& title, const QString& message);
|
||||
bool confirmMessage(const QString& title, const QString& message);
|
||||
void onStatusMessage(const QString& message);
|
||||
@@ -254,13 +258,10 @@ private:
|
||||
void updateDisplayWidgetCursor();
|
||||
|
||||
SettingsWindow* getSettingsWindow();
|
||||
void doSettings(const char* category = nullptr);
|
||||
|
||||
InputRecordingViewer* getInputRecordingViewer();
|
||||
void updateInputRecordingActions(bool started);
|
||||
|
||||
DebuggerWindow* getDebuggerWindow();
|
||||
|
||||
void doControllerSettings(ControllerSettingsWindow::Category category = ControllerSettingsWindow::Category::Count);
|
||||
|
||||
QString getDiscDevicePath(const QString& title);
|
||||
@@ -290,8 +291,6 @@ private:
|
||||
InputRecordingViewer* m_input_recording_viewer = nullptr;
|
||||
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
||||
|
||||
DebuggerWindow* m_debugger_window = nullptr;
|
||||
|
||||
QProgressBar* m_status_progress_widget = nullptr;
|
||||
QLabel* m_status_verbose_widget = nullptr;
|
||||
QLabel* m_status_renderer_widget = nullptr;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "AutoUpdaterDialog.h"
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
#include "DisplayWidget.h"
|
||||
#include "GameList/GameListWidget.h"
|
||||
#include "LogWindow.h"
|
||||
@@ -1064,12 +1065,12 @@ void EmuThread::updatePerformanceMetrics(bool force)
|
||||
Q_ARG(const QString&, tr("VPS: %1 ").arg(vfps, 0, 'f', 0)));
|
||||
m_last_video_fps = vfps;
|
||||
|
||||
if (speed != m_last_speed || force)
|
||||
{
|
||||
QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection,
|
||||
Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0)));
|
||||
m_last_speed = speed;
|
||||
}
|
||||
if (speed != m_last_speed || force)
|
||||
{
|
||||
QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection,
|
||||
Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0)));
|
||||
m_last_speed = speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1612,6 +1613,18 @@ bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url
|
||||
return true;
|
||||
}
|
||||
|
||||
void Host::ReportInfoAsync(const std::string_view title, const std::string_view message)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
INFO_LOG("ReportInfoAsync: {}: {}", title, message);
|
||||
else if (!message.empty())
|
||||
INFO_LOG("ReportInfoAsync: {}", message);
|
||||
|
||||
QMetaObject::invokeMethod(g_main_window, "reportInfo", Qt::QueuedConnection,
|
||||
Q_ARG(const QString&, title.empty() ? QString() : QString::fromUtf8(title.data(), title.size())),
|
||||
Q_ARG(const QString&, message.empty() ? QString() : QString::fromUtf8(message.data(), message.size())));
|
||||
}
|
||||
|
||||
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
@@ -1712,57 +1725,58 @@ void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
|
||||
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class QtHostProgressCallback final : public BaseProgressCallback
|
||||
namespace
|
||||
{
|
||||
public:
|
||||
QtHostProgressCallback();
|
||||
~QtHostProgressCallback() override;
|
||||
|
||||
__fi const std::string& GetName() const { return m_name; }
|
||||
|
||||
void PushState() override;
|
||||
void PopState() override;
|
||||
|
||||
bool IsCancelled() const override;
|
||||
|
||||
void SetCancellable(bool cancellable) override;
|
||||
void SetTitle(const char* title) override;
|
||||
void SetStatusText(const char* text) override;
|
||||
void SetProgressRange(u32 range) override;
|
||||
void SetProgressValue(u32 value) override;
|
||||
|
||||
void DisplayError(const char* message) override;
|
||||
void DisplayWarning(const char* message) override;
|
||||
void DisplayInformation(const char* message) override;
|
||||
void DisplayDebugMessage(const char* message) override;
|
||||
|
||||
void ModalError(const char* message) override;
|
||||
bool ModalConfirmation(const char* message) override;
|
||||
void ModalInformation(const char* message) override;
|
||||
|
||||
void SetCancelled();
|
||||
|
||||
private:
|
||||
struct SharedData
|
||||
class QtHostProgressCallback final : public BaseProgressCallback
|
||||
{
|
||||
QProgressDialog* dialog = nullptr;
|
||||
QString init_title;
|
||||
QString init_status_text;
|
||||
std::atomic_bool cancelled{false};
|
||||
bool cancellable = true;
|
||||
bool was_fullscreen = false;
|
||||
public:
|
||||
QtHostProgressCallback();
|
||||
~QtHostProgressCallback() override;
|
||||
|
||||
__fi const std::string& GetName() const { return m_name; }
|
||||
|
||||
void PushState() override;
|
||||
void PopState() override;
|
||||
|
||||
bool IsCancelled() const override;
|
||||
|
||||
void SetCancellable(bool cancellable) override;
|
||||
void SetTitle(const char* title) override;
|
||||
void SetStatusText(const char* text) override;
|
||||
void SetProgressRange(u32 range) override;
|
||||
void SetProgressValue(u32 value) override;
|
||||
|
||||
void DisplayError(const char* message) override;
|
||||
void DisplayWarning(const char* message) override;
|
||||
void DisplayInformation(const char* message) override;
|
||||
void DisplayDebugMessage(const char* message) override;
|
||||
|
||||
void ModalError(const char* message) override;
|
||||
bool ModalConfirmation(const char* message) override;
|
||||
void ModalInformation(const char* message) override;
|
||||
|
||||
void SetCancelled();
|
||||
|
||||
private:
|
||||
struct SharedData
|
||||
{
|
||||
QProgressDialog* dialog = nullptr;
|
||||
QString init_title;
|
||||
QString init_status_text;
|
||||
std::atomic_bool cancelled{false};
|
||||
bool cancellable = true;
|
||||
bool was_fullscreen = false;
|
||||
};
|
||||
|
||||
void EnsureHasData();
|
||||
static void EnsureDialogVisible(const std::shared_ptr<SharedData>& data);
|
||||
void Redraw(bool force);
|
||||
|
||||
std::string m_name;
|
||||
std::shared_ptr<SharedData> m_data;
|
||||
int m_last_progress_percent = -1;
|
||||
};
|
||||
|
||||
void EnsureHasData();
|
||||
static void EnsureDialogVisible(const std::shared_ptr<SharedData>& data);
|
||||
void Redraw(bool force);
|
||||
|
||||
std::string m_name;
|
||||
std::shared_ptr<SharedData> m_data;
|
||||
int m_last_progress_percent = -1;
|
||||
};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QtHostProgressCallback::QtHostProgressCallback()
|
||||
: BaseProgressCallback()
|
||||
@@ -1817,7 +1831,7 @@ void QtHostProgressCallback::SetTitle(const char* title)
|
||||
void QtHostProgressCallback::SetStatusText(const char* text)
|
||||
{
|
||||
BaseProgressCallback::SetStatusText(text);
|
||||
|
||||
|
||||
EnsureHasData();
|
||||
QtHost::RunOnUIThread([data = m_data, text = QString::fromUtf8(text)]() {
|
||||
if (data->dialog)
|
||||
@@ -2372,9 +2386,9 @@ int main(int argc, char* argv[])
|
||||
if (s_start_fullscreen_ui)
|
||||
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
|
||||
|
||||
if (s_boot_and_debug)
|
||||
if (s_boot_and_debug || DebuggerWindow::shouldShowOnStartup())
|
||||
{
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
DebugInterface::setPauseOnEntry(s_boot_and_debug);
|
||||
g_main_window->openDebugger();
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,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);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtGui/QAction>
|
||||
#include <QtGui/QGuiApplication>
|
||||
@@ -25,6 +26,10 @@
|
||||
#include <QtWidgets/QTableView>
|
||||
#include <QtWidgets/QTreeView>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <QtGui/private/qtx11extras_p.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <map>
|
||||
@@ -351,4 +356,22 @@ namespace QtUtils
|
||||
}
|
||||
return csv;
|
||||
}
|
||||
|
||||
bool IsLightTheme(const QPalette& palette)
|
||||
{
|
||||
return palette.text().color().lightnessF() < 0.5;
|
||||
}
|
||||
|
||||
bool IsCompositorManagerRunning()
|
||||
{
|
||||
if (qEnvironmentVariableIsSet("PCSX2_NO_COMPOSITING"))
|
||||
return false;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (QX11Info::isPlatformX11() && !QX11Info::isCompositingManagerRunning())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace QtUtils
|
||||
|
||||
@@ -97,4 +97,9 @@ namespace QtUtils
|
||||
|
||||
/// Converts an abstract item model to a CSV string.
|
||||
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole, bool useQuotes = false);
|
||||
|
||||
// Heuristic to check if the current theme is a light or dark theme.
|
||||
bool IsLightTheme(const QPalette& palette);
|
||||
|
||||
bool IsCompositorManagerRunning();
|
||||
} // namespace QtUtils
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user