mirror of https://github.com/Nheko-Reborn/nheko
Workaround for https://bugreports.qt.io/browse/QTBUG-54767 or https://bugreports.qt.io/browse/QTBUG-130996. This is probably the worst code I have written in a while, but basically we add a value interceptor to filter out any invisible menu entry. This is pretty dangerous because one false step crashes the whole menu. Menu entries are actually Cpp owned and need to be manually deleted unless they are removed via removeItem. Care needs to be taken to not mess up the contentData list. I expect this to break soon.pull/1854/head
parent
7db1711b09
commit
cd21ff6993
@ -0,0 +1,136 @@ |
||||
// SPDX-FileCopyrightText: Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "NhekoMenuVisibilityFilter.h" |
||||
|
||||
#include <QQmlListReference> |
||||
#include <QQuickItem> |
||||
|
||||
#include "Logging.h" |
||||
|
||||
QQmlListProperty<QQmlComponent> |
||||
NhekoMenuVisibilityFilter::items() |
||||
{ |
||||
return QQmlListProperty<QQmlComponent>(this, |
||||
this, |
||||
&NhekoMenuVisibilityFilter::appendItem, |
||||
&NhekoMenuVisibilityFilter::itemCount, |
||||
&NhekoMenuVisibilityFilter::getItem, |
||||
&NhekoMenuVisibilityFilter::clearItems, |
||||
&NhekoMenuVisibilityFilter::replaceItem, |
||||
&NhekoMenuVisibilityFilter::removeLast); |
||||
} |
||||
|
||||
void |
||||
NhekoMenuVisibilityFilter::appendItem(QQmlListProperty<QQmlComponent> *p, QQmlComponent *c) |
||||
{ |
||||
NhekoMenuVisibilityFilter *dc = static_cast<NhekoMenuVisibilityFilter *>(p->object); |
||||
dc->items_.append(c); |
||||
// dc->updateTarget();
|
||||
|
||||
// QQmlProperty prop(c, "visible");
|
||||
// prop.connectNotifySignal(dc, SLOT(updateTarget()));
|
||||
} |
||||
qsizetype |
||||
NhekoMenuVisibilityFilter::itemCount(QQmlListProperty<QQmlComponent> *p) |
||||
{ |
||||
return static_cast<NhekoMenuVisibilityFilter *>(p->object)->items_.count(); |
||||
} |
||||
QQmlComponent * |
||||
NhekoMenuVisibilityFilter::getItem(QQmlListProperty<QQmlComponent> *p, qsizetype index) |
||||
{ |
||||
return static_cast<NhekoMenuVisibilityFilter *>(p->object)->items_.at(index); |
||||
} |
||||
void |
||||
NhekoMenuVisibilityFilter::clearItems(QQmlListProperty<QQmlComponent> *p) |
||||
{ |
||||
static_cast<NhekoMenuVisibilityFilter *>(p->object)->items_.clear(); |
||||
// static_cast<NhekoMenuVisibilityFilter *>(p->object)->updateTarget();
|
||||
} |
||||
void |
||||
NhekoMenuVisibilityFilter::replaceItem(QQmlListProperty<QQmlComponent> *p, |
||||
qsizetype index, |
||||
QQmlComponent *c) |
||||
{ |
||||
static_cast<NhekoMenuVisibilityFilter *>(p->object)->items_.assign(index, c); |
||||
// static_cast<NhekoMenuVisibilityFilter *>(p->object)->updateTarget();
|
||||
} |
||||
void |
||||
NhekoMenuVisibilityFilter::removeLast(QQmlListProperty<QQmlComponent> *p) |
||||
{ |
||||
static_cast<NhekoMenuVisibilityFilter *>(p->object)->items_.pop_back(); |
||||
// static_cast<NhekoMenuVisibilityFilter *>(p->object)->updateTarget();
|
||||
} |
||||
|
||||
void |
||||
NhekoMenuVisibilityFilter::setTarget(const QQmlProperty &prop) |
||||
{ |
||||
if (prop.propertyTypeCategory() != QQmlProperty::List) { |
||||
nhlog::ui()->warn("Target prop of NhekoMenuVisibilityFilter set to non list property"); |
||||
return; |
||||
} |
||||
|
||||
targetProperty = prop; |
||||
// updateTarget();
|
||||
} |
||||
|
||||
void |
||||
NhekoMenuVisibilityFilter::updateTarget() |
||||
{ |
||||
if (!targetProperty.isValid()) |
||||
return; |
||||
|
||||
auto newItems = qvariant_cast<QQmlListReference>(targetProperty.read()); |
||||
// newItems.clear(); <- does not remove the visual items
|
||||
|
||||
for (qsizetype i = newItems.size(); i > 0; i--) { |
||||
// only remove items, not other random stuff in there!
|
||||
if (auto item = qobject_cast<QQuickItem *>(newItems.at(i - 1))) { |
||||
// emit removeItem(item); <- easier to "automagic" this by using invokeMethod
|
||||
QMetaObject::invokeMethod( |
||||
targetProperty.object(), "removeItem", Qt::DirectConnection, item); |
||||
} |
||||
} |
||||
|
||||
for (const auto &item : std::as_const(items_)) { |
||||
auto newItem = item->create(QQmlEngine::contextForObject(this)); |
||||
|
||||
if (auto prop = newItem->property("visible"); !prop.isValid() || prop.toBool()) { |
||||
// targetProperty.write(QVariant::fromValue(newItem)); <- appends but breaks removal
|
||||
newItems.append(newItem); |
||||
// You might think this should be JS Ownership, but no! The menu deletes stuff
|
||||
// explicitly!
|
||||
// Inb4 this causes a leak...
|
||||
//
|
||||
// QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
|
||||
// emit addItem(newItem); <- would work, but manual code, ew
|
||||
} else { |
||||
newItem->deleteLater(); |
||||
} |
||||
} |
||||
|
||||
if (delegate) { |
||||
for (const auto &modelData : std::as_const(model)) { |
||||
QVariantMap initial; |
||||
initial["modelData"] = modelData; |
||||
|
||||
auto newItem = |
||||
delegate->createWithInitialProperties(initial, QQmlEngine::contextForObject(this)); |
||||
|
||||
// targetProperty.write(QVariant::fromValue(newItem)); <- appends but breaks
|
||||
// removal
|
||||
newItems.append(newItem); |
||||
// You might think this should be JS Ownership, but no! The menu deletes stuff
|
||||
// explicitly!
|
||||
// Inb4 this causes a leak...
|
||||
//
|
||||
// QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
|
||||
// emit addItem(newItem); <- would work, but manual code, ew
|
||||
} |
||||
} |
||||
|
||||
// targetProperty.write(QVariant::fromValue(std::move(newItems)));
|
||||
} |
||||
|
||||
#include "moc_NhekoMenuVisibilityFilter.cpp" |
@ -0,0 +1,53 @@ |
||||
// SPDX-FileCopyrightText: Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QQmlComponent> |
||||
#include <QQmlEngine> |
||||
#include <QQmlListProperty> |
||||
#include <QQmlProperty> |
||||
#include <QQmlPropertyValueSource> |
||||
#include <QVariantList> |
||||
|
||||
class NhekoMenuVisibilityFilter |
||||
: public QObject |
||||
, public QQmlPropertyValueSource |
||||
{ |
||||
Q_OBJECT |
||||
Q_INTERFACES(QQmlPropertyValueSource) |
||||
Q_CLASSINFO("DefaultProperty", "items") |
||||
Q_PROPERTY(QQmlListProperty<QQmlComponent> items READ items CONSTANT FINAL) |
||||
Q_PROPERTY(QQmlComponent *delegate MEMBER delegate FINAL) |
||||
Q_PROPERTY(QVariantList model MEMBER model FINAL) |
||||
QML_ELEMENT |
||||
|
||||
public: |
||||
NhekoMenuVisibilityFilter(QObject *parent = nullptr) |
||||
: QObject(parent) |
||||
{ |
||||
} |
||||
|
||||
QQmlListProperty<QQmlComponent> items(); |
||||
|
||||
void setTarget(const QQmlProperty &prop) override; |
||||
|
||||
private: |
||||
QQmlProperty targetProperty; |
||||
QList<QQmlComponent *> items_; |
||||
QQmlComponent *delegate = nullptr; |
||||
QVariantList model; |
||||
|
||||
static void appendItem(QQmlListProperty<QQmlComponent> *, QQmlComponent *); |
||||
static qsizetype itemCount(QQmlListProperty<QQmlComponent> *); |
||||
static QQmlComponent *getItem(QQmlListProperty<QQmlComponent> *, qsizetype index); |
||||
static void clearItems(QQmlListProperty<QQmlComponent> *); |
||||
static void replaceItem(QQmlListProperty<QQmlComponent> *, qsizetype index, QQmlComponent *); |
||||
static void removeLast(QQmlListProperty<QQmlComponent> *); |
||||
|
||||
public slots: |
||||
// call this before showing the menu. We don't want to update elsewhere to prevent jumping menus
|
||||
// and useless work
|
||||
void updateTarget(); |
||||
}; |
Loading…
Reference in new issue