nheko/src/timeline/EventDelegateChooser.cpp

373 lines
12 KiB
C++

// 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>
#include <ranges>
// 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(eventId_);
replyIncubator.reset(replyId);
// eventIncubator.forceCompletion();
}
void
EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
{
auto item = qobject_cast<QQuickItem *>(obj);
if (!item)
return;
item->setParentItem(&chooser);
item->setParent(&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);
Qt::beginPropertyUpdateGroup();
auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
qmlAttachedPropertiesObject<EventDelegateChooser>(obj));
Q_ASSERT(attached != nullptr);
attached->setIsReply(this->forReply || chooser.limitAsReply_);
for (const auto &role : roles) {
const auto &roleName = roleNames[role.role()];
// nhlog::ui()->critical("Setting role {}, {} to {}",
// role.role(),
// roleName.toStdString(),
// role.data().toString().toStdString());
// nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[role.role()]).name());
mo->property(roleToPropIdx[role.role()]).write(obj, role.data());
if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end())
QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req);
}
Qt::endPropertyUpdateGroup();
// setInitialProperties(rolesToSet);
auto update = [this, obj, roleToPropIdx = std::move(roleToPropIdx)](
const QList<int> &changedRoles, TimelineModel *room) {
if (!room)
return;
if (changedRoles.empty() || changedRoles.contains(TimelineModel::Roles::Type)) {
int type = room
->dataById(currentId,
TimelineModel::Roles::Type,
forReply ? chooser.eventId_ : QString())
.toInt();
if (type != oldType) {
// nhlog::ui()->debug("Type changed!");
reset(currentId);
return;
}
}
std::vector<QModelRoleData> rolesToRequest;
if (changedRoles.empty()) {
for (const auto role :
std::ranges::subrange(roleToPropIdx.keyBegin(), roleToPropIdx.keyEnd()))
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();
room->multiData(currentId, forReply ? chooser.eventId_ : QString(), rolesToRequest);
Qt::beginPropertyUpdateGroup();
for (const auto &role : rolesToRequest) {
mo->property(roleToPropIdx[role.role()]).write(obj, role.data());
}
Qt::endPropertyUpdateGroup();
};
if (!forReply) {
auto row = chooser.room_->idToIndex(currentId);
auto connection = connect(
chooser.room_,
&QAbstractItemModel::dataChanged,
obj,
[row, update, room = chooser.room_](const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QList<int> &changedRoles) {
if (row < topLeft.row() || row > bottomRight.row())
return;
update(changedRoles, room);
},
Qt::QueuedConnection);
connect(
&this->chooser,
&EventDelegateChooser::destroyed,
obj,
[connection]() { QObject::disconnect(connection); },
Qt::SingleShotConnection);
connect(
&this->chooser,
&EventDelegateChooser::roomChanged,
obj,
[connection]() { QObject::disconnect(connection); },
Qt::SingleShotConnection);
}
}
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();
this->oldType = role;
for (const auto choice : std::as_const(chooser.choices_)) {
const auto &choiceValue = choice->roleValues();
if (choiceValue.contains(role) || choiceValue.empty()) {
// nhlog::ui()->debug(
// "Instantiating type: {}, c {}", (int)role, choiceValue.contains(role));
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::CppOwnership);
// connect(child, &QQuickItem::parentChanged, child, [child](QQuickItem *) {
// // QTBUG-115687
// if (child->flags().testFlag(QQuickItem::ItemObservesViewport)) {
// nhlog::ui()->critical("SETTING OBSERVES VIEWPORT");
// // Re-trigger the parent traversal to get subtreeTransformChangedEnabled turned
// on child->setFlag(QQuickItem::ItemObservesViewport);
// }
// });
if (forReply)
emit chooser.replyChanged();
else
emit chooser.mainChanged();
chooser.polish();
} else if (status == QQmlIncubator::Error) {
auto errors_ = errors();
for (const auto &e : std::as_const(errors_))
nhlog::ui()->error("Error instantiating delegate: {}", e.toString().toStdString());
}
}
void
EventDelegateChooser::updatePolish()
{
auto mainChild = qobject_cast<QQuickItem *>(eventIncubator.object());
auto replyChild = qobject_cast<QQuickItem *>(replyIncubator.object());
// nhlog::ui()->trace("POLISHING {}", (void *)this);
auto layoutItem = [this](QQuickItem *item, int inset) {
if (item) {
QObject::disconnect(item, &QQuickItem::implicitWidthChanged, this, &QQuickItem::polish);
auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
qmlAttachedPropertiesObject<EventDelegateChooser>(item));
Q_ASSERT(attached != nullptr);
int maxWidth = maxWidth_ - inset;
// in theory we could also reset the width, but that doesn't seem to work nicely for
// text areas because of how they cache it.
if (attached->maxWidth() > 0)
item->setWidth(attached->maxWidth());
else
item->setWidth(maxWidth);
item->ensurePolished();
auto width = item->implicitWidth();
if (width < 1 || width > maxWidth)
width = maxWidth;
if (attached->maxWidth() > 0 && width > attached->maxWidth())
width = attached->maxWidth();
if (attached->keepAspectRatio()) {
auto height = width * attached->aspectRatio();
if (attached->maxHeight() && height > attached->maxHeight()) {
height = attached->maxHeight();
width = height / attached->aspectRatio();
}
item->setHeight(height);
}
item->setWidth(width);
item->ensurePolished();
QObject::connect(item, &QQuickItem::implicitWidthChanged, this, &QQuickItem::polish);
}
};
layoutItem(mainChild, mainInset_);
layoutItem(replyChild, replyInset_);
}
void
EventDelegateChooserAttachedType::polishChooser()
{
auto p = parent();
if (p) {
auto chooser = qobject_cast<EventDelegateChooser *>(p->parent());
if (chooser) {
chooser->polish();
}
}
}
#include "moc_EventDelegateChooser.cpp"