File indexing completed on 2024-05-05 04:43:04

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 "geostrategyselector_p.h"
0019 
0020 // Qt
0021 #include <QtCore/QAbstractItemModel>
0022 #include <QtCore/QDebug>
0023 
0024 // KQuickItemViews
0025 #include <viewport.h>
0026 #include <proxies/sizehintproxymodel.h>
0027 #include <strategies/justintime.h>
0028 #include <strategies/proxy.h>
0029 #include <strategies/role.h>
0030 #include <strategies/delegate.h>
0031 #include <strategies/aheadoftime.h>
0032 #include <strategies/uniform.h>
0033 
0034 
0035 class GeoStrategySelectorPrivate : public QObject
0036 {
0037 public:
0038     explicit GeoStrategySelectorPrivate(GeoStrategySelector *q) : QObject(q), q_ptr(q) {}
0039 
0040     enum class BuiltInStrategies {
0041         AOT     , /*!< Load everything ahead of time, doesn't scale but very reliable */
0042         JIT     , /*!< Do not try to compute the total size, scrollbars wont work     */
0043         UNIFORM , /*!< Assume all elements have the same size, scales well when true  */
0044         PROXY   , /*!< Use a QSizeHintProxyModel, require work by all developers      */
0045         ROLE    , /*!< Use one of the QAbstractItemModel role as size                 */
0046         DELEGATE, /*!< Assume the view re-implemented ::sizeHint is correct           */
0047     };
0048 
0049     BuiltInStrategies m_CurrentStrategy { BuiltInStrategies::JIT };
0050 
0051     GeometryAdapter    *m_A      {nullptr};
0052     QAbstractItemModel *m_pModel {nullptr};
0053     bool                m_Auto   { true  };
0054 
0055     uint m_Features {GeoStrategySelector::Features::NONE};
0056 
0057     bool checkHasContent();
0058     bool checkHasRole   ();
0059     bool checkProxyModel();
0060 
0061     void optimize();
0062 
0063     void replaceStrategy(BuiltInStrategies s);
0064 
0065     GeoStrategySelector *q_ptr;
0066 
0067 public Q_SLOTS:
0068     void slotRowsInserted();
0069 };
0070 
0071 GeoStrategySelector::GeoStrategySelector(Viewport *parent) : GeometryAdapter(parent),
0072     d_ptr(new GeoStrategySelectorPrivate(this))
0073 {
0074     d_ptr->m_A = new GeometryStrategies::JustInTime(parent);
0075 }
0076 
0077 GeoStrategySelector::~GeoStrategySelector()
0078 {}
0079 
0080 QSizeF GeoStrategySelector::sizeHint(const QModelIndex& i, AbstractItemAdapter *a) const
0081 {
0082     return d_ptr->m_A ?
0083         d_ptr->m_A->sizeHint(i, a) : GeometryAdapter::sizeHint(i, a);
0084 }
0085 
0086 QPointF GeoStrategySelector::positionHint(const QModelIndex& i, AbstractItemAdapter *a) const
0087 {
0088     if (d_ptr->m_A && d_ptr->m_A->capabilities() & Capabilities::HAS_POSITION_HINTS)
0089         return d_ptr->m_A->positionHint(i, a);
0090 
0091     //TODO implement the fallback
0092     return GeometryAdapter::positionHint(i, a);
0093 }
0094 
0095 int GeoStrategySelector::capabilities() const
0096 {
0097     return d_ptr->m_A ?
0098         d_ptr->m_A->capabilities() : GeometryAdapter::capabilities();
0099 }
0100 
0101 bool GeoStrategySelector::isSizeForced() const
0102 {
0103     return d_ptr->m_A ?
0104         d_ptr->m_A->isSizeForced() : GeometryAdapter::isSizeForced();
0105 }
0106 
0107 void GeoStrategySelector::setSizeForced(bool f)
0108 {
0109     GeometryAdapter::setSizeForced(f);
0110 
0111     if (d_ptr->m_A)
0112         d_ptr->m_A->setSizeForced(f);
0113 }
0114 
0115 void GeoStrategySelector::setModel(QAbstractItemModel *m)
0116 {
0117     if (m == d_ptr->m_pModel)
0118         return;
0119 
0120     d_ptr->m_pModel = m;
0121 
0122     static constexpr auto toClean = Features::HAS_MODEL
0123         | Features::HAS_SHP_MODEL
0124         | Features::HAS_MODEL_CONTENT
0125         | Features::HAS_SIZE_ROLE;
0126 
0127     d_ptr->m_Features = d_ptr->m_Features & (~toClean);
0128 
0129     // Reload the model-dependent features
0130     d_ptr->m_Features |= m ?
0131         Features::HAS_MODEL : Features::NONE;
0132     d_ptr->m_Features |= d_ptr->checkHasContent() ?
0133         Features::HAS_MODEL_CONTENT : Features::NONE;
0134     d_ptr->m_Features |= d_ptr->checkHasRole() ?
0135         Features::HAS_SIZE_ROLE : Features::NONE;
0136     d_ptr->m_Features |= d_ptr->checkProxyModel() ?
0137         Features::HAS_SHP_MODEL : Features::NONE;
0138 
0139     d_ptr->optimize();
0140 }
0141 
0142 void GeoStrategySelector::setHasScrollbar(bool v)
0143 {
0144     d_ptr->m_Features = d_ptr->m_Features & (~Features::HAS_SCROLLBAR);
0145     d_ptr->m_Features |= v ? Features::HAS_SCROLLBAR : Features::NONE;
0146 }
0147 
0148 void GeoStrategySelectorPrivate::slotRowsInserted()
0149 {
0150     checkHasRole();
0151 
0152     // Now that the introspection is done, there is no further need for this
0153     disconnect(m_pModel, &QAbstractItemModel::rowsInserted,
0154         this, &GeoStrategySelectorPrivate::slotRowsInserted);
0155 }
0156 
0157 bool GeoStrategySelectorPrivate::checkHasContent()
0158 {
0159     if (!m_pModel)
0160         return false;
0161 
0162     return m_pModel->index(0, 0).isValid();
0163 }
0164 
0165 bool GeoStrategySelectorPrivate::checkHasRole()
0166 {
0167     if (!m_pModel)
0168         return false;
0169 
0170     const QModelIndex i = m_pModel->index(0, 0);
0171 
0172     return i.data(Qt::SizeHintRole).isValid();
0173 }
0174 
0175 bool GeoStrategySelectorPrivate::checkProxyModel()
0176 {
0177     return m_pModel && m_pModel->metaObject()->inherits(
0178         &SizeHintProxyModel::staticMetaObject
0179     );
0180 }
0181 
0182 void GeoStrategySelectorPrivate::optimize()
0183 {
0184     if (!m_Auto)
0185         return;
0186 
0187     // Here will eventually reside the main optimization algorithm. For now
0188     // just choose between the JustInTime and Proxy adapters, the only ones
0189     // fully implemented.
0190 
0191     BuiltInStrategies next = m_CurrentStrategy;
0192 
0193     if (m_Features & GeoStrategySelector::Features::HAS_SHP_MODEL) {
0194         next = BuiltInStrategies::PROXY;
0195     }
0196     else {
0197         next = BuiltInStrategies::JIT;
0198     }
0199 
0200     if (next != m_CurrentStrategy) {
0201         replaceStrategy(next);
0202     }
0203 }
0204 
0205 void GeoStrategySelectorPrivate::replaceStrategy(BuiltInStrategies s)
0206 {
0207     delete m_A;
0208     m_A = nullptr;
0209 
0210     switch(s) {
0211         case BuiltInStrategies::AOT:
0212             //TODO this requires ViewportAdapter to work
0213             break;
0214         case BuiltInStrategies::JIT:
0215             m_A = new GeometryStrategies::JustInTime(q_ptr->viewport());
0216             break;
0217         case BuiltInStrategies::UNIFORM:
0218             m_A = new GeometryStrategies::Uniform(q_ptr->viewport());
0219             break;
0220         case BuiltInStrategies::PROXY:
0221             m_A = new GeometryStrategies::Proxy(q_ptr->viewport());
0222             break;
0223         case BuiltInStrategies::ROLE:
0224             m_A = new GeometryStrategies::Role(q_ptr->viewport());
0225             break;
0226         case BuiltInStrategies::DELEGATE:
0227             m_A = new GeometryStrategies::Delegate(q_ptr->viewport());
0228             break;
0229     }
0230 
0231     emit q_ptr->dismissResult();
0232 }
0233 
0234 bool GeoStrategySelector::isAutomatic() const
0235 {
0236     return d_ptr->m_Auto;
0237 }
0238 
0239 GeometryAdapter *GeoStrategySelector::currentAdapter() const
0240 {
0241     return d_ptr->m_A;
0242 }
0243 
0244 void GeoStrategySelector::setCurrentAdapter(GeometryAdapter *a)
0245 {
0246     if (a == d_ptr->m_A)
0247         return;
0248 
0249     if (d_ptr->m_Auto || a->parent() == viewport())
0250         delete d_ptr->m_A;
0251 
0252     d_ptr->m_Auto = !a;
0253 
0254     d_ptr->m_A = a;
0255 
0256     if (d_ptr->m_Auto)
0257         d_ptr->optimize();
0258 
0259     // Mitigate a race condition when setSizeForced is called before setCurrentAdapter
0260     // due to the undefined property order in QML.
0261     if (d_ptr->m_A && GeometryAdapter::isSizeForced())
0262         d_ptr->m_A->setSizeForced(true);
0263 
0264     Q_ASSERT(d_ptr->m_A);
0265 
0266     emit dismissResult();
0267 }