File indexing completed on 2024-05-19 04:48:41

0001 /****************************************************************************************
0002  * Copyright (c) 2007-2011 Bart Cerneels <bart.cerneels@kde.org>                        *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "QtGroupingProxy.h"
0018 
0019 #include <QDebug>
0020 #include <QIcon>
0021 #include <QInputDialog>
0022 #include <QTimer>
0023 
0024 /*!
0025     \class QtGroupingProxy
0026     \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level.
0027     The source model can be flat or tree organized, but only the original top level rows are used
0028     for determining the grouping.
0029     \ingroup model-view
0030 */
0031 
0032 
0033 QtGroupingProxy::QtGroupingProxy( QObject *parent )
0034     : QAbstractProxyModel( parent )
0035 {
0036 }
0037 
0038 QtGroupingProxy::QtGroupingProxy( QAbstractItemModel *model, const QModelIndex &rootIndex,
0039                                   int groupedColumn, QObject *parent )
0040     : QAbstractProxyModel( parent )
0041     , m_rootIndex( rootIndex )
0042     , m_groupedColumn( 0 )
0043 {
0044     setSourceModel( model );
0045 
0046     if( groupedColumn != -1 )
0047         setGroupedColumn( groupedColumn );
0048 }
0049 
0050 QtGroupingProxy::~QtGroupingProxy()
0051 {
0052 }
0053 
0054 void
0055 QtGroupingProxy::setSourceModel( QAbstractItemModel *sourceModel )
0056 {
0057     QAbstractProxyModel::setSourceModel( sourceModel );
0058     // signal proxies
0059     connect( sourceModel, &QAbstractItemModel::dataChanged,
0060              this, &QtGroupingProxy::modelDataChanged );
0061     connect( sourceModel, &QAbstractItemModel::rowsInserted,
0062              this, &QtGroupingProxy::modelRowsInserted );
0063     connect( sourceModel, &QAbstractItemModel::rowsAboutToBeInserted,
0064              this, &QtGroupingProxy::modelRowsAboutToBeInserted );
0065     connect( sourceModel, &QAbstractItemModel::rowsRemoved,
0066              this, &QtGroupingProxy::modelRowsRemoved );
0067     connect( sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved,
0068              this, &QtGroupingProxy::modelRowsAboutToBeRemoved );
0069     connect( sourceModel, &QAbstractItemModel::layoutChanged,
0070              this, &QtGroupingProxy::buildTree );
0071     connect( sourceModel, &QAbstractItemModel::dataChanged,
0072              this, &QtGroupingProxy::modelDataChanged );
0073     //set invalid index from source as root index
0074     m_rootIndex = sourceModel->index( -1, -1 );
0075 }
0076 
0077 void
0078 QtGroupingProxy::setRootIndex( const QModelIndex &rootIndex )
0079 {
0080     if( m_rootIndex == rootIndex )
0081         return;
0082 
0083     m_rootIndex = rootIndex;
0084     //TODO: invalidate tree so buildTree() can be called later.
0085 }
0086 
0087 void
0088 QtGroupingProxy::setGroupedColumn( int groupedColumn )
0089 {
0090     m_groupedColumn = groupedColumn;
0091     //TODO: invalidate tree so buildTree() can be called later.
0092     buildTree();
0093 }
0094 
0095 /** Maps to what groups the source row belongs by returning the data of those groups.
0096   *
0097   * @returns a list of data for the rows the argument belongs to. In common cases this list will
0098   * contain only one entry. An empty list means that the source item will be placed in the root of
0099   * this proxyModel. There is no support for hiding source items.
0100   *
0101   * Group data can be pre-loaded in the return value so it's added to the cache maintained by this
0102   * class. This is required if you want to have data that is not present in the source model.
0103   */
0104 QList<RowData>
0105 QtGroupingProxy::belongsTo( const QModelIndex &idx )
0106 {
0107     //qDebug() << __FILE__ << __FUNCTION__;
0108     QList<RowData> rowDataList;
0109 
0110     //get all the data for this index from the model
0111     ItemData itemData = sourceModel()->itemData( idx );
0112     QMapIterator<int, QVariant> i( itemData );
0113     while( i.hasNext() )
0114     {
0115         i.next();
0116         int role = i.key();
0117         QVariant variant = i.value();
0118         // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant;
0119         if( variant.type() == QVariant::List )
0120         {
0121             //a list of variants get's expanded to multiple rows
0122             QVariantList list = variant.toList();
0123             for( int i = 0; i < list.length(); i++ )
0124             {
0125                 //take an existing row data or create a new one
0126                 RowData rowData = (rowDataList.count() > i) ?  rowDataList.takeAt( i )
0127                                        : RowData();
0128 
0129                 //we only gather data for the first column
0130                 ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData();
0131                 indexData.insert( role, list.value( i ) );
0132                 rowData.insert( 0, indexData );
0133                 //for the grouped column the data should not be gathered from the children
0134                 //this will allow filtering on the content of this column with a
0135                 //QSortFilterProxyModel
0136                 rowData.insert( m_groupedColumn, indexData );
0137                 rowDataList.insert( i, rowData );
0138             }
0139         }
0140         else if( !variant.isNull() )
0141         {
0142             //it's just a normal item. Copy all the data and break this loop.
0143             RowData rowData;
0144             rowData.insert( 0, itemData );
0145             rowDataList << rowData;
0146             break;
0147         }
0148     }
0149 
0150     return rowDataList;
0151 }
0152 
0153 /* m_groupHash layout
0154 *  key : index of the group in m_groupMaps
0155 *  value : a QList of the original rows in sourceModel() for the children of this group
0156 *
0157 *  key = -1  contains a QList of the non-grouped indexes
0158 *
0159 * TODO: sub-groups
0160 */
0161 void
0162 QtGroupingProxy::buildTree()
0163 {
0164     if( !sourceModel() )
0165         return;
0166 
0167     beginResetModel();
0168 
0169     m_groupHash.clear();
0170     //don't clear the data maps since most of it will probably be needed again.
0171     m_parentCreateList.clear();
0172 
0173     int max = sourceModel()->rowCount( m_rootIndex );
0174     //qDebug() << QStringLiteral("building tree with %1 leafs.").arg( max );
0175     //WARNING: these have to be added in order because the addToGroups function is optimized for
0176     //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best.
0177     for( int row = 0; row < max; row++ )
0178     {
0179         QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootIndex );
0180         addSourceRow( idx );
0181     }
0182 //    dumpGroups();
0183 
0184     endResetModel();
0185 }
0186 
0187 QList<int>
0188 QtGroupingProxy::addSourceRow( const QModelIndex &idx )
0189 {
0190     QList<int> updatedGroups;
0191 
0192     QList<RowData> groupData = belongsTo( idx );
0193 
0194     //an empty list here means it's supposed to go in root.
0195     if( groupData.isEmpty() )
0196     {
0197         updatedGroups << -1;
0198         if( !m_groupHash.keys().contains( -1 ) )
0199             m_groupHash.insert( -1, QList<int>() ); //add an empty placeholder
0200     }
0201 
0202     //an item can be in multiple groups
0203     foreach( RowData data, groupData )
0204     {
0205         int updatedGroup = -1;
0206         if( !data.isEmpty() )
0207         {
0208 //            qDebug() << QStringLiteral("index %1 belongs to group %2").arg( row )
0209 //                         .arg( data[0][Qt::DisplayRole].toString() );
0210 
0211             foreach( const RowData &cachedData, m_groupMaps )
0212             {
0213                 //when this matches the index belongs to an existing group
0214                 if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] )
0215                 {
0216                     data = cachedData;
0217                     break;
0218                 }
0219             }
0220 
0221             updatedGroup = m_groupMaps.indexOf( data );
0222             //-1 means not found
0223             if( updatedGroup == -1 )
0224             {
0225                 QModelIndex newGroupIdx = addEmptyGroup( data );
0226                 updatedGroup = newGroupIdx.row();
0227             }
0228 
0229             if( !m_groupHash.keys().contains( updatedGroup ) )
0230                 m_groupHash.insert( updatedGroup, QList<int>() ); //add an empty placeholder
0231         }
0232 
0233         if( !updatedGroups.contains( updatedGroup ) )
0234             updatedGroups << updatedGroup;
0235     }
0236 
0237 
0238     //update m_groupHash to the new source-model layout (one row added)
0239     QMutableHashIterator<quint32, QList<int> > i( m_groupHash );
0240     while( i.hasNext() )
0241     {
0242         i.next();
0243         QList<int> &groupList = i.value();
0244         int insertedProxyRow = groupList.count();
0245         for( ; insertedProxyRow > 0 ; insertedProxyRow-- )
0246         {
0247             int &rowValue = groupList[insertedProxyRow-1];
0248             if( idx.row() <= rowValue )
0249             {
0250                 //increment the rows that come after the new row since they moved one place up.
0251                 rowValue++;
0252             }
0253             else
0254             {
0255                 break;
0256             }
0257         }
0258 
0259         if( updatedGroups.contains( i.key() ) )
0260         {
0261             //the row needs to be added to this group
0262             beginInsertRows( index( i.key() ), insertedProxyRow, insertedProxyRow );
0263             groupList.insert( insertedProxyRow, idx.row() );
0264             endInsertRows();
0265         }
0266     }
0267 
0268     return updatedGroups;
0269 }
0270 
0271 /** Each ModelIndex has in it's internalId a position in the parentCreateList.
0272   * struct ParentCreate are the instructions to recreate the parent index.
0273   * It contains the proxy row number of the parent and the position in this list of the grandfather.
0274   * This function creates the ParentCreate structs and saves them in a list.
0275   */
0276 int
0277 QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const
0278 {
0279     if( !parent.isValid() )
0280         return -1;
0281 
0282     struct ParentCreate pc;
0283     for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
0284     {
0285         pc = m_parentCreateList[i];
0286         if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() )
0287             return i;
0288     }
0289     //there is no parentCreate yet for this index, so let's create one.
0290     pc.parentCreateIndex = parent.internalId();
0291     pc.row = parent.row();
0292     m_parentCreateList << pc;
0293 
0294     //dumpParentCreateList();
0295 //    qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() );
0296 //    for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
0297 //    {
0298 //        qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex <<
0299 //                 " | " << m_parentCreateList[i].row;
0300 //    }
0301 
0302     return m_parentCreateList.size() - 1;
0303 }
0304 
0305 QModelIndex
0306 QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
0307 {
0308 //    qDebug() << "index requested for: (" << row << "," << column << "), " << parent;
0309     if( !hasIndex(row, column, parent) )
0310         return QModelIndex();
0311 
0312     if( parent.column() > 0 )
0313         return QModelIndex();
0314 
0315     /* We save the instructions to make the parent of the index in a struct.
0316      * The place of the struct in the list is stored in the internalId
0317      */
0318     int parentCreateIndex = indexOfParentCreate( parent );
0319 
0320     return createIndex( row, column, parentCreateIndex );
0321 }
0322 
0323 QModelIndex
0324 QtGroupingProxy::parent( const QModelIndex &index ) const
0325 {
0326     //qDebug() << "parent: " << index;
0327     if( !index.isValid() )
0328         return QModelIndex();
0329 
0330     int parentCreateIndex = index.internalId();
0331     //qDebug() << "parentCreateIndex: " << parentCreateIndex;
0332     if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() )
0333         return QModelIndex();
0334 
0335     struct ParentCreate pc = m_parentCreateList[parentCreateIndex];
0336     //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")";
0337     //only items at column 0 have children
0338     return createIndex( pc.row, 0, pc.parentCreateIndex );
0339 }
0340 
0341 int
0342 QtGroupingProxy::rowCount( const QModelIndex &index ) const
0343 {
0344     //qDebug() << "rowCount: " << index;
0345     if( !index.isValid() )
0346     {
0347         //the number of top level groups + the number of non-grouped playlists
0348         int rows = m_groupMaps.count() + m_groupHash.value( -1 ).count();
0349         //qDebug() << rows << " in root group";
0350         return rows;
0351     }
0352 
0353     //TODO:group in group support.
0354     if( isGroup( index ) )
0355     {
0356         qint64 groupIndex = index.row();
0357         int rows = m_groupHash.value( groupIndex ).count();
0358         //qDebug() << rows << " in group " << m_groupMaps[groupIndex];
0359         return rows;
0360     }
0361 
0362     QModelIndex originalIndex = mapToSource( index );
0363     int rowCount = sourceModel()->rowCount( originalIndex );
0364     //qDebug() << "original item: rowCount == " << rowCount;
0365     return rowCount;
0366 }
0367 
0368 int
0369 QtGroupingProxy::columnCount( const QModelIndex &index ) const
0370 {
0371     if( !index.isValid() )
0372         return sourceModel()->columnCount( m_rootIndex );
0373 
0374     if( index.column() != 0 )
0375         return 0;
0376 
0377     return sourceModel()->columnCount( mapToSource( index ) );
0378 }
0379 
0380 QVariant
0381 QtGroupingProxy::data( const QModelIndex &index, int role ) const
0382 {
0383     if( !index.isValid() )
0384         return sourceModel()->data( m_rootIndex, role ); //rootNode could have useful data
0385 
0386     //qDebug() << __FUNCTION__ << index << " role: " << role;
0387     int row = index.row();
0388     int column = index.column();
0389     if( isGroup( index ) )
0390     {
0391         //qDebug() << __FUNCTION__ << "is a group";
0392         //use cached or precalculated data
0393         if( m_groupMaps[row][column].contains( role ) )
0394         {
0395             //qDebug() << "Using cached data";
0396             return m_groupMaps[row][column].value( role );
0397         }
0398 
0399         //for column 0 we gather data from the grouped column instead
0400         if( column == 0 )
0401             column = m_groupedColumn;
0402 
0403         //map all data from children to columns of group to allow grouping one level up
0404         QVariantList variantsOfChildren;
0405         int childCount = m_groupHash.value( row ).count();
0406         if( childCount == 0 )
0407             return QVariant();
0408 
0409         //qDebug() << __FUNCTION__ << "childCount: " << childCount;
0410         //Need a parentIndex with column == 0 because only those have children.
0411         QModelIndex parentIndex = this->index( row, 0, index.parent() );
0412         for( int childRow = 0; childRow < childCount; childRow++ )
0413         {
0414             QModelIndex childIndex = this->index( childRow, column, parentIndex );
0415             QVariant data = mapToSource( childIndex ).data( role );
0416             //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type());
0417             if( data.isValid() && !variantsOfChildren.contains( data ) )
0418                 variantsOfChildren << data;
0419         }
0420         //qDebug() << "gathered this data from children: " << variantsOfChildren;
0421         //saving in cache
0422         ItemData roleMap = m_groupMaps[row].value( column );
0423         foreach( const QVariant &variant, variantsOfChildren )
0424         {
0425             if( roleMap[ role ] != variant )
0426                 roleMap.insert( role, variantsOfChildren );
0427         }
0428 
0429         //qDebug() << QStringLiteral("roleMap[%1]:").arg(role) << roleMap[role];
0430         //only one unique variant? No need to return a list
0431         if( variantsOfChildren.count() == 1 )
0432             return variantsOfChildren.first();
0433 
0434         if( variantsOfChildren.isEmpty() )
0435             return QVariant();
0436 
0437         return variantsOfChildren;
0438     }
0439 
0440     return mapToSource( index ).data( role );
0441 }
0442 
0443 bool
0444 QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role )
0445 {
0446     if( !idx.isValid() )
0447         return false;
0448 
0449     //no need to set data to exactly the same value
0450     if( idx.data( role ) == value )
0451         return false;
0452 
0453     if( isGroup( idx ) )
0454     {
0455         ItemData columnData = m_groupMaps[idx.row()][idx.column()];
0456 
0457         columnData.insert( role, value );
0458         //QItemDelegate will always use Qt::EditRole
0459         if( role == Qt::EditRole )
0460             columnData.insert( Qt::DisplayRole, value );
0461 
0462         //and make sure it's stored in the map
0463         m_groupMaps[idx.row()].insert( idx.column(), columnData );
0464 
0465         int columnToChange = idx.column() ? idx.column() : m_groupedColumn;
0466         foreach( int originalRow, m_groupHash.value( idx.row() ) )
0467         {
0468             QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange,
0469                                                    m_rootIndex );
0470             if( childIdx.isValid() )
0471                 sourceModel()->setData( childIdx, value, role );
0472         }
0473         //TODO: we might need to reload the data from the children at this point
0474 
0475         Q_EMIT dataChanged( idx, idx );
0476         return true;
0477     }
0478 
0479     return sourceModel()->setData( mapToSource( idx ), value, role );
0480 }
0481 
0482 bool
0483 QtGroupingProxy::isGroup( const QModelIndex &index ) const
0484 {
0485     int parentCreateIndex = index.internalId();
0486     if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() )
0487         return true;
0488     return false;
0489 }
0490 
0491 QModelIndex
0492 QtGroupingProxy::mapToSource( const QModelIndex &index ) const
0493 {
0494     //qDebug() << "mapToSource: " << index;
0495     if( !index.isValid() )
0496         return m_rootIndex;
0497 
0498     if( isGroup( index ) )
0499     {
0500         //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString();
0501         return m_rootIndex;
0502     }
0503 
0504     QModelIndex proxyParent = index.parent();
0505     //qDebug() << "parent: " << proxyParent;
0506     QModelIndex originalParent = mapToSource( proxyParent );
0507     //qDebug() << "originalParent: " << originalParent;
0508     int originalRow = index.row();
0509     if( originalParent == m_rootIndex )
0510     {
0511         int indexInGroup = index.row();
0512         if( !proxyParent.isValid() )
0513             indexInGroup -= m_groupMaps.count();
0514         //qDebug() << "indexInGroup" << indexInGroup;
0515         QList<int> childRows = m_groupHash.value( proxyParent.row() );
0516         if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 )
0517             return QModelIndex();
0518 
0519         originalRow = childRows.at( indexInGroup );
0520         //qDebug() << "originalRow: " << originalRow;
0521     }
0522     return sourceModel()->index( originalRow, index.column(), originalParent );
0523 }
0524 
0525 QModelIndexList
0526 QtGroupingProxy::mapToSource( const QModelIndexList& list ) const
0527 {
0528     QModelIndexList originalList;
0529     foreach( const QModelIndex &index, list )
0530     {
0531         QModelIndex originalIndex = mapToSource( index );
0532         if( originalIndex.isValid() )
0533             originalList << originalIndex;
0534     }
0535     return originalList;
0536 }
0537 
0538 QModelIndex
0539 QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const
0540 {
0541     if( !idx.isValid() )
0542         return QModelIndex();
0543 
0544     QModelIndex proxyParent;
0545     QModelIndex sourceParent = idx.parent();
0546     //qDebug() << "sourceParent: " << sourceParent;
0547     int proxyRow = idx.row();
0548     int sourceRow = idx.row();
0549 
0550     if( sourceParent.isValid() && ( sourceParent != m_rootIndex ) )
0551     {
0552         //idx is a child of one of the items in the source model
0553         proxyParent = mapFromSource( sourceParent );
0554     }
0555     else
0556     {
0557         //idx is an item in the top level of the source model (child of the rootnode)
0558         int groupRow = -1;
0559         QHashIterator<quint32, QList<int> > iterator( m_groupHash );
0560         while( iterator.hasNext() )
0561         {
0562             iterator.next();
0563             if( iterator.value().contains( sourceRow ) )
0564             {
0565                 groupRow = iterator.key();
0566                 break;
0567             }
0568         }
0569 
0570         if( groupRow != -1 ) //it's in a group, let's find the correct row.
0571         {
0572             proxyParent = this->index( groupRow, 0, QModelIndex() );
0573             proxyRow = m_groupHash.value( groupRow ).indexOf( sourceRow );
0574         }
0575         else
0576         {
0577             proxyParent = QModelIndex();
0578             // if the proxy item is not in a group it will be below the groups.
0579             int groupLength = m_groupMaps.count();
0580             //qDebug() << "groupNames length: " << groupLength;
0581             int i = m_groupHash.value( -1 ).indexOf( sourceRow );
0582             //qDebug() << "index in hash: " << i;
0583             proxyRow = groupLength + i;
0584         }
0585     }
0586 
0587     //qDebug() << "proxyParent: " << proxyParent;
0588     //qDebug() << "proxyRow: " << proxyRow;
0589     return this->index( proxyRow, 0, proxyParent );
0590 }
0591 
0592 Qt::ItemFlags
0593 QtGroupingProxy::flags( const QModelIndex &idx ) const
0594 {
0595     if( !idx.isValid() )
0596     {
0597         Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootIndex );
0598         if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) )
0599             return Qt::ItemFlags( Qt::ItemIsDropEnabled );
0600 
0601         return {};
0602     }
0603     //only if the grouped column has the editable flag set allow the
0604     //actions leading to setData on the source (edit & drop)
0605 //    qDebug() << idx;
0606     if( isGroup( idx ) )
0607     {
0608 //        dumpGroups();
0609         Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
0610         bool groupIsEditable = true;
0611 
0612         //it's possible to have empty groups
0613         if( m_groupHash.value( idx.row() ).isEmpty() )
0614         {
0615             //check the flags of this column with the root node
0616             QModelIndex originalRootNode = sourceModel()->index( m_rootIndex.row(), m_groupedColumn,
0617                                                            m_rootIndex.parent() );
0618             groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable );
0619         }
0620         else
0621         {
0622             foreach( int originalRow, m_groupHash.value( idx.row() ) )
0623             {
0624                 QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn,
0625                                                           m_rootIndex );
0626 //                qDebug() << "originalIdx: " << originalIdx;
0627                 groupIsEditable = groupIsEditable
0628                                   ? originalIdx.flags().testFlag( Qt::ItemIsEditable )
0629                                   : false;
0630                 if( !groupIsEditable ) //all children need to have an editable grouped column
0631                     break;
0632             }
0633         }
0634 
0635         if( groupIsEditable )
0636             return (  defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled );
0637         return defaultFlags;
0638     }
0639 
0640     QModelIndex originalIdx = mapToSource( idx );
0641     Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx );
0642 
0643     //check the source model to see if the grouped column is editable;
0644     QModelIndex groupedColumnIndex =
0645             sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() );
0646     bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable );
0647     if( groupIsEditable )
0648         return originalItemFlags | Qt::ItemIsDragEnabled;
0649 
0650     return originalItemFlags;
0651 }
0652 
0653 QModelIndex
0654 QtGroupingProxy::buddy( const QModelIndex &index ) const
0655 {
0656     /* We need to override this method in case of groups. Otherwise, at least editing
0657      * of groups is prevented, following sequence occurs:
0658      *
0659      * #0  QtGroupingProxy::mapToSource (this=0x15ad8a0, index=...) at /home/strohel/projekty/amarok/src/browsers/playlistbrowser/QtGroupingProxy.cpp:492
0660      * #1  0x00007ffff609d7b6 in QAbstractProxyModel::buddy (this=0x15ad8a0, index=...) at itemviews/qabstractproxymodel.cpp:306
0661      * #2  0x00007ffff609ed25 in QSortFilterProxyModel::buddy (this=0x15ae730, index=...) at itemviews/qsortfilterproxymodel.cpp:2015
0662      * #3  0x00007ffff6012a2c in QAbstractItemView::edit (this=0x15aec30, index=..., trigger=QAbstractItemView::AllEditTriggers, event=0x0) at itemviews/qabstractitemview.cpp:2569
0663      * #4  0x00007ffff6f9aa9f in Amarok::PrettyTreeView::edit (this=0x15aec30, index=..., trigger=QAbstractItemView::AllEditTriggers, event=0x0)
0664      *     at /home/strohel/projekty/amarok/src/widgets/PrettyTreeView.cpp:58
0665      * #5  0x00007ffff6007f1e in QAbstractItemView::edit (this=0x15aec30, index=...) at itemviews/qabstractitemview.cpp:1138
0666      * #6  0x00007ffff6dc86e4 in PlaylistBrowserNS::PlaylistBrowserCategory::createNewFolder (this=0x159bf90)
0667      *     at /home/strohel/projekty/amarok/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp:298
0668      *
0669      * but we return invalid index in mapToSource() for group index.
0670      */
0671     if( index.isValid() && isGroup( index ) )
0672         return index;
0673     return QAbstractProxyModel::buddy( index );
0674 }
0675 
0676 QVariant
0677 QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const
0678 {
0679     return sourceModel()->headerData( section, orientation, role );
0680 }
0681 
0682 bool
0683 QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const
0684 {
0685     if( !parent.isValid() )
0686         return false;
0687 
0688     if( isGroup( parent ) )
0689         return false;
0690 
0691     return sourceModel()->canFetchMore( mapToSource( parent ) );
0692 }
0693 
0694 void
0695 QtGroupingProxy::fetchMore ( const QModelIndex & parent )
0696 {
0697     if( !parent.isValid() )
0698         return;
0699 
0700     if( isGroup( parent ) )
0701         return;
0702 
0703     sourceModel()->fetchMore( mapToSource( parent ) );
0704 }
0705 
0706 QModelIndex
0707 QtGroupingProxy::addEmptyGroup( const RowData &data )
0708 {
0709     int newRow = m_groupMaps.count();
0710     beginInsertRows( QModelIndex(), newRow, newRow );
0711     m_groupMaps << data;
0712     endInsertRows();
0713     return index( newRow, 0, QModelIndex() );
0714 }
0715 
0716 bool
0717 QtGroupingProxy::removeGroup( const QModelIndex &idx )
0718 {
0719     beginRemoveRows( idx.parent(), idx.row(), idx.row() );
0720     m_groupHash.remove( idx.row() );
0721     m_groupMaps.removeAt( idx.row() );
0722     m_parentCreateList.removeAt( idx.internalId() );
0723     endRemoveRows();
0724 
0725     //TODO: only true if all data could be unset.
0726     return true;
0727 }
0728 
0729 bool
0730 QtGroupingProxy::hasChildren( const QModelIndex &parent ) const
0731 {
0732     if( !parent.isValid() )
0733         return true;
0734 
0735     if( isGroup( parent ) )
0736         return !m_groupHash.value( parent.row() ).isEmpty();
0737 
0738     return sourceModel()->hasChildren( mapToSource( parent ) );
0739 }
0740 
0741 void
0742 QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end )
0743 {
0744     if( parent != m_rootIndex )
0745     {
0746         //an item will be added to an original index, remap and pass it on
0747         QModelIndex proxyParent = mapFromSource( parent );
0748         beginInsertRows( proxyParent, start, end );
0749     }
0750 }
0751 
0752 void
0753 QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end )
0754 {
0755     if( parent == m_rootIndex )
0756     {
0757         //top level of the model changed, these new rows need to be put in groups
0758         for( int modelRow = start; modelRow <= end ; modelRow++ )
0759         {
0760             addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootIndex ) );
0761         }
0762     }
0763     else
0764     {
0765         //beginInsertRows had to be called in modelRowsAboutToBeInserted()
0766         endInsertRows();
0767     }
0768 }
0769 
0770 void
0771 QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end )
0772 {
0773     if( parent == m_rootIndex )
0774     {
0775         QHash<quint32, QList<int> >::const_iterator i;
0776         //HACK, we are going to call beginRemoveRows() multiple times without
0777         // endRemoveRows() if a source index is in multiple groups.
0778         // This can be a problem for some views/proxies, but Q*Views can handle it.
0779         // TODO: investigate a queue for applying proxy model changes in the correct order
0780         for( i = m_groupHash.constBegin(); i != m_groupHash.constEnd(); ++i )
0781         {
0782             int groupIndex = i.key();
0783             const QList<int> &groupList = i.value();
0784             QModelIndex proxyParent = index( groupIndex, 0 );
0785             foreach( int originalRow, groupList )
0786             {
0787                 if( originalRow >= start && originalRow <= end )
0788                 {
0789                     int proxyRow = groupList.indexOf( originalRow );
0790                     if( groupIndex == -1 ) //adjust for non-grouped (root level) original items
0791                         proxyRow += m_groupMaps.count();
0792                     //TODO: optimize for continues original rows in the same group
0793                     beginRemoveRows( proxyParent, proxyRow, proxyRow );
0794                 }
0795             }
0796         }
0797     }
0798     else
0799     {
0800         //child item(s) of an original item will be removed, remap and pass it on
0801 //        qDebug() << parent;
0802         QModelIndex proxyParent = mapFromSource( parent );
0803 //        qDebug() << proxyParent;
0804         beginRemoveRows( proxyParent, start, end );
0805     }
0806 }
0807 
0808 void
0809 QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end )
0810 {
0811     if( parent == m_rootIndex )
0812     {
0813         //TODO: can be optimised by iterating over m_groupHash and checking start <= r < end
0814 
0815         //rather than increasing i we change the stored sourceRows in-place and reuse argument start
0816         //X-times (where X = end - start).
0817         for( int i = start; i <= end; i++ )
0818         {
0819             //HACK: we are going to iterate the hash in reverse so calls to endRemoveRows()
0820             // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved()
0821             //NOTE: easier to do reverse with java style iterator
0822             QMutableHashIterator<quint32, QList<int> > it( m_groupHash );
0823             it.toBack();
0824             while( it.hasPrevious() )
0825             {
0826                 it.previous();
0827                 //has to be a modifiable reference for remove and replace operations
0828                 QList<int> &groupList = it.value();
0829                 int rowIndex = groupList.indexOf( start );
0830                 if( rowIndex != -1 )
0831                 {
0832                     groupList.removeAt( rowIndex );
0833                 }
0834                 //Now decrement all source rows that are after the removed row
0835                 for( int j = 0; j < groupList.count(); j++ )
0836                 {
0837                     int sourceRow = groupList.at( j );
0838                     if( sourceRow > start )
0839                         groupList.replace( j, sourceRow-1 );
0840                 }
0841                 if( rowIndex != -1)
0842                     endRemoveRows(); //end remove operation only after group was updated.
0843             }
0844         }
0845 
0846         return;
0847     }
0848 
0849     //beginRemoveRows had to be called in modelRowsAboutToBeRemoved();
0850     endRemoveRows();
0851 }
0852 
0853 void
0854 QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
0855 {
0856     //TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly
0857     QModelIndex proxyTopLeft = mapFromSource( topLeft );
0858     if( !proxyTopLeft.isValid() )
0859         return;
0860 
0861     if( topLeft == bottomRight )
0862     {
0863         Q_EMIT dataChanged( proxyTopLeft, proxyTopLeft );
0864     }
0865     else
0866     {
0867         QModelIndex proxyBottomRight = mapFromSource( bottomRight );
0868         Q_EMIT dataChanged( proxyTopLeft, proxyBottomRight );
0869     }
0870 }
0871 
0872 bool
0873 QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const
0874 {
0875     foreach( const QModelIndex &index, list )
0876     {
0877         if( isGroup( index ) )
0878             return true;
0879     }
0880     return false;
0881 }
0882 
0883 void
0884 QtGroupingProxy::dumpGroups() const
0885 {
0886     qDebug() << "m_groupHash: ";
0887     for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ )
0888     {
0889         qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex );
0890     }
0891 
0892     qDebug() << "m_groupMaps: ";
0893     for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ )
0894         qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex );
0895     qDebug() << m_groupHash.value( -1 );
0896 }