urkon
urkon

Reputation: 263

How to make transparent proxy model : QAbstractProxyModel?

Since I should make only little modification on the model, transparent proxy would be the best starting point. This proxy will be inserted into model chain.

What is the cleanest way of making proxy model so, that every thing goes unchanged between source and destination model in both directions?

Is there any trivial index(), mapToSource(), mapFromSource(), ... translation using sourceModel()?

All I need to extend are data() with roles and flags().

I know this should be easy to make but I do not want to make it unnecessary complicated making it with learning by doing.

Thanx for your time.

Upvotes: 2

Views: 3854

Answers (3)

The following works under Qt 4.7, and shows a minimal implementation that retains the semantics of a QAbstractItemModel:

// https://github.com/KubaO/stackoverflown/tree/master/questions/transparent-proxy-19835618
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif

class TransparentProxyModel : public QAbstractProxyModel {
   Q_OBJECT
   struct Helper : QAbstractItemModel {
      using QAbstractItemModel::createIndex;
   };
   struct Op {
      enum Kind { AddRow, RemoveRow, AddCol, RemoveCol, MoveRow, MoveCol } kind;
      QModelIndex parentSrc;
      int first, last;
      QModelIndex parentDst;
      int index;
      bool checkSrc(Kind k, const QModelIndex &i, int f, int l) const {
         return kind == k && parentSrc == i && first == f && last == l;
      }
      bool checkDst(const QModelIndex &i, int n) const {
         return parentDst == i && index == n;
      }
   };
   QVector<Op> m_addsRemoves;
   QModelIndex createSourceIndex(int r, int c, void *data) const {
      return static_cast<Helper *>(sourceModel())->createIndex(r, c, data);
   }
   Q_SLOT void onDataChanged(const QModelIndex &tl, const QModelIndex &br) {
      emit dataChanged(mapFromSource(tl), mapFromSource(br));
   }
   Q_SLOT void onRowsAboutToBeInserted(const QModelIndex &parent, int first, int last) {
      m_addsRemoves.push_back({Op::AddRow, parent, first, last});
      beginInsertRows(mapFromSource(parent), first, last);
   }
   Q_SLOT void onRowsInserted(const QModelIndex &parent, int first, int last) {
      Q_ASSERT(!m_addsRemoves.isEmpty());
      Q_ASSERT(m_addsRemoves.last().checkSrc(Op::AddRow, parent, first, last));
      m_addsRemoves.pop_back();
      endInsertRows();
   }
   Q_SLOT void onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) {
      m_addsRemoves.push_back({Op::RemoveRow, parent, first, last});
      beginRemoveRows(mapFromSource(parent), first, last);
   }
   Q_SLOT void onRowsRemoved(const QModelIndex &parent, int first, int last) {
      Q_ASSERT(!m_addsRemoves.isEmpty());
      Q_ASSERT(m_addsRemoves.last().checkSrc(Op::RemoveRow, parent, first, last));
      m_addsRemoves.pop_back();
      endRemoveRows();
   }
   Q_SLOT void onColumnsAboutToBeInserted(const QModelIndex &parent, int first,
                                          int last) {
      m_addsRemoves.push_back({Op::AddCol, parent, first, last});
      beginInsertColumns(parent, first, last);
   }
   Q_SLOT void onColumnsInserted(const QModelIndex &parent, int first, int last) {
      Q_ASSERT(!m_addsRemoves.isEmpty());
      Q_ASSERT(m_addsRemoves.last().checkSrc(Op::AddCol, parent, first, last));
      m_addsRemoves.pop_back();
      endInsertColumns();
   }
   Q_SLOT void onColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last) {
      m_addsRemoves.push_back({Op::RemoveCol, parent, first, last});
      beginRemoveColumns(mapFromSource(parent), first, last);
   }
   Q_SLOT void onColumnsRemoved(const QModelIndex &parent, int first, int last) {
      Q_ASSERT(!m_addsRemoves.isEmpty());
      Q_ASSERT(m_addsRemoves.last().checkSrc(Op::RemoveCol, parent, first, last));
      m_addsRemoves.pop_back();
      endRemoveColumns();
   }
   Q_SLOT void onRowsAboutToBeMoved(const QModelIndex &srcParent, int start, int end,
                                    const QModelIndex &dstParent, int row) {
      m_addsRemoves.push_back({Op::MoveRow, srcParent, start, end, dstParent, row});
      beginMoveRows(mapFromSource(srcParent), start, end, mapFromSource(dstParent), row);
   }
   Q_SLOT void onRowsMoved(const QModelIndex &srcParent, int start, int end,
                           const QModelIndex &dstParent, int row) {
      Q_ASSERT(!m_addsRemoves.isEmpty());
      auto const &op = m_addsRemoves.last();
      Q_ASSERT(op.checkSrc(Op::MoveRow, srcParent, start, end) &&
               op.checkDst(dstParent, row));
      m_addsRemoves.pop_back();
      endMoveRows();
   }
   Q_SLOT void onColumnsAboutToBeMoved(const QModelIndex &srcParent, int start, int end,
                                       const QModelIndex &dstParent, int col) {
      m_addsRemoves.push_back({Op::MoveCol, srcParent, start, end, dstParent, col});
      beginMoveColumns(mapFromSource(srcParent), start, end, mapFromSource(dstParent),
                       col);
   }
   Q_SLOT void onColumnsMoved(const QModelIndex &srcParent, int start, int end,
                              const QModelIndex &dstParent, int col) {
      Q_ASSERT(!m_addsRemoves.isEmpty());
      auto const &op = m_addsRemoves.last();
      Q_ASSERT(op.checkSrc(Op::MoveRow, srcParent, start, end) &&
               op.checkDst(dstParent, col));
      m_addsRemoves.pop_back();
      endMoveColumns();
   }

  public:
   TransparentProxyModel(QObject *parent = nullptr) : QAbstractProxyModel(parent) {}
   QModelIndex mapFromSource(const QModelIndex &src) const override {
      if (!src.isValid() || !sourceModel()) return {};
      Q_ASSERT(src.model() == sourceModel());
      return createIndex(src.row(), src.column(), src.internalPointer());
   }
   QModelIndex mapToSource(const QModelIndex &prx) const override {
      if (!prx.isValid() || !sourceModel()) return {};
      Q_ASSERT(prx.model() == this);
      return createSourceIndex(prx.row(), prx.column(), prx.internalPointer());
   }
   QModelIndex index(int row, int column, const QModelIndex &parent) const override {
      if (!sourceModel()) return {};
      Q_ASSERT(!parent.isValid() || parent.model() == this);
      return mapFromSource(sourceModel()->index(row, column, mapToSource(parent)));
   }
   int rowCount(const QModelIndex &parent) const override {
      if (!sourceModel()) return 0;
      Q_ASSERT(!parent.isValid() || parent.model() == this);
      return sourceModel()->rowCount(mapToSource(parent));
   }
   int columnCount(const QModelIndex &parent) const override {
      if (!sourceModel()) return 0;
      Q_ASSERT(!parent.isValid() || parent.model() == this);
      return sourceModel()->columnCount(mapToSource(parent));
   }
   QModelIndex parent(const QModelIndex &child) const override {
      if (!child.isValid() || !sourceModel()) return {};
      Q_ASSERT(child.model() == this);
      return mapFromSource(sourceModel()->parent(mapToSource(child)));
   }
   void setSourceModel(QAbstractItemModel *model) override {
      if (sourceModel()) disconnect(sourceModel(), 0, this, 0);
      QAbstractProxyModel::setSourceModel(model);
      if (!sourceModel()) return;
      connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this,
              SLOT(onDataChanged(QModelIndex, QModelIndex)));
      connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), this,
              SIGNAL(headerDataChanged(Qt::Orientation, int, int)));
      connect(model, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged()));
      connect(model, SIGNAL(layoutAboutToBeChanged()), this,
              SIGNAL(layoutAboutToBeChanged()));
      connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this,
              SLOT(onRowsAboutToBeInserted(QModelIndex, int, int)));
      connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), this,
              SLOT(onRowsInserted(QModelIndex, int, int)));
      connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this,
              SLOT(onRowsAboutToBeRemoved(QModelIndex, int, int)));
      connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
              SLOT(onRowsRemoved(QModelIndex, int, int)));
      connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)), this,
              SLOT(onColumnsAboutToBeInserted(QModelIndex, int, int)));
      connect(model, SIGNAL(columnsInserted(QModelIndex, int, int)), this,
              SLOT(onColumnsInserted(QModelIndex, int, int)));
      connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int)), this,
              SLOT(onColumnsAboutToBeRemoved(QModelIndex, int, int)));
      connect(model, SIGNAL(columnsRemoved(QModelIndex, int, int)), this,
              SLOT(onColumnsRemoved(QModelIndex, int, int)));
      connect(model, SIGNAL(modelAboutToBeReset()), this, SIGNAL(modelAboutToBeReset()));
      connect(model, SIGNAL(modelReset()), this, SIGNAL(modelReset()));
      connect(model, SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)),
              this, SLOT(onRowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)));
      connect(model, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), this,
              SLOT(onRowsMoved(QModelIndex, int, int, QModelIndex, int)));
      connect(
          model, SIGNAL(columnsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)),
          this, SLOT(onColumnsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)));
      connect(model, SIGNAL(columnsMoved(QModelIndex, int, int, QModelIndex, int)), this,
              SLOT(onColumnsMoved(QModelIndex, int, int, QModelIndex, int)));
   }
};

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);
   QFileSystemModel model;
   TransparentProxyModel proxy;
   proxy.setSourceModel(&model);
   QTreeView view;
   view.setModel(&proxy);
   model.setRootPath(QDir::homePath());
   view.setRootIndex(proxy.mapFromSource(model.index(QDir::homePath())));
   view.show();
   return app.exec();
}
#include "main.moc"

Upvotes: 2

urkon
urkon

Reputation: 263

I have made my own solution. It doesn't make any index change. It should not, actually. Signals are transferred.

The model chain should not be affected if this model is inserted into it.

#ifndef TTRANSPARENTPROXYMODEL_H
#define TTRANSPARENTPROXYMODEL_H

#include <QAbstractProxyModel>

class TTransparentProxyModel :
    public QAbstractProxyModel
{
    Q_OBJECT
public:
    TTransparentProxyModel(QObject *parent = 0);

    void setSourceModel(QAbstractItemModel* newSourceModel);

    /* QAbstractProxyModel methods */
    virtual QModelIndex index( int, int c = 0, const QModelIndex& parent = QModelIndex() ) const;
    virtual QModelIndex parent( const QModelIndex &child ) const;
    virtual int rowCount( const QModelIndex &idx = QModelIndex() ) const;
    virtual int columnCount(const QModelIndex &parent ) const;
    virtual QModelIndex mapToSource( const QModelIndex &index ) const;
    virtual QModelIndex mapFromSource( const QModelIndex &idx ) const;
};

#endif // TTRANSPARENTPROXYMODEL_H

and cpp file:

#include "TransparentProxyModel.h"

TTransparentProxyModel::TTransparentProxyModel(QObject *parent)
    : QAbstractProxyModel(parent)
{
}

void TTransparentProxyModel::setSourceModel(QAbstractItemModel* newSourceModel)
{
    beginResetModel();

    if (sourceModel()) {
    disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
               this, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
               this, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
               this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
               this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
               this, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    disconnect(sourceModel(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
               this, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    disconnect(sourceModel(), SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
               this, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(columnsInserted(const QModelIndex &, int, int)),
               this, SIGNAL(columnsInserted(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
               this, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
               this, SIGNAL(columnsRemoved(const QModelIndex &, int, int)));
    disconnect(sourceModel(), SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
               this, SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    disconnect(sourceModel(), SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
               this, SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    disconnect(sourceModel(), SIGNAL(modelAboutToBeReset()),
               this, SIGNAL(modelAboutToBeReset()));
    disconnect(sourceModel(), SIGNAL(modelReset()),
               this, SIGNAL(modelReset()));
    disconnect(sourceModel(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
               this, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)));
    disconnect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
               this, SIGNAL(headerDataChanged(Qt::Orientation,int,int)));
    disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()),
               this, SIGNAL(layoutAboutToBeChanged()));
    disconnect(sourceModel(), SIGNAL(layoutChanged()),
               this, SIGNAL(layoutChanged()));
    }

    QAbstractProxyModel::setSourceModel(newSourceModel);

    if (sourceModel()) {
    connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
            this, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
            this, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
            this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
            this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            this, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(sourceModel(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            this, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(sourceModel(), SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
            this, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(columnsInserted(const QModelIndex &, int, int)),
            this, SIGNAL(columnsInserted(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
            this, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
            this, SIGNAL(columnsRemoved(const QModelIndex &, int, int)));
    connect(sourceModel(), SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            this, SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(sourceModel(), SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            this, SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(sourceModel(), SIGNAL(modelAboutToBeReset()),
            this, SIGNAL(modelAboutToBeReset()));
    connect(sourceModel(), SIGNAL(modelReset()),
            this, SIGNAL(modelReset()));
    connect(sourceModel(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
            this, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)));
    connect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
            this, SIGNAL(headerDataChanged(Qt::Orientation,int,int)));
    connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()),
            this, SIGNAL(layoutAboutToBeChanged()));
    connect(sourceModel(), SIGNAL(layoutChanged()),
            this, SIGNAL(layoutChanged()));
    }
    endResetModel();
}

//virtual int rowCount( const QModelIndex &idx = QModelIndex() ) const;
int TTransparentProxyModel::rowCount(const QModelIndex &parent) const
{
    if(!sourceModel())
    {
    return 0;
    }
    return this->sourceModel()->rowCount(parent);
}

//virtual int columnCount( const QModelIndex &idx ) const;
int TTransparentProxyModel::columnCount(const QModelIndex &parent) const
{
    if(!sourceModel())
    {
    return 0;
    }
    return this->sourceModel()->columnCount(parent);
}

//virtual QModelIndex index( int, int c = 0, const QModelIndex& parent = QModelIndex() ) const;
QModelIndex TTransparentProxyModel::index(int row, int column, const QModelIndex &parent) const
{
    if(!this->sourceModel())
    {
    return QModelIndex();
    }
    return this->sourceModel()->index(row,column,parent);
}

//virtual QModelIndex parent( const QModelIndex &idx ) const;
QModelIndex TTransparentProxyModel::parent(const QModelIndex &child) const
{
//  TODO: check if this is valid.
    QModelIndex mi =  mapFromSource(child);
    if (mi.isValid())
    {
    return mi.parent();
    }
    return QModelIndex();
}

//virtual QModelIndex mapToSource( const QModelIndex &idx ) const;
QModelIndex TTransparentProxyModel::mapToSource(const QModelIndex &index) const
{
    if(!this->sourceModel())
    {
    return QModelIndex();
    }
    return this->sourceModel()->index(index.row(),index.column());
}


//virtual QModelIndex mapFromSource( const QModelIndex &idx ) const;
QModelIndex TTransparentProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
    if(sourceIndex.isValid())
    if(!this->sourceModel())
    {
    return QModelIndex();
    }
    return this->sourceModel()->index(sourceIndex.row(),sourceIndex.column());
}

I tried to catch all of the signals from the source and signal the same way to next model.

The only method, I am not sure if it is properly implemented, is parent().

I hope this is helpful.

Upvotes: 1

Frank Osterfeld
Frank Osterfeld

Reputation: 25155

If you're using Qt 4.8 or newer, you can use QIdentityProxyModel, which does exactly that: it maps the source model directly without changing the structure, so you can reimplement data() and/or flags() to modify what's returned.

In Qt 4.7 and older, the easiest way should be to reimplement QSortFilterProxyModel without using anything of the sorting or filtering and just override data() and flags().

Upvotes: 7

Related Questions