Qt的一些经验总结(6)-- 多root model

254 阅读9分钟

需求

QTreeview中需要显示多个不同根路径的节点

为什么需要开发

如果是使用单节点,通过过滤的方式隐藏其他目录节点,在setRootPath后,其他不显示的文件夹和文件发生变化都会监听到,导致性能浪费。如果仿照QFileSystemModel单独实现一个多root的model,代价比较高,开发周期过长。

整理逻辑

QFileSystemModel只能监听一个rootPath,局限比较大。当需要监听多个rootPath的时候,则需要使用多个model,多root方案是自定义一个QAbstractItemModel,将多个model进行管理和映射来实现,类似于proxymodel。

代码

#ifndef FOLDERLISTMODEL_H
#define FOLDERLISTMODEL_H

#include <QAbstractItemModel>
#include <QFileSystemModel>
#include <QItemSelection>
#include <QModelIndex>

#include "utils_global.h"

class QFileSystemWatcher;

struct SourceModel
{
    QFileSystemModel *model;
    QString rootPath;
    // 用于监听当前model,如果当前这个model在本地删除了,要将其移除
    QString watchPath;
    // 实际在view中的位置
    QModelIndex rootIndex;
    // setRootPath后的index
    QModelIndex rootSourceIndex;
};

class MultiFolderModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit MultiFolderModel(QObject *parent = nullptr);
    virtual ~MultiFolderModel();
    void clear();
    QModelIndex addRootPath(const QString &path);
    QModelIndex insertRootPath(int targetIndex, const QString &path);
    QModelIndex refreshRoot(const QModelIndex &index);
    void removeRootPath(const QString &path);
    void removeRoot(const QModelIndex &index);
    bool isRootPath(const QString &path);
    QList<QModelIndex> rootIndexs() const;
    QStringList rootPathList() const;
    QString filePath(const QModelIndex &index) const;
    QString fileName(const QModelIndex &index) const;
    QFileInfo fileInfo(const QModelIndex &index) const;
    bool isDir(const QModelIndex &index) const;
    QModelIndex mkdir(const QModelIndex &parent, const QString &name);
    bool rmdir(const QModelIndex &index);
    bool remove(const QModelIndex &index);
    void setFilter(QDir::Filters filters);
    QDir::Filters filter() const;
    void setNameFilters(const QStringList &filters);
    QStringList nameFilters() const;
    void setNameFilterDisables(bool enable);
    bool nameFilterDisables() const;
    void setResolveSymlinks(bool enable);
    bool resolveSymlinks() const;
    bool isRootIndex(const QModelIndex &index) const;
    void setWatcherRoot(bool b);
    QList<QModelIndex> indexForPath(const QString &path) const;
    QModelIndex indexForPath(QFileSystemModel *model, const QString &path) const;
    QModelIndex rootIndex(const QModelIndex &index) const;
    void topRootPath(const QModelIndex &index);

Q_SIGNALS:
    void directoryLoaded(QFileSystemModel *model, const QString &path);
    void fileRenamed(const QString &path, const QString &oldName, const QString &newName);
    void rootRemoved(const QString &path);

protected:
    QFileSystemModel *findSource(const QModelIndex &proxyIndex) const;
    QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
    QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
    QItemSelection mapSelectionToSource(const QItemSelection &proxySelection) const;
    QItemSelection mapSelectionFromSource(const QItemSelection &sourceSelection) const;
    bool isRootSourceIndex(const QModelIndex &sourceIndex) const;
    void resort();

public:
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex index(int row, int column,
                      const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &child) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value,
                       int role = Qt::EditRole) override;

    bool canFetchMore(const QModelIndex &parent) const override;
    void fetchMore(const QModelIndex &parent) override;
    void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
    QSize span(const QModelIndex &index) const override;
    bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;

    bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
    bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
    bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
public Q_SLOTS:
    void directoryChanged(const QString &path);

    void sourceRowsInserted(const QModelIndex &, int, int);
    void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int);
    void sourceRowsRemoved(const QModelIndex &, int, int);
    void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int);
    void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int);

    void sourceColumnsAboutToBeInserted(const QModelIndex &, int, int);
    void sourceColumnsInserted(const QModelIndex &, int, int);
    void sourceColumnsAboutToBeRemoved(const QModelIndex &, int, int);
    void sourceColumnsRemoved(const QModelIndex &, int, int);
    void sourceColumnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int);
    void sourceColumnsMoved(const QModelIndex &, int, int, const QModelIndex &, int);

    void sourceDataChanged(const QModelIndex &, const QModelIndex &);
    void sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last);

    void sourceLayoutAboutToBeChanged();
    void sourceLayoutChanged();
    void sourceModelAboutToBeReset();
    void sourceModelReset();

protected:
    QList<SourceModel> m_modelList;
    mutable QHash<qint64, QAbstractItemModel *> m_indexMap;
    QList<QPersistentModelIndex> layoutChangePersistentIndexes;
    QModelIndexList proxyIndexes;
    QFileSystemWatcher *m_watcher = nullptr;
    QDir::Filters m_filters = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs;
    QStringList m_nameFilters;
    bool m_resolveSymlinks = false;
    bool m_nameFilterDisables = true;
};

#endif // FOLDERLISTMODEL_H
#include "multifoldermodel.h"
#include <QCollator>
#include <QDebug>
#include <QFileSystemWatcher>
#include <QSortFilterProxyModel>

class FileSystemModelEx : public QFileSystemModel
{
public:
    using QFileSystemModel::QFileSystemModel;
    Qt::ItemFlags flags(const QModelIndex &index) const override
    {
        return QFileSystemModel::flags(index) | Qt::ItemIsEditable;
    }

    QModelIndex createIndex(int arow, int acolumn, void *adata) const
    {
        return QFileSystemModel::createIndex(arow, acolumn, adata);
    }
};

MultiFolderModel::MultiFolderModel(QObject *parent)
    : QAbstractItemModel(parent)
{ }

MultiFolderModel::~MultiFolderModel() = default;

void MultiFolderModel::clear()
{
    if (m_modelList.isEmpty()) {
        return;
    }
    this->beginRemoveRows(QModelIndex(), 0, m_modelList.size());
    this->removeRows(0, m_modelList.size());
    if (m_watcher) {
        for (auto &s : m_modelList) {
            m_watcher->removePath(s.watchPath);
        }
    }
    for (auto &s : m_modelList) {
        delete s.model;
    }
    m_modelList.clear();
    m_indexMap.clear();
    this->endRemoveRows();
}

QModelIndex MultiFolderModel::addRootPath(const QString &path)
{
    int index = this->m_modelList.size();
    return this->insertRootPath(index, path);
}

QModelIndex MultiFolderModel::insertRootPath(int targetIndex, const QString &path)
{
    if (!QDir(path).exists()) {
        return QModelIndex();
    }
    auto model = new FileSystemModelEx(this);
    model->setFilter(m_filters);
    if (!m_nameFilters.isEmpty()) {
        model->setNameFilters(m_nameFilters);
    }
    model->setNameFilterDisables(m_nameFilterDisables);
    model->setResolveSymlinks(m_resolveSymlinks);
    QModelIndex sourceIndex = model->setRootPath(path);
    if (!sourceIndex.isValid()) {
        return sourceIndex;
    }
    SourceModel m;
    m.model = model;
    m.rootPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    m.watchPath = model->filePath(sourceIndex.parent());
    if (m_watcher && !m_watcher->directories().contains(m.watchPath)) {
        m_watcher->addPath(m.watchPath);
    }
    m.rootSourceIndex = sourceIndex;
    m.rootIndex = createIndex(targetIndex, 0, sourceIndex.internalPointer());
    m_indexMap.insert(sourceIndex.internalId(), model);

    for (int i = targetIndex; i < m_modelList.size(); i++) {
        auto model = m_modelList.at(i);
        model.rootIndex = createIndex(model.rootIndex.row() + 1, 0, sourceIndex.internalPointer());
    }

    m_modelList.insert(targetIndex, m);

    connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
            SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
            SLOT(sourceRowsAboutToBeRemoved(const QModelIndex &, int, int)));
    connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
            SLOT(sourceRowsRemoved(const QModelIndex &, int, int)));
    connect(
        model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
        SLOT(sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            SLOT(sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
            SLOT(sourceColumnsAboutToBeInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
            SLOT(sourceColumnsInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
            SLOT(sourceColumnsAboutToBeRemoved(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
            SLOT(sourceColumnsRemoved(const QModelIndex &, int, int)));
    connect(
        model,
        SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
        SLOT(sourceColumnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            SLOT(sourceColumnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(modelAboutToBeReset()), SLOT(sourceModelAboutToBeReset()));
    connect(model, SIGNAL(modelReset()), SLOT(sourceModelReset()));
    connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
            SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &)));
    connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
            SLOT(sourceHeaderDataChanged(Qt::Orientation, int, int)));
    connect(model, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged()));
    connect(model, SIGNAL(layoutChanged()), SLOT(sourceLayoutChanged()));
    connect(model, &QFileSystemModel::directoryLoaded, this,
            [=](const QString &path) { Q_EMIT directoryLoaded(model, path); });
    connect(model, &QFileSystemModel::fileRenamed, this,
            [=](const QString &path, const QString &oldName, const QString &newName) {
                this->resort();
                Q_EMIT fileRenamed(path, oldName, newName);
            });
    return m.rootIndex;
}

QModelIndex MultiFolderModel::refreshRoot(const QModelIndex &index)
{
    // 刷新root需要先记录旧的索引,将旧的移除,然后将新的重新插入到对应索引的位置
    int pos = index.row();
    auto path = this->filePath(index);
    this->removeRoot(index);
    return this->insertRootPath(pos, path);
}

void MultiFolderModel::removeRootPath(const QString &path)
{
    QString rootPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    QModelIndex index;
    for (auto &s : m_modelList) {
        if (s.rootPath == rootPath) {
            index = s.rootIndex;
            break;
        }
    }
    if (index.isValid()) {
        removeRoot(index);
    }
}

void MultiFolderModel::removeRoot(const QModelIndex &index)
{
    QMutableListIterator<SourceModel> i(m_modelList);
    while (i.hasNext()) {
        SourceModel s = i.next();
        if (s.rootIndex.internalId() == index.internalId()) {
            this->beginRemoveRows(QModelIndex(), index.row(), index.row());
            if (m_watcher) {
                m_watcher->removePath(s.watchPath);
            }
            this->removeRow(index.row());
            i.remove();
            this->endRemoveRows();
            QMutableHashIterator<qint64, QAbstractItemModel *> i2(m_indexMap);
            while (i2.hasNext()) {
                i2.next();
                if (i2.value() == s.model) {
                    i2.remove();
                }
            }
            delete s.model;
            break;
        }
    }
}

bool MultiFolderModel::isRootPath(const QString &path)
{
    QString rootPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    for (auto &s : m_modelList) {
        if (s.rootPath == rootPath) {
            return true;
        }
    }
    return false;
}

QList<QModelIndex> MultiFolderModel::rootIndexs() const
{
    QList<QModelIndex> indexs;
    for (auto &s : m_modelList) {
        indexs.append(s.rootIndex);
    }
    return indexs;
}

QStringList MultiFolderModel::rootPathList() const
{
    QStringList paths;
    for (auto &s : m_modelList) {
        paths.append(s.rootPath);
    }
    return paths;
}

QFileSystemModel *MultiFolderModel::findSource(const QModelIndex &proxyIndex) const
{
    return (QFileSystemModel *)m_indexMap[proxyIndex.internalId()];
}

QItemSelection MultiFolderModel::mapSelectionToSource(const QItemSelection &proxySelection) const
{
    QModelIndexList proxyIndexes = proxySelection.indexes();
    QItemSelection sourceSelection;
    for (int i = 0; i < proxyIndexes.size(); ++i) {
        const QModelIndex proxyIdx = mapToSource(proxyIndexes.at(i));
        if (!proxyIdx.isValid())
            continue;
        sourceSelection << QItemSelectionRange(proxyIdx);
    }
    return sourceSelection;
}

QItemSelection MultiFolderModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const
{
    QModelIndexList sourceIndexes = sourceSelection.indexes();
    QItemSelection proxySelection;
    for (int i = 0; i < sourceIndexes.size(); ++i) {
        const QModelIndex srcIdx = mapFromSource(sourceIndexes.at(i));
        if (!srcIdx.isValid())
            continue;
        proxySelection << QItemSelectionRange(srcIdx);
    }
    return proxySelection;
}

QModelIndex MultiFolderModel::mapFromSource(const QModelIndex &sourceIndex) const
{
    if (!sourceIndex.isValid())
        return QModelIndex();
    int row = sourceIndex.row();
    for (int i = 0; i < m_modelList.size(); i++) {
        if (m_modelList[i].rootSourceIndex.internalId() == sourceIndex.internalId()) {
            row = i;
            break;
        }
    }
    QModelIndex index = createIndex(row, sourceIndex.column(), sourceIndex.internalPointer());
    m_indexMap.insert(sourceIndex.internalId(), (QAbstractItemModel *)sourceIndex.model());
    return index;
}

QModelIndex MultiFolderModel::mapToSource(const QModelIndex &proxyIndex) const
{
    if (!proxyIndex.isValid()) {
        return QModelIndex();
    }
    int row = proxyIndex.row();
    for (int i = 0; i < m_modelList.size(); i++) {
        if (m_modelList[i].rootIndex.internalId() == proxyIndex.internalId()) {
            row = i;
            break;
        }
    }
    auto model = (FileSystemModelEx *)findSource(proxyIndex);
    return model->createIndex(row, proxyIndex.column(), proxyIndex.internalPointer());
}

QString MultiFolderModel::filePath(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QString();
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((QFileSystemModel *)sourceIndex.model())->filePath(sourceIndex);
}

QString MultiFolderModel::fileName(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QString();
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((QFileSystemModel *)sourceIndex.model())->fileName(sourceIndex);
}

QFileInfo MultiFolderModel::fileInfo(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QFileInfo();
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((QFileSystemModel *)sourceIndex.model())->fileInfo(sourceIndex);
}

bool MultiFolderModel::isDir(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return true;
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((QFileSystemModel *)sourceIndex.model())->isDir(sourceIndex);
}

QModelIndex MultiFolderModel::mkdir(const QModelIndex &parent, const QString &name)
{
    if (!parent.isValid()) {
        return QModelIndex();
    }
    QModelIndex sourceIndex = mapToSource(parent);
    auto index = ((QFileSystemModel *)sourceIndex.model())->mkdir(sourceIndex, name);
    this->resort();
    return index;
}

bool MultiFolderModel::rmdir(const QModelIndex &index)
{
    if (!index.isValid()) {
        return false;
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((QFileSystemModel *)sourceIndex.model())->rmdir(sourceIndex);
}

bool MultiFolderModel::remove(const QModelIndex &index)
{
    if (!index.isValid()) {
        return false;
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((QFileSystemModel *)sourceIndex.model())->remove(sourceIndex);
}

void MultiFolderModel::setFilter(QDir::Filters filters)
{
    m_filters = filters;
    for (auto &s : m_modelList) {
        s.model->setFilter(filters);
    }
}

QDir::Filters MultiFolderModel::filter() const
{
    return m_filters;
}

void MultiFolderModel::setNameFilters(const QStringList &filters)
{
    m_nameFilters = filters;
    for (auto &s : m_modelList) {
        s.model->setNameFilters(filters);
    }
}

QStringList MultiFolderModel::nameFilters() const
{
    return m_nameFilters;
}

void MultiFolderModel::setNameFilterDisables(bool enable)
{
    if (m_nameFilterDisables == enable) {
        return;
    }
    m_nameFilterDisables = enable;
    for (auto &s : m_modelList) {
        s.model->setNameFilterDisables(enable);
    }
}

bool MultiFolderModel::nameFilterDisables() const
{
    return m_nameFilterDisables;
}

void MultiFolderModel::setResolveSymlinks(bool enable)
{
    if (m_resolveSymlinks == enable) {
        return;
    }
    m_resolveSymlinks = enable;
    for (auto &s : m_modelList) {
        s.model->setResolveSymlinks(enable);
    }
}

bool MultiFolderModel::resolveSymlinks() const
{
    return m_resolveSymlinks;
}

bool MultiFolderModel::isRootIndex(const QModelIndex &index) const
{
    for (auto &s : m_modelList) {
        if (s.rootIndex.internalId() == index.internalId()) {
            return true;
        }
    }
    return false;
}

void MultiFolderModel::setWatcherRoot(bool b)
{
    if (m_watcher) {
        delete m_watcher;
        m_watcher = nullptr;
    }

    if (b) {
        m_watcher = new QFileSystemWatcher(this);
        connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
                SLOT(directoryChanged(QString)));
        QStringList paths;
        for (auto &s : m_modelList) {
            paths.append(s.watchPath);
        }
        paths.removeDuplicates();
        if (!paths.isEmpty()) {
            m_watcher->addPaths(paths);
        }
    }
}

QList<QModelIndex> MultiFolderModel::indexForPath(const QString &path) const
{
    QList<QModelIndex> indexs;
    QString findPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    for (auto &s : m_modelList) {
        if (!findPath.startsWith(s.rootPath)) {
            continue;
        }
        QModelIndex sourceIndex = s.model->index(path);
        if (sourceIndex.isValid()) {
            indexs.append(this->mapFromSource(sourceIndex));
        }
    }
    return indexs;
}

QModelIndex MultiFolderModel::indexForPath(QFileSystemModel *model, const QString &path) const
{
    auto index = model->index(path);
    if (index.isValid()) {
        return this->mapFromSource(index);
    }
    return index;
}

QModelIndex MultiFolderModel::rootIndex(const QModelIndex &index) const
{
    auto path = this->filePath(index);
    for (auto &model : m_modelList) {
        if (path == model.rootPath || path.startsWith(model.rootPath + "/")) {
            return model.rootIndex;
        }
    }
    return {};
}

void MultiFolderModel::topRootPath(const QModelIndex &index)
{
    if (index.row() == 0) {
        return;
    }
    this->beginMoveRows({}, index.row(), index.row(), {}, 0);
    this->m_modelList.move(index.row(), 0);
    int pos = 0;
    for (auto &s : this->m_modelList) {
        s.rootIndex = createIndex(pos, 0, s.rootSourceIndex.internalPointer());
        pos++;
    }
    this->endMoveRows();
}

bool MultiFolderModel::isRootSourceIndex(const QModelIndex &sourceIndex) const
{
    for (auto &s : m_modelList) {
        if (s.rootSourceIndex.internalId() == sourceIndex.internalId()) {
            return true;
        }
    }
    return false;
}

void MultiFolderModel::resort()
{
    // 查看源码后,无法单独对某个节点进行重新排序
    // 如果需要排序,只能通过重新设置rootpath才能做到,其余方式都为私有方法并且又成员变量控制
    for (auto &s : m_modelList) {
        auto rootPath = s.model->rootPath();
        s.model->setRootPath("");
        s.model->setRootPath(rootPath);
    }
}

int MultiFolderModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 1;
}

QModelIndex MultiFolderModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();
    if (!parent.isValid()) {
        QModelIndex sourceIndex = m_modelList[row].rootSourceIndex;
        if (sourceIndex.column() != column) {
            sourceIndex = sourceIndex.sibling(sourceIndex.row(), column);
            m_indexMap.insert(sourceIndex.internalId(), m_modelList[row].model);
        }
        return createIndex(row, column, sourceIndex.internalPointer());
    }
    const QModelIndex sourceParent = mapToSource(parent);
    const QModelIndex sourceIndex = sourceParent.model()->index(row, column, sourceParent);
    Q_ASSERT(sourceIndex.isValid());
    return mapFromSource(sourceIndex);
}

QModelIndex MultiFolderModel::parent(const QModelIndex &child) const
{
    Q_ASSERT(child.isValid() ? child.model() == this : true);
    if (isRootIndex(child)) {
        return QModelIndex();
    }
    const QModelIndex sourceIndex = mapToSource(child);
    const QModelIndex sourceParent = sourceIndex.parent();
    return mapFromSource(sourceParent);
}

int MultiFolderModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_modelList.size();
    }
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return sourceIndex.model()->rowCount(sourceIndex);
}

QVariant MultiFolderModel::data(const QModelIndex &proxyIndex, int role) const
{
    if (!proxyIndex.isValid()) {
        return QVariant();
    }
    QModelIndex sourceIndex = mapToSource(proxyIndex);
    return sourceIndex.model()->data(sourceIndex, role);
}

QVariant MultiFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (m_modelList.isEmpty()) {
        return QVariant();
    }
    if (orientation == Qt::Horizontal) {
        const QModelIndex proxyIndex = index(0, section);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.column();
        return sourceIndex.model()->headerData(sourceSection, orientation, role);
    } else {
        const QModelIndex proxyIndex = index(section, 0);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.row();
        return sourceIndex.model()->headerData(sourceSection, orientation, role);
    }
}

Qt::ItemFlags MultiFolderModel::flags(const QModelIndex &index) const
{
    QModelIndex sourceIndex = mapToSource(index);
    if (!sourceIndex.isValid()) {
        return Qt::NoItemFlags;
    }
    return sourceIndex.model()->flags(sourceIndex);
}

bool MultiFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    QModelIndex sourceIndex = mapToSource(index);
    return ((QAbstractItemModel *)sourceIndex.model())->setData(sourceIndex, value, role);
}

bool MultiFolderModel::setHeaderData(int section, Qt::Orientation orientation,
                                     const QVariant &value, int role)
{
    if (orientation == Qt::Horizontal) {
        const QModelIndex proxyIndex = index(0, section);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.column();
        return ((QAbstractItemModel *)sourceIndex.model())
            ->setHeaderData(sourceSection, orientation, value, role);
    } else {
        const QModelIndex proxyIndex = index(section, 0);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.row();
        return ((QAbstractItemModel *)sourceIndex.model())
            ->setHeaderData(sourceSection, orientation, value, role);
    }
}

bool MultiFolderModel::insertColumns(int column, int count, const QModelIndex &parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel *)sourceIndex.model())->insertColumns(column, count, sourceIndex);
}

bool MultiFolderModel::insertRows(int row, int count, const QModelIndex &parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel *)sourceIndex.model())->insertRows(row, count, sourceIndex);
}

bool MultiFolderModel::removeColumns(int column, int count, const QModelIndex &parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel *)sourceIndex.model())->removeColumns(column, count, sourceIndex);
}

bool MultiFolderModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    if (!parent.isValid()) {
        return QAbstractItemModel::removeRows(row, count);
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel *)sourceIndex.model())->removeRows(row, count, sourceIndex);
}

void MultiFolderModel::directoryChanged(const QString &path)
{
    for (auto &s : m_modelList) {
        if (s.watchPath == path && !QDir(s.rootPath).exists()) {
            this->removeRoot(s.rootIndex);
            Q_EMIT rootRemoved(path);
            break;
        }
    }
}

bool MultiFolderModel::canFetchMore(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return false;
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return sourceIndex.model()->canFetchMore(sourceIndex);
}

void MultiFolderModel::fetchMore(const QModelIndex &parent)
{
    if (!parent.isValid()) {
        return;
    }
    QModelIndex sourceIndex = mapToSource(parent);
    QAbstractItemModel *model = (QAbstractItemModel *)sourceIndex.model();
    model->fetchMore(sourceIndex);
}

void MultiFolderModel::sort(int column, Qt::SortOrder order)
{
    for (auto &s : m_modelList) {
        s.model->sort(column, order);
    }
}

QSize MultiFolderModel::span(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QAbstractItemModel::span(index);
    }
    QModelIndex sourceIndex = mapToSource(index);
    return sourceIndex.model()->span(sourceIndex);
}

bool MultiFolderModel::hasChildren(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_modelList.size() > 0;
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return sourceIndex.model()->hasChildren(sourceIndex);
}

void MultiFolderModel::sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end)
{
    this->beginInsertColumns(mapFromSource(parent), start, end);
}

void MultiFolderModel::sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart,
                                                   int sourceEnd, const QModelIndex &destParent,
                                                   int dest)
{
    this->beginMoveColumns(this->mapFromSource(sourceParent), sourceStart, sourceEnd,
                           this->mapFromSource(destParent), dest);
}

void MultiFolderModel::sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    this->beginRemoveColumns(this->mapFromSource(parent), start, end);
}

void MultiFolderModel::sourceColumnsInserted(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    this->endInsertColumns();
}

void MultiFolderModel::sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart,
                                          int sourceEnd, const QModelIndex &destParent, int dest)
{
    Q_UNUSED(sourceParent)
    Q_UNUSED(sourceStart)
    Q_UNUSED(sourceEnd)
    Q_UNUSED(destParent)
    Q_UNUSED(dest)
    this->endMoveColumns();
}

void MultiFolderModel::sourceColumnsRemoved(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    this->endRemoveColumns();
}

void MultiFolderModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    this->dataChanged(this->mapFromSource(topLeft), this->mapFromSource(bottomRight));
}

void MultiFolderModel::sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last)
{
    this->headerDataChanged(orientation, first, last);
}

void MultiFolderModel::sourceLayoutAboutToBeChanged()
{
    for (auto &proxyPersistentIndex : this->persistentIndexList()) {
        proxyIndexes << proxyPersistentIndex;
        const QPersistentModelIndex srcPersistentIndex = this->mapToSource(proxyPersistentIndex);
        layoutChangePersistentIndexes << srcPersistentIndex;
    }

    this->layoutAboutToBeChanged();
}

void MultiFolderModel::sourceLayoutChanged()
{
    for (int i = 0; i < proxyIndexes.size(); ++i) {
        this->changePersistentIndex(proxyIndexes.at(i),
                                    this->mapFromSource(layoutChangePersistentIndexes.at(i)));
    }

    layoutChangePersistentIndexes.clear();
    proxyIndexes.clear();

    this->layoutChanged();
}

void MultiFolderModel::sourceModelAboutToBeReset()
{
    this->beginResetModel();
}

void MultiFolderModel::sourceModelReset()
{
    this->endResetModel();
}

void MultiFolderModel::sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart,
                                                int sourceEnd, const QModelIndex &destParent,
                                                int dest)
{
    this->beginMoveRows(this->mapFromSource(sourceParent), sourceStart, sourceEnd,
                        this->mapFromSource(destParent), dest);
}

void MultiFolderModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    this->beginRemoveRows(this->mapFromSource(parent), start, end);
}

void MultiFolderModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
{
    for (int i = start; i <= end; i++) {
        this->beginInsertRows(this->mapFromSource(parent), i, i);
        this->endInsertRows();
    }
}

void MultiFolderModel::sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart,
                                       int sourceEnd, const QModelIndex &destParent, int dest)
{
    Q_UNUSED(sourceParent)
    Q_UNUSED(sourceStart)
    Q_UNUSED(sourceEnd)
    Q_UNUSED(destParent)
    Q_UNUSED(dest)
    this->endMoveRows();
}

void MultiFolderModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    this->endRemoveRows();
}