File indexing completed on 2024-04-14 04:36:55

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 "qmodelindexbinder.h"
0019 
0020 // Qt
0021 #include <QQmlEngine>
0022 #include <QQmlContext>
0023 #include <QtCore/QTimer>
0024 
0025 // KQuickItemViews
0026 #include "qmodelindexwatcher.h"
0027 #include <adapters/contextadapter.h>
0028 
0029 class QModelIndexBinderPrivate : public QObject
0030 {
0031     Q_OBJECT
0032 public:
0033     enum class Mode {
0034         CONTAINER,
0035         ATTACHED,
0036     };
0037 
0038     QByteArray          m_Role     {       };
0039     QByteArray          m_Prop     {       };
0040     QObject            *m_pContent {nullptr};
0041     int                 m_Delay    {   0   };
0042     QTimer             *m_pTimer   {nullptr};
0043     bool                m_AutoSave { true  };
0044     QQmlContext        *m_pCTX     {nullptr};
0045     ContextAdapter     *m_pAdapter {nullptr};
0046     QModelIndexWatcher *m_pWatcher {nullptr};
0047     bool                m_isBinded { false };
0048     Mode                m_Mode;
0049 
0050     // Helper
0051     void bind();
0052 
0053     QModelIndexBinder *q_ptr;
0054 
0055 public Q_SLOTS:
0056     void loadWatcher();
0057     void slotModelPropChanged();
0058     void slotObjectPropChanged();
0059 };
0060 
0061 QModelIndexBinder::QModelIndexBinder(QQuickItem *parent) :
0062     QQuickItem(parent), d_ptr(new QModelIndexBinderPrivate())
0063 {
0064     d_ptr->q_ptr = this;
0065     d_ptr->m_Mode = QModelIndexBinderPrivate::Mode::CONTAINER;
0066 
0067     //TODO actually track the context, it could be reparented
0068     QTimer::singleShot(0, d_ptr, SLOT(loadWatcher()));
0069 }
0070 
0071 QModelIndexBinder::QModelIndexBinder(QObject *parent) :
0072     QQuickItem(nullptr), d_ptr(new QModelIndexBinderPrivate())
0073 {
0074     QObject::setParent(parent);
0075 
0076     d_ptr->q_ptr = this;
0077     d_ptr->m_Mode = QModelIndexBinderPrivate::Mode::ATTACHED;
0078 
0079     //TODO actually track the context, it could be reparented
0080     QTimer::singleShot(0, d_ptr, SLOT(loadWatcher()));
0081     _setObject(parent);
0082 }
0083 
0084 
0085 QModelIndexBinder::~QModelIndexBinder()
0086 {
0087     delete d_ptr;
0088 }
0089 
0090 QString QModelIndexBinder::modelRole() const
0091 {
0092     return d_ptr->m_Role;
0093 }
0094 
0095 void QModelIndexBinder::setModelRole(const QString& role)
0096 {
0097     d_ptr->m_Role = role.toLatin1();
0098     emit changed();
0099     d_ptr->bind();
0100 }
0101 
0102 QString QModelIndexBinder::objectProperty() const
0103 {
0104     return d_ptr->m_Prop;
0105 }
0106 
0107 void QModelIndexBinder::setObjectProperty(const QString& op)
0108 {
0109     d_ptr->m_Prop = op.toLatin1();
0110 }
0111 
0112 QObject *QModelIndexBinder::_object() const
0113 {
0114     return d_ptr->m_pContent;
0115 }
0116 
0117 void QModelIndexBinder::_setObject(QObject *o)
0118 {
0119     // If the object is a QQuickItem, add it to the view
0120     if (d_ptr->m_Mode == QModelIndexBinderPrivate::Mode::CONTAINER) {
0121         if (auto w = qobject_cast<QQuickItem*>(o)) {
0122             w->setParentItem(this);
0123         }
0124     }
0125 
0126     d_ptr->m_pContent = o;
0127     emit changed();
0128     d_ptr->bind();
0129 }
0130 
0131 int QModelIndexBinder::delay() const
0132 {
0133     return d_ptr->m_Delay;
0134 }
0135 
0136 void QModelIndexBinder::setDelay(int d)
0137 {
0138     d_ptr->m_Delay = d;
0139     emit changed();
0140 }
0141 
0142 bool QModelIndexBinder::autoSave() const
0143 {
0144     return d_ptr->m_AutoSave;
0145 }
0146 
0147 void QModelIndexBinder::setAutoSave(bool v)
0148 {
0149     d_ptr->m_AutoSave = v;
0150     emit changed();
0151 }
0152 
0153 bool QModelIndexBinder::isSynchronized() const
0154 {
0155     //TODO
0156     return true;
0157 }
0158 
0159 void QModelIndexBinder::reset() const
0160 {
0161     //TODO
0162 }
0163 
0164 bool QModelIndexBinder::applyNow() const
0165 {
0166     //TODO
0167     return true;
0168 }
0169 
0170 void QModelIndexBinderPrivate::loadWatcher()
0171 {
0172     if (m_pWatcher)
0173         return;
0174 
0175     // Container mode
0176     m_pCTX = QQmlEngine::contextForObject(q_ptr);
0177 
0178     // Attached mode
0179     if ((!m_pCTX) || m_pContent)
0180         m_pCTX = QQmlEngine::contextForObject(m_pContent);
0181 
0182     Q_ASSERT(m_pCTX);
0183 
0184     if (!m_pCTX)
0185         return;
0186 
0187     auto v = m_pCTX->contextProperty("_modelIndexWatcher");
0188     auto c = m_pCTX->contextProperty("_contextAdapter");
0189 
0190     m_pAdapter = qvariant_cast<ContextAdapter*>(c);
0191     m_pWatcher = qobject_cast<QModelIndexWatcher*>(
0192         qvariant_cast<QObject*>(v)
0193     );
0194 
0195     Q_ASSERT(m_pAdapter);
0196     Q_ASSERT(m_pWatcher);
0197 
0198     bind();
0199 }
0200 
0201 void QModelIndexBinderPrivate::bind()
0202 {
0203     if (m_isBinded || m_Role.isEmpty() || m_Prop.isEmpty() || (!m_pContent) || (!m_pWatcher) || (!m_pAdapter))
0204         return;
0205 
0206     const auto co = m_pAdapter->contextObject();
0207 
0208     // Find the properties on both side
0209     const int objPropId  = m_pContent->metaObject()->indexOfProperty(m_Prop);
0210     const int rolePropId = co->metaObject()->indexOfProperty(m_Role);
0211 
0212     if (rolePropId == -1)
0213         qWarning() << "Role" << m_Role << "not found";
0214 
0215     if (objPropId == -1)
0216         qWarning() << "Property" << m_Prop << "not found";
0217 
0218     Q_ASSERT(objPropId  != -1 && rolePropId != -1);
0219 
0220     auto metaProp = m_pContent->metaObject()->property(objPropId);
0221     auto metaRole = co->metaObject()->property(rolePropId);
0222 
0223     // Connect to the metaSlots
0224     auto metaSlotProp = metaObject()->method(metaObject()->indexOfMethod("slotObjectPropChanged()"));
0225     auto metaSlotRole = metaObject()->method(metaObject()->indexOfMethod("slotModelPropChanged()"));
0226 
0227     // Set the initial value before connecting
0228     slotModelPropChanged();
0229 
0230     connect(m_pContent, metaProp.notifySignal(), this, metaSlotProp);
0231     connect(co        , metaRole.notifySignal(), this, metaSlotRole);
0232 }
0233 
0234 void QModelIndexBinderPrivate::slotModelPropChanged()
0235 {
0236     const auto role = m_pAdapter->contextObject()->property(m_Role);
0237 
0238     //HACK this is a bug in ContextAdapterFactory
0239     if (!role.isValid())
0240         return;
0241 
0242     const auto prop = m_pContent->property(m_Prop);
0243 
0244     // Some widgets may not try to detect if the value **really** changes and
0245     // emit signals anyway.
0246     if (role != m_pContent->property(m_Prop))
0247         m_pContent->setProperty(m_Prop, role);
0248 }
0249 
0250 void QModelIndexBinderPrivate::slotObjectPropChanged()
0251 {
0252     const auto role = m_pAdapter->contextObject()->property(m_Role);
0253 
0254     Q_ASSERT(role.isValid());
0255     //HACK this is a bug in ContextAdapterFactory
0256     if (!role.isValid())
0257         return;
0258 
0259     const auto prop = m_pContent->property(m_Prop);
0260 
0261     if (role != prop)
0262         m_pAdapter->contextObject()->setProperty(m_Role, prop); //FIXME fix the model::setData support
0263 }
0264 
0265 QModelIndexBinder *QModelIndexBinder::qmlAttachedProperties(QObject *object)
0266 {
0267     return new QModelIndexBinder(object);
0268 }
0269 
0270 #include <qmodelindexbinder.moc>