mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Single-click type chooser, popup warmup fix, rename ProcessMemory plugin
- Type chooser popup now opens on single click (no need to pre-select node) - Fix ~170ms first-open delay by pre-initializing Qt popup subsystem at startup - Rename ProcessMemoryWindows -> ProcessMemory (already supports Linux)
This commit is contained in:
@@ -288,5 +288,5 @@ if(BUILD_TESTING)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(plugins/ProcessMemoryWindows)
|
add_subdirectory(plugins/ProcessMemory)
|
||||||
add_subdirectory(plugins/WinDbgMemory)
|
add_subdirectory(plugins/WinDbgMemory)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(ProcessMemoryWindowsPlugin LANGUAGES CXX)
|
project(ProcessMemoryPlugin LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -12,36 +12,36 @@ set(CMAKE_AUTOUIC ON)
|
|||||||
|
|
||||||
# Plugin sources
|
# Plugin sources
|
||||||
set(PLUGIN_SOURCES
|
set(PLUGIN_SOURCES
|
||||||
ProcessMemoryWindowsPlugin.h
|
ProcessMemoryPlugin.h
|
||||||
ProcessMemoryWindowsPlugin.cpp
|
ProcessMemoryPlugin.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create shared library (DLL)
|
# Create shared library (DLL)
|
||||||
add_library(ProcessMemoryWindowsPlugin SHARED ${PLUGIN_SOURCES})
|
add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||||
|
|
||||||
# Link Qt
|
# Link Qt
|
||||||
target_link_libraries(ProcessMemoryWindowsPlugin PRIVATE ${QT}::Widgets ${_QT_WINEXTRAS})
|
target_link_libraries(ProcessMemoryPlugin PRIVATE ${QT}::Widgets ${_QT_WINEXTRAS})
|
||||||
|
|
||||||
# Platform-specific linking
|
# Platform-specific linking
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(ProcessMemoryWindowsPlugin PRIVATE psapi shell32)
|
target_link_libraries(ProcessMemoryPlugin PRIVATE psapi shell32)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# On Linux, hide all symbols by default so only RCX_PLUGIN_EXPORT-marked ones are exported
|
# On Linux, hide all symbols by default so only RCX_PLUGIN_EXPORT-marked ones are exported
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_compile_options(ProcessMemoryWindowsPlugin PRIVATE -fvisibility=hidden)
|
target_compile_options(ProcessMemoryPlugin PRIVATE -fvisibility=hidden)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(ProcessMemoryWindowsPlugin PRIVATE
|
target_include_directories(ProcessMemoryPlugin PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||||
)
|
)
|
||||||
|
|
||||||
# Output to Plugins folder
|
# Output to Plugins folder
|
||||||
set_target_properties(ProcessMemoryWindowsPlugin PROPERTIES
|
set_target_properties(ProcessMemoryPlugin PROPERTIES
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||||
)
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "ProcessMemoryWindowsPlugin.h"
|
#include "ProcessMemoryPlugin.h"
|
||||||
|
|
||||||
#include "../../src/processpicker.h"
|
#include "../../src/processpicker.h"
|
||||||
|
|
||||||
@@ -32,12 +32,12 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// ProcessMemoryWindowsProvider implementation
|
// ProcessMemoryProvider implementation
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
ProcessMemoryWindowsProvider::ProcessMemoryWindowsProvider(uint32_t pid, const QString& processName)
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||||
: m_handle(nullptr)
|
: m_handle(nullptr)
|
||||||
, m_pid(pid)
|
, m_pid(pid)
|
||||||
, m_processName(processName)
|
, m_processName(processName)
|
||||||
@@ -60,7 +60,7 @@ ProcessMemoryWindowsProvider::ProcessMemoryWindowsProvider(uint32_t pid, const Q
|
|||||||
cacheModules();
|
cacheModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryWindowsProvider::read(uint64_t addr, void* buf, int len) const
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||||
{
|
{
|
||||||
if (!m_handle || len <= 0) return false;
|
if (!m_handle || len <= 0) return false;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ bool ProcessMemoryWindowsProvider::read(uint64_t addr, void* buf, int len) const
|
|||||||
return bytesRead > 0;
|
return bytesRead > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryWindowsProvider::write(uint64_t addr, const void* buf, int len)
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
{
|
{
|
||||||
if (!m_handle || !m_writable || len <= 0) return false;
|
if (!m_handle || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ bool ProcessMemoryWindowsProvider::write(uint64_t addr, const void* buf, int len
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ProcessMemoryWindowsProvider::getSymbol(uint64_t addr) const
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
{
|
{
|
||||||
for (const auto& mod : m_modules)
|
for (const auto& mod : m_modules)
|
||||||
{
|
{
|
||||||
@@ -96,7 +96,7 @@ QString ProcessMemoryWindowsProvider::getSymbol(uint64_t addr) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessMemoryWindowsProvider::cacheModules()
|
void ProcessMemoryProvider::cacheModules()
|
||||||
{
|
{
|
||||||
HMODULE mods[1024];
|
HMODULE mods[1024];
|
||||||
DWORD needed = 0;
|
DWORD needed = 0;
|
||||||
@@ -126,7 +126,7 @@ void ProcessMemoryWindowsProvider::cacheModules()
|
|||||||
|
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
|
|
||||||
ProcessMemoryWindowsProvider::ProcessMemoryWindowsProvider(uint32_t pid, const QString& processName)
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||||
: m_fd(-1)
|
: m_fd(-1)
|
||||||
, m_pid(pid)
|
, m_pid(pid)
|
||||||
, m_processName(processName)
|
, m_processName(processName)
|
||||||
@@ -152,7 +152,7 @@ ProcessMemoryWindowsProvider::ProcessMemoryWindowsProvider(uint32_t pid, const Q
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryWindowsProvider::read(uint64_t addr, void* buf, int len) const
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||||
{
|
{
|
||||||
if (m_fd < 0 || len <= 0) return false;
|
if (m_fd < 0 || len <= 0) return false;
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ bool ProcessMemoryWindowsProvider::read(uint64_t addr, void* buf, int len) const
|
|||||||
return nread == static_cast<ssize_t>(len);
|
return nread == static_cast<ssize_t>(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryWindowsProvider::write(uint64_t addr, const void* buf, int len)
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
{
|
{
|
||||||
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ bool ProcessMemoryWindowsProvider::write(uint64_t addr, const void* buf, int len
|
|||||||
return nwritten == static_cast<ssize_t>(len);
|
return nwritten == static_cast<ssize_t>(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ProcessMemoryWindowsProvider::getSymbol(uint64_t addr) const
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||||
{
|
{
|
||||||
for (const auto& mod : m_modules)
|
for (const auto& mod : m_modules)
|
||||||
{
|
{
|
||||||
@@ -215,7 +215,7 @@ QString ProcessMemoryWindowsProvider::getSymbol(uint64_t addr) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessMemoryWindowsProvider::cacheModules()
|
void ProcessMemoryProvider::cacheModules()
|
||||||
{
|
{
|
||||||
// Parse /proc/<pid>/maps to discover loaded modules
|
// Parse /proc/<pid>/maps to discover loaded modules
|
||||||
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
||||||
@@ -288,7 +288,7 @@ void ProcessMemoryWindowsProvider::cacheModules()
|
|||||||
|
|
||||||
#endif // platform
|
#endif // platform
|
||||||
|
|
||||||
ProcessMemoryWindowsProvider::~ProcessMemoryWindowsProvider()
|
ProcessMemoryProvider::~ProcessMemoryProvider()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (m_handle)
|
if (m_handle)
|
||||||
@@ -299,7 +299,7 @@ ProcessMemoryWindowsProvider::~ProcessMemoryWindowsProvider()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int ProcessMemoryWindowsProvider::size() const
|
int ProcessMemoryProvider::size() const
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return m_handle ? 0x10000 : 0;
|
return m_handle ? 0x10000 : 0;
|
||||||
@@ -309,22 +309,22 @@ int ProcessMemoryWindowsProvider::size() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// ProcessMemoryWindowsPlugin implementation
|
// ProcessMemoryPlugin implementation
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
QIcon ProcessMemoryWindowsPlugin::Icon() const
|
QIcon ProcessMemoryPlugin::Icon() const
|
||||||
{
|
{
|
||||||
return qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
return qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryWindowsPlugin::canHandle(const QString& target) const
|
bool ProcessMemoryPlugin::canHandle(const QString& target) const
|
||||||
{
|
{
|
||||||
// Target format: "pid:name" or just "pid"
|
// Target format: "pid:name" or just "pid"
|
||||||
QRegularExpression re("^\\d+");
|
QRegularExpression re("^\\d+");
|
||||||
return re.match(target).hasMatch();
|
return re.match(target).hasMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<rcx::Provider> ProcessMemoryWindowsPlugin::createProvider(const QString& target, QString* errorMsg)
|
std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
|
||||||
{
|
{
|
||||||
// Parse target: "pid:name" or just "pid"
|
// Parse target: "pid:name" or just "pid"
|
||||||
QStringList parts = target.split(':');
|
QStringList parts = target.split(':');
|
||||||
@@ -339,7 +339,7 @@ std::unique_ptr<rcx::Provider> ProcessMemoryWindowsPlugin::createProvider(const
|
|||||||
|
|
||||||
QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid);
|
QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid);
|
||||||
|
|
||||||
auto provider = std::make_unique<ProcessMemoryWindowsProvider>(pid, name);
|
auto provider = std::make_unique<ProcessMemoryProvider>(pid, name);
|
||||||
if (!provider->isValid())
|
if (!provider->isValid())
|
||||||
{
|
{
|
||||||
if (errorMsg)
|
if (errorMsg)
|
||||||
@@ -352,7 +352,7 @@ std::unique_ptr<rcx::Provider> ProcessMemoryWindowsPlugin::createProvider(const
|
|||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ProcessMemoryWindowsPlugin::getInitialBaseAddress(const QString& target) const
|
uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Parse PID from target
|
// Parse PID from target
|
||||||
@@ -409,7 +409,7 @@ uint64_t ProcessMemoryWindowsPlugin::getInitialBaseAddress(const QString& target
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryWindowsPlugin::selectTarget(QWidget* parent, QString* target)
|
bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||||
{
|
{
|
||||||
// Use custom process enumeration from plugin
|
// Use custom process enumeration from plugin
|
||||||
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
||||||
@@ -440,7 +440,7 @@ bool ProcessMemoryWindowsPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<PluginProcessInfo> ProcessMemoryWindowsPlugin::enumerateProcesses()
|
QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||||
{
|
{
|
||||||
QVector<PluginProcessInfo> processes;
|
QVector<PluginProcessInfo> processes;
|
||||||
|
|
||||||
@@ -543,5 +543,5 @@ QVector<PluginProcessInfo> ProcessMemoryWindowsPlugin::enumerateProcesses()
|
|||||||
|
|
||||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||||
{
|
{
|
||||||
return new ProcessMemoryWindowsPlugin();
|
return new ProcessMemoryPlugin();
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process memory provider (Windows)
|
* Process memory provider
|
||||||
* Reads/writes memory from a live process using Windows platform APIs
|
* Reads/writes memory from a live process using platform APIs
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryWindowsProvider : public rcx::Provider
|
class ProcessMemoryProvider : public rcx::Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ProcessMemoryWindowsProvider(uint32_t pid, const QString& processName);
|
ProcessMemoryProvider(uint32_t pid, const QString& processName);
|
||||||
~ProcessMemoryWindowsProvider() override;
|
~ProcessMemoryProvider() override;
|
||||||
|
|
||||||
// Required overrides
|
// Required overrides
|
||||||
bool read(uint64_t addr, void* buf, int len) const override;
|
bool read(uint64_t addr, void* buf, int len) const override;
|
||||||
@@ -57,15 +57,15 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin that provides ProcessMemoryWindowsProvider
|
* Plugin that provides ProcessMemoryProvider
|
||||||
*/
|
*/
|
||||||
class ProcessMemoryWindowsPlugin : public IProviderPlugin
|
class ProcessMemoryPlugin : public IProviderPlugin
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string Name() const override { return "Process Memory Windows"; }
|
std::string Name() const override { return "Process Memory"; }
|
||||||
std::string Version() const override { return "1.0.0"; }
|
std::string Version() const override { return "1.0.0"; }
|
||||||
std::string Author() const override { return "Reclass"; }
|
std::string Author() const override { return "Reclass"; }
|
||||||
std::string Description() const override { return "Read and write memory from local running processes (Windows)"; }
|
std::string Description() const override { return "Read and write memory from local running processes"; }
|
||||||
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||||
QIcon Icon() const override;
|
QIcon Icon() const override;
|
||||||
|
|
||||||
@@ -178,6 +178,14 @@ RcxEditor* RcxController::addSplitEditor(QWidget* parent) {
|
|||||||
editor->applyDocument(m_lastResult);
|
editor->applyDocument(m_lastResult);
|
||||||
}
|
}
|
||||||
updateCommandRow();
|
updateCommandRow();
|
||||||
|
|
||||||
|
// Eagerly pre-warm the type popup so first click isn't slow (~350ms cold start).
|
||||||
|
if (!m_cachedPopup) {
|
||||||
|
QTimer::singleShot(0, this, [this, editor]() {
|
||||||
|
if (!m_cachedPopup && !m_editors.isEmpty())
|
||||||
|
ensurePopup(editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1330,7 +1330,15 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
// Single-click on editable token of already-selected node → edit
|
// Single-click on editable token of already-selected node → edit
|
||||||
int tLine, tCol; EditTarget t;
|
int tLine, tCol; EditTarget t;
|
||||||
if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, tCol, t)) {
|
if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, tCol, t)) {
|
||||||
if (alreadySelected && plain) {
|
// Type/ArrayElementType/PointerTarget open a dismissible popup
|
||||||
|
// (not inline text edit), so allow on first click without
|
||||||
|
// requiring the node to be pre-selected.
|
||||||
|
bool isPopupTarget = (t == EditTarget::Type
|
||||||
|
|| t == EditTarget::ArrayElementType
|
||||||
|
|| t == EditTarget::PointerTarget);
|
||||||
|
if ((alreadySelected || isPopupTarget) && plain) {
|
||||||
|
if (!alreadySelected)
|
||||||
|
emit nodeClicked(h.line, h.nodeId, me->modifiers());
|
||||||
m_pendingClickNodeId = 0;
|
m_pendingClickNodeId = 0;
|
||||||
return beginInlineEdit(t, tLine, tCol);
|
return beginInlineEdit(t, tLine, tCol);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QIntValidator>
|
#include <QIntValidator>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include "themes/thememanager.h"
|
#include "themes/thememanager.h"
|
||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
@@ -384,10 +385,33 @@ TypeSelectorPopup::TypeSelectorPopup(QWidget* parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TypeSelectorPopup::warmUp() {
|
void TypeSelectorPopup::warmUp() {
|
||||||
|
// One-time per-process cost (~170ms): Qt lazily initializes the style/font/DLL
|
||||||
|
// subsystem the first time a popup with complex children is shown. Pre-pay it
|
||||||
|
// by briefly showing a throwaway dummy popup with a QListView, then show+hide
|
||||||
|
// ourselves.
|
||||||
|
{
|
||||||
|
auto* primer = new QFrame(nullptr, Qt::Popup | Qt::FramelessWindowHint);
|
||||||
|
primer->resize(300, 400);
|
||||||
|
auto* lay = new QVBoxLayout(primer);
|
||||||
|
lay->addWidget(new QLabel(QStringLiteral("x")));
|
||||||
|
lay->addWidget(new QLineEdit);
|
||||||
|
auto* model = new QStringListModel(primer);
|
||||||
|
QStringList items; for (int i = 0; i < 10; i++) items << QStringLiteral("x");
|
||||||
|
model->setStringList(items);
|
||||||
|
auto* lv = new QListView;
|
||||||
|
lv->setModel(model);
|
||||||
|
lay->addWidget(lv);
|
||||||
|
primer->show();
|
||||||
|
QApplication::processEvents();
|
||||||
|
primer->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
delete primer;
|
||||||
|
}
|
||||||
|
|
||||||
TypeEntry dummy;
|
TypeEntry dummy;
|
||||||
dummy.entryKind = TypeEntry::Primitive;
|
dummy.entryKind = TypeEntry::Primitive;
|
||||||
dummy.primitiveKind = NodeKind::Hex8;
|
dummy.primitiveKind = NodeKind::Hex8;
|
||||||
dummy.displayName = "warmup";
|
dummy.displayName = QStringLiteral("warmup");
|
||||||
setTypes({dummy});
|
setTypes({dummy});
|
||||||
popup(QPoint(-9999, -9999));
|
popup(QPoint(-9999, -9999));
|
||||||
hide();
|
hide();
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QFrame>
|
||||||
#include <Qsci/qsciscintilla.h>
|
#include <Qsci/qsciscintilla.h>
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "typeselectorpopup.h"
|
#include "typeselectorpopup.h"
|
||||||
@@ -198,6 +200,127 @@ private slots:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Isolate first-show cost with different window flags ──
|
||||||
|
|
||||||
|
void benchmarkFirstShow() {
|
||||||
|
auto ms = [](qint64 ns) { return QString::number(ns / 1000000.0, 'f', 2); };
|
||||||
|
|
||||||
|
struct FlagTest {
|
||||||
|
const char* name;
|
||||||
|
Qt::WindowFlags flags;
|
||||||
|
};
|
||||||
|
FlagTest tests[] = {
|
||||||
|
{"Qt::Popup|Frameless", Qt::Popup | Qt::FramelessWindowHint},
|
||||||
|
{"Qt::Tool|Frameless", Qt::Tool | Qt::FramelessWindowHint},
|
||||||
|
{"Qt::ToolTip", Qt::ToolTip},
|
||||||
|
{"Qt::Window|Frameless", Qt::Window | Qt::FramelessWindowHint},
|
||||||
|
{"Qt::Popup|Frameless (2nd)", Qt::Popup | Qt::FramelessWindowHint},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests) {
|
||||||
|
auto* f = new QFrame(nullptr, test.flags);
|
||||||
|
f->resize(300, 400);
|
||||||
|
|
||||||
|
QElapsedTimer t; t.start();
|
||||||
|
f->show();
|
||||||
|
qint64 t1 = t.nsecsElapsed(); t.restart();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qint64 t2 = t.nsecsElapsed();
|
||||||
|
f->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
t.restart();
|
||||||
|
f->show();
|
||||||
|
qint64 t3 = t.nsecsElapsed(); t.restart();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qint64 t4 = t.nsecsElapsed();
|
||||||
|
f->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
qDebug() << "";
|
||||||
|
qDebug().noquote() << QString("=== %1 ===").arg(test.name);
|
||||||
|
qDebug().noquote() << QString(" 1st: show=%1ms events=%2ms | 2nd: show=%3ms events=%4ms")
|
||||||
|
.arg(ms(t1)).arg(ms(t2)).arg(ms(t3)).arg(ms(t4));
|
||||||
|
delete f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeSelectorPopup: cold vs after warmUp
|
||||||
|
{
|
||||||
|
auto* popup = new TypeSelectorPopup();
|
||||||
|
TypeEntry dummy;
|
||||||
|
dummy.entryKind = TypeEntry::Primitive;
|
||||||
|
dummy.primitiveKind = NodeKind::Hex8;
|
||||||
|
dummy.displayName = "test";
|
||||||
|
popup->setTypes({dummy});
|
||||||
|
|
||||||
|
QElapsedTimer t; t.start();
|
||||||
|
popup->show();
|
||||||
|
qint64 t1 = t.nsecsElapsed(); t.restart();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qint64 t2 = t.nsecsElapsed();
|
||||||
|
popup->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
t.restart();
|
||||||
|
popup->show();
|
||||||
|
qint64 t3 = t.nsecsElapsed(); t.restart();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qint64 t4 = t.nsecsElapsed();
|
||||||
|
popup->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
qDebug() << "";
|
||||||
|
qDebug().noquote() << QString("=== TypeSelectorPopup (cold, Qt::Popup) ===");
|
||||||
|
qDebug().noquote() << QString(" 1st: show=%1ms events=%2ms | 2nd: show=%3ms events=%4ms")
|
||||||
|
.arg(ms(t1)).arg(ms(t2)).arg(ms(t3)).arg(ms(t4));
|
||||||
|
delete popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean order test: dummy popup with children FIRST, then TypeSelectorPopup
|
||||||
|
qDebug() << "";
|
||||||
|
qDebug() << "=== CLEAN: dummy popup first, then TypeSelectorPopup ===";
|
||||||
|
{
|
||||||
|
auto* dummy = new QFrame(nullptr, Qt::Popup | Qt::FramelessWindowHint);
|
||||||
|
dummy->resize(300, 400);
|
||||||
|
auto* dLay = new QVBoxLayout(dummy);
|
||||||
|
dLay->addWidget(new QLabel("dummy"));
|
||||||
|
dLay->addWidget(new QLineEdit);
|
||||||
|
auto* dModel = new QStringListModel(dummy);
|
||||||
|
QStringList dItems; for (int i = 0; i < 10; i++) dItems << "x";
|
||||||
|
dModel->setStringList(dItems);
|
||||||
|
auto* dLv = new QListView; dLv->setModel(dModel);
|
||||||
|
dLay->addWidget(dLv);
|
||||||
|
|
||||||
|
QElapsedTimer t; t.start();
|
||||||
|
dummy->show();
|
||||||
|
qint64 t1 = t.nsecsElapsed(); t.restart();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qint64 t2 = t.nsecsElapsed();
|
||||||
|
dummy->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qDebug().noquote() << QString(" Dummy popup: show=%1ms events=%2ms").arg(ms(t1)).arg(ms(t2));
|
||||||
|
delete dummy;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto* popup = new TypeSelectorPopup();
|
||||||
|
TypeEntry e;
|
||||||
|
e.entryKind = TypeEntry::Primitive;
|
||||||
|
e.primitiveKind = NodeKind::Hex8;
|
||||||
|
e.displayName = "test";
|
||||||
|
popup->setTypes({e});
|
||||||
|
popup->resize(300, 400);
|
||||||
|
QElapsedTimer t; t.start();
|
||||||
|
popup->show();
|
||||||
|
qint64 t1 = t.nsecsElapsed(); t.restart();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qint64 t2 = t.nsecsElapsed();
|
||||||
|
popup->hide();
|
||||||
|
QApplication::processEvents();
|
||||||
|
qDebug().noquote() << QString(" TypeSelectorPopup (after dummy): show=%1ms events=%2ms").arg(ms(t1)).arg(ms(t2));
|
||||||
|
delete popup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Popup data model ──
|
// ── Popup data model ──
|
||||||
|
|
||||||
void testPopupListsRootStructs() {
|
void testPopupListsRootStructs() {
|
||||||
|
|||||||
Reference in New Issue
Block a user