mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
883 lines
29 KiB
C++
883 lines
29 KiB
C++
/*
|
|
Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
As a special exception, permission is given to link this program
|
|
with any edition of Qt, and distribute the resulting executable,
|
|
without including the source code for Qt in the source distribution.
|
|
*/
|
|
|
|
#include "incidencetreemodel_p.h"
|
|
|
|
#include <Akonadi/EntityTreeModel>
|
|
#include <QElapsedTimer>
|
|
|
|
using namespace Akonadi;
|
|
QDebug operator<<( QDebug s, const Node::Ptr &node );
|
|
|
|
static void calculateDepth( const Node::Ptr &node )
|
|
{
|
|
Q_ASSERT( node );
|
|
node->depth = node->parentNode ? 1+node->parentNode->depth : 0;
|
|
foreach( const Node::Ptr &child, node->directChilds )
|
|
calculateDepth( child );
|
|
}
|
|
|
|
bool lessThan( const Node::Ptr &node1, const Node::Ptr &node2 )
|
|
{
|
|
return node1->depth > node2->depth;
|
|
}
|
|
|
|
bool greaterThan( const PreNode::Ptr &node1, const PreNode::Ptr &node2 )
|
|
{
|
|
return node1->depth < node2->depth || node2->depth == -1;
|
|
}
|
|
|
|
static PreNode::List sortedPrenodes( const PreNode::List &nodes )
|
|
{
|
|
const int count = nodes.count();
|
|
QHash<QString,PreNode::Ptr> prenodeByUid;
|
|
PreNode::List remainingNodes = nodes;
|
|
|
|
while ( prenodeByUid.count() < count ) {
|
|
bool foundAtLeastOne = false; // this bool saves us from infinit looping if the parent doesn't exist
|
|
foreach( const PreNode::Ptr &node, remainingNodes ) {
|
|
Q_ASSERT( node );
|
|
const QString uid = node->incidence->instanceIdentifier();
|
|
const QString parentUid = node->incidence->relatedTo();
|
|
if ( parentUid.isEmpty() ) { // toplevel todo
|
|
prenodeByUid.insert( uid, node );
|
|
remainingNodes.removeAll( node );
|
|
node->depth = 0;
|
|
foundAtLeastOne = true;
|
|
} else {
|
|
if ( prenodeByUid.contains( parentUid ) ) {
|
|
node->depth = 1 + prenodeByUid.value( parentUid )->depth;
|
|
remainingNodes.removeAll( node );
|
|
prenodeByUid.insert( uid, node );
|
|
foundAtLeastOne = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !foundAtLeastOne ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
PreNode::List sorted = nodes;
|
|
qSort( sorted.begin(), sorted.end(), greaterThan );
|
|
return sorted;
|
|
}
|
|
|
|
IncidenceTreeModel::Private::Private( IncidenceTreeModel *qq,
|
|
const QStringList &mimeTypes ) : QObject()
|
|
, m_mimeTypes( mimeTypes )
|
|
, q( qq )
|
|
|
|
{
|
|
}
|
|
|
|
int IncidenceTreeModel::Private::rowForNode( const Node::Ptr &node ) const
|
|
{
|
|
// Returns it's row number
|
|
const int row = node->parentNode ? node->parentNode->directChilds.indexOf( node )
|
|
: m_toplevelNodeList.indexOf( node );
|
|
Q_ASSERT ( row != -1 );
|
|
return row;
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::assert_and_dump( bool condition, const QString &message )
|
|
{
|
|
if ( !condition ) {
|
|
kError() << "This should never happen: " << message;
|
|
dumpTree();
|
|
Q_ASSERT( false );
|
|
}
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::dumpTree()
|
|
{
|
|
foreach( const Node::Ptr &node, m_toplevelNodeList )
|
|
qDebug() << node;
|
|
}
|
|
|
|
QModelIndex IncidenceTreeModel::Private::indexForNode( const Node::Ptr &node ) const
|
|
{
|
|
if ( !node ) {
|
|
return QModelIndex();
|
|
}
|
|
const int row = node->parentNode ? node->parentNode->directChilds.indexOf( node )
|
|
: m_toplevelNodeList.indexOf( node );
|
|
|
|
Q_ASSERT( row != -1 );
|
|
return q->createIndex( row, 0, node.data() );
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::reset( bool silent )
|
|
{
|
|
if ( !silent ) {
|
|
q->beginResetModel();
|
|
}
|
|
m_toplevelNodeList.clear();
|
|
m_nodeMap.clear();
|
|
m_itemByUid.clear();
|
|
m_waitingForParent.clear();
|
|
m_uidMap.clear();
|
|
if ( q->sourceModel() ) {
|
|
const int sourceCount = q->sourceModel()->rowCount();
|
|
for ( int i=0; i<sourceCount; ++i ) {
|
|
PreNode::Ptr prenode = prenodeFromSourceRow( i );
|
|
if ( prenode && ( m_mimeTypes.isEmpty() || m_mimeTypes.contains( prenode->incidence->mimeType() ) ) )
|
|
insertNode( prenode, /**silent=*/true );
|
|
}
|
|
}
|
|
if ( !silent ) {
|
|
q->endResetModel();
|
|
}
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onHeaderDataChanged( Qt::Orientation orientation, int first, int last )
|
|
{
|
|
emit q->headerDataChanged( orientation, first, last );
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onDataChanged( const QModelIndex &begin, const QModelIndex &end )
|
|
{
|
|
Q_ASSERT( begin.isValid() );
|
|
Q_ASSERT( end.isValid() );
|
|
Q_ASSERT( q->sourceModel() );
|
|
Q_ASSERT( !begin.parent().isValid() );
|
|
Q_ASSERT( !end.parent().isValid() );
|
|
Q_ASSERT( begin.row() <= end.row() );
|
|
const int first_row = begin.row();
|
|
const int last_row = end.row();
|
|
|
|
for( int i=first_row; i<=last_row; ++i ) {
|
|
QModelIndex sourceIndex = q->sourceModel()->index( i, 0 );
|
|
Q_ASSERT( sourceIndex.isValid() );
|
|
QModelIndex index = q->mapFromSource( sourceIndex );
|
|
// Index might be invalid if we filter by incidence type.
|
|
if ( index.isValid() ) {
|
|
Q_ASSERT( index.internalPointer() );
|
|
|
|
// Did we this node change parent? If no, just emit dataChanged(), if
|
|
// yes, we must emit rowsMoved(), so we see a visual effect in the view.
|
|
Node *rawNode = reinterpret_cast<Node*>( index.internalPointer() );
|
|
Node::Ptr node = m_uidMap.value( rawNode->uid ); // Looks hackish but it's safe
|
|
Q_ASSERT( node );
|
|
Node::Ptr oldParentNode = node->parentNode;
|
|
Akonadi::Item item = q->data( index, Akonadi::EntityTreeModel::ItemRole ).value<Akonadi::Item>();
|
|
Q_ASSERT( item.isValid() );
|
|
KCalCore::Incidence::Ptr incidence = !item.hasPayload<KCalCore::Incidence::Ptr>() ? KCalCore::Incidence::Ptr() :
|
|
item.payload<KCalCore::Incidence::Ptr>();
|
|
if ( !incidence ) {
|
|
kError() << "Incidence shouldn't be invalid." << item.hasPayload() << item.id();
|
|
Q_ASSERT( false );
|
|
return;
|
|
}
|
|
m_itemByUid.insert( incidence->instanceIdentifier(), item );
|
|
|
|
Node::Ptr newParentNode;
|
|
const QString newParentUid = incidence->relatedTo();
|
|
if ( !newParentUid.isEmpty() ) {
|
|
Q_ASSERT( m_uidMap.contains( newParentUid ) );
|
|
newParentNode = m_uidMap.value( newParentUid );
|
|
Q_ASSERT( newParentNode );
|
|
}
|
|
|
|
const bool parentChanged = newParentNode.data() != oldParentNode.data();
|
|
|
|
if ( parentChanged ) {
|
|
const int fromRow = rowForNode( node );
|
|
int toRow = -1;
|
|
QModelIndex newParentIndex;
|
|
|
|
// Calculate parameters for beginMoveRows()
|
|
if ( newParentNode ) {
|
|
newParentIndex = q->mapFromSource( newParentNode->sourceIndex );
|
|
Q_ASSERT( newParentIndex.isValid() );
|
|
toRow = newParentNode->directChilds.count();
|
|
} else {
|
|
// New parent is 0, it's son of root now
|
|
newParentIndex = QModelIndex();
|
|
toRow = m_toplevelNodeList.count();
|
|
}
|
|
|
|
const bool res = q->beginMoveRows( /**fromParent*/index.parent(), fromRow,
|
|
fromRow, newParentIndex, toRow );
|
|
Q_ASSERT( res );
|
|
Q_UNUSED( res );
|
|
|
|
// Now that beginmoveRows() was called, we can do the actual moving:
|
|
if ( newParentNode ) {
|
|
newParentNode->directChilds.append( node ); // Add to new parent
|
|
node->parentNode = newParentNode;
|
|
|
|
if ( oldParentNode ) {
|
|
oldParentNode->directChilds.remove( fromRow ); // Remove from parent
|
|
Q_ASSERT( oldParentNode->directChilds.indexOf( node ) == -1 );
|
|
} else {
|
|
m_toplevelNodeList.remove( fromRow ); // Remove from root
|
|
Q_ASSERT( m_toplevelNodeList.indexOf( node ) == -1 );
|
|
}
|
|
} else {
|
|
// New parent is 0, it's son of root now
|
|
m_toplevelNodeList.append( node );
|
|
node->parentNode = Node::Ptr();
|
|
oldParentNode->directChilds.remove( fromRow );
|
|
Q_ASSERT( oldParentNode->directChilds.indexOf( node ) == -1 );
|
|
}
|
|
|
|
q->endMoveRows();
|
|
|
|
// index is rotten after the move, retrieve it again
|
|
index = indexForNode( node );
|
|
Q_ASSERT( index.isValid() );
|
|
|
|
if ( newParentNode )
|
|
emit q->indexChangedParent( index.parent() );
|
|
} else {
|
|
emit q->dataChanged( index, index );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onRowsAboutToBeInserted( const QModelIndex &parent, int, int )
|
|
{
|
|
// We are a reparenting proxy, the source proxy is flat
|
|
Q_ASSERT( !parent.isValid() );
|
|
Q_UNUSED( parent );
|
|
// Nothing to do yet. We don't know if all the new incidences in this range belong to the same
|
|
// parent yet.
|
|
}
|
|
|
|
PreNode::Ptr IncidenceTreeModel::Private::prenodeFromSourceRow( int row ) const
|
|
{
|
|
PreNode::Ptr node = PreNode::Ptr( new PreNode() );
|
|
node->sourceIndex = q->sourceModel()->index( row, 0, QModelIndex() );
|
|
Q_ASSERT( node->sourceIndex.isValid() );
|
|
Q_ASSERT( node->sourceIndex.model() == q->sourceModel() );
|
|
const Akonadi::Item item = node->sourceIndex.data( EntityTreeModel::ItemRole ).value<Akonadi::Item>();
|
|
|
|
if ( !item.isValid() ) {
|
|
// It's a Collection, ignore that, we only want items.
|
|
return PreNode::Ptr();
|
|
}
|
|
|
|
node->item = item;
|
|
node->incidence = item.payload<KCalCore::Incidence::Ptr>();
|
|
Q_ASSERT( node->incidence );
|
|
|
|
return node;
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onRowsInserted( const QModelIndex &parent, int begin, int end )
|
|
{
|
|
//QElapsedTimer timer;
|
|
//timer.start();
|
|
Q_ASSERT( !parent.isValid() );
|
|
Q_UNUSED( parent );
|
|
Q_ASSERT( begin <= end );
|
|
PreNode::List nodes;
|
|
for ( int i=begin; i<=end; ++i ) {
|
|
PreNode::Ptr node = prenodeFromSourceRow( i );
|
|
// if m_mimeTypes is empty, we ignore this feature
|
|
if ( !node || ( !m_mimeTypes.isEmpty() && !m_mimeTypes.contains( node->incidence->mimeType() ) ) )
|
|
continue;
|
|
nodes << node;
|
|
}
|
|
|
|
PreNode::List sortedNodes = sortedPrenodes( nodes );
|
|
|
|
foreach( const PreNode::Ptr &node, sortedNodes ) {
|
|
insertNode( node );
|
|
}
|
|
|
|
// view can now call KViewStateSaver::restoreState(), to expand nodes.
|
|
if ( end > begin )
|
|
emit q->batchInsertionFinished();
|
|
//kDebug() << "Took " << timer.elapsed() << " to insert " << end-begin+1;
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::insertNode( const PreNode::Ptr &prenode, bool silent )
|
|
{
|
|
KCalCore::Incidence::Ptr incidence = prenode->incidence;
|
|
Akonadi::Item item = prenode->item;
|
|
Node::Ptr node( new Node() );
|
|
node->sourceIndex = prenode->sourceIndex;
|
|
node->id = item.id();
|
|
node->uid = incidence->instanceIdentifier();
|
|
m_itemByUid.insert( node->uid, item );
|
|
//kDebug() << "New node " << node.data() << node->uid << node->id;
|
|
node->parentUid = incidence->relatedTo();
|
|
if ( node->uid == node->parentUid ) {
|
|
kWarning() << "Incidence with itself as parent!" << node->uid << "Akonadi item" << item.id() << "remoteId=" << item.remoteId();
|
|
node->parentUid.clear();
|
|
}
|
|
|
|
if (m_uidMap.contains(node->uid)) {
|
|
kWarning() << "Duplicate incidence detected. File a bug against the resource. collection=" << item.storageCollectionId();
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT( !m_nodeMap.contains( node->id ) );
|
|
m_uidMap.insert( node->uid, node );
|
|
m_nodeMap.insert( item.id(), node );
|
|
|
|
int rowToUse = -1;
|
|
bool mustInsertIntoParent = false;
|
|
|
|
const bool hasParent = !node->parentUid.isEmpty();
|
|
if ( hasParent ) {
|
|
// We have a parent, did he arrive yet ?
|
|
if ( m_uidMap.contains( node->parentUid ) ) {
|
|
node->parentNode = m_uidMap.value( node->parentUid );
|
|
|
|
// We can only insert after beginInsertRows(), because it affects rowCounts
|
|
mustInsertIntoParent = true;
|
|
rowToUse = node->parentNode->directChilds.count();
|
|
} else {
|
|
// Parent unknown, we are orphan for now
|
|
Q_ASSERT( !m_waitingForParent.contains( node->parentUid, node ) );
|
|
m_waitingForParent.insert( node->parentUid, node );
|
|
}
|
|
}
|
|
|
|
if ( !node->parentNode ) {
|
|
rowToUse = m_toplevelNodeList.count();
|
|
}
|
|
|
|
// Lets insert the row:
|
|
const QModelIndex &parent = indexForNode( node->parentNode );
|
|
if ( !silent )
|
|
q->beginInsertRows( parent, rowToUse, rowToUse );
|
|
|
|
if ( !node->parentNode ) {
|
|
m_toplevelNodeList.append( node );
|
|
}
|
|
|
|
if ( mustInsertIntoParent ) {
|
|
node->parentNode->directChilds.append( node );
|
|
}
|
|
|
|
if ( !silent )
|
|
q->endInsertRows();
|
|
|
|
// Are we a parent?
|
|
if ( m_waitingForParent.contains( node->uid ) ) {
|
|
Q_ASSERT( m_waitingForParent.count( node->uid ) > 0 );
|
|
QList<Node::Ptr> childs = m_waitingForParent.values( node->uid );
|
|
m_waitingForParent.remove( node->uid );
|
|
Q_ASSERT( !childs.isEmpty() );
|
|
const QModelIndex fromParent = QModelIndex();
|
|
|
|
foreach( const Node::Ptr &child, childs ) {
|
|
const int fromRow = m_toplevelNodeList.indexOf( child );
|
|
Q_ASSERT( fromRow != -1 );
|
|
const QModelIndex toParent = indexForNode( node );
|
|
Q_ASSERT( toParent.isValid() );
|
|
Q_ASSERT( toParent.model() == q );
|
|
//const int toRow = node->directChilds.count();
|
|
|
|
if ( !silent ) {
|
|
//const bool res = q->beginMoveRows( /**fromParent*/QModelIndex(), fromRow,
|
|
// fromRow, toParent, toRow );
|
|
//emit q->layoutAboutToBeChanged();
|
|
q->beginResetModel();
|
|
//Q_ASSERT( res );
|
|
}
|
|
child->parentNode = node;
|
|
node->directChilds.append( child );
|
|
m_toplevelNodeList.remove( fromRow );
|
|
|
|
if ( !silent ) {
|
|
//q->endMoveRows();
|
|
q->endResetModel();
|
|
//emit q->layoutChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sorts childs first parents last
|
|
Node::List IncidenceTreeModel::Private::sorted( const Node::List &nodes ) const
|
|
{
|
|
if ( nodes.isEmpty() ) {
|
|
return nodes;
|
|
}
|
|
|
|
// Initialize depths
|
|
foreach( const Node::Ptr &topLevelNode, m_toplevelNodeList )
|
|
calculateDepth( topLevelNode );
|
|
|
|
Node::List sorted = nodes;
|
|
qSort( sorted.begin(), sorted.end(), lessThan );
|
|
|
|
return sorted;
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onRowsAboutToBeRemoved( const QModelIndex &parent, int begin, int end )
|
|
{
|
|
//QElapsedTimer timer;
|
|
//timer.start();
|
|
Q_ASSERT( !parent.isValid() );
|
|
Q_UNUSED( parent );
|
|
Q_ASSERT( begin <= end );
|
|
|
|
// First, gather nodes to remove
|
|
Node::List nodesToRemove;
|
|
for ( int i=begin; i<=end; ++i ) {
|
|
QModelIndex sourceIndex = q->sourceModel()->index( i, 0, QModelIndex() );
|
|
Q_ASSERT( sourceIndex.isValid() );
|
|
Q_ASSERT( sourceIndex.model() == q->sourceModel() );
|
|
const Akonadi::Item::Id id = sourceIndex.data( EntityTreeModel::ItemIdRole ).toLongLong();
|
|
Q_ASSERT( id != -1 );
|
|
if ( !m_nodeMap.contains( id ) ) {
|
|
// We don't know about this one because we're ignoring it's mime type.
|
|
Q_ASSERT( m_mimeTypes.count() != 3 );
|
|
continue;
|
|
}
|
|
Node::Ptr node = m_nodeMap.value( id );
|
|
Q_ASSERT( node->id == id );
|
|
nodesToRemove << node;
|
|
}
|
|
|
|
// We want to remove childs first, to avoid row moving
|
|
Node::List nodesToRemoveSorted = sorted( nodesToRemove );
|
|
|
|
foreach( const Node::Ptr &node, nodesToRemoveSorted ) {
|
|
// Go ahead and remove it now. We don't do it in ::onRowsRemoved(), because
|
|
// while unparenting childs with moveRows() the view might call data() on the
|
|
// item that is already removed from ETM.
|
|
removeNode( node );
|
|
//kDebug() << "Just removed a node, here's the tree";
|
|
//dumpTree();
|
|
}
|
|
|
|
m_removedNodes.clear();
|
|
//kDebug() << "Took " << timer.elapsed() << " to remove " << end-begin+1;
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::removeNode( const Node::Ptr &node )
|
|
{
|
|
Q_ASSERT( node );
|
|
//kDebug() << "Dealing with parent: " << node->id << node.data()
|
|
// << node->uid << node->directChilds.count() << indexForNode( node );
|
|
|
|
// First, unparent the children
|
|
if ( !node->directChilds.isEmpty() ) {
|
|
Node::List childs = node->directChilds;
|
|
const QModelIndex fromParent = indexForNode( node );
|
|
Q_ASSERT( fromParent.isValid() );
|
|
// const int firstSourceRow = 0;
|
|
// const int lastSourceRow = node->directChilds.count() - 1;
|
|
//const int toRow = m_toplevelNodeList.count();
|
|
//q->beginMoveRows( fromParent, firstSourceRow, lastSourceRow,
|
|
// /**toParent is root*/QModelIndex(), toRow );
|
|
q->beginResetModel();
|
|
node->directChilds.clear();
|
|
foreach( const Node::Ptr &child, childs ) {
|
|
//kDebug() << "Dealing with child: " << child.data() << child->uid;
|
|
m_toplevelNodeList.append( child );
|
|
child->parentNode = Node::Ptr();
|
|
m_waitingForParent.insert( node->uid, child );
|
|
}
|
|
//q->endMoveRows();
|
|
q->endResetModel();
|
|
}
|
|
|
|
const QModelIndex parent = indexForNode( node->parentNode );
|
|
|
|
const int rowToRemove = rowForNode( node );
|
|
|
|
// Now remove the row
|
|
Q_ASSERT( !( parent.isValid() && parent.model() != q ) );
|
|
q->beginRemoveRows( parent, rowToRemove, rowToRemove );
|
|
m_itemByUid.remove( node->uid );
|
|
|
|
if ( parent.isValid() ) {
|
|
node->parentNode->directChilds.remove( rowToRemove );
|
|
node->parentNode = Node::Ptr();
|
|
} else {
|
|
m_toplevelNodeList.remove( rowToRemove );
|
|
}
|
|
|
|
if ( !node->parentUid.isEmpty() ) {
|
|
m_waitingForParent.remove( node->parentUid, node );
|
|
}
|
|
|
|
m_uidMap.remove( node->uid );
|
|
m_nodeMap.remove( node->id );
|
|
|
|
q->endRemoveRows();
|
|
m_removedNodes << node.data();
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onRowsRemoved( const QModelIndex &parent, int begin, int end )
|
|
{
|
|
Q_UNUSED( parent );
|
|
Q_UNUSED( begin );
|
|
Q_UNUSED( end );
|
|
// Nothing to do here, see comment on ::onRowsAboutToBeRemoved()
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onModelAboutToBeReset()
|
|
{
|
|
q->beginResetModel();
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onModelReset()
|
|
{
|
|
reset( /**silent=*/false );
|
|
q->endResetModel();
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onLayoutAboutToBeChanged()
|
|
{
|
|
Q_ASSERT( q->persistentIndexList().isEmpty() );
|
|
emit q->layoutAboutToBeChanged();
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onLayoutChanged()
|
|
{
|
|
reset( /**silent=*/true );
|
|
Q_ASSERT( q->persistentIndexList().isEmpty() );
|
|
emit q->layoutChanged();
|
|
}
|
|
|
|
void IncidenceTreeModel::Private::onRowsMoved( const QModelIndex &, int, int, const QModelIndex &, int )
|
|
{
|
|
// Not implemented yet
|
|
Q_ASSERT( false );
|
|
}
|
|
|
|
IncidenceTreeModel::IncidenceTreeModel( QObject *parent ) : QAbstractProxyModel( parent )
|
|
, d( new Private( this, QStringList() ) )
|
|
{
|
|
setObjectName( QLatin1String("IncidenceTreeModel") );
|
|
}
|
|
|
|
IncidenceTreeModel::IncidenceTreeModel( const QStringList &mimeTypes,
|
|
QObject *parent ) : QAbstractProxyModel( parent )
|
|
, d( new Private( this, mimeTypes ) )
|
|
{
|
|
setObjectName( QLatin1String("IncidenceTreeModel") );
|
|
}
|
|
|
|
IncidenceTreeModel::~IncidenceTreeModel()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
QVariant IncidenceTreeModel::data( const QModelIndex &index, int role ) const
|
|
{
|
|
Q_ASSERT( index.isValid() );
|
|
if ( !index.isValid() || !sourceModel() ) {
|
|
return QVariant();
|
|
}
|
|
|
|
QModelIndex sourceIndex = mapToSource( index );
|
|
Q_ASSERT( sourceIndex.isValid() );
|
|
|
|
return sourceModel()->data( sourceIndex, role );
|
|
}
|
|
|
|
int IncidenceTreeModel::rowCount( const QModelIndex &parent ) const
|
|
{
|
|
if ( parent.isValid() ) {
|
|
Q_ASSERT( parent.model() == this );
|
|
Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() );
|
|
Q_ASSERT( parentNode );
|
|
d->assert_and_dump( !d->m_removedNodes.contains( parentNode ),
|
|
QString::number( (quintptr)parentNode, 16 ) + QLatin1String(" was already deleted") );
|
|
|
|
const int count = parentNode->directChilds.count();
|
|
return count;
|
|
}
|
|
|
|
return d->m_toplevelNodeList.count();
|
|
}
|
|
|
|
int IncidenceTreeModel::columnCount( const QModelIndex &parent ) const
|
|
{
|
|
if ( parent.isValid() ) {
|
|
Q_ASSERT( parent.model() == this );
|
|
}
|
|
return sourceModel() ? sourceModel()->columnCount() : 1;
|
|
}
|
|
|
|
void IncidenceTreeModel::setSourceModel( QAbstractItemModel *model )
|
|
{
|
|
if ( model == sourceModel() ) {
|
|
return;
|
|
}
|
|
|
|
beginResetModel();
|
|
|
|
if ( sourceModel() ) {
|
|
disconnect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
|
|
d, SLOT(onDataChanged(QModelIndex,QModelIndex)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
|
|
d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
|
|
d, SLOT(onRowsInserted(QModelIndex,int,int)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
|
|
d, SLOT(onRowsRemoved(QModelIndex,int,int)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
|
d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
|
|
d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
|
|
d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(modelAboutToBeReset()),
|
|
d, SLOT(onModelAboutToBeReset()) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(modelReset()),
|
|
d, SLOT(onModelReset()) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(layoutAboutToBeChanged()),
|
|
d, SLOT(onLayoutAboutToBeChanged()) );
|
|
|
|
disconnect( sourceModel(), SIGNAL(layoutChanged()),
|
|
d, SLOT(onLayoutChanged()) );
|
|
}
|
|
|
|
QAbstractProxyModel::setSourceModel( model );
|
|
|
|
if ( sourceModel() ) {
|
|
connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
|
|
d, SLOT(onDataChanged(QModelIndex,QModelIndex)) );
|
|
|
|
connect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
|
|
d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) );
|
|
|
|
connect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
|
|
d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) );
|
|
|
|
connect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
|
|
d, SLOT(onRowsInserted(QModelIndex,int,int)) );
|
|
|
|
connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
|
|
d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) );
|
|
|
|
connect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
|
|
d, SLOT(onRowsRemoved(QModelIndex,int,int)) );
|
|
|
|
connect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
|
d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
|
|
|
connect( sourceModel(), SIGNAL(modelAboutToBeReset()),
|
|
d, SLOT(onModelAboutToBeReset()) );
|
|
|
|
connect( sourceModel(), SIGNAL(modelReset()),
|
|
d, SLOT(onModelReset()) );
|
|
|
|
connect( sourceModel(), SIGNAL(layoutAboutToBeChanged()),
|
|
d, SLOT(onLayoutAboutToBeChanged()) );
|
|
|
|
connect( sourceModel(), SIGNAL(layoutChanged()),
|
|
d, SLOT(onLayoutChanged()) );
|
|
}
|
|
|
|
d->reset( /**silent=*/true );
|
|
endResetModel();
|
|
}
|
|
|
|
QModelIndex IncidenceTreeModel::mapFromSource( const QModelIndex &sourceIndex ) const
|
|
{
|
|
if ( !sourceIndex.isValid() ) {
|
|
kWarning() << "IncidenceTreeModel::mapFromSource() source index is invalid";
|
|
// Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
|
|
if ( !sourceModel() ) {
|
|
return QModelIndex();
|
|
}
|
|
Q_ASSERT( sourceIndex.column() < sourceModel()->columnCount() );
|
|
Q_ASSERT( sourceModel() == sourceIndex.model() );
|
|
const Akonadi::Item::Id id = sourceIndex.data( Akonadi::EntityTreeModel::ItemIdRole ).toLongLong();
|
|
|
|
if ( id == -1 || !d->m_nodeMap.contains( id ) )
|
|
return QModelIndex();
|
|
|
|
const Node::Ptr node = d->m_nodeMap.value( id );
|
|
Q_ASSERT( node );
|
|
|
|
return d->indexForNode( node );
|
|
}
|
|
|
|
QModelIndex IncidenceTreeModel::mapToSource( const QModelIndex &proxyIndex ) const
|
|
{
|
|
if ( !proxyIndex.isValid() || !sourceModel() ) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
Q_ASSERT( proxyIndex.column() < columnCount() );
|
|
Q_ASSERT( proxyIndex.internalPointer() );
|
|
Q_ASSERT( proxyIndex.model() == this );
|
|
Node *node = reinterpret_cast<Node*>( proxyIndex.internalPointer() );
|
|
|
|
/*
|
|
This code is slow, using a persistent model index instead.
|
|
QModelIndexList indexes = EntityTreeModel::modelIndexesForItem( sourceModel(), Akonadi::Item( node->id ) );
|
|
if ( indexes.isEmpty() ) {
|
|
Q_ASSERT( sourceModel() );
|
|
kError() << "IncidenceTreeModel::mapToSource() no indexes."
|
|
<< proxyIndex << node->id << "; source.rowCount() = "
|
|
<< sourceModel()->rowCount() << "; source=" << sourceModel()
|
|
<< "rowCount()" << rowCount();
|
|
Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
QModelIndex index = indexes.first();*/
|
|
QModelIndex index = node->sourceIndex;
|
|
if ( !index.isValid() ) {
|
|
kWarning() << "IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid";
|
|
Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
Q_ASSERT( index.model() == sourceModel() );
|
|
|
|
return index.sibling( index.row(), proxyIndex.column() );
|
|
}
|
|
|
|
QModelIndex IncidenceTreeModel::parent( const QModelIndex &child ) const
|
|
{
|
|
if ( !child.isValid() ) {
|
|
kWarning() << "IncidenceTreeModel::parent(): child is invalid";
|
|
Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
|
|
Q_ASSERT( child.model() == this );
|
|
Q_ASSERT( child.internalPointer() );
|
|
Node *childNode = reinterpret_cast<Node*>( child.internalPointer() );
|
|
if ( d->m_removedNodes.contains( childNode ) ) {
|
|
kWarning() << "IncidenceTreeModel::parent() Node already removed.";
|
|
return QModelIndex();
|
|
}
|
|
|
|
if ( !childNode->parentNode ) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
const QModelIndex parentIndex = d->indexForNode( childNode->parentNode );
|
|
|
|
if ( !parentIndex.isValid() ) {
|
|
kWarning() << "IncidenceTreeModel::parent(): proxyModelIndex is invalid.";
|
|
Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
|
|
Q_ASSERT( parentIndex.model() == this );
|
|
Q_ASSERT( childNode->parentNode.data() );
|
|
|
|
// Parent is always at row 0
|
|
return parentIndex;
|
|
}
|
|
|
|
QModelIndex IncidenceTreeModel::index( int row, int column, const QModelIndex &parent ) const
|
|
{
|
|
if ( row < 0 || row >= rowCount( parent ) ) {
|
|
// This is ok apparently
|
|
/*kWarning() << "IncidenceTreeModel::index() parent.isValid()" << parent.isValid()
|
|
<< "; row=" << row << "; column=" << column
|
|
<< "; rowCount() = " << rowCount( parent ); */
|
|
// Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
|
|
Q_ASSERT( column >= 0 );
|
|
Q_ASSERT( column < columnCount() );
|
|
|
|
if ( parent.isValid() ) {
|
|
Q_ASSERT( parent.model() == this );
|
|
Q_ASSERT( parent.internalPointer() );
|
|
Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() );
|
|
|
|
if ( row >= parentNode->directChilds.count() ) {
|
|
kError() << "IncidenceTreeModel::index() row=" << row << "; column=" << column;
|
|
Q_ASSERT( false );
|
|
return QModelIndex();
|
|
}
|
|
|
|
return createIndex( row, column,
|
|
parentNode->directChilds.at( row ).data() );
|
|
} else {
|
|
Q_ASSERT( row < d->m_toplevelNodeList.count() );
|
|
Node::Ptr node = d->m_toplevelNodeList.at( row );
|
|
Q_ASSERT( node );
|
|
return createIndex( row, column, node.data() );
|
|
}
|
|
}
|
|
|
|
bool IncidenceTreeModel::hasChildren( const QModelIndex &parent ) const
|
|
{
|
|
if ( parent.isValid() ) {
|
|
Q_ASSERT( parent.column() < columnCount() );
|
|
if ( parent.column() != 0 ) {
|
|
// Indexes at column >0 don't have parent, says Qt documentation
|
|
return false;
|
|
}
|
|
Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() );
|
|
Q_ASSERT( parentNode );
|
|
return !parentNode->directChilds.isEmpty();
|
|
} else {
|
|
return !d->m_toplevelNodeList.isEmpty();
|
|
}
|
|
}
|
|
|
|
Akonadi::Item IncidenceTreeModel::item( const QString &uid ) const
|
|
{
|
|
Akonadi::Item item;
|
|
if ( uid.isEmpty() ) {
|
|
kWarning() << "Called with an empty uid";
|
|
} else {
|
|
if ( d->m_itemByUid.contains( uid ) ) {
|
|
item = d->m_itemByUid.value( uid );
|
|
} else {
|
|
kWarning() << "There's no incidence with uid " << uid;
|
|
}
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
QDebug operator<<( QDebug s, const Node::Ptr &node )
|
|
{
|
|
Q_ASSERT( node );
|
|
static int level = 0;
|
|
++level;
|
|
QString padding = QString(level-1, QLatin1Char(' ') );
|
|
s << padding+QLatin1String("node") << node.data() << QLatin1String(";uid=") << node->uid << QLatin1String(";id=") << node->id << QLatin1String(";parentUid=") << node->parentUid << QLatin1String(";parentNode=") << (void*)(node->parentNode.data()) << '\n';
|
|
|
|
foreach( const Node::Ptr &child, node->directChilds ) {
|
|
s << child;
|
|
}
|
|
|
|
--level;
|
|
return s;
|
|
}
|