fix(framework): ensure that widgets are recreated and reparented after the owning view is closed

This commit is contained in:
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 2026-01-14 23:37:37 -05:00
parent 06c0a4ad6f
commit db0477b80b
Signed by: tablet
GPG Key ID: 924A5F6AF051E87C

View File

@ -20,36 +20,38 @@ concept isQWidget = std::is_base_of<QWidget, T>::value;
*/ */
template <isQWidget T> class TUWidgetLayoutViewBase : public TULayoutView { template <isQWidget T> class TUWidgetLayoutViewBase : public TULayoutView {
public: public:
TUWidgetLayoutViewBase() { m_widget = nullptr; }; TUWidgetLayoutViewBase() {
TUWidgetLayoutViewBase(T *widget) { m_widget = widget; }; m_widget = nullptr;
virtual ~TUWidgetLayoutViewBase() { m_parentConnected = nullptr;
}; };
TUWidgetLayoutViewBase(T *widget) {
m_widget = widget;
m_parentConnected = nullptr;
};
virtual ~TUWidgetLayoutViewBase() {};
virtual void triggerMenuChanged() override {} virtual void triggerMenuChanged() override {}
QWidget *widget() override { QWidget *widget() override {
return reinterpret_cast<QWidget *>(static_cast<TULayoutView *>(this)); return reinterpret_cast<QWidget *>(static_cast<TULayoutView *>(this));
} }
const QWidget *getWidget() const override { const QWidget *getWidget() const override {
const_cast<TUWidgetLayoutViewBase *>(this)->ensureWidget();
const_cast<TUWidgetLayoutViewBase *>(this)->connectToParentIfNeeded();
return m_widget; return m_widget;
} }
QWidget *getWidget() override { QWidget *getWidget() override {
ensureWidget(); ensureWidget();
connectToParentIfNeeded();
return m_widget; return m_widget;
} }
// Note: initiate() is NOT called by Toon Boom! The actual parenting
// is done externally via getWidget() + setParent(). We keep this for
// API compatibility but the real work is done in connectToParentIfNeeded().
TULayoutView *initiate(QWidget *parent) override { TULayoutView *initiate(QWidget *parent) override {
ensureWidget(); ensureWidget();
if (parent && m_widget) { if (parent && m_widget) {
m_widget->setParent(parent); m_widget->setParent(parent);
connectToParent(parent);
} }
QObject::connect(
parent, &QObject::destroyed, m_widget,
[this]() {
if (m_widget) {
m_widget->setParent(nullptr); // Prevent Qt from deleting us
}
},
Qt::DirectConnection); // DirectConnection ensures this runs BEFORE
// deletion
return this; return this;
} }
@ -58,6 +60,7 @@ public:
protected: protected:
QPointer<T> m_widget; QPointer<T> m_widget;
QWidget *m_parentConnected; // Track which parent we've connected to
void ensureWidget() { void ensureWidget() {
if (!m_widget) { if (!m_widget) {
m_widget = createWidget(); m_widget = createWidget();
@ -65,9 +68,34 @@ protected:
m_widget->setAttribute(Qt::WA_QuitOnClose, false); m_widget->setAttribute(Qt::WA_QuitOnClose, false);
} }
} }
void isTULayoutView() override {} // Connect to the widget's current parent to unparent before deletion
void disconnectView() override { // This prevents cross-DLL heap corruption when Qt tries to delete our widget
void connectToParentIfNeeded() {
if (!m_widget)
return;
QWidget *parent = m_widget->parentWidget();
if (parent && parent != m_parentConnected) {
connectToParent(parent);
}
}
void connectToParent(QWidget *parent) {
if (!parent || !m_widget)
return;
m_parentConnected = parent;
QObject::connect(
parent, &QObject::destroyed, m_widget.data(),
[this]() {
std::cout << "[parent destroyed] Unparenting widget to prevent "
"cross-DLL heap deletion"
<< std::endl;
if (m_widget) {
m_widget->setParent(nullptr);
}
m_parentConnected = nullptr;
onParentDisconnect();
},
Qt::DirectConnection);
} }
virtual T *createWidget() = 0; virtual T *createWidget() = 0;
}; };