File indexing completed on 2024-05-05 03:56:42
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0003 SPDX-FileContributor: Stephen Kelly <stephen@kdab.com> 0004 SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com> 0005 SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "klinkitemselectionmodel.h" 0011 #include "kitemmodels_debug.h" 0012 #include "kmodelindexproxymapper.h" 0013 0014 #include <QItemSelection> 0015 0016 class KLinkItemSelectionModelPrivate 0017 { 0018 public: 0019 KLinkItemSelectionModelPrivate(KLinkItemSelectionModel *proxySelectionModel) 0020 : q_ptr(proxySelectionModel) 0021 { 0022 QObject::connect(q_ptr, &QItemSelectionModel::currentChanged, q_ptr, [this](const QModelIndex &idx) { 0023 slotCurrentChanged(idx); 0024 }); 0025 0026 QObject::connect(q_ptr, &QItemSelectionModel::modelChanged, q_ptr, [this] { 0027 reinitializeIndexMapper(); 0028 }); 0029 } 0030 0031 Q_DECLARE_PUBLIC(KLinkItemSelectionModel) 0032 KLinkItemSelectionModel *const q_ptr; 0033 0034 bool assertSelectionValid(const QItemSelection &selection) const 0035 { 0036 for (const QItemSelectionRange &range : selection) { 0037 if (!range.isValid()) { 0038 qCDebug(KITEMMODELS_LOG) << selection; 0039 } 0040 Q_ASSERT(range.isValid()); 0041 } 0042 return true; 0043 } 0044 0045 void reinitializeIndexMapper() 0046 { 0047 delete m_indexMapper; 0048 m_indexMapper = nullptr; 0049 if (!q_ptr->model() || !m_linkedItemSelectionModel || !m_linkedItemSelectionModel->model()) { 0050 return; 0051 } 0052 m_indexMapper = new KModelIndexProxyMapper(q_ptr->model(), m_linkedItemSelectionModel->model(), q_ptr); 0053 const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(m_linkedItemSelectionModel->selection()); 0054 q_ptr->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::ClearAndSelect); 0055 } 0056 0057 void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); 0058 void sourceCurrentChanged(const QModelIndex ¤t); 0059 void slotCurrentChanged(const QModelIndex ¤t); 0060 0061 QItemSelectionModel *m_linkedItemSelectionModel = nullptr; 0062 bool m_ignoreCurrentChanged = false; 0063 KModelIndexProxyMapper *m_indexMapper = nullptr; 0064 }; 0065 0066 KLinkItemSelectionModel::KLinkItemSelectionModel(QAbstractItemModel *model, QItemSelectionModel *proxySelector, QObject *parent) 0067 : QItemSelectionModel(model, parent) 0068 , d_ptr(new KLinkItemSelectionModelPrivate(this)) 0069 { 0070 setLinkedItemSelectionModel(proxySelector); 0071 } 0072 0073 KLinkItemSelectionModel::KLinkItemSelectionModel(QObject *parent) 0074 : QItemSelectionModel(nullptr, parent) 0075 , d_ptr(new KLinkItemSelectionModelPrivate(this)) 0076 { 0077 } 0078 0079 KLinkItemSelectionModel::~KLinkItemSelectionModel() = default; 0080 0081 QItemSelectionModel *KLinkItemSelectionModel::linkedItemSelectionModel() const 0082 { 0083 Q_D(const KLinkItemSelectionModel); 0084 return d->m_linkedItemSelectionModel; 0085 } 0086 0087 void KLinkItemSelectionModel::setLinkedItemSelectionModel(QItemSelectionModel *selectionModel) 0088 { 0089 Q_D(KLinkItemSelectionModel); 0090 if (d->m_linkedItemSelectionModel != selectionModel) { 0091 if (d->m_linkedItemSelectionModel) { 0092 disconnect(d->m_linkedItemSelectionModel); 0093 } 0094 0095 d->m_linkedItemSelectionModel = selectionModel; 0096 0097 if (d->m_linkedItemSelectionModel) { 0098 connect(d->m_linkedItemSelectionModel, 0099 &QItemSelectionModel::selectionChanged, 0100 this, 0101 [d](const QItemSelection &selected, const QItemSelection &deselected) { 0102 d->sourceSelectionChanged(selected, deselected); 0103 }); 0104 connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::currentChanged, this, [d](const QModelIndex ¤t) { 0105 d->sourceCurrentChanged(current); 0106 }); 0107 0108 connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::modelChanged, this, [this] { 0109 d_ptr->reinitializeIndexMapper(); 0110 }); 0111 } 0112 d->reinitializeIndexMapper(); 0113 Q_EMIT linkedItemSelectionModelChanged(); 0114 } 0115 } 0116 0117 void KLinkItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) 0118 { 0119 Q_D(KLinkItemSelectionModel); 0120 // When an item is removed, the current index is set to the top index in the model. 0121 // That causes a selectionChanged signal with a selection which we do not want. 0122 if (d->m_ignoreCurrentChanged) { 0123 return; 0124 } 0125 // Do *not* replace next line with: QItemSelectionModel::select(index, command) 0126 // 0127 // Doing so would end up calling KLinkItemSelectionModel::select(QItemSelection, QItemSelectionModel::SelectionFlags) 0128 // 0129 // This is because the code for QItemSelectionModel::select(QModelIndex, QItemSelectionModel::SelectionFlags) looks like this: 0130 // { 0131 // QItemSelection selection(index, index); 0132 // select(selection, command); 0133 // } 0134 // So it calls KLinkItemSelectionModel overload of 0135 // select(QItemSelection, QItemSelectionModel::SelectionFlags) 0136 // 0137 // When this happens and the selection flags include Toggle, it causes the 0138 // selection to be toggled twice. 0139 QItemSelectionModel::select(QItemSelection(index, index), command); 0140 if (index.isValid()) { 0141 d->m_linkedItemSelectionModel->select(d->m_indexMapper->mapSelectionLeftToRight(QItemSelection(index, index)), command); 0142 } else { 0143 d->m_linkedItemSelectionModel->clearSelection(); 0144 } 0145 } 0146 0147 void KLinkItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) 0148 { 0149 Q_D(KLinkItemSelectionModel); 0150 d->m_ignoreCurrentChanged = true; 0151 QItemSelection _selection = selection; 0152 QItemSelectionModel::select(_selection, command); 0153 Q_ASSERT(d->assertSelectionValid(_selection)); 0154 QItemSelection mappedSelection = d->m_indexMapper->mapSelectionLeftToRight(_selection); 0155 Q_ASSERT(d->assertSelectionValid(mappedSelection)); 0156 d->m_linkedItemSelectionModel->select(mappedSelection, command); 0157 d->m_ignoreCurrentChanged = false; 0158 } 0159 0160 void KLinkItemSelectionModelPrivate::slotCurrentChanged(const QModelIndex ¤t) 0161 { 0162 const QModelIndex mappedCurrent = m_indexMapper->mapLeftToRight(current); 0163 if (!mappedCurrent.isValid()) { 0164 return; 0165 } 0166 m_linkedItemSelectionModel->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate); 0167 } 0168 0169 void KLinkItemSelectionModelPrivate::sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 0170 { 0171 Q_Q(KLinkItemSelectionModel); 0172 QItemSelection _selected = selected; 0173 QItemSelection _deselected = deselected; 0174 Q_ASSERT(assertSelectionValid(_selected)); 0175 Q_ASSERT(assertSelectionValid(_deselected)); 0176 const QItemSelection mappedDeselection = m_indexMapper->mapSelectionRightToLeft(_deselected); 0177 const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(_selected); 0178 0179 q->QItemSelectionModel::select(mappedDeselection, QItemSelectionModel::Deselect); 0180 q->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::Select); 0181 } 0182 0183 void KLinkItemSelectionModelPrivate::sourceCurrentChanged(const QModelIndex ¤t) 0184 { 0185 Q_Q(KLinkItemSelectionModel); 0186 const QModelIndex mappedCurrent = m_indexMapper->mapRightToLeft(current); 0187 if (!mappedCurrent.isValid()) { 0188 return; 0189 } 0190 q->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate); 0191 } 0192 0193 #include "moc_klinkitemselectionmodel.cpp"