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 }