/* SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company SPDX-FileContributor: Stephen Kelly SPDX-FileCopyrightText: 2016 Ableton AG SPDX-FileContributor: Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #include "klinkitemselectionmodel.h" #include "kitemmodels_debug.h" #include "kmodelindexproxymapper.h" #include class KLinkItemSelectionModelPrivate { public: KLinkItemSelectionModelPrivate(KLinkItemSelectionModel *proxySelectionModel) : q_ptr(proxySelectionModel) { QObject::connect(q_ptr, &QItemSelectionModel::currentChanged, q_ptr, [this](const QModelIndex &idx) { slotCurrentChanged(idx); }); QObject::connect(q_ptr, &QItemSelectionModel::modelChanged, q_ptr, [this] { reinitializeIndexMapper(); }); } Q_DECLARE_PUBLIC(KLinkItemSelectionModel) KLinkItemSelectionModel *const q_ptr; bool assertSelectionValid(const QItemSelection &selection) const { for (const QItemSelectionRange &range : selection) { if (!range.isValid()) { qCDebug(KITEMMODELS_LOG) << selection; } Q_ASSERT(range.isValid()); } return true; } void reinitializeIndexMapper() { delete m_indexMapper; m_indexMapper = nullptr; if (!q_ptr->model() || !m_linkedItemSelectionModel || !m_linkedItemSelectionModel->model()) { return; } m_indexMapper = new KModelIndexProxyMapper(q_ptr->model(), m_linkedItemSelectionModel->model(), q_ptr); const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(m_linkedItemSelectionModel->selection()); q_ptr->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::ClearAndSelect); } void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void sourceCurrentChanged(const QModelIndex ¤t); void slotCurrentChanged(const QModelIndex ¤t); QItemSelectionModel *m_linkedItemSelectionModel = nullptr; bool m_ignoreCurrentChanged = false; KModelIndexProxyMapper *m_indexMapper = nullptr; }; KLinkItemSelectionModel::KLinkItemSelectionModel(QAbstractItemModel *model, QItemSelectionModel *proxySelector, QObject *parent) : QItemSelectionModel(model, parent) , d_ptr(new KLinkItemSelectionModelPrivate(this)) { setLinkedItemSelectionModel(proxySelector); } KLinkItemSelectionModel::KLinkItemSelectionModel(QObject *parent) : QItemSelectionModel(nullptr, parent) , d_ptr(new KLinkItemSelectionModelPrivate(this)) { } KLinkItemSelectionModel::~KLinkItemSelectionModel() = default; QItemSelectionModel *KLinkItemSelectionModel::linkedItemSelectionModel() const { Q_D(const KLinkItemSelectionModel); return d->m_linkedItemSelectionModel; } void KLinkItemSelectionModel::setLinkedItemSelectionModel(QItemSelectionModel *selectionModel) { Q_D(KLinkItemSelectionModel); if (d->m_linkedItemSelectionModel != selectionModel) { if (d->m_linkedItemSelectionModel) { disconnect(d->m_linkedItemSelectionModel); } d->m_linkedItemSelectionModel = selectionModel; if (d->m_linkedItemSelectionModel) { connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::selectionChanged, this, [d](const QItemSelection &selected, const QItemSelection &deselected) { d->sourceSelectionChanged(selected, deselected); }); connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::currentChanged, this, [d](const QModelIndex ¤t) { d->sourceCurrentChanged(current); }); connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::modelChanged, this, [this] { d_ptr->reinitializeIndexMapper(); }); } d->reinitializeIndexMapper(); Q_EMIT linkedItemSelectionModelChanged(); } } void KLinkItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) { Q_D(KLinkItemSelectionModel); // When an item is removed, the current index is set to the top index in the model. // That causes a selectionChanged signal with a selection which we do not want. if (d->m_ignoreCurrentChanged) { return; } // Do *not* replace next line with: QItemSelectionModel::select(index, command) // // Doing so would end up calling KLinkItemSelectionModel::select(QItemSelection, QItemSelectionModel::SelectionFlags) // // This is because the code for QItemSelectionModel::select(QModelIndex, QItemSelectionModel::SelectionFlags) looks like this: // { // QItemSelection selection(index, index); // select(selection, command); // } // So it calls KLinkItemSelectionModel overload of // select(QItemSelection, QItemSelectionModel::SelectionFlags) // // When this happens and the selection flags include Toggle, it causes the // selection to be toggled twice. QItemSelectionModel::select(QItemSelection(index, index), command); if (index.isValid()) { d->m_linkedItemSelectionModel->select(d->m_indexMapper->mapSelectionLeftToRight(QItemSelection(index, index)), command); } else { d->m_linkedItemSelectionModel->clearSelection(); } } void KLinkItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { Q_D(KLinkItemSelectionModel); d->m_ignoreCurrentChanged = true; QItemSelection _selection = selection; QItemSelectionModel::select(_selection, command); Q_ASSERT(d->assertSelectionValid(_selection)); QItemSelection mappedSelection = d->m_indexMapper->mapSelectionLeftToRight(_selection); Q_ASSERT(d->assertSelectionValid(mappedSelection)); d->m_linkedItemSelectionModel->select(mappedSelection, command); d->m_ignoreCurrentChanged = false; } void KLinkItemSelectionModelPrivate::slotCurrentChanged(const QModelIndex ¤t) { const QModelIndex mappedCurrent = m_indexMapper->mapLeftToRight(current); if (!mappedCurrent.isValid()) { return; } m_linkedItemSelectionModel->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate); } void KLinkItemSelectionModelPrivate::sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_Q(KLinkItemSelectionModel); QItemSelection _selected = selected; QItemSelection _deselected = deselected; Q_ASSERT(assertSelectionValid(_selected)); Q_ASSERT(assertSelectionValid(_deselected)); const QItemSelection mappedDeselection = m_indexMapper->mapSelectionRightToLeft(_deselected); const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(_selected); q->QItemSelectionModel::select(mappedDeselection, QItemSelectionModel::Deselect); q->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::Select); } void KLinkItemSelectionModelPrivate::sourceCurrentChanged(const QModelIndex ¤t) { Q_Q(KLinkItemSelectionModel); const QModelIndex mappedCurrent = m_indexMapper->mapRightToLeft(current); if (!mappedCurrent.isValid()) { return; } q->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate); } #include "moc_klinkitemselectionmodel.cpp"