File indexing completed on 2024-07-21 06:38:22

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 &current);
0059     void slotCurrentChanged(const QModelIndex &current);
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 &current) {
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 &current)
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 &current)
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"