diff options
Diffstat (limited to 'src/qtgroupingproxy.cpp')
| -rw-r--r-- | src/qtgroupingproxy.cpp | 2076 |
1 files changed, 1038 insertions, 1038 deletions
diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index d00bd288..bfbf955c 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -1,1038 +1,1038 @@ -/****************************************************************************************
- * Copyright (c) 2007-2011 Bart Cerneels <bart.cerneels@kde.org> *
- * *
- * 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, see <http://www.gnu.org/licenses/>. *
- ****************************************************************************************/
-
-// Modifications 2013-03-27 to 2013-03-29 by Sebastian Herbord
-
-
-#include "qtgroupingproxy.h"
-#include <log.h>
-
-#include <QDebug>
-#include <QIcon>
-#include <QInputDialog>
-
-using namespace MOBase;
-
-/*!
- \class QtGroupingProxy
- \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level.
- The source model can be flat or tree organized, but only the original top level rows are used
- for determining the grouping.
- \ingroup model-view
-*/
-
-QtGroupingProxy::QtGroupingProxy(QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags, int aggregateRole)
- : QAbstractProxyModel()
- , m_rootNode(rootNode)
- , m_groupedColumn(0)
- , m_groupedRole(groupedRole)
- , m_aggregateRole(aggregateRole)
- , m_flags(flags)
-{
- if (groupedColumn != -1) {
- setGroupedColumn(groupedColumn);
- }
-}
-
-QtGroupingProxy::~QtGroupingProxy()
-{
-}
-
-void QtGroupingProxy::setSourceModel(QAbstractItemModel* model)
-{
- if (sourceModel()) {
- disconnect(sourceModel(), nullptr, this, nullptr);
- }
-
- QAbstractProxyModel::setSourceModel(model);
-
- if (sourceModel()) {
- // signal proxies
- connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)),
- SLOT(modelRowsInserted(const QModelIndex&, int, int)));
- connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)),
- SLOT(modelRowsAboutToBeInserted(const QModelIndex&, int, int)));
- connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
- SLOT(modelRowsRemoved(const QModelIndex&, int, int)));
- connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
- SLOT(modelRowsAboutToBeRemoved(QModelIndex, int, int)));
- connect(sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree()));
- connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)),
- SLOT(modelDataChanged(QModelIndex, QModelIndex)));
- connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel()));
-
- buildTree();
- }
-}
-
-void
-QtGroupingProxy::setGroupedColumn( int groupedColumn )
-{
- m_groupedColumn = groupedColumn;
- buildTree();
-}
-
-/** Maps to what groups the source row belongs by returning the data of those groups.
- *
- * @returns a list of data for the rows the argument belongs to. In common cases this list will
- * contain only one entry. An empty list means that the source item will be placed in the root of
- * this proxyModel. There is no support for hiding source items.
- *
- * Group data can be pre-loaded in the return value so it's added to the cache maintained by this
- * class. This is required if you want to have data that is not present in the source model.
- */
-QList<RowData>
-QtGroupingProxy::belongsTo( const QModelIndex &idx )
-{
- QList<RowData> rowDataList;
-
- //get all the data for this index from the model
- ItemData itemData = sourceModel()->itemData( idx );
- if (m_groupedRole != Qt::DisplayRole) {
- itemData[Qt::DisplayRole] = itemData[m_groupedRole];
- }
-
- // invalid value in grouped role -> ungrouped
- if (!itemData[Qt::DisplayRole].isValid()) {
- return rowDataList;
- }
-
- QMapIterator<int, QVariant> i( itemData );
- while( i.hasNext() )
- {
- i.next();
- int role = i.key();
- QVariant variant = i.value();
-
- if ( variant.type() == QVariant::List )
- {
- //a list of variants get's expanded to multiple rows
- QVariantList list = variant.toList();
- for( int i = 0; i < list.length(); i++ )
- {
- //take an existing row data or create a new one
- RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt( i )
- : RowData();
-
- //we only gather data for the first column
- ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData();
- indexData.insert( role, list.value( i ) );
- rowData.insert( 0, indexData );
- //for the grouped column the data should not be gathered from the children
- //this will allow filtering on the content of this column with a
- //QSortFilterProxyModel
- rowData.insert( m_groupedColumn, indexData );
- rowDataList.insert( i, rowData );
- }
- break;
- }
- else if( !variant.isNull() )
- {
- //it's just a normal item. Copy all the data and break this loop.
- RowData rowData;
- rowData.insert( 0, itemData );
- rowDataList << rowData;
- break;
- }
- }
-
- return rowDataList;
-}
-
-/* m_groupMap layout
-* key : index of the group in m_groupMaps
-* value : a QList of the original rows in sourceModel() for the children of this group
-*
-* key = -1 contains a QList of the non-grouped indexes
-*
-* TODO: sub-groups
-*/
-void
-QtGroupingProxy::buildTree()
-{
- if( !sourceModel() )
- return;
- beginResetModel();
-
- m_groupMap.clear();
- //don't clear the data maps since most of it will probably be needed again.
- m_parentCreateList.clear();
-
- int max = sourceModel()->rowCount( m_rootNode );
-
- //WARNING: these have to be added in order because the addToGroups function is optimized for
- //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best.
- for( int row = 0; row < max; row++ )
- {
- QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootNode );
- addSourceRow( idx );
- }
- //dumpGroups();
-
- if (m_flags & FLAG_NOSINGLE) {
- // awkward: flatten single-item groups as a post-processing steps.
-
- int currentKey = 0;
- quint32 quint32max = std::numeric_limits<quint32>::max();
- std::vector<int> rmgroups;
-
- QMap<quint32, QList<int> > temp;
-
- for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); ++iter) {
- if ((iter.key() == quint32max) ||
- (iter->count() < 2)) {
- temp[quint32max].append(iter.value());
- if (iter.key() != quint32max) {
- rmgroups.push_back(iter.key());
- }
- } else {
- temp[currentKey++] = *iter;
- }
- }
- m_groupMap = temp;
-
- // second loop is necessary because qt containers can't be iterated from end to front
- // and removing by index from begin to end is ugly
- std::sort(rmgroups.begin(), rmgroups.end(), [] (int lhs, int rhs) { return rhs < lhs; });
- for (auto iter = rmgroups.begin(); iter != rmgroups.end(); ++iter) {
- m_groupMaps.removeAt(*iter);
- }
- }
-
- endResetModel();
-}
-
-QList<int>
-QtGroupingProxy::addSourceRow( const QModelIndex &idx )
-{
- QList<int> updatedGroups;
- QList<RowData> groupData = belongsTo( idx );
-
- //an empty list here means it's supposed to go in root.
- if( groupData.isEmpty() )
- {
- updatedGroups << -1;
- if( !m_groupMap.keys().contains( std::numeric_limits<quint32>::max() ) )
- m_groupMap.insert( std::numeric_limits<quint32>::max(), QList<int>() ); //add an empty placeholder
- }
-
- //an item can be in multiple groups
- foreach( RowData data, groupData )
- {
- int updatedGroup = -1;
- if( !data.isEmpty() )
- {
- foreach( const RowData &cachedData, m_groupMaps )
- {
- //when this matches the index belongs to an existing group
- if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] )
- {
- data = cachedData;
- break;
- }
- }
-
- updatedGroup = m_groupMaps.indexOf( data );
- //-1 means not found
- if( updatedGroup == -1 )
- {
- //new groups are added to the end of the existing list
- m_groupMaps << data;
- updatedGroup = m_groupMaps.count() - 1;
- }
-
- if( !m_groupMap.keys().contains( updatedGroup ) )
- m_groupMap.insert( updatedGroup, QList<int>() ); //add an empty placeholder
- }
-
- if( !updatedGroups.contains( updatedGroup ) )
- updatedGroups << updatedGroup;
- }
-
- //update m_groupMap to the new source-model layout (one row added)
- QMutableMapIterator<quint32, QList<int> > i( m_groupMap );
- while( i.hasNext() )
- {
- i.next();
- QList<int> &groupList = i.value();
- int insertedProxyRow = groupList.count();
- for( ; insertedProxyRow > 0 ; insertedProxyRow-- )
- {
- int &rowValue = groupList[insertedProxyRow-1];
- if( idx.row() <= rowValue )
- {
- //increment the rows that come after the new row since they moved one place up.
- rowValue++;
- }
- else
- {
- break;
- }
- }
-
- if( updatedGroups.contains( i.key() ) )
- {
- //the row needs to be added to this group
- groupList.insert( insertedProxyRow, idx.row() );
- }
- }
-
- return updatedGroups;
-}
-
-/** Each ModelIndex has in it's internalId a position in the parentCreateList.
- * struct ParentCreate are the instructions to recreate the parent index.
- * It contains the proxy row number of the parent and the postion in this list of the grandfather.
- * This function creates the ParentCreate structs and saves them in a list.
- */
-int
-QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const
-{
- if( !parent.isValid() )
- return -1;
-
- struct ParentCreate pc;
- for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
- {
- pc = m_parentCreateList[i];
- if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() )
- return i;
- }
- //there is no parentCreate yet for this index, so let's create one.
- pc.parentCreateIndex = parent.internalId();
- pc.row = parent.row();
- m_parentCreateList << pc;
-
- return m_parentCreateList.size() - 1;
-}
-
-QModelIndex
-QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
-{
- if( !hasIndex(row, column, parent) ) {
- return QModelIndex();
- }
-
- if( parent.column() > 0 ) {
- return QModelIndex();
- }
-
- /* We save the instructions to make the parent of the index in a struct.
- * The place of the struct in the list is stored in the internalId
- */
- int parentCreateIndex = indexOfParentCreate( parent );
-
- return createIndex( row, column, parentCreateIndex );
-}
-
-QModelIndex
-QtGroupingProxy::parent( const QModelIndex &index ) const
-{
- if( !index.isValid() )
- return QModelIndex();
-
- int parentCreateIndex = index.internalId();
- if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() )
- return QModelIndex();
-
- struct ParentCreate pc = m_parentCreateList[parentCreateIndex];
-
- //only items at column 0 have children
- return createIndex( pc.row, 0, pc.parentCreateIndex );
-}
-
-int
-QtGroupingProxy::rowCount( const QModelIndex &index ) const
-{
- if( !index.isValid() )
- {
- //the number of top level groups + the number of non-grouped items
- int rows = m_groupMaps.count() + m_groupMap.value( std::numeric_limits<quint32>::max() ).count();
- return rows;
- }
-
- //TODO:group in group support.
- if( isGroup( index ) )
- {
- qint64 groupIndex = index.row();
- int rows = m_groupMap.value( groupIndex ).count();
- return rows;
- } else {
- QModelIndex originalIndex = mapToSource( index );
- int rowCount = sourceModel()->rowCount( originalIndex );
- return rowCount;
- }
-}
-
-int
-QtGroupingProxy::columnCount( const QModelIndex &index ) const
-{
- if( !index.isValid() )
- return sourceModel()->columnCount( m_rootNode );
-
- if( index.column() != 0 )
- return 0;
-
- return sourceModel()->columnCount( mapToSource( index ) );
-}
-
-
-static bool variantLess(const QVariant &LHS, const QVariant &RHS)
-{
- if ((LHS.type() == RHS.type()) &&
- ((LHS.type() == QVariant::Int) || (LHS.type() == QVariant::UInt))) {
- return LHS.toInt() < RHS.toInt();
- }
-
- // this should always work (comparing empty strings in the worst case) but
- // the results may be wrong
- return LHS.toString() < RHS.toString();
-}
-
-
-static QVariant variantMax(const QVariantList &variants)
-{
- QVariant result = variants.first();
- foreach (const QVariant &iter, variants) {
- if (variantLess(result, iter)) {
- result = iter;
- }
- }
- return result;
-}
-
-
-static QVariant variantMin(const QVariantList &variants)
-{
- QVariant result = variants.first();
- foreach (const QVariant &iter, variants) {
- if (variantLess(iter, result)) {
- result = iter;
- }
- }
- return result;
-}
-
-
-QVariant
-QtGroupingProxy::data( const QModelIndex &index, int role ) const
-{
- if( !index.isValid() )
- return QVariant();
-
- int row = index.row();
- int column = index.column();
- if( isGroup( index ) )
- {
- if ((role != Qt::DisplayRole) && (role != Qt::EditRole)) {
- switch (role) {
- case Qt::ForegroundRole: {
- return QBrush(Qt::gray);
- } break;
- case Qt::FontRole: {
- QFont font(m_groupMaps[row][column].value(Qt::FontRole).value<QFont>());
- font.setItalic(true);
- return font;
- } break;
- case Qt::TextAlignmentRole: {
- return Qt::AlignHCenter;
- } break;
- case Qt::UserRole: {
- return m_groupMaps[row][column].value( Qt::DisplayRole ).toString();
- } break;
- case Qt::CheckStateRole: {
- if (column != 0) return QVariant();
- int childCount = m_groupMap.value( row ).count();
- int checked = 0;
- QModelIndex parentIndex = this->index( row, 0, index.parent() );
- for( int childRow = 0; childRow < childCount; ++childRow )
- {
- QModelIndex childIndex = this->index( childRow, 0, parentIndex );
- QVariant data = mapToSource( childIndex ).data( Qt::CheckStateRole );
- if (data.toInt() == 2) ++checked;
- }
- if (checked == childCount) return Qt::Checked;
- else if (checked == 0) return Qt::Unchecked;
- else return Qt::PartiallyChecked;
- } break;
- default: {
- QModelIndex parentIndex = this->index( row, 0, index.parent() );
- if (m_groupMap.value( row ).count() > 0) {
- return this->index(0, column, parentIndex).data(role);
- } else {
- return QVariant();
- }
- // return m_groupMaps[row][column].value( role );
- } break;
- }
- }
-
- //use cached or precalculated data
- if( m_groupMaps[row][column].contains( Qt::DisplayRole ) )
- {
- if ((m_flags & FLAG_NOGROUPNAME) != 0) {
- QModelIndex parentIndex = this->index( row, 0, index.parent() );
- QModelIndex childIndex = this->index( 0, column, parentIndex );
- return childIndex.data(role).toString();
- } else {
- return m_groupMaps[row][column].value( role ).toString();
- }
- }
-
- //for column 0 we gather data from the grouped column instead
- if( column == 0 )
- column = m_groupedColumn;
-
- //map all data from children to columns of group to allow grouping one level up
- QVariantList variantsOfChildren;
- int childCount = m_groupMap.value( row ).count();
- if( childCount == 0 )
- return QVariant();
-
- int function = AGGR_NONE;
- if (m_aggregateRole >= Qt::UserRole) {
- QModelIndex parentIndex = this->index( row, 0, index.parent() );
- QModelIndex childIndex = this->index( 0, column, parentIndex );
- function = mapToSource(childIndex).data(m_aggregateRole).toInt();
- }
-
- //Need a parentIndex with column == 0 because only those have children.
- QModelIndex parentIndex = this->index( row, 0, index.parent() );
- for( int childRow = 0; childRow < childCount; childRow++ )
- {
- QModelIndex childIndex = this->index( childRow, column, parentIndex );
- QVariant data = mapToSource( childIndex ).data( role );
-
- if( data.isValid() && !variantsOfChildren.contains( data ) )
- variantsOfChildren << data;
- }
-
- //saving in cache
- ItemData roleMap = m_groupMaps[row].value( column );
- foreach( const QVariant &variant, variantsOfChildren )
- {
- if( roleMap[ role ] != variant ) {
- roleMap.insert( role, variantsOfChildren );
- }
- }
-
- if( variantsOfChildren.count() == 0 )
- return QVariant();
-
- //only one unique variant? No need to return a list
- switch (function) {
- case AGGR_EMPTY: return QVariant();
- case AGGR_FIRST: return variantsOfChildren.first();
- case AGGR_MAX: return variantMax(variantsOfChildren);
- case AGGR_MIN: return variantMin(variantsOfChildren);
- default: {
- if( variantsOfChildren.count() == 1 )
- return variantsOfChildren.first();
-
- return variantsOfChildren;
- } break;
- }
- }
-
- return mapToSource( index ).data( role );
-}
-
-bool
-QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role )
-{
- if( !idx.isValid() )
- return false;
-
- //no need to set data to exactly the same value
- if( idx.data( role ) == value )
- return false;
-
- if( isGroup( idx ) )
- {
- ItemData columnData = m_groupMaps[idx.row()][idx.column()];
-
- columnData.insert( role, value );
- //QItemDelegate will always use Qt::EditRole
- if( role == Qt::EditRole )
- columnData.insert( Qt::DisplayRole, value );
-
- //and make sure it's stored in the map
- m_groupMaps[idx.row()].insert( idx.column(), columnData );
-
- int columnToChange = idx.column() ? idx.column() : m_groupedColumn;
- foreach( int originalRow, m_groupMap.value( idx.row() ) )
- {
- QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange,
- m_rootNode );
- if( childIdx.isValid() )
- sourceModel()->setData( childIdx, value, role );
- }
- //TODO: we might need to reload the data from the children at this point
-
- emit dataChanged( idx, idx );
- return true;
- }
-
- return sourceModel()->setData( mapToSource( idx ), value, role );
-}
-
-bool
-QtGroupingProxy::isGroup( const QModelIndex &index ) const
-{
- int parentCreateIndex = index.internalId();
- if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() )
- return true;
- return false;
-}
-
-QModelIndex
-QtGroupingProxy::mapToSource( const QModelIndex &index ) const
-{
- if( !index.isValid() ) {
- return m_rootNode;
- }
-
- if( isGroup( index ) )
- {
- return m_rootNode;
- }
-
- QModelIndex proxyParent = index.parent();
- QModelIndex originalParent = mapToSource( proxyParent );
-
- int originalRow = index.row();
- if( originalParent == m_rootNode )
- {
- int indexInGroup = index.row();
- if( !proxyParent.isValid() )
- indexInGroup -= m_groupMaps.count();
-
- QList<int> childRows = m_groupMap.value( proxyParent.row() );
- if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 )
- return QModelIndex();
-
- originalRow = childRows.at( indexInGroup );
- }
- return sourceModel()->index( originalRow, index.column(), originalParent );
-}
-
-QModelIndexList
-QtGroupingProxy::mapToSource( const QModelIndexList& list ) const
-{
- QModelIndexList originalList;
- foreach( const QModelIndex &index, list )
- {
- QModelIndex originalIndex = mapToSource( index );
- if( originalIndex.isValid() )
- originalList << originalIndex;
- }
- return originalList;
-}
-
-QModelIndex
-QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const
-{
- if( !idx.isValid() )
- return QModelIndex();
-
- QModelIndex proxyParent;
- QModelIndex sourceParent = idx.parent();
-
- int proxyRow = idx.row();
- int sourceRow = idx.row();
-
- if( sourceParent.isValid() && ( sourceParent != m_rootNode ) )
- {
- //idx is a child of one of the items in the source model
- proxyParent = mapFromSource( sourceParent );
- }
- else
- {
- //idx is an item in the top level of the source model (child of the rootnode)
- int groupRow = -1;
- QMapIterator<quint32, QList<int> > iterator( m_groupMap );
- while( iterator.hasNext() )
- {
- iterator.next();
- if( iterator.value().contains( sourceRow ) )
- {
- groupRow = iterator.key();
- break;
- }
- }
-
- if( groupRow != -1 ) //it's in a group, let's find the correct row.
- {
- proxyParent = this->index( groupRow, 0, QModelIndex() );
- proxyRow = m_groupMap.value( groupRow ).indexOf( sourceRow );
- }
- else
- {
- proxyParent = QModelIndex();
- // if the proxy item is not in a group it will be below the groups.
- int groupLength = m_groupMaps.count();
- int i = m_groupMap.value( std::numeric_limits<quint32>::max() ).indexOf( sourceRow );
-
- proxyRow = groupLength + i;
- }
- }
-
- return this->index( proxyRow, idx.column(), proxyParent );
-}
-
-Qt::ItemFlags
-QtGroupingProxy::flags( const QModelIndex &idx ) const
-{
- if( !idx.isValid() )
- {
- Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootNode );
- if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) )
- return Qt::ItemFlags( Qt::ItemIsDropEnabled );
-
- return Qt::ItemFlags(0);
- }
-
- //only if the grouped column has the editable flag set allow the
- //actions leading to setData on the source (edit & drop)
- if( isGroup( idx ) )
- {
- // dumpGroups();
- Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
- //Qt::ItemFlags defaultFlags(Qt::ItemIsEnabled);
- bool groupIsEditable = true;
-
- if (idx.column() == 0) {
- bool checkable = true;
- foreach ( int originalRow, m_groupMap.value( idx.row() ) )
- {
- QModelIndex originalIdx = sourceModel()->index( originalRow, 0,
- m_rootNode.parent() );
- if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 )
- {
- checkable = false;
- }
- }
-
- if ( checkable ) {
- defaultFlags |= Qt::ItemIsUserCheckable;
- }
- }
-
- //it's possible to have empty groups
- if( m_groupMap.value( idx.row() ).count() == 0 )
- {
- //check the flags of this column with the root node
- QModelIndex originalRootNode = sourceModel()->index( m_rootNode.row(), m_groupedColumn,
- m_rootNode.parent() );
- groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable );
- }
- else
- {
- foreach( int originalRow, m_groupMap.value( idx.row() ) )
- {
- QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn,
- m_rootNode );
-
- groupIsEditable = groupIsEditable
- ? originalIdx.flags().testFlag( Qt::ItemIsEditable )
- : false;
- if( !groupIsEditable ) //all children need to have an editable grouped column
- break;
- }
- }
- if( groupIsEditable )
- return ( defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled );
- return defaultFlags;
- }
-
- QModelIndex originalIdx = mapToSource( idx );
- Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx );
-
- //check the source model to see if the grouped column is editable;
- QModelIndex groupedColumnIndex =
- sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() );
- bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable );
-
- if( groupIsEditable )
- return originalItemFlags | Qt::ItemIsDragEnabled;
- return originalItemFlags;
-}
-
-QVariant
-QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const
-{
- return sourceModel()->headerData( section, orientation, role );
-}
-
-bool
-QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const
-{
- if( !parent.isValid() )
- return false;
-
- if( isGroup( parent ) )
- return false;
-
- return sourceModel()->canFetchMore( mapToSource( parent ) );
-}
-
-void
-QtGroupingProxy::fetchMore ( const QModelIndex & parent )
-{
- if( !parent.isValid() )
- return;
-
- if( isGroup( parent ) )
- return;
-
- return sourceModel()->fetchMore( mapToSource( parent ) );
-}
-
-QModelIndex
-QtGroupingProxy::addEmptyGroup( const RowData &data )
-{
- int newRow = m_groupMaps.count();
- beginInsertRows( QModelIndex(), newRow, newRow );
- m_groupMaps << data;
- endInsertRows();
- return index( newRow, 0, QModelIndex() );
-}
-
-bool
-QtGroupingProxy::removeGroup( const QModelIndex &idx )
-{
- beginRemoveRows( idx.parent(), idx.row(), idx.row() );
- m_groupMap.remove( idx.row() );
- m_groupMaps.removeAt( idx.row() );
- m_parentCreateList.removeAt( idx.internalId() );
- endRemoveRows();
-
- //TODO: only true if all data could be unset.
- return true;
-}
-
-bool
-QtGroupingProxy::hasChildren( const QModelIndex &parent ) const
-{
- if( !parent.isValid() ) {
- return true;
- }
-
- if( isGroup( parent ) ) {
- return !m_groupMap.value( parent.row() ).isEmpty();
- }
-
- return sourceModel()->hasChildren( mapToSource( parent ) );
-}
-
-bool
-QtGroupingProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
-{
- QModelIndex idx = index(row, column, parent);
- if (isGroup(idx)) {
- QList<int> childRows = m_groupMap.value(idx.row());
- int max = *std::max_element(childRows.begin(), childRows.end());
-
- QModelIndex newIdx = mapToSource(index(max, column, idx));
- return sourceModel()->dropMimeData(data, action, max, column, newIdx);
- } else {
- if (row == -1) {
- return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent));
- } else {
- QModelIndex idx = mapToSource(index(row, column, parent));
- return sourceModel()->dropMimeData(data, action, idx.row(), idx.column(), idx.parent());
- }
- }
-}
-
-void
-QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end )
-{
- if( parent != m_rootNode )
- {
- //an item will be added to an original index, remap and pass it on
- QModelIndex proxyParent = mapFromSource( parent );
- beginInsertRows( proxyParent, start, end );
- }
-}
-
-void
-QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end )
-{
- if( parent == m_rootNode )
- {
- //top level of the model changed, these new rows need to be put in groups
- for( int modelRow = start; modelRow <= end ; modelRow++ )
- {
- addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootNode ) );
- }
- }
- else
- {
- //an item was added to an original index, remap and pass it on
- QModelIndex proxyParent = mapFromSource( parent );
-
- QString s;
- QDebug debug(&s);
- debug << proxyParent;
- log::debug("{}", s);
-
- //beginInsertRows had to be called in modelRowsAboutToBeInserted()
- endInsertRows();
- }
-}
-
-void
-QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end )
-{
- if( parent == m_rootNode )
- {
- QMap<quint32, QList<int> >::const_iterator i;
- //HACK, we are going to call beginRemoveRows() multiple times without
- // endRemoveRows() if a source index is in multiple groups.
- // This can be a problem for some views/proxies, but Q*Views can handle it.
- // TODO: investigate a queue for applying proxy model changes in the correct order
- for( i = m_groupMap.constBegin(); i != m_groupMap.constEnd(); ++i )
- {
- int groupIndex = i.key();
- const QList<int> &groupList = i.value();
- QModelIndex proxyParent = index( groupIndex, 0 );
- foreach( int originalRow, groupList )
- {
- if( originalRow >= start && originalRow <= end )
- {
- int proxyRow = groupList.indexOf( originalRow );
- if( groupIndex == -1 ) //adjust for non-grouped (root level) original items
- proxyRow += m_groupMaps.count();
- //TODO: optimize for continues original rows in the same group
- beginRemoveRows( proxyParent, proxyRow, proxyRow );
- }
- }
- }
- }
- else
- {
- //child item(s) of an original item will be removed, remap and pass it on
- QModelIndex proxyParent = mapFromSource( parent );
- beginRemoveRows( proxyParent, start, end );
- }
-}
-
-void
-QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end )
-{
- if( parent == m_rootNode )
- {
- //TODO: can be optimised by iterating over m_groupMap and checking start <= r < end
-
- //rather than increasing i we change the stored sourceRows in-place and reuse argument start
- //X-times (where X = end - start).
- for( int i = start; i <= end; i++ )
- {
- //HACK: we are going to iterate the hash in reverse so calls to endRemoveRows()
- // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved()
- //NOTE: easier to do reverse with java style iterator
- QMutableMapIterator<quint32, QList<int> > iter( m_groupMap );
- iter.toBack();
- while( iter.hasPrevious() )
- {
- iter.previous();
- int groupIndex = iter.key();
- //has to be a modifiable reference for remove and replace operations
- QList<int> &groupList = iter.value();
- int rowIndex = groupList.indexOf( start );
- if( rowIndex != -1 )
- {
- QModelIndex proxyParent = index( groupIndex, 0 );
- groupList.removeAt( rowIndex );
- }
- //Now decrement all source rows that are after the removed row
- for( int j = 0; j < groupList.count(); j++ )
- {
- int sourceRow = groupList.at( j );
- if( sourceRow > start )
- groupList.replace( j, sourceRow-1 );
- }
- if( rowIndex != -1)
- endRemoveRows(); //end remove operation only after group was updated.
- }
- }
-
- return;
- }
-
- //beginRemoveRows had to be called in modelRowsAboutToBeRemoved();
- endRemoveRows();
-}
-
-void
-QtGroupingProxy::resetModel()
-{
- buildTree();
-}
-
-void
-QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
-{
- //TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly
- QModelIndex proxyTopLeft = mapFromSource( topLeft );
- if( !proxyTopLeft.isValid() )
- return;
-
- if( topLeft == bottomRight )
- {
- emit dataChanged( proxyTopLeft, proxyTopLeft );
- }
- else
- {
- QModelIndex proxyBottomRight = mapFromSource( bottomRight );
- emit dataChanged( proxyTopLeft, proxyBottomRight );
- }
-}
-
-bool
-QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const
-{
- foreach( const QModelIndex &index, list )
- {
- if( isGroup( index ) )
- return true;
- }
- return false;
-}
-
-void
-QtGroupingProxy::dumpGroups() const
-{
- QString s;
- QDebug debug(&s);
-
- debug << "m_groupMap:\n";
- for( int groupIndex = -1; groupIndex < m_groupMap.keys().count() - 1; groupIndex++ )
- {
- debug << groupIndex << " : " << m_groupMap.value( groupIndex ) << "\n";
- }
-
- debug << "m_groupMaps:\n";
- for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ )
- {
- debug << m_groupMaps[groupIndex] << ": " << m_groupMap.value( groupIndex ) << "\n";
- }
-
- debug << m_groupMap.value( std::numeric_limits<quint32>::max() );
-
- log::debug("{}", s);
-}
+/**************************************************************************************** + * Copyright (c) 2007-2011 Bart Cerneels <bart.cerneels@kde.org> * + * * + * 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, see <http://www.gnu.org/licenses/>. * + ****************************************************************************************/ + +// Modifications 2013-03-27 to 2013-03-29 by Sebastian Herbord + + +#include "qtgroupingproxy.h" +#include <log.h> + +#include <QDebug> +#include <QIcon> +#include <QInputDialog> + +using namespace MOBase; + +/*! + \class QtGroupingProxy + \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level. + The source model can be flat or tree organized, but only the original top level rows are used + for determining the grouping. + \ingroup model-view +*/ + +QtGroupingProxy::QtGroupingProxy(QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags, int aggregateRole) + : QAbstractProxyModel() + , m_rootNode(rootNode) + , m_groupedColumn(0) + , m_groupedRole(groupedRole) + , m_aggregateRole(aggregateRole) + , m_flags(flags) +{ + if (groupedColumn != -1) { + setGroupedColumn(groupedColumn); + } +} + +QtGroupingProxy::~QtGroupingProxy() +{ +} + +void QtGroupingProxy::setSourceModel(QAbstractItemModel* model) +{ + if (sourceModel()) { + disconnect(sourceModel(), nullptr, this, nullptr); + } + + QAbstractProxyModel::setSourceModel(model); + + if (sourceModel()) { + // signal proxies + connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), + SLOT(modelRowsInserted(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), + SLOT(modelRowsAboutToBeInserted(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)), + SLOT(modelRowsRemoved(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + SLOT(modelRowsAboutToBeRemoved(QModelIndex, int, int))); + connect(sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree())); + connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), + SLOT(modelDataChanged(QModelIndex, QModelIndex))); + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel())); + + buildTree(); + } +} + +void +QtGroupingProxy::setGroupedColumn( int groupedColumn ) +{ + m_groupedColumn = groupedColumn; + buildTree(); +} + +/** Maps to what groups the source row belongs by returning the data of those groups. + * + * @returns a list of data for the rows the argument belongs to. In common cases this list will + * contain only one entry. An empty list means that the source item will be placed in the root of + * this proxyModel. There is no support for hiding source items. + * + * Group data can be pre-loaded in the return value so it's added to the cache maintained by this + * class. This is required if you want to have data that is not present in the source model. + */ +QList<RowData> +QtGroupingProxy::belongsTo( const QModelIndex &idx ) +{ + QList<RowData> rowDataList; + + //get all the data for this index from the model + ItemData itemData = sourceModel()->itemData( idx ); + if (m_groupedRole != Qt::DisplayRole) { + itemData[Qt::DisplayRole] = itemData[m_groupedRole]; + } + + // invalid value in grouped role -> ungrouped + if (!itemData[Qt::DisplayRole].isValid()) { + return rowDataList; + } + + QMapIterator<int, QVariant> i( itemData ); + while( i.hasNext() ) + { + i.next(); + int role = i.key(); + QVariant variant = i.value(); + + if ( variant.type() == QVariant::List ) + { + //a list of variants get's expanded to multiple rows + QVariantList list = variant.toList(); + for( int i = 0; i < list.length(); i++ ) + { + //take an existing row data or create a new one + RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt( i ) + : RowData(); + + //we only gather data for the first column + ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData(); + indexData.insert( role, list.value( i ) ); + rowData.insert( 0, indexData ); + //for the grouped column the data should not be gathered from the children + //this will allow filtering on the content of this column with a + //QSortFilterProxyModel + rowData.insert( m_groupedColumn, indexData ); + rowDataList.insert( i, rowData ); + } + break; + } + else if( !variant.isNull() ) + { + //it's just a normal item. Copy all the data and break this loop. + RowData rowData; + rowData.insert( 0, itemData ); + rowDataList << rowData; + break; + } + } + + return rowDataList; +} + +/* m_groupMap layout +* key : index of the group in m_groupMaps +* value : a QList of the original rows in sourceModel() for the children of this group +* +* key = -1 contains a QList of the non-grouped indexes +* +* TODO: sub-groups +*/ +void +QtGroupingProxy::buildTree() +{ + if( !sourceModel() ) + return; + beginResetModel(); + + m_groupMap.clear(); + //don't clear the data maps since most of it will probably be needed again. + m_parentCreateList.clear(); + + int max = sourceModel()->rowCount( m_rootNode ); + + //WARNING: these have to be added in order because the addToGroups function is optimized for + //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best. + for( int row = 0; row < max; row++ ) + { + QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootNode ); + addSourceRow( idx ); + } + //dumpGroups(); + + if (m_flags & FLAG_NOSINGLE) { + // awkward: flatten single-item groups as a post-processing steps. + + int currentKey = 0; + quint32 quint32max = std::numeric_limits<quint32>::max(); + std::vector<int> rmgroups; + + QMap<quint32, QList<int> > temp; + + for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); ++iter) { + if ((iter.key() == quint32max) || + (iter->count() < 2)) { + temp[quint32max].append(iter.value()); + if (iter.key() != quint32max) { + rmgroups.push_back(iter.key()); + } + } else { + temp[currentKey++] = *iter; + } + } + m_groupMap = temp; + + // second loop is necessary because qt containers can't be iterated from end to front + // and removing by index from begin to end is ugly + std::sort(rmgroups.begin(), rmgroups.end(), [] (int lhs, int rhs) { return rhs < lhs; }); + for (auto iter = rmgroups.begin(); iter != rmgroups.end(); ++iter) { + m_groupMaps.removeAt(*iter); + } + } + + endResetModel(); +} + +QList<int> +QtGroupingProxy::addSourceRow( const QModelIndex &idx ) +{ + QList<int> updatedGroups; + QList<RowData> groupData = belongsTo( idx ); + + //an empty list here means it's supposed to go in root. + if( groupData.isEmpty() ) + { + updatedGroups << -1; + if( !m_groupMap.keys().contains( std::numeric_limits<quint32>::max() ) ) + m_groupMap.insert( std::numeric_limits<quint32>::max(), QList<int>() ); //add an empty placeholder + } + + //an item can be in multiple groups + foreach( RowData data, groupData ) + { + int updatedGroup = -1; + if( !data.isEmpty() ) + { + foreach( const RowData &cachedData, m_groupMaps ) + { + //when this matches the index belongs to an existing group + if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] ) + { + data = cachedData; + break; + } + } + + updatedGroup = m_groupMaps.indexOf( data ); + //-1 means not found + if( updatedGroup == -1 ) + { + //new groups are added to the end of the existing list + m_groupMaps << data; + updatedGroup = m_groupMaps.count() - 1; + } + + if( !m_groupMap.keys().contains( updatedGroup ) ) + m_groupMap.insert( updatedGroup, QList<int>() ); //add an empty placeholder + } + + if( !updatedGroups.contains( updatedGroup ) ) + updatedGroups << updatedGroup; + } + + //update m_groupMap to the new source-model layout (one row added) + QMutableMapIterator<quint32, QList<int> > i( m_groupMap ); + while( i.hasNext() ) + { + i.next(); + QList<int> &groupList = i.value(); + int insertedProxyRow = groupList.count(); + for( ; insertedProxyRow > 0 ; insertedProxyRow-- ) + { + int &rowValue = groupList[insertedProxyRow-1]; + if( idx.row() <= rowValue ) + { + //increment the rows that come after the new row since they moved one place up. + rowValue++; + } + else + { + break; + } + } + + if( updatedGroups.contains( i.key() ) ) + { + //the row needs to be added to this group + groupList.insert( insertedProxyRow, idx.row() ); + } + } + + return updatedGroups; +} + +/** Each ModelIndex has in it's internalId a position in the parentCreateList. + * struct ParentCreate are the instructions to recreate the parent index. + * It contains the proxy row number of the parent and the postion in this list of the grandfather. + * This function creates the ParentCreate structs and saves them in a list. + */ +int +QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const +{ + if( !parent.isValid() ) + return -1; + + struct ParentCreate pc; + for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) + { + pc = m_parentCreateList[i]; + if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() ) + return i; + } + //there is no parentCreate yet for this index, so let's create one. + pc.parentCreateIndex = parent.internalId(); + pc.row = parent.row(); + m_parentCreateList << pc; + + return m_parentCreateList.size() - 1; +} + +QModelIndex +QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const +{ + if( !hasIndex(row, column, parent) ) { + return QModelIndex(); + } + + if( parent.column() > 0 ) { + return QModelIndex(); + } + + /* We save the instructions to make the parent of the index in a struct. + * The place of the struct in the list is stored in the internalId + */ + int parentCreateIndex = indexOfParentCreate( parent ); + + return createIndex( row, column, parentCreateIndex ); +} + +QModelIndex +QtGroupingProxy::parent( const QModelIndex &index ) const +{ + if( !index.isValid() ) + return QModelIndex(); + + int parentCreateIndex = index.internalId(); + if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() ) + return QModelIndex(); + + struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; + + //only items at column 0 have children + return createIndex( pc.row, 0, pc.parentCreateIndex ); +} + +int +QtGroupingProxy::rowCount( const QModelIndex &index ) const +{ + if( !index.isValid() ) + { + //the number of top level groups + the number of non-grouped items + int rows = m_groupMaps.count() + m_groupMap.value( std::numeric_limits<quint32>::max() ).count(); + return rows; + } + + //TODO:group in group support. + if( isGroup( index ) ) + { + qint64 groupIndex = index.row(); + int rows = m_groupMap.value( groupIndex ).count(); + return rows; + } else { + QModelIndex originalIndex = mapToSource( index ); + int rowCount = sourceModel()->rowCount( originalIndex ); + return rowCount; + } +} + +int +QtGroupingProxy::columnCount( const QModelIndex &index ) const +{ + if( !index.isValid() ) + return sourceModel()->columnCount( m_rootNode ); + + if( index.column() != 0 ) + return 0; + + return sourceModel()->columnCount( mapToSource( index ) ); +} + + +static bool variantLess(const QVariant &LHS, const QVariant &RHS) +{ + if ((LHS.type() == RHS.type()) && + ((LHS.type() == QVariant::Int) || (LHS.type() == QVariant::UInt))) { + return LHS.toInt() < RHS.toInt(); + } + + // this should always work (comparing empty strings in the worst case) but + // the results may be wrong + return LHS.toString() < RHS.toString(); +} + + +static QVariant variantMax(const QVariantList &variants) +{ + QVariant result = variants.first(); + foreach (const QVariant &iter, variants) { + if (variantLess(result, iter)) { + result = iter; + } + } + return result; +} + + +static QVariant variantMin(const QVariantList &variants) +{ + QVariant result = variants.first(); + foreach (const QVariant &iter, variants) { + if (variantLess(iter, result)) { + result = iter; + } + } + return result; +} + + +QVariant +QtGroupingProxy::data( const QModelIndex &index, int role ) const +{ + if( !index.isValid() ) + return QVariant(); + + int row = index.row(); + int column = index.column(); + if( isGroup( index ) ) + { + if ((role != Qt::DisplayRole) && (role != Qt::EditRole)) { + switch (role) { + case Qt::ForegroundRole: { + return QBrush(Qt::gray); + } break; + case Qt::FontRole: { + QFont font(m_groupMaps[row][column].value(Qt::FontRole).value<QFont>()); + font.setItalic(true); + return font; + } break; + case Qt::TextAlignmentRole: { + return Qt::AlignHCenter; + } break; + case Qt::UserRole: { + return m_groupMaps[row][column].value( Qt::DisplayRole ).toString(); + } break; + case Qt::CheckStateRole: { + if (column != 0) return QVariant(); + int childCount = m_groupMap.value( row ).count(); + int checked = 0; + QModelIndex parentIndex = this->index( row, 0, index.parent() ); + for( int childRow = 0; childRow < childCount; ++childRow ) + { + QModelIndex childIndex = this->index( childRow, 0, parentIndex ); + QVariant data = mapToSource( childIndex ).data( Qt::CheckStateRole ); + if (data.toInt() == 2) ++checked; + } + if (checked == childCount) return Qt::Checked; + else if (checked == 0) return Qt::Unchecked; + else return Qt::PartiallyChecked; + } break; + default: { + QModelIndex parentIndex = this->index( row, 0, index.parent() ); + if (m_groupMap.value( row ).count() > 0) { + return this->index(0, column, parentIndex).data(role); + } else { + return QVariant(); + } + // return m_groupMaps[row][column].value( role ); + } break; + } + } + + //use cached or precalculated data + if( m_groupMaps[row][column].contains( Qt::DisplayRole ) ) + { + if ((m_flags & FLAG_NOGROUPNAME) != 0) { + QModelIndex parentIndex = this->index( row, 0, index.parent() ); + QModelIndex childIndex = this->index( 0, column, parentIndex ); + return childIndex.data(role).toString(); + } else { + return m_groupMaps[row][column].value( role ).toString(); + } + } + + //for column 0 we gather data from the grouped column instead + if( column == 0 ) + column = m_groupedColumn; + + //map all data from children to columns of group to allow grouping one level up + QVariantList variantsOfChildren; + int childCount = m_groupMap.value( row ).count(); + if( childCount == 0 ) + return QVariant(); + + int function = AGGR_NONE; + if (m_aggregateRole >= Qt::UserRole) { + QModelIndex parentIndex = this->index( row, 0, index.parent() ); + QModelIndex childIndex = this->index( 0, column, parentIndex ); + function = mapToSource(childIndex).data(m_aggregateRole).toInt(); + } + + //Need a parentIndex with column == 0 because only those have children. + QModelIndex parentIndex = this->index( row, 0, index.parent() ); + for( int childRow = 0; childRow < childCount; childRow++ ) + { + QModelIndex childIndex = this->index( childRow, column, parentIndex ); + QVariant data = mapToSource( childIndex ).data( role ); + + if( data.isValid() && !variantsOfChildren.contains( data ) ) + variantsOfChildren << data; + } + + //saving in cache + ItemData roleMap = m_groupMaps[row].value( column ); + foreach( const QVariant &variant, variantsOfChildren ) + { + if( roleMap[ role ] != variant ) { + roleMap.insert( role, variantsOfChildren ); + } + } + + if( variantsOfChildren.count() == 0 ) + return QVariant(); + + //only one unique variant? No need to return a list + switch (function) { + case AGGR_EMPTY: return QVariant(); + case AGGR_FIRST: return variantsOfChildren.first(); + case AGGR_MAX: return variantMax(variantsOfChildren); + case AGGR_MIN: return variantMin(variantsOfChildren); + default: { + if( variantsOfChildren.count() == 1 ) + return variantsOfChildren.first(); + + return variantsOfChildren; + } break; + } + } + + return mapToSource( index ).data( role ); +} + +bool +QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role ) +{ + if( !idx.isValid() ) + return false; + + //no need to set data to exactly the same value + if( idx.data( role ) == value ) + return false; + + if( isGroup( idx ) ) + { + ItemData columnData = m_groupMaps[idx.row()][idx.column()]; + + columnData.insert( role, value ); + //QItemDelegate will always use Qt::EditRole + if( role == Qt::EditRole ) + columnData.insert( Qt::DisplayRole, value ); + + //and make sure it's stored in the map + m_groupMaps[idx.row()].insert( idx.column(), columnData ); + + int columnToChange = idx.column() ? idx.column() : m_groupedColumn; + foreach( int originalRow, m_groupMap.value( idx.row() ) ) + { + QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange, + m_rootNode ); + if( childIdx.isValid() ) + sourceModel()->setData( childIdx, value, role ); + } + //TODO: we might need to reload the data from the children at this point + + emit dataChanged( idx, idx ); + return true; + } + + return sourceModel()->setData( mapToSource( idx ), value, role ); +} + +bool +QtGroupingProxy::isGroup( const QModelIndex &index ) const +{ + int parentCreateIndex = index.internalId(); + if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() ) + return true; + return false; +} + +QModelIndex +QtGroupingProxy::mapToSource( const QModelIndex &index ) const +{ + if( !index.isValid() ) { + return m_rootNode; + } + + if( isGroup( index ) ) + { + return m_rootNode; + } + + QModelIndex proxyParent = index.parent(); + QModelIndex originalParent = mapToSource( proxyParent ); + + int originalRow = index.row(); + if( originalParent == m_rootNode ) + { + int indexInGroup = index.row(); + if( !proxyParent.isValid() ) + indexInGroup -= m_groupMaps.count(); + + QList<int> childRows = m_groupMap.value( proxyParent.row() ); + if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 ) + return QModelIndex(); + + originalRow = childRows.at( indexInGroup ); + } + return sourceModel()->index( originalRow, index.column(), originalParent ); +} + +QModelIndexList +QtGroupingProxy::mapToSource( const QModelIndexList& list ) const +{ + QModelIndexList originalList; + foreach( const QModelIndex &index, list ) + { + QModelIndex originalIndex = mapToSource( index ); + if( originalIndex.isValid() ) + originalList << originalIndex; + } + return originalList; +} + +QModelIndex +QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const +{ + if( !idx.isValid() ) + return QModelIndex(); + + QModelIndex proxyParent; + QModelIndex sourceParent = idx.parent(); + + int proxyRow = idx.row(); + int sourceRow = idx.row(); + + if( sourceParent.isValid() && ( sourceParent != m_rootNode ) ) + { + //idx is a child of one of the items in the source model + proxyParent = mapFromSource( sourceParent ); + } + else + { + //idx is an item in the top level of the source model (child of the rootnode) + int groupRow = -1; + QMapIterator<quint32, QList<int> > iterator( m_groupMap ); + while( iterator.hasNext() ) + { + iterator.next(); + if( iterator.value().contains( sourceRow ) ) + { + groupRow = iterator.key(); + break; + } + } + + if( groupRow != -1 ) //it's in a group, let's find the correct row. + { + proxyParent = this->index( groupRow, 0, QModelIndex() ); + proxyRow = m_groupMap.value( groupRow ).indexOf( sourceRow ); + } + else + { + proxyParent = QModelIndex(); + // if the proxy item is not in a group it will be below the groups. + int groupLength = m_groupMaps.count(); + int i = m_groupMap.value( std::numeric_limits<quint32>::max() ).indexOf( sourceRow ); + + proxyRow = groupLength + i; + } + } + + return this->index( proxyRow, idx.column(), proxyParent ); +} + +Qt::ItemFlags +QtGroupingProxy::flags( const QModelIndex &idx ) const +{ + if( !idx.isValid() ) + { + Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootNode ); + if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) ) + return Qt::ItemFlags( Qt::ItemIsDropEnabled ); + + return Qt::ItemFlags(0); + } + + //only if the grouped column has the editable flag set allow the + //actions leading to setData on the source (edit & drop) + if( isGroup( idx ) ) + { + // dumpGroups(); + Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + //Qt::ItemFlags defaultFlags(Qt::ItemIsEnabled); + bool groupIsEditable = true; + + if (idx.column() == 0) { + bool checkable = true; + foreach ( int originalRow, m_groupMap.value( idx.row() ) ) + { + QModelIndex originalIdx = sourceModel()->index( originalRow, 0, + m_rootNode.parent() ); + if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 ) + { + checkable = false; + } + } + + if ( checkable ) { + defaultFlags |= Qt::ItemIsUserCheckable; + } + } + + //it's possible to have empty groups + if( m_groupMap.value( idx.row() ).count() == 0 ) + { + //check the flags of this column with the root node + QModelIndex originalRootNode = sourceModel()->index( m_rootNode.row(), m_groupedColumn, + m_rootNode.parent() ); + groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable ); + } + else + { + foreach( int originalRow, m_groupMap.value( idx.row() ) ) + { + QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn, + m_rootNode ); + + groupIsEditable = groupIsEditable + ? originalIdx.flags().testFlag( Qt::ItemIsEditable ) + : false; + if( !groupIsEditable ) //all children need to have an editable grouped column + break; + } + } + if( groupIsEditable ) + return ( defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled ); + return defaultFlags; + } + + QModelIndex originalIdx = mapToSource( idx ); + Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx ); + + //check the source model to see if the grouped column is editable; + QModelIndex groupedColumnIndex = + sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() ); + bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable ); + + if( groupIsEditable ) + return originalItemFlags | Qt::ItemIsDragEnabled; + return originalItemFlags; +} + +QVariant +QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const +{ + return sourceModel()->headerData( section, orientation, role ); +} + +bool +QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const +{ + if( !parent.isValid() ) + return false; + + if( isGroup( parent ) ) + return false; + + return sourceModel()->canFetchMore( mapToSource( parent ) ); +} + +void +QtGroupingProxy::fetchMore ( const QModelIndex & parent ) +{ + if( !parent.isValid() ) + return; + + if( isGroup( parent ) ) + return; + + return sourceModel()->fetchMore( mapToSource( parent ) ); +} + +QModelIndex +QtGroupingProxy::addEmptyGroup( const RowData &data ) +{ + int newRow = m_groupMaps.count(); + beginInsertRows( QModelIndex(), newRow, newRow ); + m_groupMaps << data; + endInsertRows(); + return index( newRow, 0, QModelIndex() ); +} + +bool +QtGroupingProxy::removeGroup( const QModelIndex &idx ) +{ + beginRemoveRows( idx.parent(), idx.row(), idx.row() ); + m_groupMap.remove( idx.row() ); + m_groupMaps.removeAt( idx.row() ); + m_parentCreateList.removeAt( idx.internalId() ); + endRemoveRows(); + + //TODO: only true if all data could be unset. + return true; +} + +bool +QtGroupingProxy::hasChildren( const QModelIndex &parent ) const +{ + if( !parent.isValid() ) { + return true; + } + + if( isGroup( parent ) ) { + return !m_groupMap.value( parent.row() ).isEmpty(); + } + + return sourceModel()->hasChildren( mapToSource( parent ) ); +} + +bool +QtGroupingProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + QModelIndex idx = index(row, column, parent); + if (isGroup(idx)) { + QList<int> childRows = m_groupMap.value(idx.row()); + int max = *std::max_element(childRows.begin(), childRows.end()); + + QModelIndex newIdx = mapToSource(index(max, column, idx)); + return sourceModel()->dropMimeData(data, action, max, column, newIdx); + } else { + if (row == -1) { + return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } else { + QModelIndex idx = mapToSource(index(row, column, parent)); + return sourceModel()->dropMimeData(data, action, idx.row(), idx.column(), idx.parent()); + } + } +} + +void +QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end ) +{ + if( parent != m_rootNode ) + { + //an item will be added to an original index, remap and pass it on + QModelIndex proxyParent = mapFromSource( parent ); + beginInsertRows( proxyParent, start, end ); + } +} + +void +QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end ) +{ + if( parent == m_rootNode ) + { + //top level of the model changed, these new rows need to be put in groups + for( int modelRow = start; modelRow <= end ; modelRow++ ) + { + addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootNode ) ); + } + } + else + { + //an item was added to an original index, remap and pass it on + QModelIndex proxyParent = mapFromSource( parent ); + + QString s; + QDebug debug(&s); + debug << proxyParent; + log::debug("{}", s); + + //beginInsertRows had to be called in modelRowsAboutToBeInserted() + endInsertRows(); + } +} + +void +QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ) +{ + if( parent == m_rootNode ) + { + QMap<quint32, QList<int> >::const_iterator i; + //HACK, we are going to call beginRemoveRows() multiple times without + // endRemoveRows() if a source index is in multiple groups. + // This can be a problem for some views/proxies, but Q*Views can handle it. + // TODO: investigate a queue for applying proxy model changes in the correct order + for( i = m_groupMap.constBegin(); i != m_groupMap.constEnd(); ++i ) + { + int groupIndex = i.key(); + const QList<int> &groupList = i.value(); + QModelIndex proxyParent = index( groupIndex, 0 ); + foreach( int originalRow, groupList ) + { + if( originalRow >= start && originalRow <= end ) + { + int proxyRow = groupList.indexOf( originalRow ); + if( groupIndex == -1 ) //adjust for non-grouped (root level) original items + proxyRow += m_groupMaps.count(); + //TODO: optimize for continues original rows in the same group + beginRemoveRows( proxyParent, proxyRow, proxyRow ); + } + } + } + } + else + { + //child item(s) of an original item will be removed, remap and pass it on + QModelIndex proxyParent = mapFromSource( parent ); + beginRemoveRows( proxyParent, start, end ); + } +} + +void +QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end ) +{ + if( parent == m_rootNode ) + { + //TODO: can be optimised by iterating over m_groupMap and checking start <= r < end + + //rather than increasing i we change the stored sourceRows in-place and reuse argument start + //X-times (where X = end - start). + for( int i = start; i <= end; i++ ) + { + //HACK: we are going to iterate the hash in reverse so calls to endRemoveRows() + // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved() + //NOTE: easier to do reverse with java style iterator + QMutableMapIterator<quint32, QList<int> > iter( m_groupMap ); + iter.toBack(); + while( iter.hasPrevious() ) + { + iter.previous(); + int groupIndex = iter.key(); + //has to be a modifiable reference for remove and replace operations + QList<int> &groupList = iter.value(); + int rowIndex = groupList.indexOf( start ); + if( rowIndex != -1 ) + { + QModelIndex proxyParent = index( groupIndex, 0 ); + groupList.removeAt( rowIndex ); + } + //Now decrement all source rows that are after the removed row + for( int j = 0; j < groupList.count(); j++ ) + { + int sourceRow = groupList.at( j ); + if( sourceRow > start ) + groupList.replace( j, sourceRow-1 ); + } + if( rowIndex != -1) + endRemoveRows(); //end remove operation only after group was updated. + } + } + + return; + } + + //beginRemoveRows had to be called in modelRowsAboutToBeRemoved(); + endRemoveRows(); +} + +void +QtGroupingProxy::resetModel() +{ + buildTree(); +} + +void +QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ) +{ + //TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly + QModelIndex proxyTopLeft = mapFromSource( topLeft ); + if( !proxyTopLeft.isValid() ) + return; + + if( topLeft == bottomRight ) + { + emit dataChanged( proxyTopLeft, proxyTopLeft ); + } + else + { + QModelIndex proxyBottomRight = mapFromSource( bottomRight ); + emit dataChanged( proxyTopLeft, proxyBottomRight ); + } +} + +bool +QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const +{ + foreach( const QModelIndex &index, list ) + { + if( isGroup( index ) ) + return true; + } + return false; +} + +void +QtGroupingProxy::dumpGroups() const +{ + QString s; + QDebug debug(&s); + + debug << "m_groupMap:\n"; + for( int groupIndex = -1; groupIndex < m_groupMap.keys().count() - 1; groupIndex++ ) + { + debug << groupIndex << " : " << m_groupMap.value( groupIndex ) << "\n"; + } + + debug << "m_groupMaps:\n"; + for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ ) + { + debug << m_groupMaps[groupIndex] << ": " << m_groupMap.value( groupIndex ) << "\n"; + } + + debug << m_groupMap.value( std::numeric_limits<quint32>::max() ); + + log::debug("{}", s); +} |
