kde-playground/kdepim/akonadi_next/kreparentingproxymodel.cpp
2015-04-14 21:49:29 +00:00

1469 lines
46 KiB
C++

/*
Copyright (c) 2009 Stephen Kelly <steveire@gmail.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 "kreparentingproxymodel.h"
#include <kdebug.h>
#include <QStack>
#include <functional>
#include <algorithm>
class KReparentingProxyModelPrivate
{
KReparentingProxyModelPrivate(KReparentingProxyModel *proxyModel)
: q_ptr(proxyModel), m_nextId(0)
{
}
qint64 newId() const { return m_nextId++; }
enum MapStrategy
{
MapDescendants,
MapChildrenOnly
};
/**
Creates mappings of indexes in the source model between @p start
and @p end which should be represented in the proxy model as descendants
of @p parent.
*/
QHash<QModelIndex, QModelIndexList> recreateMappings(const QModelIndex &parent, int start, int end = -1, int strategy = MapChildrenOnly) const;
/**
Merges all indexes from @p mappings which are descendants of @p parent into the model.
Returns the remaining mappings.
Note that this changes the internal model structure and must only be called between begin/end insert/remove/move/reset calls.
*/
QHash<QModelIndex, QModelIndexList> mergeDescendants(QHash<QModelIndex, QModelIndexList> mappings, const QModelIndex &parent, int start );
/**
Verifies that the indexes below @p parent between @p start and rowCount(parent)
are in the correct positions in the proxy model. Repositions them if not.
*/
void verifyStructure(const QModelIndex &parent, int start);
/**
Returns the index vertically below index in the model @p model.
If @p model is 0, the sourceModel is used
Returns an invalid index if there is no index below @p index.
*/
QModelIndex getIndexBelow(const QModelIndex &index, QAbstractItemModel *model = 0) const;
/**
Returns the last descendant of @p index or itself if it has no children
*/
QModelIndex getLastDescendant(const QModelIndex &index) const;
bool isDescendantInModel(const QModelIndex &ancestor, const QModelIndex &descendant) const;
/**
Returns the ancestors of @p descendant that are already in the proxy model.
Note that @p descendant does not have to be in the proxy yet, and it is not part of the
result list.
*/
QVector<QModelIndex> getExistingAncestors(const QModelIndex &descendant) const;
void sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
void sourceRowsInserted(const QModelIndex &parent, int start, int end);
void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
void sourceRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destRow);
void sourceRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int destRow);
void sourceModelAboutToBeReset();
void sourceModelReset();
void sourceLayoutAboutToBeChanged();
void sourceLayoutChanged();
void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
mutable QHash<qint64, QPersistentModelIndex> m_parents;
mutable QHash<QPersistentModelIndex, QList<QPersistentModelIndex> > m_childIndexes;
struct PendingInsertion
{
PendingInsertion()
: parentId(-1), start(-1), end(-1)
{ }
PendingInsertion(const QModelIndex &_index, int _start, int _end)
: index(_index), parentId(-1), start(_start), end(_end)
{ }
QPersistentModelIndex index;
QModelIndex sourceIndex;
qint64 parentId;
int start;
int end;
};
struct PendingRemoval : PendingInsertion
{
int numTrailing;
};
// Needed between the beginRemoveRows and endRemoveRows signals.
mutable QHash<qint64, QPersistentModelIndex> m_pendingRemovalParents;
mutable QHash<QPersistentModelIndex, QList<QPersistentModelIndex> > m_pendingRemovalChildIndexes;
QHash<QModelIndex, QModelIndexList> insertTree( QHash<QModelIndex, QModelIndexList> mappings, const QModelIndex &parent );
void handleInsertion(const PendingInsertion &pendingInsertion);
void handleRemoval(const PendingRemoval &pendingRemoval);
mutable QHash<QModelIndex, PendingInsertion> m_pendingInsertions;
mutable QVector<PendingRemoval> m_pendingRemovals;
mutable qint64 m_nextId;
Q_DECLARE_PUBLIC( KReparentingProxyModel )
KReparentingProxyModel *q_ptr;
QList<QPersistentModelIndex> m_layoutChangePersistentIndexes;
QModelIndexList m_proxyIndexes;
void emitDataChangedSignals(const QModelIndex &parent, int maxChanged);
/**
Given @p parent in the proxy model, return the last index lying between @p start and @p end
which is also a descendant of @p parent.
*/
QModelIndex findLastInParent(QModelIndex parent, int start, int end);
/**
Removes @P idx (which is a source model index) and its children from the model data structures.
*/
void removeTree(const QPersistentModelIndex &idx, int start = 0, int end = -1);
int pendingRemovalRowCount(const QModelIndex &sourceIndex) const;
};
class LessThan
{
const KReparentingProxyModel *m_model;
public:
LessThan(const KReparentingProxyModel *model) : m_model(model) {}
bool operator()(const QModelIndex &ancestor, const QModelIndex &descendant)
{
return m_model->isDescendantOf(ancestor, descendant);
}
};
QModelIndex KReparentingProxyModelPrivate::getIndexBelow(const QModelIndex &index, QAbstractItemModel *model) const
{
Q_Q(const KReparentingProxyModel);
if (!model)
model = q->sourceModel();
if (model->hasChildren(index))
return model->index(0, 0, index);
QModelIndex sibling = index.sibling(index.row() + 1, index.column());
if (sibling.isValid())
return sibling;
QModelIndex parent = sibling.parent();
if(!parent.isValid())
return QModelIndex();
int affectedRow = index.row();
const int column = 0;
while (parent.isValid())
{
if (model->rowCount(parent) >= affectedRow)
return model->index(affectedRow + 1, column, parent);
affectedRow = parent.row();
parent = parent.parent();
}
if (model->rowCount(parent) >= affectedRow)
return model->index(affectedRow + 1, column, parent);
return QModelIndex();
}
QModelIndex KReparentingProxyModelPrivate::getLastDescendant(const QModelIndex &index) const
{
Q_Q(const KReparentingProxyModel);
QModelIndex proxyIndex = q->mapFromSource(index);
while (q->hasChildren(proxyIndex))
{
proxyIndex = proxyIndex.child(q->rowCount(proxyIndex), proxyIndex.column());
if (!proxyIndex.isValid())
break;
}
return q->mapToSource(proxyIndex);
}
QVector<QModelIndex> KReparentingProxyModelPrivate::getExistingAncestors(const QModelIndex &descendant) const
{
Q_Q(const KReparentingProxyModel);
QVector<QModelIndex> vector;
if (!descendant.isValid())
return vector;
QModelIndex parent = q->mapFromSource(descendant).parent();
vector.append(q->mapToSource(parent));
while (parent.isValid())
{
parent = parent.parent();
vector.prepend(q->mapToSource(parent));
}
return vector;
}
QHash<QModelIndex, QModelIndexList> KReparentingProxyModelPrivate::recreateMappings(const QModelIndex &ancestor, int start, int end, int strategy) const
{
Q_Q(const KReparentingProxyModel);
const int column = 0;
QHash<QModelIndex, QModelIndexList> mappings;
// Handle listing the root QModelIndex().
if (!ancestor.isValid() && !q->sourceModel()->hasChildren())
// Empty model. Nothing to do.
return mappings;
// A
// - B
// - - C
// - D
// If start refers to D, existing ancestors will contain only A.
// We need to go 'up' to C and get its ancestors in case D is to be made a child of B or C (for example if B and C have just been inserted)
QModelIndex indexAbove;
if (start > 0)
indexAbove = getLastDescendant(q->sourceModel()->index(start - 1, column, ancestor));
else
indexAbove = ancestor;
QVector<QModelIndex> ancestors = getExistingAncestors(indexAbove);
ancestors.append(indexAbove);
QModelIndex nextIndex = ancestor;
for (int row = start; (row <= end || end == -1); ++row)
{
// TODO: Change this to get the next index by asking the proxy model, not the source model.
if (MapDescendants == strategy)
{
nextIndex = getIndexBelow(nextIndex);
} else {
nextIndex = q->sourceModel()->index(row, column, ancestor);
}
if (!nextIndex.isValid())
break;
QModelIndex deepestAncestor = ancestors.last();
if (q->isDescendantOf(deepestAncestor, nextIndex))
{
mappings[deepestAncestor].append(nextIndex);
ancestors.append(nextIndex);
continue;
}
kDebug()<< "######\nNextIndex" << nextIndex.data();
foreach(const QModelIndex &idx, ancestors)
{
kDebug() << "#" << idx << idx.data();
}
QVector<QModelIndex>::iterator ancestorIt = qLowerBound(ancestors.begin(), ancestors.end(), nextIndex, LessThan(q));
QModelIndex parent;
if (ancestorIt != ancestors.begin())
{
parent = *(ancestorIt - 1);
}
//kDebug() << parent << *ancestorIt; // this crashes if ancestorIt == ancestors.end()
ancestorIt = ancestors.insert(ancestorIt, nextIndex);
if (ancestorIt != ancestors.end())
{
++ancestorIt;
while (ancestorIt != ancestors.end())
{
ancestorIt = ancestors.erase(ancestorIt);
}
}
mappings[parent].append(nextIndex);
}
return mappings;
}
void KReparentingProxyModelPrivate::verifyStructure(const QModelIndex &sourceParent, int sourceStart)
{
Q_Q( KReparentingProxyModel );
// If the start structure is:
// C
// D
// E
// and then A and B are inserted, we may need to move C D and E. Not all of the siblings will
// necessarily be moved to the same destination parent.
// Some example finished scenarios depending on the outcome of isDescendantOf:
// A
// B
// C
// D
// E
// A
// B
// - C
// - D
// - E
// A
// - B
// - C
// - D
// - E
// A
// - B
// - - C
// - D
// E
// Local variable mappings now contains all the information about finished state
// When we locate the first child to be moved, we process it and its siblings
QHash<QModelIndex, QModelIndexList> mappings = recreateMappings(sourceParent, sourceStart, -1);
if (mappings.isEmpty())
return;
QModelIndex sourceFirstIndex = q->sourceModel()->index(sourceStart, 0, sourceParent);
QModelIndex destinationParent;
QModelIndexList movedIndexes;
QHashIterator<QModelIndex, QModelIndexList> it(mappings);
while (it.hasNext())
{
it.next();
kDebug() << it.key() << it.key().data() << it.value();
if (it.value().at(0) == sourceFirstIndex)
{
destinationParent = it.key();
movedIndexes = it.value();
break;
}
}
forever
{
if (destinationParent == sourceParent)
// No indexes moved
return;
Q_ASSERT(destinationParent.isValid());
Q_ASSERT(!movedIndexes.isEmpty());
// It's only possible for things to move right, and even that's only an option
// for children of parent, but not their descendants. ie, children of C D and E will not need to be reparented.
// They are already in the correct positions.
QList<QPersistentModelIndex> &existingSourceIndexes = m_childIndexes[sourceParent];
QList<QPersistentModelIndex> existingDestinationIndexes = m_childIndexes[destinationParent];
QModelIndex proxySourceParent = q->mapFromSource(sourceParent);
QModelIndex proxyDestinationParent = q->mapFromSource(destinationParent);
// That is, start position of indexes to be moved from the source parent.
int proxySourceStart = m_childIndexes.value(sourceParent).indexOf(movedIndexes.at(0));
int proxySourceEnd = proxySourceStart + movedIndexes.size() - 1;
// The moved indexes are appended to the destinationParent. Nothing else is possible.
// If they were to be inserted in the middle somewhere, they would already be there.
int destinationRow = existingDestinationIndexes.size();
bool allowMove = q->beginMoveRows(proxySourceParent, proxySourceStart, proxySourceEnd, proxyDestinationParent, destinationRow);
Q_ASSERT(allowMove);
for (int row = proxySourceEnd; row >= proxySourceStart; --row)
{
existingSourceIndexes.removeAt(row);
}
QHash<QModelIndex, QModelIndexList> mapping;
mapping.insert(destinationParent, movedIndexes);
mergeDescendants(mapping, destinationParent, existingDestinationIndexes.size());
q->endMoveRows();
if (!mappings.contains(q->mapToSource(proxyDestinationParent.parent())))
break;
destinationParent = q->mapToSource(proxyDestinationParent.parent());
movedIndexes = mappings.value(destinationParent);
}
}
KReparentingProxyModel::KReparentingProxyModel(QObject* parent)
: QAbstractProxyModel(parent), d_ptr(new KReparentingProxyModelPrivate(this))
{
}
KReparentingProxyModel::~KReparentingProxyModel()
{
delete d_ptr;
}
bool KReparentingProxyModelPrivate::isDescendantInModel(const QModelIndex& ancestor, const QModelIndex& descendant) const
{
kDebug() << ancestor.data() << descendant.data();
// if (!ancestor.isValid())
// return true;
QModelIndex _ancestor = descendant.parent();
while (_ancestor.isValid())
{
if (_ancestor == ancestor)
return true;
_ancestor = _ancestor.parent();
}
return (!ancestor.isValid() && descendant.isValid());
}
bool KReparentingProxyModel::isDescendantOf(const QModelIndex& ancestor, const QModelIndex& descendant) const
{
Q_D( const KReparentingProxyModel );
return d->isDescendantInModel(ancestor, descendant);
// return (!ancestor.isValid() && descendant.isValid());
}
QModelIndex KReparentingProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
{
Q_D( const KReparentingProxyModel );
if (!sourceIndex.isValid())
return QModelIndex();
QModelIndex sourceIndexFirstColumn = sourceIndex.sibling(sourceIndex.row(), 0);
QHash<QPersistentModelIndex, QList<QPersistentModelIndex> >::const_iterator it;
const QHash<QPersistentModelIndex, QList<QPersistentModelIndex> >::const_iterator begin = d->m_childIndexes.constBegin();
const QHash<QPersistentModelIndex, QList<QPersistentModelIndex> >::const_iterator end = d->m_childIndexes.constEnd();
for(it = begin; it != end; ++it)
{
QList<QPersistentModelIndex> list = it.value();
if (list.contains(sourceIndexFirstColumn))
{
QModelIndex sourceParent = it.key();
QModelIndex proxyParent = mapFromSource(sourceParent);
int row = list.indexOf(sourceIndexFirstColumn);
// There must have been a mapping made for it.
Q_ASSERT(d->m_parents.values().contains(sourceParent));
qint64 id = d->m_parents.key(sourceParent);
// id refers to the parent.
return createIndex(row, sourceIndex.column(), reinterpret_cast<void*>(id));
}
}
return QModelIndex();
}
QModelIndex KReparentingProxyModel::mapToSource(const QModelIndex& proxyIndex) const
{
Q_D( const KReparentingProxyModel );
// kDebug() << "MMMMMM" << proxyIndex;
if (!proxyIndex.isValid())
return QModelIndex();
qint64 id = reinterpret_cast<qint64>(proxyIndex.internalPointer());
// if (!d->m_parents.contains(id))
// kDebug() << d->m_parents << id;
QModelIndex sourceParent;
if (d->m_pendingRemovalParents.contains(id))
{
// kDebug() << "pending";
sourceParent = d->m_pendingRemovalParents.value(id);
} else {
Q_ASSERT(d->m_parents.contains(id));
sourceParent = d->m_parents.value(id);
}
// kDebug() << sourceParent << sourceParent.data();
QModelIndex sourceIndexFirstColumn;
if (d->m_pendingRemovalChildIndexes.contains(sourceParent)) {
// kDebug() << "#############";
foreach( const KReparentingProxyModelPrivate::PendingRemoval &pendingRemoval, d->m_pendingRemovals)
{
// kDebug() << "In" << pendingRemoval.index << pendingRemoval.sourceIndex << sourceParent;
if (pendingRemoval.sourceIndex == sourceParent)
{
// kDebug() << "Out" << pendingRemoval.sourceIndex << sourceParent;
int proxyRow = proxyIndex.row();
int row = proxyRow - pendingRemoval.start;
// kDebug() << d->m_pendingRemovalChildIndexes.value(sourceParent) << proxyRow << row << pendingRemoval.end;
if (proxyRow > pendingRemoval.end)
{
Q_ASSERT(d->m_childIndexes.contains(sourceParent));
row = proxyRow - (pendingRemoval.end - pendingRemoval.start + 1);
// kDebug() << "new row" << row;
sourceIndexFirstColumn = d->m_childIndexes.value(sourceParent).at(row);
}
else
sourceIndexFirstColumn = d->m_pendingRemovalChildIndexes.value(sourceParent).at(row);
break;
}
}
} else {
Q_ASSERT(d->m_childIndexes.contains(sourceParent));
sourceIndexFirstColumn = d->m_childIndexes.value(sourceParent).at(proxyIndex.row());
}
Q_ASSERT(sourceIndexFirstColumn.isValid());
return sourceIndexFirstColumn.sibling(sourceIndexFirstColumn.row(), proxyIndex.column());
}
int KReparentingProxyModel::columnCount(const QModelIndex& parent) const
{
Q_D( const KReparentingProxyModel );
if (!parent.isValid())
return sourceModel()->columnCount();
if ( parent.column() > 0 )
{
return 0;
}
QModelIndex sourceIndex = mapToSource(parent);
return (d->m_childIndexes.value(sourceIndex).size() > 0)
? sourceModel()->columnCount() : 0;
}
QVariant KReparentingProxyModel::data(const QModelIndex& proxyIndex, int role) const
{
return QAbstractProxyModel::data(proxyIndex, role);
}
QModelIndex KReparentingProxyModel::index(int row, int column, const QModelIndex& parent) const
{
Q_D( const KReparentingProxyModel );
if (!hasIndex(row, column, parent))
return QModelIndex();
QModelIndex sourceParent = mapToSource(parent);
// if (!d->m_pendingRemovals.isEmpty())
// kDebug() << sourceParent << sourceParent.data();
// ### This is where we need to have the children of removed indexes stored.
// if (!d->m_parents.values().contains(sourceParent))
// {
// kDebug() << d->m_pendingRemovalParents.values() << sourceParent << d->m_pendingRemovalParents.values().contains(sourceParent);
// }
qint64 id;
if(d->m_pendingRemovalParents.values().contains(sourceParent))
{
id = d->m_pendingRemovalParents.key(sourceParent);
} else {
// There must have been a mapping made for it.
Q_ASSERT(d->m_parents.values().contains(sourceParent));
id = d->m_parents.key(sourceParent);
}
return createIndex(row, column, reinterpret_cast<void*>(id));
}
QModelIndex KReparentingProxyModel::parent(const QModelIndex& child) const
{
Q_D( const KReparentingProxyModel );
if (!child.isValid())
return QModelIndex();
QModelIndex sourceIndex = mapToSource(child);
QModelIndex firstColumnChild = sourceIndex;
if (sourceIndex.column() > 0)
firstColumnChild = sourceIndex.sibling(sourceIndex.row(), 0);
QHashIterator<QPersistentModelIndex, QList<QPersistentModelIndex> > itPending(d->m_pendingRemovalChildIndexes);
while (itPending.hasNext())
{
itPending.next();
if (itPending.value().contains(firstColumnChild))
{
return mapFromSource(itPending.key());
}
}
QHashIterator<QPersistentModelIndex, QList<QPersistentModelIndex> > it(d->m_childIndexes);
while (it.hasNext())
{
it.next();
if (it.value().contains(firstColumnChild))
{
return mapFromSource(it.key());
}
}
return QModelIndex();
}
int KReparentingProxyModelPrivate::pendingRemovalRowCount(const QModelIndex &sourceIndex) const
{
foreach(const PendingRemoval &pendingRemoval, m_pendingRemovals)
{
// kDebug() << pendingRemoval.sourceIndex;
if (pendingRemoval.sourceIndex == sourceIndex)
return pendingRemoval.end - pendingRemoval.start + 1;
}
return 0;
}
int KReparentingProxyModel::rowCount(const QModelIndex& parent) const
{
Q_D( const KReparentingProxyModel );
if ( parent.column() > 0 )
return 0;
QModelIndex sourceIndex = mapToSource(parent);
int size = d->m_childIndexes.value(sourceIndex).size() + d->m_pendingRemovalChildIndexes.value(sourceIndex).size();
// kDebug() << d->m_pendingRemovalChildIndexes.value(sourceIndex).size();
// if (!d->m_pendingRemovals.isEmpty())
// {
// kDebug() << "SIZE" << sourceIndex << sourceIndex.data() << size << d->m_pendingRemovals.size() << d->pendingRemovalRowCount(sourceIndex);
// }
return size;
}
bool KReparentingProxyModel::hasChildren(const QModelIndex& parent) const
{
return rowCount(parent) > 0;
}
void KReparentingProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
{
Q_D( KReparentingProxyModel );
beginResetModel();
disconnect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
disconnect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
disconnect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
disconnect(sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
this, SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
disconnect(sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
this, SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)));
disconnect(sourceModel, SIGNAL(modelAboutToBeReset()),
this, SLOT(sourceModelAboutToBeReset()));
disconnect(sourceModel, SIGNAL(modelReset()),
this, SLOT(sourceModelReset()));
disconnect(sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
disconnect(sourceModel, SIGNAL(layoutAboutToBeChanged()),
this, SLOT(sourceLayoutAboutToBeChanged()));
disconnect(sourceModel, SIGNAL(layoutChanged()),
this, SLOT(sourceLayoutChanged()));
QAbstractProxyModel::setSourceModel(sourceModel);
QHash<QModelIndex, QModelIndexList> mappings = d->recreateMappings(QModelIndex(), 0, sourceModel->rowCount() - 1, KReparentingProxyModelPrivate::MapDescendants);
d->mergeDescendants(mappings, QModelIndex(), 0);
connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
SLOT(sourceRowsInserted(QModelIndex,int,int)));
connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
SLOT(sourceRowsRemoved(QModelIndex,int,int)));
connect(sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
connect(sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)));
connect(sourceModel, SIGNAL(modelAboutToBeReset()),
SLOT(sourceModelAboutToBeReset()));
connect(sourceModel, SIGNAL(modelReset()),
SLOT(sourceModelReset()));
connect(sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
connect(sourceModel, SIGNAL(layoutAboutToBeChanged()),
SLOT(sourceLayoutAboutToBeChanged()));
connect(sourceModel, SIGNAL(layoutChanged()),
SLOT(sourceLayoutChanged()));
endResetModel();
}
void KReparentingProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
{
Q_Q(KReparentingProxyModel);
// We can't figure out the structure until the indexes are in the model.
// Store the signal until the new rows are actually there in sourceRowsInserted.
PendingInsertion pendingInsertion(parent, start, end);
m_pendingInsertions.insert(parent, pendingInsertion);
}
QHash<QModelIndex, QModelIndexList> KReparentingProxyModelPrivate::mergeDescendants(QHash<QModelIndex, QModelIndexList> mappings, const QModelIndex &parent, int start)
{
QModelIndexList childIndexes = mappings.take(parent);
kDebug() << childIndexes;
if (!childIndexes.isEmpty())
{
if (!m_parents.values().contains(parent))
{
m_parents.insert(newId(), QPersistentModelIndex(parent));
}
}
int row = start;
foreach (const QModelIndex &idx, childIndexes)
{
m_childIndexes[parent].insert(row++, QPersistentModelIndex(idx));
mappings = mergeDescendants(mappings, idx, 0);
}
return mappings;
}
QHash<QModelIndex, QModelIndexList> KReparentingProxyModelPrivate::insertTree( QHash<QModelIndex, QModelIndexList> mappings, const QModelIndex &parent )
{
return QHash<QModelIndex, QModelIndexList>();
}
void KReparentingProxyModelPrivate::handleInsertion(const PendingInsertion &pendingInsertion)
{
Q_Q(KReparentingProxyModel);
QModelIndex parent = pendingInsertion.index;
int start = pendingInsertion.start;
int end = pendingInsertion.end;
kDebug() << parent << parent.data() << start << end;
// for (int i = start; i < end; ++i)
// {
// QModelIndex idx = q->sourceModel()->index(i, 0, parent);
// kDebug() << idx << idx.data();
// }
QHash<QModelIndex, QModelIndexList> newItemMappings = recreateMappings(parent, start, end, KReparentingProxyModelPrivate::MapDescendants);
// iterate over keys. if key in keys iterate up. This gives list of top level parents.
// Pick the one whoes parent is @p parent. Insert it. Look up until find the parent of another one and insert that.
// If one of the parents is invalid it is necessarily the last one to be processed (if there are more to process, they'll be children of it)
// That case should work too.
kDebug() << "new item mappings" << newItemMappings;
const int column = 0;
kDebug() << m_childIndexes.contains(parent);
if (newItemMappings.contains(parent))
{
QModelIndexList newItemList = newItemMappings.value(parent);
kDebug() << "newItemList" << newItemList;
int proxyStart = 0;
// A single insertion in the source model might be multiple insertions in the proxy model.
forever
{
if (newItemList.isEmpty())
{
if (!newItemMappings.contains(parent.parent()))
break;
newItemList = newItemMappings.value(parent.parent());
continue;
}
proxyStart = 0;
QModelIndex proxyParent = q->mapFromSource(parent);
if (start > 0)
{
QModelIndex lastDesc = q->mapFromSource(getLastDescendant(q->sourceModel()->index(start - 1, column, parent)));
while (lastDesc.parent() != proxyParent)
{
lastDesc = lastDesc.parent();
}
proxyStart = lastDesc.row() + 1;
}
q->beginInsertRows(proxyParent, proxyStart, proxyStart + newItemList.size() - 1);
newItemMappings = mergeDescendants(newItemMappings, parent, proxyStart);
q->endInsertRows();
if (!newItemMappings.contains(parent.parent()))
break;
newItemList = newItemMappings.value(parent.parent());
}
}
// // The rest are not descendants of pendingInsertion.index in the proxy model, but are elsewhere.
// foreach(const QModelIndex &parent, newItemMappings.keys())
// {
//
// }
return;
}
void KReparentingProxyModelPrivate::sourceRowsInserted(const QModelIndex &parent, int start, int end)
{
Q_Q(KReparentingProxyModel);
if (m_pendingInsertions.contains(parent))
{
PendingInsertion pendingInsertion = m_pendingInsertions.value(parent);
handleInsertion(pendingInsertion);
if (q->sourceModel()->rowCount(parent) <= (end + 1))
return;
// The presence of new rows might affect the structure of indexes below.
verifyStructure(parent, end + 1);
}
}
void KReparentingProxyModelPrivate::removeTree(const QPersistentModelIndex &idxToRemove, int start, int end)
{
if (!m_childIndexes.contains(idxToRemove))
return;
// kDebug() << "idxToRemove" << idxToRemove << start << end;
QList<QPersistentModelIndex> &toRemove = m_childIndexes[ idxToRemove ];
// kDebug() << toRemove << toRemove.size();
// QList<int> intList;
// intList << 1 << 2 << 3 << 4 << 5;
//
// QList<int>::iterator intit = intList.begin();
// QList<int>::iterator intendIt = intList.end();
//
// if (end == 0)
// intendIt = intit + 1;
//
// if (end > 0)
// {
// intendIt = intit + (end - start + 1) + 1;
// kDebug() << "intend" << *intendIt;
// }
// intit += start;
//
// while (intit != intendIt)
// {
// int i = *intit;
// kDebug() << i;
// intit = intList.erase(intit);
// }
QList<QPersistentModelIndex>::iterator it = toRemove.begin();
QList<QPersistentModelIndex>::iterator endIt = toRemove.end();
if (end == 0)
endIt = it + 1;
if (end > 0)
{
endIt = it + (end - start + 1) + 1;
}
it += start;
int i = start;
while(it != endIt)
{
QPersistentModelIndex idx = *it;
// kDebug() << "removing" << idx << idx.data();
if (m_parents.values().contains(idx))
{
qint64 key = m_parents.key(idx);
QPersistentModelIndex value = m_parents.take(key);
m_pendingRemovalParents.insert(key, value);
// kDebug() << "take from parent" << value;
}
removeTree(idx);
++i;
m_pendingRemovalChildIndexes[idxToRemove].append(idx);
// kDebug() << idxToRemove << idxToRemove.data() << idx << idx.data();
it = toRemove.erase(it);
// kDebug() << (it == endIt);
// if (i > end)
// break;
// if (it == toRemove.end())
// break;
}
// kDebug() << "toRemove" << toRemove;
// for(int i = start; (i <= end || (end == -1 && toRemove.size() > i)); )
// {
// kDebug() << i;
// QPersistentModelIndex idx = toRemove.takeAt(i);
// --end;
//
// kDebug() << "removing" << idx.data();
//
// if (m_parents.values().contains(idx))
// {
// QPersistentModelIndex bah = m_parents.take(m_parents.key(idx));
// // kDebug() << "take from parent" << bah;
// }
// removeTree(idx);
// }
}
void KReparentingProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
Q_Q(KReparentingProxyModel);
kDebug() << parent << start << end;
// This is really tricky.
//
// We could have something like:
//
// A A
// B - B
// C -> - - C
// D D
// E - E
//
// And have to remove something like B to D. That would mean a remove signal for B, move E to its grandparent, remove D.
// QHashIterator<QPersistentModelIndex, QList< QPersistentModelIndex> > it(m_childIndexes);
// while (it.hasNext())
// {
// it.next();
// kDebug() << it.key() << it.key().data();
// kDebug() << it.value();
// }
const int column = 0;
QModelIndex firstAffectedIndex = q->mapFromSource(q->sourceModel()->index(start, column, parent));
QModelIndex lastAffectedIndex = q->mapFromSource(q->sourceModel()->index(end, column, parent));
kDebug() << "firstAffectedIndex" << firstAffectedIndex.data();
kDebug() << "lastAffectedIndex" << lastAffectedIndex.data();
QModelIndex proxyParent = firstAffectedIndex.parent();
Q_ASSERT(firstAffectedIndex.isValid() && lastAffectedIndex.isValid());
forever
{
if (isDescendantInModel(proxyParent, lastAffectedIndex))
{
// They share a common ancestor.
QModelIndex _parent = lastAffectedIndex.parent();
QModelIndex lastAffectedAncestor = lastAffectedIndex;
kDebug() << "last affected ancestor" << lastAffectedAncestor.data();
while (_parent != proxyParent)
{
lastAffectedAncestor = _parent;
_parent = _parent.parent();
}
if (q->hasChildren(lastAffectedAncestor))
{
QModelIndex next = q->index(0, 0, lastAffectedAncestor);
QModelIndex proxySourceParent = lastAffectedAncestor;
int startRow = next.row();
int lastRow = q->rowCount(lastAffectedAncestor) - 1;
QList<QPersistentModelIndex> &existingSourceIndexes = m_childIndexes[q->mapToSource(proxySourceParent)];
QList<QPersistentModelIndex> &existingDestinationIndexes = m_childIndexes[q->mapToSource(proxyParent)];
int destRow = lastAffectedAncestor.row() + 1;
kDebug() << "Move from" << lastAffectedAncestor.data() << startRow << lastRow << " To " << proxyParent.data() << destRow;
bool allowMove = q->beginMoveRows(lastAffectedAncestor, startRow, lastRow, proxyParent, destRow);
Q_ASSERT(allowMove);
for (int i = startRow; i <= lastRow; ++i)
{
QPersistentModelIndex movingIdx = existingSourceIndexes.takeAt(startRow);
existingDestinationIndexes.insert(destRow + (i - startRow), movingIdx);
}
// TODO: If source was a parent before, it might not be now.
// dest was already a parent.
q->endMoveRows();
}
PendingRemoval removal;
removal.index = proxyParent;
removal.start = firstAffectedIndex.row();
removal.end = lastAffectedAncestor.row();
removal.parentId = proxyParent.internalId();
removal.sourceIndex = q->mapToSource(proxyParent);
m_pendingRemovals.append(removal);
removeTree(q->mapToSource(proxyParent), removal.start, removal.end);
kDebug() << "beg rem 1";
q->beginRemoveRows(proxyParent, removal.start, removal.end);
return;
} else {
QModelIndex next = getIndexBelow(firstAffectedIndex);
proxyParent = next.parent();
while (isDescendantInModel(proxyParent, next))
{
next = getIndexBelow(next);
}
QModelIndex _parent = next.parent();
QModelIndex lastAffectedAncestor = next;
while (_parent != proxyParent)
{
lastAffectedAncestor = _parent;
_parent = _parent.parent();
}
PendingRemoval removal;
removal.index = proxyParent;
removal.start = firstAffectedIndex.row();
removal.end = lastAffectedAncestor.row();
removal.parentId = proxyParent.internalId();
removal.sourceIndex = q->mapToSource(proxyParent);
m_pendingRemovals.append(removal);
removeTree(q->mapToSource(proxyParent), removal.start, removal.end);
kDebug() << "beg rem 1";
q->beginRemoveRows(proxyParent, removal.start, removal.end);
proxyParent = next.parent();
}
}
// // kDebug() << proxyParent.data() << lastAffectedIndex.parent().data() << proxyParent << lastAffectedIndex.parent();
// if (proxyParent == lastAffectedIndex.parent())
// {
// PendingRemoval removal;
// removal.index = proxyParent;
// removal.start = firstAffectedIndex.row();
// removal.end = lastAffectedIndex.row();
// removal.parentId = proxyParent.internalId();
// removal.sourceIndex = q->mapToSource(proxyParent);
// m_pendingRemovals.append(removal);
//
// // Also need to store a removal object for each of the descendants.
//
// removeTree(q->mapToSource(proxyParent), removal.start, removal.end);
//
// // kDebug() << "beg rem 1";
// q->beginRemoveRows(proxyParent, removal.start, removal.end);
// return;
// }
//
// QModelIndex lastParent = lastAffectedIndex.parent();
// while (lastParent.parent().isValid())
// {
// if (lastParent.parent() == proxyParent)
// {
// PendingRemoval removal;
// removal.index = proxyParent;
// removal.start = firstAffectedIndex.row();
// removal.end = lastParent.row();
// removal.parentId = proxyParent.internalId();
// removal.sourceIndex = q->mapToSource(proxyParent);
// m_pendingRemovals.append(removal);
//
// // kDebug() << "beg rem 2";
// q->beginRemoveRows(proxyParent, removal.start, removal.end);
// return;
// }
// lastParent = lastParent.parent();
// }
//
// // Several blocks need to be removed from the proxy model.
// // Divide and conquer to find them.
//
// int proxyStart = firstAffectedIndex.row();
// int proxyEnd = proxyStart + (end - start);
// int processedUntil = start;
//
// while (processedUntil <= end)
// {
// QModelIndex lastInParent = findLastInParent(proxyParent, proxyStart, proxyEnd);
// qDebug() << "lastInParent" << lastInParent;
//
// QModelIndex sourceLast = q->mapToSource(lastInParent);
// processedUntil = sourceLast.row();
//
// PendingRemoval removal;
// removal.index = proxyParent;
// removal.start = proxyStart;
// removal.end = lastInParent.row();
// removal.parentId = proxyParent.internalId();
// removal.sourceIndex = q->mapToSource(proxyParent);
// m_pendingRemovals.append(removal);
//
// kDebug() << "beg rem 3";
// q->beginRemoveRows(proxyParent, removal.start, removal.end);
//
// QModelIndex proxyIndexBelow = getIndexBelow(lastInParent, q);
//
// if (!proxyIndexBelow.isValid())
// return;
//
// proxyParent = proxyIndexBelow.parent();
// proxyStart = proxyIndexBelow.row();
// }
}
QModelIndex KReparentingProxyModelPrivate::findLastInParent(QModelIndex parent, int start, int end)
{
Q_Q(KReparentingProxyModel);
const int column = 0;
if (start == end)
return q->index(start, column, parent);
int middle = start + (end - start / 2);
QModelIndex sourceParent = q->mapToSource(parent);
QModelIndex middleIndex = q->mapFromSource(q->sourceModel()->index(middle, column, sourceParent));
if (middleIndex.parent() == parent)
{
return findLastInParent(parent, middle, end);
} else {
return findLastInParent(parent, start + ((middle - start) / 2), middle);
}
}
// qDebug() << affectedIndex << affectedIndex.data() << proxyParent;
//
// QHash<QModelIndex, PendingRemoval> pendingRemovals;
//
// int i = start;
// while (i <= end)
// {
// affectedIndex = affectedIndex.sibling(i, column);
//
// // affectedIndex = getIndexBelow(affectedIndex, q);
// if (!affectedIndex.isValid())
// break;
// // Q_ASSERT(affectedIndex.isValid());
//
// if (affectedIndex.parent() != proxyParent)
// {
// // affectedIndex.parent() must be left of proxyParent
//
// PendingRemoval removal;
// removal.index = proxyParent;
// removal.start = start;
// removal.end = i;
// pendingRemovals.insert(proxyParent, removal);
//
// emit q->rowsAboutToBeRemoved(proxyParent, start, i);
// proxyParent = affectedIndex.parent();
//
// end -= (i - start + 1);
// start = affectedIndex.row();
// i = start;
// }
//
// ++i;
// }
// Move younger siblings out of the way so that the rows can be removed easily
// No. It's easier to use verifyStructure afterward.
// // Removing rows in the source model could require sending the children to their grandparents.
//
// QHash<QModelIndex, QModelIndexList> mappings;
// recreateMappings(parent, start, end);
//
// QHashIterator<QModelIndex, QModelIndexList> it(mappings);
// while (it.hasNext())
// {
// it.next();
// QModelIndexList removedList = it.value();
// PendingRemoval pendingRemoval;
// pendingRemoval.index = it.key();
// pendingRemoval.start = q->mapFromSource(removedList.at(0)).row();
// pendingRemoval.end = pendingRemoval.start + removedList.size() - 1;
// m_pendingRemovals.insert(parent, pendingRemoval);
// }
// }
void KReparentingProxyModelPrivate::handleRemoval(const PendingRemoval &pendingRemoval)
{
// Q_Q(KReparentingProxyModel);
// q->beginRemoveRows(pendingRemoval.index, pendingRemoval.start, pendingRemoval.end);
// m_childIndexes.remove(pendingRemoval.index);
// // Remove stuff from m_parents.
// q->endRemoveRows();
}
void KReparentingProxyModelPrivate::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
{
kDebug() << parent << start << end;
Q_Q(KReparentingProxyModel);
// loop over pending removals and process each one. Then look after the last one
// to move displaced rows to where they should be.
int lastAffectedRow = m_pendingRemovals.last().end;
QModelIndex lastAffectedIndex = m_pendingRemovals.last().index;
QMutableVectorIterator<PendingRemoval> it(m_pendingRemovals);
while (it.hasNext())
{
PendingRemoval removal = it.next();
m_pendingRemovalChildIndexes.remove(removal.sourceIndex);
m_pendingRemovalParents.remove(parent.internalId());
it.remove();
emit q->endRemoveRows();
}
kDebug() << "Remove done ##########";
kDebug() << lastAffectedIndex << lastAffectedIndex.data() << lastAffectedRow;
verifyStructure(lastAffectedIndex, lastAffectedRow - 1);
}
void KReparentingProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex& destParent, int destRow)
{
// This could be several individual moves in the proxy model, or it could be no moves at all.
// We can get the top indexes of the moved list and move those.
// because their children won't be moved anywhere different.
Q_Q(KReparentingProxyModel);
QModelIndex proxySourceParent = q->mapFromSource(parent);
QModelIndex proxyDestinationParent = q->mapFromSource(destParent);
// I could look at the indexes between start and end (proxied could be several blocks), and move them to dest.
// Then verify structure.
// This could lead to an illegal move.
// If we have
//
// Source: Proxy:
// A A
// B B
// C - C
// D - D
// E E
//
// then source can legally move B to between C and D, however, implemented naively the proxymodel would attempt an illegal move.
// We must first reparent everything below destRow in the proxy to the parent of parent in this case, then perform the move, then
// verifyStructure.
//
// Moving B C and D to below E would be a legal move in the proxy model.
//
// Children of moved indexes which are not themselves moved must be first sent to their grandparents.
// So if B and C were moved in the source model above to below E, D would first be moved to its grandparent, then B would be moved below E,
// then the structure would need to be verified.
//
// Proxy start state: Intermediate state: Intermediate or final state: Possible alternative final state:
// A A A A
// B B E E
// - C - C D - D
// - D D B B
// E E - C - C
// So, I could iterate from start to end in proxySourceParent and if the depth goes less than parent, emit a block move, then start again.
QHash<QModelIndex, QModelIndexList> newMappings = recreateMappings(parent, start, end);
}
void KReparentingProxyModelPrivate::sourceRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex& destParent, int destRow)
{
}
void KReparentingProxyModelPrivate::sourceLayoutAboutToBeChanged()
{
Q_Q(KReparentingProxyModel);
emit q->layoutAboutToBeChanged();
foreach( const QPersistentModelIndex &proxyPersistentIndex, q->persistentIndexList()) {
m_proxyIndexes << proxyPersistentIndex;
m_layoutChangePersistentIndexes << QPersistentModelIndex(q->mapToSource(proxyPersistentIndex));
}
}
void KReparentingProxyModelPrivate::sourceLayoutChanged()
{
Q_Q(KReparentingProxyModel);
for(int i = 0; i < m_proxyIndexes.size(); ++i)
{
q->changePersistentIndex(m_proxyIndexes.at(i), q->mapFromSource(m_layoutChangePersistentIndexes.at(i)));
}
m_layoutChangePersistentIndexes.clear();
m_proxyIndexes.clear();
emit q->layoutChanged();
}
void KReparentingProxyModelPrivate::sourceModelAboutToBeReset()
{
Q_Q(KReparentingProxyModel);
q->beginResetModel();
}
void KReparentingProxyModelPrivate::sourceModelReset()
{
Q_Q(KReparentingProxyModel);
m_parents.clear();
m_childIndexes.clear();
m_nextId = 0;
m_pendingInsertions.clear();
m_pendingRemovals.clear();
m_pendingRemovalChildIndexes.clear();
m_pendingRemovalParents.clear();
q->endResetModel();
}
void KReparentingProxyModelPrivate::emitDataChangedSignals(const QModelIndex &startIndex, int maxChanged)
{
Q_Q(KReparentingProxyModel);
QModelIndex proxyParent = startIndex.parent();
const int column = 0;
int numChanged = 1;
QModelIndex lastAffectedSibling = startIndex;
QModelIndex proxySibling = getIndexBelow(startIndex, q);
forever
{
if (proxySibling.parent() != proxyParent || numChanged >= maxChanged)
break;
numChanged++;
lastAffectedSibling = proxySibling;
proxySibling = getIndexBelow(proxySibling);
}
emit q->dataChanged(startIndex, lastAffectedSibling);
if (numChanged < maxChanged)
{
emitDataChangedSignals(proxySibling, maxChanged - numChanged);
}
}
void KReparentingProxyModelPrivate::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
Q_Q(KReparentingProxyModel);
QModelIndex parent = topLeft.parent();
const int start = topLeft.row();
const int end = bottomRight.row();
const int column = 0;
const int maxChanged = end - start + 1;
// Create mappings to the end because changing data can affect structure of siblings.
verifyStructure(parent, start);
// mapFromSource and emit signals.
QModelIndex proxyStartIndex = q->mapFromSource(q->sourceModel()->index(start, column, parent));
emitDataChangedSignals(proxyStartIndex, maxChanged);
}
Qt::DropActions KReparentingProxyModel::supportedDropActions() const
{
Q_ASSERT(sourceModel());
return sourceModel()->supportedDropActions();
}
#include "moc_kreparentingproxymodel.cpp"