You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
331 lines
11 KiB
331 lines
11 KiB
/* |
|
Copyright (C) 2010 Klarälvdalens Datakonsult AB, |
|
a KDAB Group company, info@kdab.net, |
|
author Stephen Kelly <stephen@kdab.com> |
|
Copyright (c) 2016 Ableton AG <info@ableton.com> |
|
Author Stephen Kelly <stephen.kelly@ableton.com> |
|
|
|
This library is free software; you can redistribute it and/or modify it |
|
under the terms of the GNU Library General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or (at your |
|
option) any later version. |
|
|
|
This library is distributed in the hope that it will be useful, but WITHOUT |
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
|
License for more details. |
|
|
|
You should have received a copy of the GNU Library General Public License |
|
along with this library; see the file COPYING.LIB. If not, write to the |
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301, USA. |
|
*/ |
|
|
|
#include "kmodelindexproxymapper.h" |
|
|
|
#include <QtCore/QAbstractItemModel> |
|
#include <QtCore/QPointer> |
|
#include <QtCore/QDebug> |
|
#include <QAbstractProxyModel> |
|
#include <QItemSelectionModel> |
|
|
|
class KModelIndexProxyMapperPrivate |
|
{ |
|
KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq) |
|
: q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel), mConnected(false) |
|
{ |
|
createProxyChain(); |
|
} |
|
|
|
void createProxyChain(); |
|
void checkConnected(); |
|
void setConnected(bool connected); |
|
|
|
bool assertSelectionValid(const QItemSelection &selection) const |
|
{ |
|
Q_FOREACH (const QItemSelectionRange &range, selection) { |
|
if (!range.isValid()) { |
|
qDebug() << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp; |
|
} |
|
Q_ASSERT(range.isValid()); |
|
} |
|
return true; |
|
} |
|
|
|
Q_DECLARE_PUBLIC(KModelIndexProxyMapper) |
|
KModelIndexProxyMapper *const q_ptr; |
|
|
|
QList<QPointer<const QAbstractProxyModel> > m_proxyChainUp; |
|
QList<QPointer<const QAbstractProxyModel> > m_proxyChainDown; |
|
|
|
QPointer<const QAbstractItemModel> m_leftModel; |
|
QPointer<const QAbstractItemModel> m_rightModel; |
|
|
|
bool mConnected; |
|
}; |
|
|
|
/* |
|
|
|
The idea here is that <tt>this</tt> selection model and proxySelectionModel might be in different parts of the |
|
proxy chain. We need to build up to two chains of proxy models to create mappings between them. |
|
|
|
Example 1: |
|
|
|
Root model |
|
| |
|
/ \ |
|
Proxy 1 Proxy 3 |
|
| | |
|
Proxy 2 Proxy 4 |
|
|
|
Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other. |
|
|
|
Example 2: |
|
|
|
Root model |
|
| |
|
Proxy 1 |
|
| |
|
Proxy 2 |
|
/ \ |
|
Proxy 3 Proxy 6 |
|
| | |
|
Proxy 4 Proxy 7 |
|
| |
|
Proxy 5 |
|
|
|
We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is |
|
already in the first chain. |
|
|
|
Stephen Kelly, 30 March 2010. |
|
*/ |
|
|
|
void KModelIndexProxyMapperPrivate::createProxyChain() |
|
{ |
|
Q_FOREACH (auto p, m_proxyChainUp) { |
|
p->disconnect(q_ptr); |
|
} |
|
Q_FOREACH (auto p, m_proxyChainDown) { |
|
p->disconnect(q_ptr); |
|
} |
|
m_proxyChainUp.clear(); |
|
m_proxyChainDown.clear(); |
|
QPointer<const QAbstractItemModel> targetModel = m_rightModel; |
|
|
|
QList<QPointer<const QAbstractProxyModel> > proxyChainDown; |
|
QPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(targetModel); |
|
while (selectionTargetProxyModel) { |
|
proxyChainDown.prepend(selectionTargetProxyModel); |
|
QObject::connect(selectionTargetProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, |
|
[this]{ createProxyChain(); }); |
|
|
|
selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(selectionTargetProxyModel->sourceModel()); |
|
|
|
if (selectionTargetProxyModel == m_leftModel) { |
|
m_proxyChainDown = proxyChainDown; |
|
checkConnected(); |
|
return; |
|
} |
|
} |
|
|
|
QPointer<const QAbstractItemModel> sourceModel = m_leftModel; |
|
QPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceModel); |
|
|
|
while (sourceProxyModel) { |
|
m_proxyChainUp.append(sourceProxyModel); |
|
QObject::connect(sourceProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, |
|
[this]{ createProxyChain(); }); |
|
|
|
sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceProxyModel->sourceModel()); |
|
|
|
const int targetIndex = proxyChainDown.indexOf(sourceProxyModel); |
|
|
|
if (targetIndex != -1) { |
|
m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size()); |
|
checkConnected(); |
|
return; |
|
} |
|
} |
|
m_proxyChainDown = proxyChainDown; |
|
checkConnected(); |
|
} |
|
|
|
void KModelIndexProxyMapperPrivate::checkConnected() |
|
{ |
|
auto konamiRight = m_proxyChainUp.isEmpty() ? m_leftModel : m_proxyChainUp.last()->sourceModel(); |
|
auto konamiLeft = m_proxyChainDown.isEmpty() ? m_rightModel : m_proxyChainDown.first()->sourceModel(); |
|
setConnected(konamiLeft && (konamiLeft == konamiRight)); |
|
} |
|
|
|
void KModelIndexProxyMapperPrivate::setConnected(bool connected) |
|
{ |
|
if (mConnected != connected) { |
|
Q_Q(KModelIndexProxyMapper); |
|
mConnected = connected; |
|
Q_EMIT q->isConnectedChanged(); |
|
} |
|
} |
|
|
|
KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent) |
|
: QObject(parent), d_ptr(new KModelIndexProxyMapperPrivate(leftModel, rightModel, this)) |
|
{ |
|
|
|
} |
|
|
|
KModelIndexProxyMapper::~KModelIndexProxyMapper() |
|
{ |
|
delete d_ptr; |
|
} |
|
|
|
QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex &index) const |
|
{ |
|
const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index)); |
|
if (selection.isEmpty()) { |
|
return QModelIndex(); |
|
} |
|
|
|
return selection.indexes().first(); |
|
} |
|
|
|
QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex &index) const |
|
{ |
|
const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index)); |
|
if (selection.isEmpty()) { |
|
return QModelIndex(); |
|
} |
|
|
|
return selection.indexes().first(); |
|
} |
|
|
|
// QAbstractProxyModel::mapSelectionFromSource creates invalid ranges to we filter |
|
// those out manually in a loop. Hopefully fixed in Qt 4.7.2, so we ifdef it out. |
|
// http://qt.gitorious.org/qt/qt/merge_requests/2474 |
|
// http://qt.gitorious.org/qt/qt/merge_requests/831 |
|
#if QT_VERSION < 0x040702 |
|
#define RANGE_FIX_HACK |
|
#endif |
|
|
|
#ifdef RANGE_FIX_HACK |
|
static QItemSelection removeInvalidRanges(const QItemSelection &selection) |
|
{ |
|
QItemSelection result; |
|
Q_FOREACH (const QItemSelectionRange &range, selection) { |
|
if (!range.isValid()) { |
|
continue; |
|
} |
|
result << range; |
|
} |
|
return result; |
|
} |
|
#endif |
|
|
|
QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection &selection) const |
|
{ |
|
Q_D(const KModelIndexProxyMapper); |
|
|
|
if (selection.isEmpty() || !d->mConnected) { |
|
return QItemSelection(); |
|
} |
|
|
|
if (selection.first().model() != d->m_leftModel) { |
|
qDebug() << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel; |
|
} |
|
Q_ASSERT(selection.first().model() == d->m_leftModel); |
|
|
|
QItemSelection seekSelection = selection; |
|
Q_ASSERT(d->assertSelectionValid(seekSelection)); |
|
QListIterator<QPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp); |
|
|
|
while (iUp.hasNext()) { |
|
const QPointer<const QAbstractProxyModel> proxy = iUp.next(); |
|
if (!proxy) { |
|
return QItemSelection(); |
|
} |
|
|
|
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy); |
|
seekSelection = proxy->mapSelectionToSource(seekSelection); |
|
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel()); |
|
|
|
#ifdef RANGE_FIX_HACK |
|
seekSelection = removeInvalidRanges(seekSelection); |
|
#endif |
|
Q_ASSERT(d->assertSelectionValid(seekSelection)); |
|
} |
|
|
|
QListIterator<QPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown); |
|
|
|
while (iDown.hasNext()) { |
|
const QPointer<const QAbstractProxyModel> proxy = iDown.next(); |
|
if (!proxy) { |
|
return QItemSelection(); |
|
} |
|
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel()); |
|
seekSelection = proxy->mapSelectionFromSource(seekSelection); |
|
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy); |
|
|
|
#ifdef RANGE_FIX_HACK |
|
seekSelection = removeInvalidRanges(seekSelection); |
|
#endif |
|
Q_ASSERT(d->assertSelectionValid(seekSelection)); |
|
} |
|
|
|
Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel) || true); |
|
return seekSelection; |
|
} |
|
|
|
QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection &selection) const |
|
{ |
|
Q_D(const KModelIndexProxyMapper); |
|
|
|
if (selection.isEmpty() || !d->mConnected) { |
|
return QItemSelection(); |
|
} |
|
|
|
if (selection.first().model() != d->m_rightModel) { |
|
qDebug() << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel; |
|
} |
|
Q_ASSERT(selection.first().model() == d->m_rightModel); |
|
|
|
QItemSelection seekSelection = selection; |
|
Q_ASSERT(d->assertSelectionValid(seekSelection)); |
|
QListIterator<QPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown); |
|
|
|
iDown.toBack(); |
|
while (iDown.hasPrevious()) { |
|
const QPointer<const QAbstractProxyModel> proxy = iDown.previous(); |
|
if (!proxy) { |
|
return QItemSelection(); |
|
} |
|
seekSelection = proxy->mapSelectionToSource(seekSelection); |
|
|
|
#ifdef RANGE_FIX_HACK |
|
seekSelection = removeInvalidRanges(seekSelection); |
|
#endif |
|
Q_ASSERT(d->assertSelectionValid(seekSelection)); |
|
} |
|
|
|
QListIterator<QPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp); |
|
|
|
iUp.toBack(); |
|
while (iUp.hasPrevious()) { |
|
const QPointer<const QAbstractProxyModel> proxy = iUp.previous(); |
|
if (!proxy) { |
|
return QItemSelection(); |
|
} |
|
seekSelection = proxy->mapSelectionFromSource(seekSelection); |
|
|
|
#ifdef RANGE_FIX_HACK |
|
seekSelection = removeInvalidRanges(seekSelection); |
|
#endif |
|
Q_ASSERT(d->assertSelectionValid(seekSelection)); |
|
} |
|
|
|
Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel) || true); |
|
return seekSelection; |
|
} |
|
|
|
bool KModelIndexProxyMapper::isConnected() const |
|
{ |
|
Q_D(const KModelIndexProxyMapper); |
|
return d->mConnected; |
|
}
|
|
|