mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-05 23:02:58 +12:00
Add thread debugger
This commit is contained in:
parent
228068901b
commit
9932e58bf0
10 changed files with 172 additions and 14 deletions
|
@ -732,10 +732,12 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
|
||||||
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
||||||
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
|
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
|
||||||
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp
|
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp
|
||||||
|
src/panda_qt/thread_debugger.cpp
|
||||||
)
|
)
|
||||||
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
|
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
|
||||||
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
||||||
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
|
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
|
||||||
|
include/panda_qt/thread_debugger.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
||||||
|
|
|
@ -127,6 +127,7 @@ class Emulator {
|
||||||
Scheduler& getScheduler() { return scheduler; }
|
Scheduler& getScheduler() { return scheduler; }
|
||||||
Memory& getMemory() { return memory; }
|
Memory& getMemory() { return memory; }
|
||||||
AudioDeviceInterface& getAudioDevice() { return audioDevice; }
|
AudioDeviceInterface& getAudioDevice() { return audioDevice; }
|
||||||
|
Kernel& getKernel() { return kernel; }
|
||||||
|
|
||||||
RendererType getRendererType() const { return config.rendererType; }
|
RendererType getRendererType() const { return config.rendererType; }
|
||||||
Renderer* getRenderer() { return gpu.getRenderer(); }
|
Renderer* getRenderer() { return gpu.getRenderer(); }
|
||||||
|
|
|
@ -48,12 +48,12 @@ class Kernel {
|
||||||
Handle currentProcess;
|
Handle currentProcess;
|
||||||
Handle mainThread;
|
Handle mainThread;
|
||||||
int currentThreadIndex;
|
int currentThreadIndex;
|
||||||
Handle srvHandle; // Handle for the special service manager port "srv:"
|
Handle srvHandle; // Handle for the special service manager port "srv:"
|
||||||
Handle errorPortHandle; // Handle for the err:f port used for displaying errors
|
Handle errorPortHandle; // Handle for the err:f port used for displaying errors
|
||||||
|
|
||||||
u32 arbiterCount;
|
u32 arbiterCount;
|
||||||
u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32)
|
u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32)
|
||||||
u32 aliveThreadCount; // How many of these threads are actually alive?
|
u32 aliveThreadCount; // How many of these threads are actually alive?
|
||||||
ServiceManager serviceManager;
|
ServiceManager serviceManager;
|
||||||
|
|
||||||
// Top 8 bits are the major version, bottom 8 are the minor version
|
// Top 8 bits are the major version, bottom 8 are the minor version
|
||||||
|
@ -66,10 +66,10 @@ class Kernel {
|
||||||
Handle makeProcess(u32 id);
|
Handle makeProcess(u32 id);
|
||||||
Handle makePort(const char* name);
|
Handle makePort(const char* name);
|
||||||
Handle makeSession(Handle port);
|
Handle makeSession(Handle port);
|
||||||
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status = ThreadStatus::Dormant);
|
||||||
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Needs to be public to be accessible to the APT/HID services
|
// Needs to be public to be accessible to the APT/HID services
|
||||||
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
|
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
|
||||||
// Needs to be public to be accessible to the APT/DSP services
|
// Needs to be public to be accessible to the APT/DSP services
|
||||||
|
@ -199,7 +199,7 @@ public:
|
||||||
void closeDirectory(u32 messagePointer, Handle directory);
|
void closeDirectory(u32 messagePointer, Handle directory);
|
||||||
void readDirectory(u32 messagePointer, Handle directory);
|
void readDirectory(u32 messagePointer, Handle directory);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua);
|
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua);
|
||||||
void initializeFS() { return serviceManager.initializeFS(); }
|
void initializeFS() { return serviceManager.initializeFS(); }
|
||||||
void setVersion(u8 major, u8 minor);
|
void setVersion(u8 major, u8 minor);
|
||||||
|
@ -225,9 +225,7 @@ public:
|
||||||
return handleCounter++;
|
return handleCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<KernelObject>& getObjects() {
|
std::vector<KernelObject>& getObjects() { return objects; }
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get pointer to the object with the specified handle
|
// Get pointer to the object with the specified handle
|
||||||
KernelObject* getObject(Handle handle) {
|
KernelObject* getObject(Handle handle) {
|
||||||
|
@ -255,4 +253,14 @@ public:
|
||||||
void clearInstructionCache();
|
void clearInstructionCache();
|
||||||
void clearInstructionCacheRange(u32 start, u32 size);
|
void clearInstructionCacheRange(u32 start, u32 size);
|
||||||
u32 getSharedFontVaddr();
|
u32 getSharedFontVaddr();
|
||||||
|
|
||||||
|
// For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display
|
||||||
|
std::vector<Thread> getMainProcessThreads();
|
||||||
|
|
||||||
|
// For debuggers: Sets the entrypoint and initial SP for the main thread (thread 0) so that the debugger can display them
|
||||||
|
void setMainThreadEntrypointAndSP(u32 entrypoint, u32 initialSP) {
|
||||||
|
auto& t = threads[0];
|
||||||
|
t.entrypoint = entrypoint;
|
||||||
|
t.initialSP = initialSP;
|
||||||
|
}
|
||||||
};
|
};
|
|
@ -21,6 +21,7 @@
|
||||||
#include "panda_qt/screen.hpp"
|
#include "panda_qt/screen.hpp"
|
||||||
#include "panda_qt/shader_editor.hpp"
|
#include "panda_qt/shader_editor.hpp"
|
||||||
#include "panda_qt/text_editor.hpp"
|
#include "panda_qt/text_editor.hpp"
|
||||||
|
#include "panda_qt/thread_debugger.hpp"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
struct CheatMessage {
|
struct CheatMessage {
|
||||||
|
@ -109,6 +110,7 @@ class MainWindow : public QMainWindow {
|
||||||
TextEditorWindow* luaEditor;
|
TextEditorWindow* luaEditor;
|
||||||
PatchWindow* patchWindow;
|
PatchWindow* patchWindow;
|
||||||
ShaderEditorWindow* shaderEditor;
|
ShaderEditorWindow* shaderEditor;
|
||||||
|
ThreadDebugger* threadDebugger;
|
||||||
|
|
||||||
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
||||||
SDL_GameController* gameController = nullptr;
|
SDL_GameController* gameController = nullptr;
|
||||||
|
@ -157,4 +159,7 @@ class MainWindow : public QMainWindow {
|
||||||
|
|
||||||
void handleScreenResize(u32 width, u32 height);
|
void handleScreenResize(u32 width, u32 height);
|
||||||
void handleTouchscreenPress(QMouseEvent* event);
|
void handleTouchscreenPress(QMouseEvent* event);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void emulatorPaused();
|
||||||
};
|
};
|
||||||
|
|
28
include/panda_qt/thread_debugger.hpp
Normal file
28
include/panda_qt/thread_debugger.hpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QtWidgets>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "emulator.hpp"
|
||||||
|
|
||||||
|
class ThreadDebugger : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
Emulator* emu;
|
||||||
|
|
||||||
|
QVBoxLayout* mainLayout;
|
||||||
|
QTableWidget* threadTable;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ThreadDebugger(Emulator* emu, QWidget* parent = nullptr);
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setListItem(int row, int column, const QString& str);
|
||||||
|
void setListItem(int row, int column, const std::string& str);
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "arm_defs.hpp"
|
#include "arm_defs.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
|
||||||
|
@ -40,8 +41,9 @@ void Kernel::setupIdleThread() {
|
||||||
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
|
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
|
||||||
|
|
||||||
t.entrypoint = codeAddress;
|
t.entrypoint = codeAddress;
|
||||||
|
t.initialSP = 0;
|
||||||
t.tlsBase = 0;
|
t.tlsBase = 0;
|
||||||
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
|
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
|
||||||
t.gprs[14] = 0;
|
t.gprs[14] = 0;
|
||||||
t.gprs[15] = codeAddress;
|
t.gprs[15] = codeAddress;
|
||||||
t.cpsr = CPSR::UserMode;
|
t.cpsr = CPSR::UserMode;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <algorithm>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -697,3 +698,18 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Thread> Kernel::getMainProcessThreads() {
|
||||||
|
// Sort the thread indices so that they appear nicer in the debugger
|
||||||
|
auto indices = threadIndices;
|
||||||
|
std::sort(indices.begin(), indices.end());
|
||||||
|
|
||||||
|
std::vector<Thread> ret;
|
||||||
|
ret.reserve(indices.size());
|
||||||
|
|
||||||
|
for (const auto& index : indices) {
|
||||||
|
ret.push_back(threads[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -272,6 +272,11 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
romType = ROMType::None;
|
romType = ROMType::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// Update the main thread entrypoint and SP so that the thread debugger can display them.
|
||||||
|
kernel.setMainThreadEntrypointAndSP(cpu.getReg(15), cpu.getReg(13));
|
||||||
|
}
|
||||||
|
|
||||||
resume(); // Start the emulator
|
resume(); // Start the emulator
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
||||||
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
||||||
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
||||||
|
auto threadDebuggerAction = toolsMenu->addAction(tr("Open Thread Debugger"));
|
||||||
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
||||||
|
|
||||||
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
||||||
|
@ -73,8 +74,11 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
|
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
|
||||||
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
||||||
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
||||||
|
connect(threadDebuggerAction, &QAction::triggered, this, [this]() { threadDebugger->show(); });
|
||||||
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
||||||
|
|
||||||
|
connect(this, &MainWindow::emulatorPaused, this, [this]() { threadDebugger->update(); }, Qt::BlockingQueuedConnection);
|
||||||
|
|
||||||
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
||||||
aboutAction->setMenuRole(QAction::AboutRole);
|
aboutAction->setMenuRole(QAction::AboutRole);
|
||||||
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
|
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
|
||||||
|
@ -89,6 +93,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
patchWindow = new PatchWindow(this);
|
patchWindow = new PatchWindow(this);
|
||||||
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
||||||
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
||||||
|
threadDebugger = new ThreadDebugger(emu, this);
|
||||||
|
|
||||||
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
||||||
if (shaderEditor->supported) {
|
if (shaderEditor->supported) {
|
||||||
|
@ -381,9 +386,19 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||||
delete message.cheat.c;
|
delete message.cheat.c;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case MessageType::Pause: emu->pause(); break;
|
|
||||||
case MessageType::Resume: emu->resume(); break;
|
case MessageType::Resume: emu->resume(); break;
|
||||||
case MessageType::TogglePause: emu->togglePause(); break;
|
case MessageType::Pause:
|
||||||
|
emu->pause();
|
||||||
|
emit emulatorPaused();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MessageType::TogglePause:
|
||||||
|
emu->togglePause();
|
||||||
|
if (!emu->running) {
|
||||||
|
emit emulatorPaused();
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
|
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
|
||||||
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
|
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
|
||||||
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
|
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
|
||||||
|
|
76
src/panda_qt/thread_debugger.cpp
Normal file
76
src/panda_qt/thread_debugger.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include "panda_qt/thread_debugger.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
static QString threadStatusToQString(ThreadStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case ThreadStatus::Running: return QObject::tr("Running");
|
||||||
|
case ThreadStatus::Ready: return QObject::tr("Ready");
|
||||||
|
case ThreadStatus::WaitArbiter: return QObject::tr("Waiting on arbiter");
|
||||||
|
case ThreadStatus::WaitSleep: return QObject::tr("Sleeping");
|
||||||
|
case ThreadStatus::WaitSync1: return QObject::tr("WaitSync (1)");
|
||||||
|
case ThreadStatus::WaitSyncAny: return QObject::tr("WaitSync (Any)");
|
||||||
|
case ThreadStatus::WaitSyncAll: return QObject::tr("WaitSync (All)");
|
||||||
|
case ThreadStatus::WaitIPC: return QObject::tr("Waiting for IPC");
|
||||||
|
case ThreadStatus::Dormant: return QObject::tr("Dormant");
|
||||||
|
case ThreadStatus::Dead: return QObject::tr("Dead");
|
||||||
|
default: return QObject::tr("Unknown thread status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadDebugger::ThreadDebugger(Emulator* emu, QWidget* parent) : emu(emu), QWidget(parent, Qt::Window) {
|
||||||
|
setWindowTitle(tr("Thread Debugger"));
|
||||||
|
resize(700, 600);
|
||||||
|
|
||||||
|
mainLayout = new QVBoxLayout(this);
|
||||||
|
threadTable = new QTableWidget(this);
|
||||||
|
threadTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||||
|
|
||||||
|
mainLayout->addWidget(threadTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDebugger::update() {
|
||||||
|
threadTable->clear();
|
||||||
|
|
||||||
|
threadTable->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
|
threadTable->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
threadTable->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
threadTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
|
threadTable->setColumnCount(5);
|
||||||
|
threadTable->verticalHeader()->setVisible(false);
|
||||||
|
threadTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
|
threadTable->setHorizontalHeaderLabels(QStringList({"ID", "Status", "PC", "Entrypoint", "Stack Top"}));
|
||||||
|
|
||||||
|
const auto threads = emu->getKernel().getMainProcessThreads();
|
||||||
|
const usize count = threads.size();
|
||||||
|
threadTable->setRowCount(count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const auto& thread = threads[i];
|
||||||
|
setListItem(i, 0, fmt::format("{}", thread.index));
|
||||||
|
setListItem(i, 1, threadStatusToQString(thread.status));
|
||||||
|
setListItem(i, 2, fmt::format("{:08X}", thread.gprs[15]));
|
||||||
|
setListItem(i, 3, fmt::format("{:08X}", thread.entrypoint));
|
||||||
|
setListItem(i, 4, fmt::format("{:08X}", thread.initialSP));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDebugger::setListItem(int row, int column, const std::string& str) { setListItem(row, column, QString::fromStdString(str)); }
|
||||||
|
|
||||||
|
void ThreadDebugger::setListItem(int row, int column, const QString& str) {
|
||||||
|
QTableWidgetItem* item = new QTableWidgetItem();
|
||||||
|
QWidget* widget = new QWidget(this);
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout(widget);
|
||||||
|
QLabel* label = new QLabel(widget);
|
||||||
|
|
||||||
|
layout->setAlignment(Qt::AlignVCenter);
|
||||||
|
layout->setContentsMargins(5, 0, 5, 0);
|
||||||
|
|
||||||
|
label->setText(str);
|
||||||
|
layout->addWidget(label);
|
||||||
|
widget->setLayout(layout);
|
||||||
|
|
||||||
|
threadTable->setItem(row, column, item);
|
||||||
|
threadTable->setCellWidget(row, column, widget);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue