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