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

0001 /***************************************************************************
0002  *   Copyright (C) 2017 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 "selectionadapter.h"
0019 
0020 // KQuickItemViews
0021 #include "private/selectionadapter_p.h"
0022 #include "private/statetracker/viewitem_p.h"
0023 #include "private/indexmetadata_p.h"
0024 #include "viewbase.h"
0025 #include "viewport.h"
0026 #include "private/viewport_p.h"
0027 #include "abstractitemadapter.h"
0028 #include "extensions/contextextension.h"
0029 
0030 // Qt
0031 #include <QtCore/QItemSelectionModel>
0032 #include <QtCore/QSharedPointer>
0033 #include <QQuickItem>
0034 #include <QQmlEngine>
0035 #include <QQmlContext>
0036 
0037 using ItemRef = QPair<QWeakPointer<AbstractItemAdapter::SelectionLocker>, AbstractItemAdapter*>;
0038 
0039 /// Add the same property as the QtQuick.ListView
0040 class ItemSelectionGroup final : public ContextExtension
0041 {
0042 public:
0043     virtual ~ItemSelectionGroup() {}
0044 
0045     SelectionAdapterPrivate* d_ptr;
0046 
0047     virtual QVector<QByteArray>& propertyNames() const override;
0048     virtual QVariant getProperty(AbstractItemAdapter* item, uint id, const QModelIndex& index) const override;
0049 };
0050 
0051 class SelectionAdapterPrivate : public QObject
0052 {
0053     friend class SelectionAdapterSyncInterface;
0054     Q_OBJECT
0055 public:
0056     QSharedPointer<QItemSelectionModel> m_pSelectionModel   {nullptr};
0057     QAbstractItemModel*                 m_pModel            {nullptr};
0058     QQuickItem*                         m_pSelectedItem     {nullptr};
0059     QQmlComponent*                      m_pHighlight        {nullptr};
0060     ViewBase*                           m_pView             {nullptr};
0061     Viewport*                           m_pViewport         {nullptr};
0062     mutable ItemSelectionGroup*         m_pContextExtension {nullptr};
0063     ItemRef                             m_pSelectedViewItem;
0064 
0065     SelectionAdapter* q_ptr;
0066 
0067 public Q_SLOTS:
0068     void slotCurrentIndexChanged(const QModelIndex& idx);
0069     void slotSelectionModelChanged();
0070     void slotModelDestroyed();
0071 };
0072 
0073 SelectionAdapter::SelectionAdapter(QObject* parent) : QObject(parent),
0074     s_ptr(new SelectionAdapterSyncInterface()),
0075     d_ptr(new SelectionAdapterPrivate())
0076 {
0077     s_ptr->q_ptr = this;
0078     d_ptr->q_ptr = this;
0079 }
0080 
0081 SelectionAdapter::~SelectionAdapter()
0082 {
0083     //
0084 }
0085 
0086 QSharedPointer<QItemSelectionModel> SelectionAdapter::selectionModel() const
0087 {
0088     if (s_ptr->model() && !d_ptr->m_pSelectionModel) {
0089         auto sm = new QItemSelectionModel(s_ptr->model());
0090         d_ptr->m_pSelectionModel = QSharedPointer<QItemSelectionModel>(sm);
0091         d_ptr->slotSelectionModelChanged();
0092         Q_EMIT selectionModelChanged();
0093         connect(d_ptr->m_pSelectionModel.data(), &QItemSelectionModel::currentChanged,
0094             d_ptr, &SelectionAdapterPrivate::slotCurrentIndexChanged);
0095     }
0096 
0097     return d_ptr->m_pSelectionModel;
0098 }
0099 
0100 void SelectionAdapter::setSelectionModel(QSharedPointer<QItemSelectionModel> m)
0101 {
0102     d_ptr->m_pSelectionModel = m;
0103 
0104     // This will cause undebugable issues. Better ban it early
0105     Q_ASSERT((!s_ptr->model()) || (!m) || s_ptr->model() == m->model());
0106 
0107     d_ptr->slotSelectionModelChanged();
0108     Q_EMIT selectionModelChanged();
0109 }
0110 
0111 QAbstractItemModel* SelectionAdapterSyncInterface::model() const
0112 {
0113     return q_ptr->d_ptr->m_pModel;
0114 }
0115 
0116 void SelectionAdapterSyncInterface::setModel(QAbstractItemModel* m)
0117 {
0118     if (q_ptr->d_ptr->m_pModel)
0119         QObject::disconnect(q_ptr->d_ptr->m_pModel, &QObject::destroyed,
0120             q_ptr->d_ptr, &SelectionAdapterPrivate::slotModelDestroyed);
0121 
0122     if (q_ptr->d_ptr->m_pSelectionModel && q_ptr->d_ptr->m_pSelectionModel->model() != m)
0123         q_ptr->d_ptr->m_pSelectionModel = nullptr;
0124 
0125     // In theory it can cause an issue when QML set both properties in random
0126     // order when replacing this model, but for until that happens, better be
0127     // strict.
0128     Q_ASSERT((!q_ptr->d_ptr->m_pSelectionModel)
0129         || (!m)
0130         || m == q_ptr->d_ptr->m_pSelectionModel->model()
0131     );
0132 
0133 
0134     q_ptr->d_ptr->m_pModel = m;
0135 
0136     if (m)
0137         QObject::connect(m, &QObject::destroyed,
0138             q_ptr->d_ptr, &SelectionAdapterPrivate::slotModelDestroyed);
0139 
0140 }
0141 
0142 ViewBase* SelectionAdapterSyncInterface::view() const
0143 {
0144     return q_ptr->d_ptr->m_pView;
0145 }
0146 
0147 void SelectionAdapterSyncInterface::setView(ViewBase* v)
0148 {
0149     q_ptr->d_ptr->m_pView = v;
0150 }
0151 
0152 
0153 void SelectionAdapterSyncInterface::updateSelection(const QModelIndex& idx)
0154 {
0155     q_ptr->d_ptr->slotCurrentIndexChanged(idx);
0156 }
0157 
0158 void SelectionAdapterPrivate::slotCurrentIndexChanged(const QModelIndex& idx)
0159 {
0160     if ((!idx.isValid()))
0161         return;
0162 
0163     Q_ASSERT(m_pView);
0164 
0165     Q_EMIT q_ptr->currentIndexChanged(idx);
0166 
0167 
0168     if (m_pSelectedItem && !idx.isValid()) {
0169         delete m_pSelectedItem;
0170         m_pSelectedItem = nullptr;
0171         return;
0172     }
0173 
0174     if (!m_pHighlight)
0175         return;
0176 
0177     auto md = m_pViewport->s_ptr->metadataForIndex(idx);
0178     auto elem = md && md->viewTracker() ? md->viewTracker()->d_ptr : nullptr;
0179 
0180     // QItemSelectionModel::setCurrentIndex isn't protected against setting the item many time
0181     if (m_pSelectedViewItem.first && elem == m_pSelectedViewItem.second) {
0182         // But it may have moved
0183         const auto geo = elem->geometry();
0184         m_pSelectedItem->setWidth(geo.width());
0185         m_pSelectedItem->setHeight(geo.height());
0186         m_pSelectedItem->setY(geo.y());
0187 
0188         return;
0189     }
0190 
0191     // There is no need to waste effort if the element is not visible
0192     /*if ((!elem) || (!elem->isVisible())) {
0193         if (m_pSelectedItem)
0194             m_pSelectedItem->setVisible(false);
0195         return;
0196     }*/ //FIXME
0197 
0198     // Create the highlighter
0199     if (!m_pSelectedItem) {
0200         m_pSelectedItem = qobject_cast<QQuickItem*>(q_ptr->highlight()->create(
0201             m_pView->rootContext()
0202         ));
0203         m_pSelectedItem->setParentItem(m_pView->contentItem());
0204         m_pView->rootContext()->engine()->setObjectOwnership(
0205             m_pSelectedItem, QQmlEngine::CppOwnership
0206         );
0207         m_pSelectedItem->setX(0);
0208     }
0209 
0210     if (!elem)
0211         return;
0212 
0213     const auto geo = elem->geometry();
0214     m_pSelectedItem->setVisible(true);
0215     m_pSelectedItem->setWidth(geo.width());
0216     m_pSelectedItem->setHeight(geo.height());
0217 
0218     elem->setSelected(true);
0219 
0220     if (m_pSelectedViewItem.first)
0221         m_pSelectedViewItem.second->setSelected(false);
0222 
0223     // Use X/Y to allow behavior to perform the silly animation
0224     m_pSelectedItem->setY(
0225         geo.y()
0226     );
0227 
0228     m_pSelectedViewItem = elem->weakReference();
0229 }
0230 
0231 void SelectionAdapterPrivate::slotSelectionModelChanged()
0232 {
0233     if (m_pSelectionModel)
0234         disconnect(m_pSelectionModel.data(), &QItemSelectionModel::currentChanged,
0235             this, &SelectionAdapterPrivate::slotCurrentIndexChanged);
0236 
0237     m_pSelectionModel = q_ptr->selectionModel();
0238 
0239     if (m_pSelectionModel)
0240         connect(m_pSelectionModel.data(), &QItemSelectionModel::currentChanged,
0241             this, &SelectionAdapterPrivate::slotCurrentIndexChanged);
0242 
0243     slotCurrentIndexChanged(
0244         m_pSelectionModel ? m_pSelectionModel->currentIndex() : QModelIndex()
0245     );
0246 }
0247 
0248 // Because smart pointer are ref counted and QItemSelectionModel is not, there
0249 // is a race condition where ::loadDelegate or ::applyRoles can be called
0250 // on a QItemSelectionModel::currentIndex while the model has just been destroyed
0251 void SelectionAdapterPrivate::slotModelDestroyed()
0252 {
0253     if (m_pSelectionModel && m_pSelectionModel->model() == QObject::sender())
0254         q_ptr->setSelectionModel(nullptr);
0255 }
0256 
0257 QQmlComponent* SelectionAdapter::highlight() const
0258 {
0259     return d_ptr->m_pHighlight;
0260 }
0261 
0262 void SelectionAdapter::setHighlight(QQmlComponent* h)
0263 {
0264     d_ptr->m_pHighlight = h;
0265 }
0266 
0267 QVector<QByteArray>& ItemSelectionGroup::propertyNames() const
0268 {
0269     static QVector<QByteArray> ret {
0270         "isCurrentItem",
0271     };
0272 
0273     return ret;
0274 }
0275 
0276 QVariant ItemSelectionGroup::getProperty(AbstractItemAdapter* item, uint id, const QModelIndex& index) const
0277 {
0278     Q_UNUSED(index)
0279     switch(id) {
0280         case 0 /*isCurrentItem*/:
0281             return d_ptr->m_pSelectionModel &&
0282                 d_ptr->m_pSelectionModel->currentIndex() == item->index();
0283     }
0284 
0285     Q_ASSERT(false);
0286     return {};
0287 }
0288 
0289 ContextExtension *SelectionAdapter::contextExtension() const
0290 {
0291     if (!d_ptr->m_pContextExtension) {
0292         d_ptr->m_pContextExtension = new ItemSelectionGroup();
0293         d_ptr->m_pContextExtension->d_ptr = d_ptr;
0294     }
0295 
0296     return d_ptr->m_pContextExtension;
0297 }
0298 
0299 void SelectionAdapterSyncInterface::setViewport(Viewport *v)
0300 {
0301     q_ptr->d_ptr->m_pViewport = v;
0302 }
0303 
0304 #include <selectionadapter.moc>