summary refs log tree commit diff
path: root/src/timeline/EventDelegateChooser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/timeline/EventDelegateChooser.cpp')
-rw-r--r--src/timeline/EventDelegateChooser.cpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/src/timeline/EventDelegateChooser.cpp b/src/timeline/EventDelegateChooser.cpp
new file mode 100644
index 00000000..4367bb9c
--- /dev/null
+++ b/src/timeline/EventDelegateChooser.cpp
@@ -0,0 +1,356 @@
+// 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);
+
+    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) {
+          if (changedRoles.empty() || changedRoles.contains(TimelineModel::Roles::Type)) {
+              int type = chooser.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();
+          chooser.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](const QModelIndex &topLeft,
+                        const QModelIndex &bottomRight,
+                        const QList<int> &changedRoles) {
+              if (row < topLeft.row() || row > bottomRight.row())
+                  return;
+
+              update(changedRoles);
+          },
+          Qt::QueuedConnection);
+        connect(&this->chooser, &EventDelegateChooser::destroyed, obj, [connection]() {
+            QObject::disconnect(connection);
+        });
+    }
+}
+
+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 : qAsConst(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::JavaScriptOwnership);
+
+        // 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 : qAsConst(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) {
+            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();
+        }
+    };
+
+    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();
+        }
+    }
+}