File indexing completed on 2024-04-28 15:51:39

0001 /*
0002     SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "annotationproxymodels.h"
0008 
0009 #include <QItemSelection>
0010 #include <QList>
0011 
0012 #include <QIcon>
0013 
0014 #include "annotationmodel.h"
0015 #include "gui/debug_ui.h"
0016 
0017 static quint32 mixIndex(int row, int column)
0018 {
0019     return (row << 4) | column;
0020 }
0021 
0022 PageFilterProxyModel::PageFilterProxyModel(QObject *parent)
0023     : QSortFilterProxyModel(parent)
0024     , mGroupByCurrentPage(false)
0025     , mCurrentPage(-1)
0026 {
0027     setDynamicSortFilter(true);
0028 }
0029 
0030 void PageFilterProxyModel::groupByCurrentPage(bool value)
0031 {
0032     if (mGroupByCurrentPage == value) {
0033         return;
0034     }
0035 
0036     mGroupByCurrentPage = value;
0037 
0038     invalidateFilter();
0039 }
0040 
0041 void PageFilterProxyModel::setCurrentPage(int page)
0042 {
0043     if (mCurrentPage == page) {
0044         return;
0045     }
0046 
0047     mCurrentPage = page;
0048 
0049     // no need to invalidate when we're not showing the current page only
0050     if (!mGroupByCurrentPage) {
0051         return;
0052     }
0053 
0054     invalidateFilter();
0055 }
0056 
0057 bool PageFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &sourceParent) const
0058 {
0059     if (!mGroupByCurrentPage) {
0060         return true;
0061     }
0062 
0063     const QModelIndex pageIndex = sourceModel()->index(row, 0, sourceParent);
0064     int page = sourceModel()->data(pageIndex, AnnotationModel::PageRole).toInt();
0065 
0066     return (page == mCurrentPage);
0067 }
0068 
0069 PageGroupProxyModel::PageGroupProxyModel(QObject *parent)
0070     : QAbstractProxyModel(parent)
0071     , mGroupByPage(false)
0072 {
0073 }
0074 
0075 int PageGroupProxyModel::columnCount(const QModelIndex &parentIndex) const
0076 {
0077     // For top-level and second level we have always only one column
0078     if (mGroupByPage) {
0079         if (parentIndex.isValid()) {
0080             if (parentIndex.parent().isValid()) {
0081                 return 0;
0082             } else {
0083                 return 1; // second-level
0084             }
0085         } else {
0086             return 1; // top-level
0087         }
0088     } else {
0089         if (!parentIndex.isValid()) { // top-level
0090             return 1;
0091         } else {
0092             return 0;
0093         }
0094     }
0095     return 1;
0096 }
0097 
0098 int PageGroupProxyModel::rowCount(const QModelIndex &parentIndex) const
0099 {
0100     if (mGroupByPage) {
0101         if (parentIndex.isValid()) {
0102             if (parentIndex.parent().isValid()) {
0103                 return 0;
0104             } else {
0105                 return mTreeIndexes[parentIndex.row()].second.count(); // second-level
0106             }
0107         } else {
0108             return mTreeIndexes.count(); // top-level
0109         }
0110     } else {
0111         if (!parentIndex.isValid()) { // top-level
0112             return mIndexes.count();
0113         } else {
0114             return 0;
0115         }
0116     }
0117 }
0118 
0119 QModelIndex PageGroupProxyModel::index(int row, int column, const QModelIndex &parentIndex) const
0120 {
0121     if (row < 0 || column != 0) {
0122         return QModelIndex();
0123     }
0124 
0125     if (mGroupByPage) {
0126         if (parentIndex.isValid()) {
0127             if (parentIndex.row() >= 0 && parentIndex.row() < mTreeIndexes.count() && row < mTreeIndexes[parentIndex.row()].second.count()) {
0128                 return createIndex(row, column, qint32(parentIndex.row() + 1));
0129             } else {
0130                 return QModelIndex();
0131             }
0132         } else {
0133             if (row < mTreeIndexes.count()) {
0134                 return createIndex(row, column);
0135             } else {
0136                 return QModelIndex();
0137             }
0138         }
0139     } else {
0140         if (row < mIndexes.count()) {
0141             return createIndex(row, column, mixIndex(parentIndex.row(), parentIndex.column()));
0142         } else {
0143             return QModelIndex();
0144         }
0145     }
0146 }
0147 
0148 QModelIndex PageGroupProxyModel::parent(const QModelIndex &idx) const
0149 {
0150     if (mGroupByPage) {
0151         if (idx.internalId() == 0) { // top-level
0152             return QModelIndex();
0153         } else {
0154             return index(idx.internalId() - 1, idx.column());
0155         }
0156     } else {
0157         // We have only top-level items
0158         return QModelIndex();
0159     }
0160 }
0161 
0162 QModelIndex PageGroupProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
0163 {
0164     if (mGroupByPage) {
0165         if (sourceIndex.parent().isValid()) {
0166             return index(sourceIndex.row(), sourceIndex.column(), sourceIndex.parent());
0167         } else {
0168             return index(sourceIndex.row(), sourceIndex.column());
0169         }
0170     } else {
0171         for (int i = 0; i < mIndexes.count(); ++i) {
0172             if (mIndexes[i] == sourceIndex) {
0173                 return index(i, 0);
0174             }
0175         }
0176 
0177         return QModelIndex();
0178     }
0179 }
0180 
0181 QModelIndex PageGroupProxyModel::mapToSource(const QModelIndex &proxyIndex) const
0182 {
0183     if (!proxyIndex.isValid()) {
0184         return QModelIndex();
0185     }
0186 
0187     if (mGroupByPage) {
0188         if (proxyIndex.internalId() == 0) {
0189             if (proxyIndex.row() >= mTreeIndexes.count() || proxyIndex.row() < 0) {
0190                 return QModelIndex();
0191             }
0192 
0193             return mTreeIndexes[proxyIndex.row()].first;
0194         } else {
0195             if (qint32(proxyIndex.internalId()) - 1 >= mTreeIndexes.count() || proxyIndex.row() >= mTreeIndexes[proxyIndex.internalId() - 1].second.count()) {
0196                 return QModelIndex();
0197             }
0198 
0199             return mTreeIndexes[proxyIndex.internalId() - 1].second[proxyIndex.row()];
0200         }
0201     } else {
0202         if (proxyIndex.column() > 0 || proxyIndex.row() >= mIndexes.count()) {
0203             return QModelIndex();
0204         } else {
0205             return mIndexes[proxyIndex.row()];
0206         }
0207     }
0208 }
0209 
0210 void PageGroupProxyModel::setSourceModel(QAbstractItemModel *model)
0211 {
0212     if (sourceModel()) {
0213         disconnect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &PageGroupProxyModel::rebuildIndexes);
0214         disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &PageGroupProxyModel::rebuildIndexes);
0215         disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &PageGroupProxyModel::rebuildIndexes);
0216         disconnect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &PageGroupProxyModel::rebuildIndexes);
0217         disconnect(sourceModel(), &QAbstractItemModel::dataChanged, this, &PageGroupProxyModel::sourceDataChanged);
0218     }
0219 
0220     QAbstractProxyModel::setSourceModel(model);
0221 
0222     connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &PageGroupProxyModel::rebuildIndexes);
0223     connect(sourceModel(), &QAbstractItemModel::modelReset, this, &PageGroupProxyModel::rebuildIndexes);
0224     connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &PageGroupProxyModel::rebuildIndexes);
0225     connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &PageGroupProxyModel::rebuildIndexes);
0226     connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &PageGroupProxyModel::sourceDataChanged);
0227 
0228     rebuildIndexes();
0229 }
0230 
0231 void PageGroupProxyModel::rebuildIndexes()
0232 {
0233     beginResetModel();
0234 
0235     if (mGroupByPage) {
0236         mTreeIndexes.clear();
0237 
0238         for (int row = 0; row < sourceModel()->rowCount(); ++row) {
0239             const QModelIndex pageIndex = sourceModel()->index(row, 0);
0240 
0241             QList<QModelIndex> itemIndexes;
0242             for (int subRow = 0; subRow < sourceModel()->rowCount(pageIndex); ++subRow) {
0243                 itemIndexes.append(sourceModel()->index(subRow, 0, pageIndex));
0244             }
0245 
0246             mTreeIndexes.append(QPair<QModelIndex, QList<QModelIndex>>(pageIndex, itemIndexes));
0247         }
0248     } else {
0249         mIndexes.clear();
0250 
0251         for (int row = 0; row < sourceModel()->rowCount(); ++row) {
0252             const QModelIndex pageIndex = sourceModel()->index(row, 0);
0253             for (int subRow = 0; subRow < sourceModel()->rowCount(pageIndex); ++subRow) {
0254                 mIndexes.append(sourceModel()->index(subRow, 0, pageIndex));
0255             }
0256         }
0257     }
0258 
0259     endResetModel();
0260 }
0261 
0262 void PageGroupProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0263 {
0264     Q_EMIT dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles);
0265 }
0266 
0267 void PageGroupProxyModel::groupByPage(bool value)
0268 {
0269     if (mGroupByPage == value) {
0270         return;
0271     }
0272 
0273     mGroupByPage = value;
0274 
0275     rebuildIndexes();
0276 }
0277 
0278 class AuthorGroupItem
0279 {
0280 public:
0281     enum Type { Page, Author, Annotation };
0282 
0283     explicit AuthorGroupItem(AuthorGroupItem *parent, Type type = Page, const QModelIndex &index = QModelIndex())
0284         : mParent(parent)
0285         , mType(type)
0286         , mIndex(index)
0287     {
0288     }
0289 
0290     ~AuthorGroupItem()
0291     {
0292         qDeleteAll(mChilds);
0293     }
0294 
0295     AuthorGroupItem(const AuthorGroupItem &) = delete;
0296     AuthorGroupItem &operator=(const AuthorGroupItem &) = delete;
0297 
0298     void appendChild(AuthorGroupItem *child)
0299     {
0300         mChilds.append(child);
0301     }
0302     AuthorGroupItem *parent() const
0303     {
0304         return mParent;
0305     }
0306     AuthorGroupItem *child(int row) const
0307     {
0308         return mChilds.value(row);
0309     }
0310     int childCount() const
0311     {
0312         return mChilds.count();
0313     }
0314 
0315     void dump(int level = 0)
0316     {
0317         QString prefix;
0318         for (int i = 0; i < level; ++i) {
0319             prefix += QLatin1Char(' ');
0320         }
0321 
0322         qCDebug(OkularUiDebug, "%s%s", qPrintable(prefix), (mType == Page ? "Page" : (mType == Author ? "Author" : "Annotation")));
0323 
0324         for (int i = 0; i < mChilds.count(); ++i) {
0325             mChilds[i]->dump(level + 2);
0326         }
0327     }
0328 
0329     const AuthorGroupItem *findIndex(const QModelIndex &index) const
0330     {
0331         if (index == mIndex) {
0332             return this;
0333         }
0334 
0335         for (int i = 0; i < mChilds.count(); ++i) {
0336             const AuthorGroupItem *item = mChilds[i]->findIndex(index);
0337             if (item) {
0338                 return item;
0339             }
0340         }
0341 
0342         return nullptr;
0343     }
0344 
0345     int row() const
0346     {
0347         return (mParent ? mParent->mChilds.indexOf(const_cast<AuthorGroupItem *>(this)) : 0);
0348     }
0349 
0350     Type type() const
0351     {
0352         return mType;
0353     }
0354     QModelIndex index() const
0355     {
0356         return mIndex;
0357     }
0358 
0359     void setAuthor(const QString &author)
0360     {
0361         mAuthor = author;
0362     }
0363     QString author() const
0364     {
0365         return mAuthor;
0366     }
0367 
0368 private:
0369     AuthorGroupItem *mParent;
0370     Type mType;
0371     QModelIndex mIndex;
0372     QList<AuthorGroupItem *> mChilds;
0373     QString mAuthor;
0374 };
0375 
0376 class AuthorGroupProxyModel::Private
0377 {
0378 public:
0379     explicit Private(AuthorGroupProxyModel *parent)
0380         : mParent(parent)
0381         , mRoot(nullptr)
0382         , mGroupByAuthor(false)
0383     {
0384     }
0385     ~Private()
0386     {
0387         delete mRoot;
0388     }
0389 
0390     AuthorGroupProxyModel *mParent;
0391     AuthorGroupItem *mRoot;
0392     bool mGroupByAuthor;
0393 };
0394 
0395 AuthorGroupProxyModel::AuthorGroupProxyModel(QObject *parent)
0396     : QAbstractProxyModel(parent)
0397     , d(new Private(this))
0398 {
0399 }
0400 
0401 AuthorGroupProxyModel::~AuthorGroupProxyModel()
0402 {
0403     delete d;
0404 }
0405 
0406 int AuthorGroupProxyModel::columnCount(const QModelIndex &) const
0407 {
0408     return 1;
0409 }
0410 
0411 int AuthorGroupProxyModel::rowCount(const QModelIndex &parentIndex) const
0412 {
0413     AuthorGroupItem *item = nullptr;
0414     if (!parentIndex.isValid()) {
0415         item = d->mRoot;
0416     } else {
0417         item = static_cast<AuthorGroupItem *>(parentIndex.internalPointer());
0418     }
0419 
0420     return item ? item->childCount() : 0;
0421 }
0422 
0423 QModelIndex AuthorGroupProxyModel::index(int row, int column, const QModelIndex &parentIndex) const
0424 {
0425     if (!hasIndex(row, column, parentIndex)) {
0426         return QModelIndex();
0427     }
0428 
0429     AuthorGroupItem *parentItem = nullptr;
0430     if (!parentIndex.isValid()) {
0431         parentItem = d->mRoot;
0432     } else {
0433         parentItem = static_cast<AuthorGroupItem *>(parentIndex.internalPointer());
0434     }
0435 
0436     AuthorGroupItem *child = parentItem->child(row);
0437     if (child) {
0438         return createIndex(row, column, child);
0439     } else {
0440         return QModelIndex();
0441     }
0442 }
0443 
0444 QModelIndex AuthorGroupProxyModel::parent(const QModelIndex &index) const
0445 {
0446     if (!index.isValid()) {
0447         return QModelIndex();
0448     }
0449 
0450     AuthorGroupItem *childItem = static_cast<AuthorGroupItem *>(index.internalPointer());
0451     AuthorGroupItem *parentItem = childItem->parent();
0452 
0453     if (parentItem == d->mRoot) {
0454         return QModelIndex();
0455     } else {
0456         return createIndex(parentItem->row(), 0, parentItem);
0457     }
0458 }
0459 
0460 QModelIndex AuthorGroupProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
0461 {
0462     if (!sourceIndex.isValid()) {
0463         return QModelIndex();
0464     }
0465 
0466     const AuthorGroupItem *item = d->mRoot->findIndex(sourceIndex);
0467     if (!item) {
0468         return QModelIndex();
0469     }
0470 
0471     return createIndex(item->row(), 0, const_cast<AuthorGroupItem *>(item));
0472 }
0473 
0474 QModelIndex AuthorGroupProxyModel::mapToSource(const QModelIndex &proxyIndex) const
0475 {
0476     if (!proxyIndex.isValid()) {
0477         return QModelIndex();
0478     }
0479 
0480     const AuthorGroupItem *item = static_cast<AuthorGroupItem *>(proxyIndex.internalPointer());
0481 
0482     return item->index();
0483 }
0484 
0485 void AuthorGroupProxyModel::setSourceModel(QAbstractItemModel *model)
0486 {
0487     if (sourceModel()) {
0488         disconnect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &AuthorGroupProxyModel::rebuildIndexes);
0489         disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &AuthorGroupProxyModel::rebuildIndexes);
0490         disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &AuthorGroupProxyModel::rebuildIndexes);
0491         disconnect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &AuthorGroupProxyModel::rebuildIndexes);
0492         disconnect(sourceModel(), &QAbstractItemModel::dataChanged, this, &AuthorGroupProxyModel::sourceDataChanged);
0493     }
0494 
0495     QAbstractProxyModel::setSourceModel(model);
0496 
0497     connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &AuthorGroupProxyModel::rebuildIndexes);
0498     connect(sourceModel(), &QAbstractItemModel::modelReset, this, &AuthorGroupProxyModel::rebuildIndexes);
0499     connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &AuthorGroupProxyModel::rebuildIndexes);
0500     connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &AuthorGroupProxyModel::rebuildIndexes);
0501     connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &AuthorGroupProxyModel::sourceDataChanged);
0502 
0503     rebuildIndexes();
0504 }
0505 
0506 static bool isAuthorItem(const QModelIndex &index)
0507 {
0508     if (!index.isValid()) {
0509         return false;
0510     }
0511 
0512     const AuthorGroupItem *item = static_cast<AuthorGroupItem *>(index.internalPointer());
0513     return (item->type() == AuthorGroupItem::Author);
0514 }
0515 
0516 QItemSelection AuthorGroupProxyModel::mapSelectionToSource(const QItemSelection &selection) const
0517 {
0518     const QModelIndexList proxyIndexes = selection.indexes();
0519     QItemSelection sourceSelection;
0520     for (const QModelIndex &proxyIndex : proxyIndexes) {
0521         if (!isAuthorItem(proxyIndex)) {
0522             sourceSelection << QItemSelectionRange(mapToSource(proxyIndex));
0523         }
0524     }
0525 
0526     return sourceSelection;
0527 }
0528 
0529 QItemSelection AuthorGroupProxyModel::mapSelectionFromSource(const QItemSelection &selection) const
0530 {
0531     return QAbstractProxyModel::mapSelectionFromSource(selection);
0532 }
0533 
0534 QVariant AuthorGroupProxyModel::data(const QModelIndex &proxyIndex, int role) const
0535 {
0536     if (isAuthorItem(proxyIndex)) {
0537         const AuthorGroupItem *item = static_cast<AuthorGroupItem *>(proxyIndex.internalPointer());
0538         if (role == Qt::DisplayRole) {
0539             return item->author();
0540         } else if (role == Qt::DecorationRole) {
0541             return QIcon::fromTheme(item->author().isEmpty() ? QStringLiteral("user-away") : QStringLiteral("user-identity"));
0542         } else {
0543             return QVariant();
0544         }
0545     } else {
0546         return QAbstractProxyModel::data(proxyIndex, role);
0547     }
0548 }
0549 
0550 QMap<int, QVariant> AuthorGroupProxyModel::itemData(const QModelIndex &index) const
0551 {
0552     if (isAuthorItem(index)) {
0553         return QMap<int, QVariant>();
0554     } else {
0555         return QAbstractProxyModel::itemData(index);
0556     }
0557 }
0558 
0559 Qt::ItemFlags AuthorGroupProxyModel::flags(const QModelIndex &index) const
0560 {
0561     if (isAuthorItem(index)) {
0562         return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0563     } else {
0564         return QAbstractProxyModel::flags(index);
0565     }
0566 }
0567 
0568 void AuthorGroupProxyModel::groupByAuthor(bool value)
0569 {
0570     if (d->mGroupByAuthor == value) {
0571         return;
0572     }
0573 
0574     d->mGroupByAuthor = value;
0575 
0576     rebuildIndexes();
0577 }
0578 
0579 void AuthorGroupProxyModel::rebuildIndexes()
0580 {
0581     beginResetModel();
0582     delete d->mRoot;
0583     d->mRoot = new AuthorGroupItem(nullptr);
0584 
0585     if (d->mGroupByAuthor) {
0586         QMap<QString, AuthorGroupItem *> authorMap;
0587 
0588         for (int row = 0; row < sourceModel()->rowCount(); ++row) {
0589             const QModelIndex idx = sourceModel()->index(row, 0);
0590             const QString author = sourceModel()->data(idx, AnnotationModel::AuthorRole).toString();
0591             if (!author.isEmpty()) {
0592                 // We have the annotations as top-level, so introduce authors as new
0593                 // top-levels and append the annotations
0594                 AuthorGroupItem *authorItem = authorMap.value(author, nullptr);
0595                 if (!authorItem) {
0596                     authorItem = new AuthorGroupItem(d->mRoot, AuthorGroupItem::Author);
0597                     authorItem->setAuthor(author);
0598 
0599                     // Add item to tree
0600                     d->mRoot->appendChild(authorItem);
0601 
0602                     // Insert to lookup list
0603                     authorMap.insert(author, authorItem);
0604                 }
0605 
0606                 AuthorGroupItem *item = new AuthorGroupItem(authorItem, AuthorGroupItem::Annotation, idx);
0607                 authorItem->appendChild(item);
0608             } else {
0609                 // We have the pages as top-level, so we use them as top-level, append the
0610                 // authors for all annotations of the page, and then the annotations themself
0611                 AuthorGroupItem *pageItem = new AuthorGroupItem(d->mRoot, AuthorGroupItem::Page, idx);
0612                 d->mRoot->appendChild(pageItem);
0613 
0614                 // First collect all authors...
0615                 QMap<QString, AuthorGroupItem *> pageAuthorMap;
0616                 for (int subRow = 0; subRow < sourceModel()->rowCount(idx); ++subRow) {
0617                     const QModelIndex annIdx = sourceModel()->index(subRow, 0, idx);
0618                     const QString pageAuthor = sourceModel()->data(annIdx, AnnotationModel::AuthorRole).toString();
0619 
0620                     AuthorGroupItem *authorItem = pageAuthorMap.value(pageAuthor, nullptr);
0621                     if (!authorItem) {
0622                         authorItem = new AuthorGroupItem(pageItem, AuthorGroupItem::Author);
0623                         authorItem->setAuthor(pageAuthor);
0624 
0625                         // Add item to tree
0626                         pageItem->appendChild(authorItem);
0627 
0628                         // Insert to lookup list
0629                         pageAuthorMap.insert(pageAuthor, authorItem);
0630                     }
0631 
0632                     AuthorGroupItem *item = new AuthorGroupItem(authorItem, AuthorGroupItem::Annotation, annIdx);
0633                     authorItem->appendChild(item);
0634                 }
0635             }
0636         }
0637     } else {
0638         for (int row = 0; row < sourceModel()->rowCount(); ++row) {
0639             const QModelIndex idx = sourceModel()->index(row, 0);
0640             const QString author = sourceModel()->data(idx, AnnotationModel::AuthorRole).toString();
0641             if (!author.isEmpty()) {
0642                 // We have the annotations as top-level items
0643                 AuthorGroupItem *item = new AuthorGroupItem(d->mRoot, AuthorGroupItem::Annotation, idx);
0644                 d->mRoot->appendChild(item);
0645             } else {
0646                 // We have the pages as top-level items
0647                 AuthorGroupItem *pageItem = new AuthorGroupItem(d->mRoot, AuthorGroupItem::Page, idx);
0648                 d->mRoot->appendChild(pageItem);
0649 
0650                 // Append all annotations as second-level
0651                 for (int subRow = 0; subRow < sourceModel()->rowCount(idx); ++subRow) {
0652                     const QModelIndex subIdx = sourceModel()->index(subRow, 0, idx);
0653                     AuthorGroupItem *item = new AuthorGroupItem(pageItem, AuthorGroupItem::Annotation, subIdx);
0654                     pageItem->appendChild(item);
0655                 }
0656             }
0657         }
0658     }
0659 
0660     endResetModel();
0661 }
0662 
0663 void AuthorGroupProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0664 {
0665     Q_EMIT dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles);
0666 }
0667 
0668 #include "moc_annotationproxymodels.cpp"