mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Create an EventDelegateChooser
This commit is contained in:
parent
797dadd7e9
commit
4d8b8c3b81
8 changed files with 430 additions and 189 deletions
|
@ -359,6 +359,8 @@ set(SRC_FILES
|
||||||
src/timeline/CommunitiesModel.h
|
src/timeline/CommunitiesModel.h
|
||||||
src/timeline/DelegateChooser.cpp
|
src/timeline/DelegateChooser.cpp
|
||||||
src/timeline/DelegateChooser.h
|
src/timeline/DelegateChooser.h
|
||||||
|
src/timeline/EventDelegateChooser.cpp
|
||||||
|
src/timeline/EventDelegateChooser.h
|
||||||
src/timeline/EventStore.cpp
|
src/timeline/EventStore.cpp
|
||||||
src/timeline/EventStore.h
|
src/timeline/EventStore.h
|
||||||
src/timeline/InputBar.cpp
|
src/timeline/InputBar.cpp
|
||||||
|
@ -882,6 +884,7 @@ target_link_libraries(nheko PRIVATE
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Multimedia
|
Qt::Multimedia
|
||||||
Qt::Qml
|
Qt::Qml
|
||||||
|
Qt::QmlPrivate
|
||||||
Qt::QuickControls2
|
Qt::QuickControls2
|
||||||
qt6keychain
|
qt6keychain
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
|
|
|
@ -20,6 +20,7 @@ Item {
|
||||||
property int availableWidth: width
|
property int availableWidth: width
|
||||||
property int padding: Nheko.paddingMedium
|
property int padding: Nheko.paddingMedium
|
||||||
property string searchString: ""
|
property string searchString: ""
|
||||||
|
property Room roommodel: room
|
||||||
|
|
||||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||||
Connections {
|
Connections {
|
||||||
|
@ -58,173 +59,33 @@ Item {
|
||||||
spacing: 2
|
spacing: 2
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
|
||||||
delegate: Item {
|
delegate: EventDelegateChooser {
|
||||||
id: wrapper
|
id: wrapper
|
||||||
|
|
||||||
required property string blurhash
|
|
||||||
required property string body
|
|
||||||
required property string callType
|
|
||||||
required property var day
|
|
||||||
required property string duration
|
|
||||||
required property int encryptionError
|
|
||||||
required property string eventId
|
|
||||||
required property string filename
|
|
||||||
required property string filesize
|
|
||||||
required property string formattedBody
|
|
||||||
required property int index
|
|
||||||
required property bool isEditable
|
|
||||||
required property bool isEdited
|
|
||||||
required property bool isEncrypted
|
|
||||||
required property bool isOnlyEmoji
|
|
||||||
required property bool isSender
|
|
||||||
required property bool isStateEvent
|
|
||||||
required property int notificationlevel
|
|
||||||
required property int originalWidth
|
|
||||||
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
|
|
||||||
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
|
|
||||||
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
|
|
||||||
required property double proportionalHeight
|
|
||||||
required property var reactions
|
|
||||||
required property int relatedEventCacheBuster
|
|
||||||
required property string replyTo
|
|
||||||
required property string roomName
|
|
||||||
required property string roomTopic
|
|
||||||
property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
|
|
||||||
required property int status
|
|
||||||
required property string threadId
|
|
||||||
required property string thumbnailUrl
|
|
||||||
required property var timestamp
|
|
||||||
required property int trustlevel
|
|
||||||
required property int type
|
|
||||||
required property string typeString
|
|
||||||
required property string url
|
|
||||||
required property string userId
|
|
||||||
required property string userName
|
|
||||||
required property int userPowerlevel
|
|
||||||
|
|
||||||
ListView.delayRemove: true
|
ListView.delayRemove: true
|
||||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
|
||||||
height: (section.item?.height ?? 0) + timelinerow.height
|
|
||||||
width: chat.delegateMaxWidth
|
width: chat.delegateMaxWidth
|
||||||
|
height: main?.height ?? 10
|
||||||
|
room: chatRoot.roommodel
|
||||||
|
|
||||||
Loader {
|
EventDelegateChoice {
|
||||||
id: section
|
roleValues: [
|
||||||
|
MtxEvent.TextMessage,
|
||||||
|
MtxEvent.NoticeMessage,
|
||||||
|
]
|
||||||
|
TextArea {
|
||||||
|
required property string body
|
||||||
|
|
||||||
property var day: wrapper.day
|
width: parent.width
|
||||||
property bool isSender: wrapper.isSender
|
text: body
|
||||||
property bool isStateEvent: wrapper.isStateEvent
|
|
||||||
property int parentWidth: parent.width
|
|
||||||
property var previousMessageDay: wrapper.previousMessageDay
|
|
||||||
property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
|
|
||||||
property string previousMessageUserId: wrapper.previousMessageUserId
|
|
||||||
property date timestamp: wrapper.timestamp
|
|
||||||
property string userId: wrapper.userId
|
|
||||||
property string userName: wrapper.userName
|
|
||||||
property int userPowerlevel: wrapper.userPowerlevel
|
|
||||||
|
|
||||||
active: previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
|
|
||||||
//asynchronous: true
|
|
||||||
sourceComponent: sectionHeader
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
z: 4
|
|
||||||
}
|
|
||||||
TimelineRow {
|
|
||||||
id: timelinerow
|
|
||||||
|
|
||||||
blurhash: wrapper.blurhash
|
|
||||||
body: wrapper.body
|
|
||||||
callType: wrapper.callType
|
|
||||||
duration: wrapper.duration
|
|
||||||
encryptionError: wrapper.encryptionError
|
|
||||||
eventId: chat.model, wrapper.eventId
|
|
||||||
filename: wrapper.filename
|
|
||||||
filesize: wrapper.filesize
|
|
||||||
formattedBody: wrapper.formattedBody
|
|
||||||
index: wrapper.index
|
|
||||||
isEditable: wrapper.isEditable
|
|
||||||
isEdited: wrapper.isEdited
|
|
||||||
isEncrypted: wrapper.isEncrypted
|
|
||||||
isOnlyEmoji: wrapper.isOnlyEmoji
|
|
||||||
isSender: wrapper.isSender
|
|
||||||
isStateEvent: wrapper.isStateEvent
|
|
||||||
notificationlevel: wrapper.notificationlevel
|
|
||||||
originalWidth: wrapper.originalWidth
|
|
||||||
proportionalHeight: wrapper.proportionalHeight
|
|
||||||
reactions: wrapper.reactions
|
|
||||||
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
|
|
||||||
replyTo: wrapper.replyTo
|
|
||||||
roomName: wrapper.roomName
|
|
||||||
roomTopic: wrapper.roomTopic
|
|
||||||
status: wrapper.status
|
|
||||||
threadId: wrapper.threadId
|
|
||||||
thumbnailUrl: wrapper.thumbnailUrl
|
|
||||||
timestamp: wrapper.timestamp
|
|
||||||
trustlevel: wrapper.trustlevel
|
|
||||||
type: chat.model, wrapper.type
|
|
||||||
typeString: wrapper.typeString
|
|
||||||
url: wrapper.url
|
|
||||||
userId: wrapper.userId
|
|
||||||
userName: wrapper.userName
|
|
||||||
width: wrapper.width
|
|
||||||
y: section.visible && section.active ? section.y + section.height : 0
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
id: scrollHighlight
|
|
||||||
|
|
||||||
color: palette.highlight
|
|
||||||
enabled: false
|
|
||||||
opacity: 0
|
|
||||||
visible: true
|
|
||||||
z: 1
|
|
||||||
|
|
||||||
states: State {
|
|
||||||
name: "revealed"
|
|
||||||
when: wrapper.scrolledToThis
|
|
||||||
}
|
|
||||||
transitions: Transition {
|
|
||||||
from: ""
|
|
||||||
to: "revealed"
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
PropertyAnimation {
|
|
||||||
duration: 500
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
from: 0
|
|
||||||
properties: "opacity"
|
|
||||||
target: scrollHighlight
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
PropertyAnimation {
|
|
||||||
duration: 500
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
from: 1
|
|
||||||
properties: "opacity"
|
|
||||||
target: scrollHighlight
|
|
||||||
to: 0
|
|
||||||
}
|
|
||||||
ScriptAction {
|
|
||||||
script: room.eventShown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (!Settings.mobileMode && hovered) {
|
|
||||||
if (!messageActions.hovered) {
|
|
||||||
messageActions.attached = timelinerow;
|
|
||||||
messageActions.model = timelinerow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Connections {
|
|
||||||
function onMovementEnded() {
|
|
||||||
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
|
||||||
chat.model.currentIndex = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
target: chat
|
EventDelegateChoice {
|
||||||
|
roleValues: [
|
||||||
|
]
|
||||||
|
TextArea {
|
||||||
|
width: parent.width
|
||||||
|
text: "Unsupported"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
footer: Item {
|
footer: Item {
|
||||||
|
|
|
@ -147,7 +147,7 @@ AbstractButton {
|
||||||
columns: Settings.bubbles ? 1 : 2
|
columns: Settings.bubbles ? 1 : 2
|
||||||
rowSpacing: 0
|
rowSpacing: 0
|
||||||
rows: Settings.bubbles ? 3 : 2
|
rows: Settings.bubbles ? 3 : 2
|
||||||
|
/*
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: 4
|
leftMargin: 4
|
||||||
|
@ -230,6 +230,7 @@ AbstractButton {
|
||||||
userId: r.userId
|
userId: r.userId
|
||||||
userName: r.userName
|
userName: r.userName
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
Row {
|
Row {
|
||||||
id: metadata
|
id: metadata
|
||||||
|
|
||||||
|
|
|
@ -95,37 +95,11 @@ AbstractButton {
|
||||||
onClicked: room.openUserProfile(userId)
|
onClicked: room.openUserProfile(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDelegate {
|
Rectangle {
|
||||||
Layout.leftMargin: 4
|
Layout.leftMargin: 4
|
||||||
Layout.preferredHeight: height
|
Layout.preferredHeight: 20
|
||||||
id: reply
|
|
||||||
blurhash: r.blurhash
|
|
||||||
body: r.body
|
|
||||||
formattedBody: r.formattedBody
|
|
||||||
eventId: r.eventId
|
|
||||||
filename: r.filename
|
|
||||||
filesize: r.filesize
|
|
||||||
proportionalHeight: r.proportionalHeight
|
|
||||||
type: r.type
|
|
||||||
typeString: r.typeString ?? ""
|
|
||||||
url: r.url
|
|
||||||
thumbnailUrl: r.thumbnailUrl
|
|
||||||
duration: r.duration
|
|
||||||
originalWidth: r.originalWidth
|
|
||||||
isOnlyEmoji: r.isOnlyEmoji
|
|
||||||
isStateEvent: r.isStateEvent
|
|
||||||
userId: r.userId
|
|
||||||
userName: r.userName
|
|
||||||
roomTopic: r.roomTopic
|
|
||||||
roomName: r.roomName
|
|
||||||
callType: r.callType
|
|
||||||
relatedEventCacheBuster: r.relatedEventCacheBuster
|
|
||||||
encryptionError: r.encryptionError
|
|
||||||
// This is disabled so that left clicking the reply goes to its location
|
|
||||||
enabled: false
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
isReply: true
|
color: "green"
|
||||||
keepFullText: r.keepFullText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
244
src/timeline/EventDelegateChooser.cpp
Normal file
244
src/timeline/EventDelegateChooser.cpp
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "EventDelegateChooser.h"
|
||||||
|
#include "TimelineModel.h"
|
||||||
|
|
||||||
|
#include "Logging.h"
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
// privat qt headers to access required properties
|
||||||
|
#include <QtQml/private/qqmlincubator_p.h>
|
||||||
|
#include <QtQml/private/qqmlobjectcreator_p.h>
|
||||||
|
|
||||||
|
QQmlComponent *
|
||||||
|
EventDelegateChoice::delegate() const
|
||||||
|
{
|
||||||
|
return delegate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChoice::setDelegate(QQmlComponent *delegate)
|
||||||
|
{
|
||||||
|
if (delegate != delegate_) {
|
||||||
|
delegate_ = delegate;
|
||||||
|
emit delegateChanged();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<int>
|
||||||
|
EventDelegateChoice::roleValues() const
|
||||||
|
{
|
||||||
|
return roleValues_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChoice::setRoleValues(const QList<int> &value)
|
||||||
|
{
|
||||||
|
if (value != roleValues_) {
|
||||||
|
roleValues_ = value;
|
||||||
|
emit roleValuesChanged();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlListProperty<EventDelegateChoice>
|
||||||
|
EventDelegateChooser::choices()
|
||||||
|
{
|
||||||
|
return QQmlListProperty<EventDelegateChoice>(this,
|
||||||
|
this,
|
||||||
|
&EventDelegateChooser::appendChoice,
|
||||||
|
&EventDelegateChooser::choiceCount,
|
||||||
|
&EventDelegateChooser::choice,
|
||||||
|
&EventDelegateChooser::clearChoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChooser::appendChoice(QQmlListProperty<EventDelegateChoice> *p, EventDelegateChoice *c)
|
||||||
|
{
|
||||||
|
EventDelegateChooser *dc = static_cast<EventDelegateChooser *>(p->object);
|
||||||
|
dc->choices_.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype
|
||||||
|
EventDelegateChooser::choiceCount(QQmlListProperty<EventDelegateChoice> *p)
|
||||||
|
{
|
||||||
|
return static_cast<EventDelegateChooser *>(p->object)->choices_.count();
|
||||||
|
}
|
||||||
|
EventDelegateChoice *
|
||||||
|
EventDelegateChooser::choice(QQmlListProperty<EventDelegateChoice> *p, qsizetype index)
|
||||||
|
{
|
||||||
|
return static_cast<EventDelegateChooser *>(p->object)->choices_.at(index);
|
||||||
|
}
|
||||||
|
void
|
||||||
|
EventDelegateChooser::clearChoices(QQmlListProperty<EventDelegateChoice> *p)
|
||||||
|
{
|
||||||
|
static_cast<EventDelegateChooser *>(p->object)->choices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChooser::componentComplete()
|
||||||
|
{
|
||||||
|
QQuickItem::componentComplete();
|
||||||
|
// eventIncubator.reset(eventIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
|
||||||
|
{
|
||||||
|
auto item = qobject_cast<QQuickItem *>(obj);
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item->setParentItem(&chooser);
|
||||||
|
|
||||||
|
auto roleNames = chooser.room_->roleNames();
|
||||||
|
QHash<QByteArray, int> nameToRole;
|
||||||
|
for (const auto &[k, v] : roleNames.asKeyValueRange()) {
|
||||||
|
nameToRole.insert(v, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, int> roleToPropIdx;
|
||||||
|
std::vector<QModelRoleData> roles;
|
||||||
|
|
||||||
|
// Workaround for https://bugreports.qt.io/browse/QTBUG-98846
|
||||||
|
QHash<QString, RequiredPropertyKey> requiredProperties;
|
||||||
|
for (const auto &[propKey, prop] :
|
||||||
|
QQmlIncubatorPrivate::get(this)->requiredProperties()->asKeyValueRange()) {
|
||||||
|
requiredProperties.insert(prop.propertyName, propKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect required properties
|
||||||
|
auto mo = obj->metaObject();
|
||||||
|
for (int i = 0; i < mo->propertyCount(); i++) {
|
||||||
|
auto prop = mo->property(i);
|
||||||
|
// nhlog::ui()->critical("Found prop {}", prop.name());
|
||||||
|
// See https://bugreports.qt.io/browse/QTBUG-98846
|
||||||
|
if (!prop.isRequired() && !requiredProperties.contains(prop.name()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) {
|
||||||
|
roleToPropIdx.insert(*role, i);
|
||||||
|
roles.emplace_back(*role);
|
||||||
|
|
||||||
|
nhlog::ui()->critical("Found prop {}, idx {}, role {}", prop.name(), i, *role);
|
||||||
|
} else {
|
||||||
|
nhlog::ui()->critical("Required property {} not found in model!", prop.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nhlog::ui()->debug("Querying data for id {}", currentId.toStdString());
|
||||||
|
chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles);
|
||||||
|
|
||||||
|
QVariantMap rolesToSet;
|
||||||
|
for (const auto &role : roles) {
|
||||||
|
const auto &roleName = roleNames[role.role()];
|
||||||
|
nhlog::ui()->critical("Setting role {}, {}", role.role(), roleName.toStdString());
|
||||||
|
|
||||||
|
mo->property(roleToPropIdx[role.role()]).write(obj, role.data());
|
||||||
|
rolesToSet.insert(roleName, role.data());
|
||||||
|
|
||||||
|
if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end())
|
||||||
|
QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setInitialProperties(rolesToSet);
|
||||||
|
|
||||||
|
auto update =
|
||||||
|
[this, obj, roleToPropIdx = std::move(roleToPropIdx)](const QList<int> &changedRoles) {
|
||||||
|
std::vector<QModelRoleData> rolesToRequest;
|
||||||
|
|
||||||
|
if (changedRoles.empty()) {
|
||||||
|
for (auto role : roleToPropIdx.keys())
|
||||||
|
rolesToRequest.emplace_back(role);
|
||||||
|
} else {
|
||||||
|
for (auto role : changedRoles) {
|
||||||
|
if (roleToPropIdx.contains(role)) {
|
||||||
|
rolesToRequest.emplace_back(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rolesToRequest.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto mo = obj->metaObject();
|
||||||
|
chooser.room_->multiData(
|
||||||
|
currentId, forReply ? chooser.eventId_ : QString(), rolesToRequest);
|
||||||
|
for (const auto &role : rolesToRequest) {
|
||||||
|
mo->property(roleToPropIdx[role.role()]).write(obj, role.data());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!forReply) {
|
||||||
|
auto row = chooser.room_->idToIndex(currentId);
|
||||||
|
connect(chooser.room_,
|
||||||
|
&QAbstractItemModel::dataChanged,
|
||||||
|
obj,
|
||||||
|
[row, update](const QModelIndex &topLeft,
|
||||||
|
const QModelIndex &bottomRight,
|
||||||
|
const QList<int> &changedRoles) {
|
||||||
|
if (row < topLeft.row() || row > bottomRight.row())
|
||||||
|
return;
|
||||||
|
|
||||||
|
update(changedRoles);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChooser::DelegateIncubator::reset(QString id)
|
||||||
|
{
|
||||||
|
if (!chooser.room_ || id.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
nhlog::ui()->debug("Reset with id {}, reply {}", id.toStdString(), forReply);
|
||||||
|
|
||||||
|
this->currentId = id;
|
||||||
|
|
||||||
|
auto role =
|
||||||
|
chooser.room_
|
||||||
|
->dataById(id, TimelineModel::Roles::Type, forReply ? chooser.eventId_ : QString())
|
||||||
|
.toInt();
|
||||||
|
|
||||||
|
for (const auto choice : qAsConst(chooser.choices_)) {
|
||||||
|
const auto &choiceValue = choice->roleValues();
|
||||||
|
if (choiceValue.contains(role) || choiceValue.empty()) {
|
||||||
|
if (auto child = qobject_cast<QQuickItem *>(object())) {
|
||||||
|
child->setParentItem(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
choice->delegate()->create(*this, QQmlEngine::contextForObject(&chooser));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventDelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
|
||||||
|
{
|
||||||
|
if (status == QQmlIncubator::Ready) {
|
||||||
|
auto child = qobject_cast<QQuickItem *>(object());
|
||||||
|
if (child == nullptr) {
|
||||||
|
nhlog::ui()->error("Delegate has to be derived of Item!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
child->setParentItem(&chooser);
|
||||||
|
QQmlEngine::setObjectOwnership(child, QQmlEngine::ObjectOwnership::JavaScriptOwnership);
|
||||||
|
if (forReply)
|
||||||
|
emit chooser.replyChanged();
|
||||||
|
else
|
||||||
|
emit chooser.mainChanged();
|
||||||
|
|
||||||
|
} else if (status == QQmlIncubator::Error) {
|
||||||
|
auto errors_ = errors();
|
||||||
|
for (const auto &e : qAsConst(errors_))
|
||||||
|
nhlog::ui()->error("Error instantiating delegate: {}", e.toString().toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
136
src/timeline/EventDelegateChooser.h
Normal file
136
src/timeline/EventDelegateChooser.h
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// A DelegateChooser like the one, that was added to Qt5.12 (in labs), but compatible with older Qt
|
||||||
|
// versions see KDE/kquickitemviews see qtdeclarative/qqmldelagatecomponent
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QQmlComponent>
|
||||||
|
#include <QQmlIncubator>
|
||||||
|
#include <QQmlListProperty>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QVariant>
|
||||||
|
|
||||||
|
#include "TimelineModel.h"
|
||||||
|
|
||||||
|
class EventDelegateChoice : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
Q_CLASSINFO("DefaultProperty", "delegate")
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_PROPERTY(QList<int> roleValues READ roleValues WRITE setRoleValues NOTIFY roleValuesChanged
|
||||||
|
REQUIRED FINAL)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged REQUIRED FINAL)
|
||||||
|
|
||||||
|
[[nodiscard]] QQmlComponent *delegate() const;
|
||||||
|
void setDelegate(QQmlComponent *delegate);
|
||||||
|
|
||||||
|
[[nodiscard]] QList<int> roleValues() const;
|
||||||
|
void setRoleValues(const QList<int> &value);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void delegateChanged();
|
||||||
|
void roleValuesChanged();
|
||||||
|
void changed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<int> roleValues_;
|
||||||
|
QQmlComponent *delegate_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EventDelegateChooser : public QQuickItem
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
Q_CLASSINFO("DefaultProperty", "choices")
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_PROPERTY(QQmlListProperty<EventDelegateChoice> choices READ choices CONSTANT FINAL)
|
||||||
|
Q_PROPERTY(QQuickItem *main READ main NOTIFY mainChanged FINAL)
|
||||||
|
Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL)
|
||||||
|
Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged REQUIRED FINAL)
|
||||||
|
Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL)
|
||||||
|
|
||||||
|
QQmlListProperty<EventDelegateChoice> choices();
|
||||||
|
|
||||||
|
[[nodiscard]] QQuickItem *main() const
|
||||||
|
{
|
||||||
|
return qobject_cast<QQuickItem *>(eventIncubator.object());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRoom(TimelineModel *m)
|
||||||
|
{
|
||||||
|
if (m != room_) {
|
||||||
|
room_ = m;
|
||||||
|
eventIncubator.reset(eventId_);
|
||||||
|
replyIncubator.reset(replyId);
|
||||||
|
emit roomChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[nodiscard]] TimelineModel *room() { return room_; }
|
||||||
|
|
||||||
|
void setEventId(QString idx)
|
||||||
|
{
|
||||||
|
eventId_ = idx;
|
||||||
|
emit eventIdChanged();
|
||||||
|
}
|
||||||
|
[[nodiscard]] QString eventId() const { return eventId_; }
|
||||||
|
void setReplyTo(QString id)
|
||||||
|
{
|
||||||
|
replyId = id;
|
||||||
|
emit replyToChanged();
|
||||||
|
}
|
||||||
|
[[nodiscard]] QString replyTo() const { return replyId; }
|
||||||
|
|
||||||
|
void componentComplete() override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void mainChanged();
|
||||||
|
void replyChanged();
|
||||||
|
void roomChanged();
|
||||||
|
void eventIdChanged();
|
||||||
|
void replyToChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct DelegateIncubator final : public QQmlIncubator
|
||||||
|
{
|
||||||
|
DelegateIncubator(EventDelegateChooser &parent, bool forReply)
|
||||||
|
: QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
|
||||||
|
, chooser(parent)
|
||||||
|
, forReply(forReply)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void setInitialState(QObject *object) override;
|
||||||
|
void statusChanged(QQmlIncubator::Status status) override;
|
||||||
|
|
||||||
|
void reset(QString id);
|
||||||
|
|
||||||
|
EventDelegateChooser &chooser;
|
||||||
|
bool forReply;
|
||||||
|
QString currentId;
|
||||||
|
|
||||||
|
QString instantiatedId;
|
||||||
|
int instantiatedRole = -1;
|
||||||
|
QAbstractItemModel *instantiatedModel = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVariant roleValue_;
|
||||||
|
QList<EventDelegateChoice *> choices_;
|
||||||
|
DelegateIncubator eventIncubator{*this, false};
|
||||||
|
DelegateIncubator replyIncubator{*this, true};
|
||||||
|
TimelineModel *room_{nullptr};
|
||||||
|
QString eventId_;
|
||||||
|
QString replyId;
|
||||||
|
|
||||||
|
static void appendChoice(QQmlListProperty<EventDelegateChoice> *, EventDelegateChoice *);
|
||||||
|
static qsizetype choiceCount(QQmlListProperty<EventDelegateChoice> *);
|
||||||
|
static EventDelegateChoice *choice(QQmlListProperty<EventDelegateChoice> *, qsizetype index);
|
||||||
|
static void clearChoices(QQmlListProperty<EventDelegateChoice> *);
|
||||||
|
};
|
|
@ -926,6 +926,26 @@ TimelineModel::multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineModel::multiData(const QString &id,
|
||||||
|
const QString &relatedTo,
|
||||||
|
QModelRoleDataSpan roleDataSpan) const
|
||||||
|
{
|
||||||
|
if (id.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto event = events.get(id.toStdString(), relatedTo.toStdString());
|
||||||
|
|
||||||
|
if (!event)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (QModelRoleData &roleData : roleDataSpan) {
|
||||||
|
int role = roleData.role();
|
||||||
|
|
||||||
|
roleData.setData(data(*event, role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QVariant
|
QVariant
|
||||||
TimelineModel::dataById(const QString &id, int role, const QString &relatedTo)
|
TimelineModel::dataById(const QString &id, int role, const QString &relatedTo)
|
||||||
{
|
{
|
||||||
|
|
|
@ -286,6 +286,8 @@ public:
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override;
|
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override;
|
||||||
|
void
|
||||||
|
multiData(const QString &id, const QString &relatedTo, QModelRoleDataSpan roleDataSpan) const;
|
||||||
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
|
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
|
||||||
Q_INVOKABLE QVariant dataById(const QString &id, int role, const QString &relatedTo);
|
Q_INVOKABLE QVariant dataById(const QString &id, int role, const QString &relatedTo);
|
||||||
Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const
|
Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const
|
||||||
|
|
Loading…
Reference in a new issue