Horrific hack to hide menu entries when invisible

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
Nicolas Werner 3 weeks ago
parent 7db1711b09
commit cd21ff6993
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 2
      CMakeLists.txt
  2. 2
      resources/qml/MatrixText.qml
  3. 339
      resources/qml/MessageView.qml
  4. 127
      resources/qml/components/SpaceMenuLevel.qml
  5. 136
      src/ui/NhekoMenuVisibilityFilter.cpp
  6. 53
      src/ui/NhekoMenuVisibilityFilter.h

@ -408,6 +408,8 @@ set(SRC_FILES
src/ui/NhekoDropArea.h
src/ui/NhekoGlobalObject.cpp
src/ui/NhekoGlobalObject.h
src/ui/NhekoMenuVisibilityFilter.h
src/ui/NhekoMenuVisibilityFilter.cpp
src/ui/RoomSettings.cpp
src/ui/RoomSettings.h
src/ui/RoomSummary.cpp

@ -35,7 +35,7 @@ TextArea {
Component.onCompleted: {
TimelineManager.fixImageRendering(r.textDocument, r);
}
onLinkActivated: Nheko.openLink(link)
onLinkActivated: (link) => Nheko.openLink(link)
// propagate events up
onPressAndHold: event => event.accepted = false

@ -419,6 +419,9 @@ Item {
link = link_;
else
link = "";
messageActionsCFilter.updateTarget();
if (showAt_)
popup(showAt_);
else
@ -453,149 +456,189 @@ Item {
}
}
MenuItem {
enabled: visible
text: qsTr("Go to &message")
visible: filteredTimeline.filterByContent
NhekoMenuVisibilityFilter on contentData {
id: messageActionsCFilter
onTriggered: function () {
topBar.searchString = "";
room.showEvent(messageContextMenuC.eventId);
}
}
MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: messageContextMenuC.text
Component {
MenuItem {
enabled: visible
text: qsTr("Go to &message")
visible: filteredTimeline.filterByContent
onTriggered: Clipboard.text = messageContextMenuC.text
}
MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: messageContextMenuC.link
onTriggered: Clipboard.text = messageContextMenuC.link
}
MenuItem {
enabled: visible
id: reactionOption
onTriggered: function () {
topBar.searchString = "";
room.showEvent(messageContextMenuC.eventId);
}
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: messageContextMenuC.text
text: qsTr("Re&act")
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
onTriggered: Clipboard.text = messageContextMenuC.text
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: messageContextMenuC.link
onTriggered: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(null, room.roomId, function (plaintext, markdown) {
room.input.reaction(messageContextMenuC.eventId, plaintext);
TimelineManager.focusMessageInput();
})
}
MenuItem {
enabled: visible
text: qsTr("Repl&y")
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
onTriggered: Clipboard.text = messageContextMenuC.link
}
}
Component {
MenuItem {
enabled: visible
id: reactionOption
onTriggered: room.reply = (messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("&Edit")
visible: messageContextMenuC.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
text: qsTr("Re&act")
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
onTriggered: room.edit = (messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("&Thread")
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(null, room.roomId, function (plaintext, markdown) {
room.input.reaction(messageContextMenuC.eventId, plaintext);
TimelineManager.focusMessageInput();
})
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("Repl&y")
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
onTriggered: room.thread = (messageContextMenuC.threadId || messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
onTriggered: room.reply = (messageContextMenuC.eventId)
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Edit")
visible: messageContextMenuC.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? room.unpin(messageContextMenuC.eventId) : room.pin(messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("&Read receipts")
onTriggered: room.edit = (messageContextMenuC.eventId)
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Thread")
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: room.showReadReceipts(messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("&Forward")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker || messageContextMenuC.eventType == MtxEvent.TextMessage || messageContextMenuC.eventType == MtxEvent.LocationMessage || messageContextMenuC.eventType == MtxEvent.EmoteMessage || messageContextMenuC.eventType == MtxEvent.NoticeMessage
onTriggered: room.thread = (messageContextMenuC.threadId || messageContextMenuC.eventId)
}
}
Component {
MenuItem {
enabled: visible
text: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenuC.eventId);
forwardMess.open();
timelineRoot.destroyOnClose(forwardMess);
onTriggered: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? room.unpin(messageContextMenuC.eventId) : room.pin(messageContextMenuC.eventId)
}
}
}
MenuItem {
enabled: visible
text: qsTr("&Mark as read")
}
MenuItem {
enabled: visible
text: qsTr("View raw message")
Component {
MenuItem {
enabled: visible
text: qsTr("&Read receipts")
onTriggered: room.viewRawMessage(messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenuC.isEncrypted
onTriggered: room.showReadReceipts(messageContextMenuC.eventId)
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Forward")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker || messageContextMenuC.eventType == MtxEvent.TextMessage || messageContextMenuC.eventType == MtxEvent.LocationMessage || messageContextMenuC.eventType == MtxEvent.EmoteMessage || messageContextMenuC.eventType == MtxEvent.NoticeMessage
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenuC.eventId);
forwardMess.open();
timelineRoot.destroyOnClose(forwardMess);
}
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Mark as read")
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("View raw message")
onTriggered: room.viewDecryptedRawMessage(messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("Remo&ve message")
visible: (room ? room.permissions.canRedact() : false) || messageContextMenuC.isSender
onTriggered: function () {
var dialog = removeReason.createObject(timelineRoot);
dialog.eventId = messageContextMenuC.eventId;
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
onTriggered: room.viewRawMessage(messageContextMenuC.eventId)
}
}
}
MenuItem {
text: qsTr("Report message")
enabled: visible
onTriggered: function () {
var dialog = reportDialog.createObject(timelineRoot, {"eventId": messageContextMenuC.eventId});
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
Component {
MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenuC.isEncrypted
onTriggered: room.viewDecryptedRawMessage(messageContextMenuC.eventId)
}
}
}
MenuItem {
enabled: visible
text: qsTr("&Save as")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
Component {
MenuItem {
enabled: visible
text: qsTr("Remo&ve message")
visible: (room ? room.permissions.canRedact() : false) || messageContextMenuC.isSender
onTriggered: function () {
var dialog = removeReason.createObject(timelineRoot);
dialog.eventId = messageContextMenuC.eventId;
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
}
}
}
Component {
MenuItem {
text: qsTr("Report message")
enabled: visible
onTriggered: function () {
var dialog = reportDialog.createObject(timelineRoot, {"eventId": messageContextMenuC.eventId});
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
}
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Save as")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
onTriggered: room.saveMedia(messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("&Open in external program")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
onTriggered: room.saveMedia(messageContextMenuC.eventId)
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Open in external program")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
onTriggered: room.openMedia(messageContextMenuC.eventId)
}
MenuItem {
enabled: visible
text: qsTr("Copy link to eve&nt")
visible: messageContextMenuC.eventId
onTriggered: room.openMedia(messageContextMenuC.eventId)
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("Copy link to eve&nt")
visible: messageContextMenuC.eventId
onTriggered: room.copyLinkToEvent(messageContextMenuC.eventId)
onTriggered: room.copyLinkToEvent(messageContextMenuC.eventId)
}
}
}
}
Component {
@ -615,6 +658,8 @@ Item {
text = text_;
link = link_;
eventId = eventId_;
replyContextMenuCFilter.updateTarget();
open();
}
@ -625,26 +670,36 @@ Item {
}
MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: replyContextMenuC.text
NhekoMenuVisibilityFilter on contentData {
id: replyContextMenuCFilter
onTriggered: Clipboard.text = replyContextMenuC.text
}
MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: replyContextMenuC.link
Component {
MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: replyContextMenuC.text
onTriggered: Clipboard.text = replyContextMenuC.link
}
MenuItem {
enabled: visible
text: qsTr("&Go to quoted message")
visible: true
onTriggered: Clipboard.text = replyContextMenuC.text
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: replyContextMenuC.link
onTriggered: room.showEvent(replyContextMenuC.eventId)
onTriggered: Clipboard.text = replyContextMenuC.link
}
}
Component {
MenuItem {
enabled: visible
text: qsTr("&Go to quoted message")
visible: true
onTriggered: room.showEvent(replyContextMenuC.eventId)
}
}
}
}
RoundButton {

@ -12,77 +12,88 @@ Menu {
property string roomid
property Component childMenu
property var modelData: undefined
property int position: modelData == undefined ? -2 : modelData.treeIndex
title: modelData != undefined ? modelData.name : qsTr("Add or remove from community")
property bool loadChildren: false
onAboutToShow: loadChildren = true
//onAboutToHide: loadChildren = false
onAboutToShow: {
loadChildren = true;
menuFilter.updateTarget();
}
ButtonGroup {
id: modificationGroup
//visible: position != -1
}
MenuItem {
text: qsTr("Official community for this room")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
}
MenuItem {
text: qsTr("Affiliated community for this room")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
}
MenuItem {
text: qsTr("Listed only for community members")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
}
MenuItem {
text: qsTr("Listed only for room members")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
}
MenuItem {
text: qsTr("Not related")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
}
NhekoMenuVisibilityFilter on contentData {
id: menuFilter
MenuSeparator {
//text: qsTr("Subcommunities")
ButtonGroup.group: modificationGroup
visible: position != -1 && inst.model != undefined
}
Component {
MenuItem {
text: qsTr("Official community for this room")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
}
}
Component {
MenuItem {
text: qsTr("Affiliated community for this room")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
}
}
Component {
MenuItem {
text: qsTr("Listed only for community members")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
}
}
Component {
MenuItem {
text: qsTr("Listed only for room members")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
}
}
Component {
MenuItem {
text: qsTr("Not related")
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
}
Instantiator {
id: inst
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
onObjectAdded: (idx, o) => {
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o)
}
onObjectRemoved: (index, object) => spacesMenu.removeMenu(object)
Component {
MenuSeparator {
//text: qsTr("Subcommunities")
visible: position != -1// && menuFilter.model != undefined
}
}
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : []
delegate: childMenu
}

@ -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…
Cancel
Save