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>