File indexing completed on 2024-04-28 04:41:51

0001 /***************************************************************************
0002  *   Copyright (C) 2018 by Emmanuel Lepage Vallee                          *
0003  *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@kde.org>             *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 3 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0017  **************************************************************************/
0018 #include "sizehintproxymodel.h"
0019 
0020 // KQuickItemViews
0021 #include "adapters/contextadapter.h"
0022 #include "contextadapterfactory.h"
0023 
0024 // Qt
0025 #include <QJSValue>
0026 #include <QDebug>
0027 #include <QQmlEngine>
0028 #include <QQmlContext>
0029 #include <QQmlExpression>
0030 #include <QtCore/QSizeF>
0031 
0032 class SizeHintProxyModelPrivate : public QObject
0033 {
0034 public:
0035     QHash<QByteArray, int> m_hInvertedRoleNames;
0036     QStringList            m_lInvalidationRoles;
0037     QJSValue               m_ConstantsCallback;
0038     QJSValue               m_Contants;
0039     QQmlScriptString       m_WidthScript;
0040     QQmlScriptString       m_HeightScript;
0041     bool                   m_ReloadContants    { false };
0042     QQmlExpression*        m_pWidthExpression  {nullptr};
0043     QQmlExpression*        m_pHeightExpression {nullptr};
0044     QQmlContext           *m_pQmlContext       {nullptr};
0045     ContextAdapterFactory        *m_pContextAdapterFactory   {nullptr};
0046     ContextAdapter        *m_pContextAdapter   {nullptr};
0047     QVariantMap            m_hCache            {       };
0048 
0049 
0050     // Helpers
0051     int   roleIndex(const QString& name);
0052     void  reloadContants();
0053     void  reloadContext(QAbstractItemModel *m);
0054     qreal evaluateForIndex(QQmlExpression* expr, const QModelIndex& idx);
0055 
0056     SizeHintProxyModel* q_ptr;
0057 
0058 
0059 public Q_SLOTS:
0060     void slotDataChanged(const QModelIndex& tl, const QModelIndex& bl, const QVector<int>& roles);
0061 };
0062 
0063 SizeHintProxyModel::SizeHintProxyModel(QObject* parent) : QIdentityProxyModel(parent),
0064     d_ptr(new SizeHintProxyModelPrivate())
0065 {
0066     d_ptr->q_ptr = this;
0067 }
0068 
0069 SizeHintProxyModel::~SizeHintProxyModel()
0070 {
0071     delete d_ptr;
0072 }
0073 
0074 void SizeHintProxyModel::setSourceModel(QAbstractItemModel *newSourceModel)
0075 {
0076     if (newSourceModel == sourceModel())
0077         return;
0078 
0079     d_ptr->reloadContext(newSourceModel);
0080     d_ptr->m_ReloadContants = true;
0081     d_ptr->m_hInvertedRoleNames.clear();
0082     QIdentityProxyModel::setSourceModel(newSourceModel);
0083     d_ptr->reloadContants();
0084     beginResetModel();
0085     endResetModel();
0086 }
0087 
0088 QJSValue SizeHintProxyModel::constants() const
0089 {
0090     return d_ptr->m_ConstantsCallback;
0091 }
0092 
0093 void SizeHintProxyModel::setConstants(const QJSValue& value)
0094 {
0095     d_ptr->m_ReloadContants    = true;
0096     d_ptr->m_ConstantsCallback = value;
0097     d_ptr->reloadContants();
0098 }
0099 
0100 int SizeHintProxyModelPrivate::roleIndex(const QString& name)
0101 {
0102     if (!q_ptr->sourceModel())
0103         return -1;
0104 
0105     const QByteArray n = name.toLatin1();
0106 
0107     if (m_hInvertedRoleNames.isEmpty()) {
0108         const QHash<int, QByteArray> roles = q_ptr->sourceModel()->roleNames();
0109         for (auto i = roles.constBegin(); i != roles.constEnd(); i++)
0110             m_hInvertedRoleNames.insert(i.value(), i.key());
0111     }
0112 
0113     return m_hInvertedRoleNames[n];
0114 }
0115 
0116 void SizeHintProxyModel::invalidateConstants()
0117 {
0118     d_ptr->m_ReloadContants = true;
0119     d_ptr->m_hCache.clear();
0120 }
0121 
0122 QVariant SizeHintProxyModel::getRoleValue(const QModelIndex& idx, const QString& roleName) const
0123 {
0124     return idx.data(d_ptr->roleIndex(roleName));
0125 }
0126 
0127 QStringList SizeHintProxyModel::invalidationRoles() const
0128 {
0129     return d_ptr->m_lInvalidationRoles;
0130 }
0131 
0132 void SizeHintProxyModel::setInvalidationRoles(const QStringList& l)
0133 {
0134     d_ptr->m_lInvalidationRoles = l;
0135 }
0136 
0137 void SizeHintProxyModelPrivate::slotDataChanged(const QModelIndex& tl, const QModelIndex& bl, const QVector<int>& roles)
0138 {
0139     Q_UNUSED(tl)
0140     Q_UNUSED(bl)
0141     Q_UNUSED(roles)
0142     //TODO check m_lInvalidationRoles and add a sync interface with the view
0143 }
0144 
0145 void SizeHintProxyModelPrivate::reloadContext(QAbstractItemModel *m)
0146 {
0147     if (m_pContextAdapter)
0148         delete m_pContextAdapter;
0149 
0150     if (m_pQmlContext) {
0151         delete m_pQmlContext;
0152         m_pQmlContext = nullptr;
0153     }
0154 
0155     if (m_pContextAdapterFactory)
0156         delete m_pContextAdapterFactory;
0157 
0158     m_pContextAdapterFactory = new ContextAdapterFactory();
0159     m_pContextAdapterFactory->setModel(m);
0160 
0161     QQmlContext* qmlCtx = QQmlEngine::contextForObject(q_ptr);
0162     Q_ASSERT(qmlCtx && qmlCtx->engine());
0163 
0164     m_pContextAdapter = m_pContextAdapterFactory->createAdapter(qmlCtx);
0165 
0166     m_pContextAdapter->setCacheEnabled(false);
0167 }
0168 
0169 void SizeHintProxyModelPrivate::reloadContants()
0170 {
0171     // Properties are initialized in a random order, this cannot be set before
0172     // the model.
0173     if (!q_ptr->sourceModel())
0174         return;
0175 
0176     if (!m_ReloadContants)
0177         return;
0178 
0179     // Allow both functions and direct values
0180     m_Contants = m_ConstantsCallback.isCallable() ?
0181         m_ConstantsCallback.call() : m_ConstantsCallback;
0182 
0183     const auto map = m_Contants.toVariant().toMap();
0184 
0185     // Replace the context because it's not allowed to write to internal
0186     // function contexts.
0187     if (!m_pQmlContext) {
0188         QQmlContext* qmlCtx = QQmlEngine::contextForObject(q_ptr);
0189         Q_ASSERT(qmlCtx && qmlCtx->engine());
0190         m_pQmlContext = m_pContextAdapter->context();
0191         Q_ASSERT(m_pQmlContext);
0192         QQmlEngine::setContextForObject(q_ptr, m_pQmlContext);
0193     }
0194 
0195     // Set them as context properties directly, no fancy QMetaType here.
0196     for (auto i = map.constBegin(); i != map.constEnd(); i++) {
0197         m_pQmlContext->setContextProperty(i.key(), i.value());
0198         q_ptr->setProperty(i.key().toLatin1(), i.value());
0199     }
0200 
0201     m_pHeightExpression = new QQmlExpression(
0202         m_HeightScript,
0203         m_pQmlContext,
0204         this
0205     );
0206 
0207     m_pWidthExpression = new QQmlExpression(
0208         m_WidthScript,
0209         m_pQmlContext,
0210         this
0211     );
0212 
0213     m_ReloadContants = false;
0214 }
0215 
0216 qreal SizeHintProxyModelPrivate::evaluateForIndex(QQmlExpression* expr, const QModelIndex& idx)
0217 {
0218     reloadContants();
0219 
0220     bool valueIsUndefined = false;
0221 
0222     m_pContextAdapter->setModelIndex(idx);
0223     const QVariant var = expr->evaluate(&valueIsUndefined);//ret.toVariant();
0224 
0225     // There was an error in the expression
0226     if (expr->hasError()) {
0227         qWarning() << expr->error();
0228         return {};
0229     }
0230     if (valueIsUndefined && !var.isValid()) {
0231         qWarning() << "The sizeHint expression contains an undefined variable" << idx;
0232         return {};
0233     }
0234     else if (!var.isValid()) {
0235         qWarning() << "The sizeHint expression had an error" << idx;
0236         return {};
0237     }
0238 
0239     static QByteArray n("int"), n2("double");
0240 
0241     Q_ASSERT(QByteArray(var.typeName()) == n || QByteArray(var.typeName()) == n2);
0242 
0243     return var.toReal();
0244 }
0245 
0246 QSizeF SizeHintProxyModel::sizeHintForIndex(const QModelIndex& idx)
0247 {
0248     const qreal w = d_ptr->m_pWidthExpression ?
0249         d_ptr->evaluateForIndex(d_ptr->m_pWidthExpression , idx) : 0;
0250     const qreal h = d_ptr->m_pHeightExpression ?
0251         d_ptr->evaluateForIndex(d_ptr->m_pHeightExpression, idx) : 0;
0252     return QSizeF {w, h};
0253 }
0254 
0255 QQmlScriptString SizeHintProxyModel::widthHint() const
0256 {
0257     return d_ptr->m_WidthScript;
0258 }
0259 
0260 void SizeHintProxyModel::setWidthHint(const QQmlScriptString& value)
0261 {
0262     if (d_ptr->m_pWidthExpression) {
0263         delete d_ptr->m_pWidthExpression;
0264         d_ptr->m_pWidthExpression = nullptr;
0265     }
0266     d_ptr->m_WidthScript = value;
0267     d_ptr->m_ReloadContants = true;
0268 }
0269 
0270 QQmlScriptString SizeHintProxyModel::heightHint() const
0271 {
0272     return d_ptr->m_HeightScript;
0273 }
0274 
0275 void SizeHintProxyModel::setHeightHint(const QQmlScriptString& value)
0276 {
0277     if (d_ptr->m_pHeightExpression) {
0278         delete d_ptr->m_pHeightExpression;
0279         d_ptr->m_pHeightExpression = nullptr;
0280     }
0281 
0282     d_ptr->m_HeightScript = value;
0283     d_ptr->m_ReloadContants = true;
0284 }
0285