mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
feat: see-through popup dismiss for disasm/value-history/struct-preview
Override mouseMoveEvent in all three popup classes to forward mouse position back to viewport hover logic. When the row underneath the popup represents a different node, the popup dismisses automatically, allowing rapid swiping through FuncPtr rows.
This commit is contained in:
112
src/editor.cpp
112
src/editor.cpp
@@ -40,18 +40,30 @@ class ValueHistoryPopup : public QFrame {
|
|||||||
QStringList m_values;
|
QStringList m_values;
|
||||||
QVector<QLabel*> m_labels;
|
QVector<QLabel*> m_labels;
|
||||||
std::function<void(const QString&)> m_onSet;
|
std::function<void(const QString&)> m_onSet;
|
||||||
|
std::function<void(QMouseEvent*)> m_onMouseMove;
|
||||||
public:
|
public:
|
||||||
explicit ValueHistoryPopup(QWidget* parent)
|
explicit ValueHistoryPopup(QWidget* parent)
|
||||||
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||||
|
setMouseTracking(true);
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t nodeId() const { return m_nodeId; }
|
uint64_t nodeId() const { return m_nodeId; }
|
||||||
|
bool hasButtons() const { return m_hasButtons; }
|
||||||
void setOnSet(std::function<void(const QString&)> fn) { m_onSet = std::move(fn); }
|
void setOnSet(std::function<void(const QString&)> fn) { m_onSet = std::move(fn); }
|
||||||
|
void setOnMouseMove(std::function<void(QMouseEvent*)> fn) { m_onMouseMove = std::move(fn); }
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
if (!m_hasButtons && m_onMouseMove)
|
||||||
|
m_onMouseMove(e);
|
||||||
|
else
|
||||||
|
QFrame::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
|
||||||
void populate(uint64_t nodeId, const ValueHistory& hist, const QFont& font,
|
void populate(uint64_t nodeId, const ValueHistory& hist, const QFont& font,
|
||||||
bool showButtons = false) {
|
bool showButtons = false) {
|
||||||
@@ -185,12 +197,14 @@ class DisasmPopup : public QFrame {
|
|||||||
QString m_body;
|
QString m_body;
|
||||||
QLabel* m_titleLabel = nullptr;
|
QLabel* m_titleLabel = nullptr;
|
||||||
QLabel* m_bodyLabel = nullptr;
|
QLabel* m_bodyLabel = nullptr;
|
||||||
|
std::function<void(QMouseEvent*)> m_onMouseMove;
|
||||||
public:
|
public:
|
||||||
explicit DisasmPopup(QWidget* parent)
|
explicit DisasmPopup(QWidget* parent)
|
||||||
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||||
|
setMouseTracking(true);
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
|
|
||||||
@@ -216,8 +230,14 @@ public:
|
|||||||
vbox->addWidget(m_bodyLabel);
|
vbox->addWidget(m_bodyLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setOnMouseMove(std::function<void(QMouseEvent*)> fn) { m_onMouseMove = std::move(fn); }
|
||||||
uint64_t nodeId() const { return m_nodeId; }
|
uint64_t nodeId() const { return m_nodeId; }
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
if (m_onMouseMove) m_onMouseMove(e);
|
||||||
|
else QFrame::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
public:
|
||||||
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
||||||
const QFont& font) {
|
const QFont& font) {
|
||||||
if (nodeId == m_nodeId && body == m_body && isVisible())
|
if (nodeId == m_nodeId && body == m_body && isVisible())
|
||||||
@@ -283,12 +303,14 @@ class StructPreviewPopup : public QFrame {
|
|||||||
QString m_body;
|
QString m_body;
|
||||||
QLabel* m_titleLabel = nullptr;
|
QLabel* m_titleLabel = nullptr;
|
||||||
QLabel* m_bodyLabel = nullptr;
|
QLabel* m_bodyLabel = nullptr;
|
||||||
|
std::function<void(QMouseEvent*)> m_onMouseMove;
|
||||||
public:
|
public:
|
||||||
explicit StructPreviewPopup(QWidget* parent)
|
explicit StructPreviewPopup(QWidget* parent)
|
||||||
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||||
|
setMouseTracking(true);
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
|
|
||||||
@@ -315,7 +337,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t nodeId() const { return m_nodeId; }
|
uint64_t nodeId() const { return m_nodeId; }
|
||||||
|
void setOnMouseMove(std::function<void(QMouseEvent*)> fn) { m_onMouseMove = std::move(fn); }
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
if (m_onMouseMove) m_onMouseMove(e);
|
||||||
|
else QFrame::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
public:
|
||||||
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
||||||
const QFont& font) {
|
const QFont& font) {
|
||||||
if (nodeId == m_nodeId && body == m_body && isVisible())
|
if (nodeId == m_nodeId && body == m_body && isVisible())
|
||||||
@@ -3002,8 +3030,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
if (lm.heatLevel > 0 && lm.nodeId != 0) {
|
if (lm.heatLevel > 0 && lm.nodeId != 0) {
|
||||||
auto it = m_valueHistory->find(lm.nodeId);
|
auto it = m_valueHistory->find(lm.nodeId);
|
||||||
if (it != m_valueHistory->end() && it->uniqueCount() > 1) {
|
if (it != m_valueHistory->end() && it->uniqueCount() > 1) {
|
||||||
if (!m_historyPopup)
|
if (!m_historyPopup) {
|
||||||
m_historyPopup = new ValueHistoryPopup(this);
|
m_historyPopup = new ValueHistoryPopup(this);
|
||||||
|
static_cast<ValueHistoryPopup*>(m_historyPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
||||||
popup->setOnSet([this](const QString& val) {
|
popup->setOnSet([this](const QString& val) {
|
||||||
if (!m_editState.active) return;
|
if (!m_editState.active) return;
|
||||||
@@ -3163,8 +3209,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
QString lineText = getLineText(m_sci, h.line);
|
QString lineText = getLineText(m_sci, h.line);
|
||||||
ColumnSpan vs = valueSpan(lm, lineText.size(), lm.effectiveTypeW, lm.effectiveNameW);
|
ColumnSpan vs = valueSpan(lm, lineText.size(), lm.effectiveTypeW, lm.effectiveNameW);
|
||||||
if (vs.valid && h.col >= vs.start && h.col < vs.end) {
|
if (vs.valid && h.col >= vs.start && h.col < vs.end) {
|
||||||
if (!m_historyPopup)
|
if (!m_historyPopup) {
|
||||||
m_historyPopup = new ValueHistoryPopup(this);
|
m_historyPopup = new ValueHistoryPopup(this);
|
||||||
|
static_cast<ValueHistoryPopup*>(m_historyPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
||||||
popup->populate(lm.nodeId, *it, editorFont(), false);
|
popup->populate(lm.nodeId, *it, editorFont(), false);
|
||||||
long linePos = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
long linePos = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
||||||
@@ -3248,8 +3312,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!body.isEmpty()) {
|
if (!body.isEmpty()) {
|
||||||
if (!m_disasmPopup)
|
if (!m_disasmPopup) {
|
||||||
m_disasmPopup = new DisasmPopup(this);
|
m_disasmPopup = new DisasmPopup(this);
|
||||||
|
static_cast<DisasmPopup*>(m_disasmPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<DisasmPopup*>(
|
auto* popup = static_cast<DisasmPopup*>(
|
||||||
m_disasmPopup);
|
m_disasmPopup);
|
||||||
popup->populate(lm.nodeId, title, body,
|
popup->populate(lm.nodeId, title, body,
|
||||||
@@ -3317,8 +3399,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!body.isEmpty()) {
|
if (!body.isEmpty()) {
|
||||||
if (!m_structPreviewPopup)
|
if (!m_structPreviewPopup) {
|
||||||
m_structPreviewPopup = new StructPreviewPopup(this);
|
m_structPreviewPopup = new StructPreviewPopup(this);
|
||||||
|
static_cast<StructPreviewPopup*>(m_structPreviewPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<StructPreviewPopup*>(m_structPreviewPopup);
|
auto* popup = static_cast<StructPreviewPopup*>(m_structPreviewPopup);
|
||||||
popup->populate(lm.nodeId,
|
popup->populate(lm.nodeId,
|
||||||
lm.pointerTargetName, body, editorFont());
|
lm.pointerTargetName, body, editorFont());
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public:
|
|||||||
void restoreViewState(const ViewState& vs);
|
void restoreViewState(const ViewState& vs);
|
||||||
|
|
||||||
QsciScintilla* scintilla() const { return m_sci; }
|
QsciScintilla* scintilla() const { return m_sci; }
|
||||||
|
QWidget* historyPopup() const { return m_historyPopup; }
|
||||||
|
QWidget* disasmPopup() const { return m_disasmPopup; }
|
||||||
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
|
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
|
||||||
const LineMeta* metaForLine(int line) const;
|
const LineMeta* metaForLine(int line) const;
|
||||||
int currentNodeIndex() const;
|
int currentNodeIndex() const;
|
||||||
|
|||||||
@@ -2768,6 +2768,125 @@ private slots:
|
|||||||
"Static fields should not have a separator line");
|
"Static fields should not have a separator line");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Test: disasm popup dismisses when mouse moves onto it ("see-through") ──
|
||||||
|
//
|
||||||
|
// Scenario: hover a FuncPtr row → disasm popup appears below the row.
|
||||||
|
// User moves mouse down onto the popup. The popup covers rows behind it
|
||||||
|
// but the mouse position maps to a different node's row in the viewport
|
||||||
|
// underneath, so the popup must dismiss.
|
||||||
|
void testDisasmPopupDismissesOnMouseMoveThrough() {
|
||||||
|
NodeTree tree;
|
||||||
|
tree.baseAddress = 0;
|
||||||
|
|
||||||
|
Node root;
|
||||||
|
root.kind = NodeKind::Struct;
|
||||||
|
root.structTypeName = "TestClass";
|
||||||
|
root.name = "TestClass";
|
||||||
|
root.parentId = 0;
|
||||||
|
root.offset = 0;
|
||||||
|
int ri = tree.addNode(root);
|
||||||
|
uint64_t rootId = tree.nodes[ri].id;
|
||||||
|
|
||||||
|
// FuncPtr64 at offset 0 — its value points to "code" at byte 256
|
||||||
|
Node fp;
|
||||||
|
fp.kind = NodeKind::FuncPtr64;
|
||||||
|
fp.name = "VFunc1";
|
||||||
|
fp.parentId = rootId;
|
||||||
|
fp.offset = 0;
|
||||||
|
tree.addNode(fp);
|
||||||
|
|
||||||
|
// A plain UInt64 after it so there's a non-FuncPtr row below
|
||||||
|
Node pad;
|
||||||
|
pad.kind = NodeKind::UInt64;
|
||||||
|
pad.name = "padding";
|
||||||
|
pad.parentId = rootId;
|
||||||
|
pad.offset = 8;
|
||||||
|
tree.addNode(pad);
|
||||||
|
|
||||||
|
// Buffer layout:
|
||||||
|
// [0..7] FuncPtr value = 256 (points to code bytes)
|
||||||
|
// [8..15] padding field value
|
||||||
|
// [256..383] x86 code bytes (push rbp; mov rbp,rsp; nop...; ret)
|
||||||
|
QByteArray data(512, '\0');
|
||||||
|
uint64_t codeAddr = 256;
|
||||||
|
memcpy(data.data(), &codeAddr, 8);
|
||||||
|
const uint8_t code[] = {
|
||||||
|
0x55, // push rbp
|
||||||
|
0x48, 0x89, 0xE5, // mov rbp, rsp
|
||||||
|
0x90, // nop
|
||||||
|
0x90, // nop
|
||||||
|
0x5D, // pop rbp
|
||||||
|
0xC3 // ret
|
||||||
|
};
|
||||||
|
memcpy(data.data() + 256, code, sizeof(code));
|
||||||
|
BufferProvider prov(data, "test_disasm_dismiss");
|
||||||
|
|
||||||
|
ComposeResult cr = compose(tree, prov);
|
||||||
|
m_editor->applyDocument(cr);
|
||||||
|
m_editor->setProviderRef(&prov, nullptr, &tree);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Find the FuncPtr line
|
||||||
|
int fpLine = -1;
|
||||||
|
for (int i = 0; i < cr.meta.size(); ++i) {
|
||||||
|
if (isFuncPtr(cr.meta[i].nodeKind)) {
|
||||||
|
fpLine = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QVERIFY2(fpLine >= 0, "Could not find FuncPtr64 line in compose output");
|
||||||
|
|
||||||
|
// Hover over the FuncPtr value column to trigger the disasm popup
|
||||||
|
const LineMeta& lm = cr.meta[fpLine];
|
||||||
|
QString lineText;
|
||||||
|
{
|
||||||
|
long len = m_editor->scintilla()->SendScintilla(
|
||||||
|
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)fpLine);
|
||||||
|
QByteArray buf(len + 1, '\0');
|
||||||
|
m_editor->scintilla()->SendScintilla(
|
||||||
|
QsciScintillaBase::SCI_GETLINE, (uintptr_t)fpLine,
|
||||||
|
static_cast<const char*>(buf.data()));
|
||||||
|
lineText = QString::fromUtf8(buf.left(len));
|
||||||
|
}
|
||||||
|
ColumnSpan vs = m_editor->valueSpan(lm, lineText.size(),
|
||||||
|
lm.effectiveTypeW, lm.effectiveNameW);
|
||||||
|
QVERIFY2(vs.valid, "Value span for FuncPtr line is not valid");
|
||||||
|
|
||||||
|
int hoverCol = (vs.start + vs.end) / 2;
|
||||||
|
QPoint vpFP = colToViewport(m_editor->scintilla(), fpLine, hoverCol);
|
||||||
|
sendMouseMove(m_editor->scintilla()->viewport(), vpFP);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
QWidget* popup = m_editor->disasmPopup();
|
||||||
|
QVERIFY2(popup && popup->isVisible(),
|
||||||
|
"Disasm popup should be visible after hovering the FuncPtr value");
|
||||||
|
|
||||||
|
// See-through behavior: when the user moves the mouse down from the
|
||||||
|
// viewport onto the popup, the popup's mouseMoveEvent override forwards
|
||||||
|
// the global position back to the viewport hover logic. If the row
|
||||||
|
// underneath the popup represents a different node, the popup dismisses.
|
||||||
|
//
|
||||||
|
// Simulate by sending a MouseMove event to the popup at a global
|
||||||
|
// position that maps to the CommandRow (line 0) — a non-FuncPtr row.
|
||||||
|
// sendEvent triggers the virtual mouseMoveEvent directly.
|
||||||
|
QPoint vpCmdRow = colToViewport(m_editor->scintilla(), 0, hoverCol);
|
||||||
|
QPoint globalCmdRow = m_editor->scintilla()->viewport()->mapToGlobal(vpCmdRow);
|
||||||
|
QPoint localOnPopup = popup->mapFromGlobal(globalCmdRow);
|
||||||
|
QMouseEvent moveOnPopup(QEvent::MouseMove,
|
||||||
|
QPointF(localOnPopup), QPointF(globalCmdRow),
|
||||||
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier);
|
||||||
|
QApplication::sendEvent(popup, &moveOnPopup);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
QVERIFY2(!popup->isVisible(),
|
||||||
|
"Disasm popup must dismiss when mouseMoveEvent forwards "
|
||||||
|
"to a non-FuncPtr row underneath (see-through behavior)");
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
m_editor->setProviderRef(nullptr, nullptr, nullptr);
|
||||||
|
m_editor->applyDocument(m_result);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
QTEST_MAIN(TestEditor)
|
QTEST_MAIN(TestEditor)
|
||||||
|
|||||||
Reference in New Issue
Block a user