Qt6: prevent QML object from being garbage collected

It appears that the QML object that is created when assigning the source
to the StatsView in the ui can be garbage collected and therefore
destroyed and re-instantiated without our knowledge. So instead of
trying to keep a pointer around, we end up looking up the object address
when needed.
We still ask the JS code to not garbage collect the object - but that
clearly isn't enough to prevent it from being destroyed when the parent
widget changes.

Without this fix opening the statistics will crash with Qt6.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2022-02-16 15:42:21 -08:00
parent 2ede1a4563
commit 2368a183e1
2 changed files with 26 additions and 9 deletions

View file

@ -5,6 +5,7 @@
#include <QCheckBox>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QQmlEngine>
class ChartItemDelegate : public QStyledItemDelegate {
private:
@ -90,12 +91,24 @@ StatsWidget::StatsWidget(QWidget *parent) : QWidget(parent)
ui.stats->setSource(urlStatsView);
ui.stats->setResizeMode(QQuickWidget::SizeRootObjectToView);
QQuickItem *root = ui.stats->rootObject();
view = qobject_cast<StatsView *>(root);
(void)getView();
}
// hack around the Qt6 bug where the QML object gets destroyed and recreated
StatsView *StatsWidget::getView()
{
StatsView *view = qobject_cast<StatsView *>(ui.stats->rootObject());
if (!view)
qWarning("Oops. The root of the StatsView is not a StatsView.");
if (view)
if (view) {
// try to prevent the JS garbage collection from freeing the object
// this appears to fail with Qt6 which is why we still look up the
// object from the ui.stats rootObject
ui.stats->engine()->setObjectOwnership(view, QQmlEngine::CppOwnership);
view->setParent(this);
view->setVisible(isVisible()); // Synchronize visibility of widget and QtQuick-view.
}
return view;
}
// Initialize QComboBox with list of variables
@ -143,13 +156,14 @@ void StatsWidget::updateUi()
connect(check, &QCheckBox::stateChanged, [this,id] (int state) { featureChanged(id, state); });
ui.features->addWidget(check);
}
StatsView *view = getView();
if (view)
view->plot(state);
}
void StatsWidget::updateRestrictionLabel()
{
StatsView *view = getView();
if (!view)
return;
int num = view->restrictionCount();
@ -211,6 +225,7 @@ void StatsWidget::featureChanged(int idx, bool status)
{
state.featureChanged(idx, status);
// No need for a full chart replot - just show/hide the features
StatsView *view = getView();
if (view)
view->updateFeatures(state);
}
@ -221,20 +236,21 @@ void StatsWidget::showEvent(QShowEvent *e)
updateUi();
QWidget::showEvent(e);
// Apparently, we have to manage the visibility of the view ourselves. That's mad.
if (view)
view->setVisible(true);
// this is implicitly done in getView() - so we can ignore the return value
(void)getView();
}
void StatsWidget::hideEvent(QHideEvent *e)
{
QWidget::hideEvent(e);
// Apparently, we have to manage the visibility of the view ourselves. That's mad.
if (view)
view->setVisible(false);
// this is implicitly done in getView() - so we can ignore the return value
(void)getView();
}
void StatsWidget::restrict()
{
StatsView *view = getView();
if (view)
view->restrictToSelection();
updateRestrictionLabel();
@ -242,6 +258,7 @@ void StatsWidget::restrict()
void StatsWidget::unrestrict()
{
StatsView *view = getView();
if (view)
view->unrestrict();
updateRestrictionLabel();

View file

@ -31,7 +31,7 @@ slots:
private:
Ui::StatsWidget ui;
StatsState state;
StatsView *view;
StatsView *getView();
void updateUi();
void updateRestrictionLabel();
std::vector<std::unique_ptr<QCheckBox>> features;