File indexing completed on 2024-04-28 03:59:17

0001 /*
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0003     SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kviewstateserializer.h"
0009 
0010 #include <QAbstractScrollArea>
0011 #include <QPointer>
0012 #include <QScrollBar>
0013 #include <QTimer>
0014 #include <QTreeView>
0015 
0016 class KViewStateSerializerPrivate
0017 {
0018 public:
0019     KViewStateSerializerPrivate(KViewStateSerializer *qq)
0020         : q_ptr(qq)
0021         , m_treeView(nullptr)
0022         , m_view(nullptr)
0023         , m_selectionModel(nullptr)
0024         , m_scrollArea(nullptr)
0025         , m_horizontalScrollBarValue(-1)
0026         , m_verticalScrollBarValue(-1)
0027     {
0028     }
0029 
0030     Q_DECLARE_PUBLIC(KViewStateSerializer)
0031     KViewStateSerializer *const q_ptr;
0032 
0033     QStringList getExpandedItems(const QModelIndex &index) const;
0034 
0035     void listenToPendingChanges();
0036     void processPendingChanges();
0037 
0038     inline void restoreScrollBarState()
0039     {
0040         if (!m_scrollArea || !m_scrollArea->horizontalScrollBar() || !m_scrollArea->verticalScrollBar()) {
0041             return;
0042         }
0043         if (m_horizontalScrollBarValue >= 0 && m_horizontalScrollBarValue <= m_scrollArea->horizontalScrollBar()->maximum()) {
0044             m_scrollArea->horizontalScrollBar()->setValue(m_horizontalScrollBarValue);
0045             m_horizontalScrollBarValue = -1;
0046         }
0047         if (m_verticalScrollBarValue >= 0 && m_verticalScrollBarValue <= m_scrollArea->verticalScrollBar()->maximum()) {
0048             m_scrollArea->verticalScrollBar()->setValue(m_verticalScrollBarValue);
0049             m_verticalScrollBarValue = -1;
0050         }
0051     }
0052 
0053     void restoreSelection();
0054     void restoreCurrentItem();
0055     void restoreExpanded();
0056 
0057     inline bool hasPendingChanges() const
0058     {
0059         return !m_pendingCurrent.isEmpty() || !m_pendingExpansions.isEmpty() || !m_pendingSelections.isEmpty();
0060     }
0061 
0062     const QAbstractItemModel *getModel()
0063     {
0064         if (m_selectionModel && m_selectionModel->model()) {
0065             return m_selectionModel->model();
0066         } else if (m_view && m_view->model()) {
0067             return m_view->model();
0068         }
0069         return nullptr;
0070     }
0071 
0072     void rowsInserted(const QModelIndex & /*index*/, int /*start*/, int /*end*/)
0073     {
0074         Q_Q(KViewStateSerializer);
0075         processPendingChanges();
0076 
0077         if (!hasPendingChanges()) {
0078             q->disconnect(m_rowsInsertedConnection);
0079             q->deleteLater();
0080         }
0081     }
0082 
0083     QTreeView *m_treeView;
0084     QAbstractItemView *m_view;
0085     QItemSelectionModel *m_selectionModel;
0086     QPointer<QAbstractScrollArea> m_scrollArea;
0087 
0088     int m_horizontalScrollBarValue;
0089     int m_verticalScrollBarValue;
0090     QSet<QString> m_pendingSelections;
0091     QSet<QString> m_pendingExpansions;
0092     QString m_pendingCurrent;
0093     QMetaObject::Connection m_rowsInsertedConnection;
0094 };
0095 
0096 KViewStateSerializer::KViewStateSerializer(QObject *parent)
0097     : QObject(nullptr)
0098     , d_ptr(new KViewStateSerializerPrivate(this))
0099 {
0100     Q_UNUSED(parent);
0101     qRegisterMetaType<QModelIndex>("QModelIndex");
0102 }
0103 
0104 KViewStateSerializer::~KViewStateSerializer() = default;
0105 
0106 void KViewStateSerializer::setView(QAbstractItemView *view)
0107 {
0108     Q_D(KViewStateSerializer);
0109     d->m_scrollArea = view;
0110     if (view) {
0111         d->m_selectionModel = view->selectionModel();
0112         d->m_treeView = qobject_cast<QTreeView *>(view);
0113     } else {
0114         d->m_selectionModel = nullptr;
0115         d->m_treeView = nullptr;
0116     }
0117     d->m_view = view;
0118 }
0119 
0120 QAbstractItemView *KViewStateSerializer::view() const
0121 {
0122     Q_D(const KViewStateSerializer);
0123     return d->m_view;
0124 }
0125 
0126 QItemSelectionModel *KViewStateSerializer::selectionModel() const
0127 {
0128     Q_D(const KViewStateSerializer);
0129     return d->m_selectionModel;
0130 }
0131 
0132 void KViewStateSerializer::setSelectionModel(QItemSelectionModel *selectionModel)
0133 {
0134     Q_D(KViewStateSerializer);
0135     d->m_selectionModel = selectionModel;
0136 }
0137 
0138 void KViewStateSerializerPrivate::listenToPendingChanges()
0139 {
0140     Q_Q(KViewStateSerializer);
0141     // watch the model for stuff coming in delayed
0142     if (hasPendingChanges()) {
0143         const QAbstractItemModel *model = getModel();
0144         if (model) {
0145             q->disconnect(m_rowsInsertedConnection);
0146             m_rowsInsertedConnection = q->connect(model, &QAbstractItemModel::rowsInserted, q, [this](const QModelIndex &parent, int first, int last) {
0147                 rowsInserted(parent, first, last);
0148             });
0149             return;
0150         } else {
0151             q->deleteLater();
0152         }
0153     } else {
0154         q->deleteLater();
0155     }
0156 }
0157 
0158 void KViewStateSerializerPrivate::processPendingChanges()
0159 {
0160     Q_Q(KViewStateSerializer);
0161 
0162     q->restoreCurrentItem(m_pendingCurrent);
0163     q->restoreSelection(m_pendingSelections.values());
0164     q->restoreExpanded(m_pendingExpansions.values());
0165     q->restoreScrollState(m_verticalScrollBarValue, m_horizontalScrollBarValue);
0166 }
0167 
0168 QStringList KViewStateSerializerPrivate::getExpandedItems(const QModelIndex &index) const
0169 {
0170     Q_Q(const KViewStateSerializer);
0171 
0172     QStringList expansion;
0173     for (int i = 0; i < m_treeView->model()->rowCount(index); ++i) {
0174         const QModelIndex child = m_treeView->model()->index(i, 0, index);
0175 
0176         // http://bugreports.qt.nokia.com/browse/QTBUG-18039
0177         if (m_treeView->model()->hasChildren(child)) {
0178             if (m_treeView->isExpanded(child)) {
0179                 expansion << q->indexToConfigString(child);
0180             }
0181             expansion << getExpandedItems(child);
0182         }
0183     }
0184     return expansion;
0185 }
0186 
0187 void KViewStateSerializerPrivate::restoreCurrentItem()
0188 {
0189     Q_Q(KViewStateSerializer);
0190 
0191     QModelIndex currentIndex = q->indexFromConfigString(m_selectionModel->model(), m_pendingCurrent);
0192     if (currentIndex.isValid()) {
0193         if (m_treeView) {
0194             m_treeView->setCurrentIndex(currentIndex);
0195         } else {
0196             m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
0197         }
0198         m_pendingCurrent.clear();
0199     }
0200 }
0201 
0202 void KViewStateSerializer::restoreCurrentItem(const QString &indexString)
0203 {
0204     Q_D(KViewStateSerializer);
0205     if (!d->m_selectionModel || !d->m_selectionModel->model()) {
0206         return;
0207     }
0208 
0209     if (indexString.isEmpty()) {
0210         return;
0211     }
0212     d->m_pendingCurrent = indexString;
0213     d->restoreCurrentItem();
0214 
0215     if (d->hasPendingChanges()) {
0216         d->listenToPendingChanges();
0217     }
0218 }
0219 
0220 void KViewStateSerializerPrivate::restoreExpanded()
0221 {
0222     Q_Q(KViewStateSerializer);
0223 
0224     QSet<QString>::iterator it = m_pendingExpansions.begin();
0225     for (; it != m_pendingExpansions.end();) {
0226         QModelIndex idx = q->indexFromConfigString(m_treeView->model(), *it);
0227         if (idx.isValid()) {
0228             m_treeView->expand(idx);
0229             it = m_pendingExpansions.erase(it);
0230         } else {
0231             ++it;
0232         }
0233     }
0234 }
0235 
0236 void KViewStateSerializer::restoreExpanded(const QStringList &indexStrings)
0237 {
0238     Q_D(KViewStateSerializer);
0239     if (!d->m_treeView || !d->m_treeView->model()) {
0240         return;
0241     }
0242 
0243     if (indexStrings.isEmpty()) {
0244         return;
0245     }
0246 
0247     d->m_pendingExpansions.unite(QSet<QString>(indexStrings.begin(), indexStrings.end()));
0248 
0249     d->restoreExpanded();
0250     if (d->hasPendingChanges()) {
0251         d->listenToPendingChanges();
0252     }
0253 }
0254 
0255 void KViewStateSerializer::restoreScrollState(int verticalScoll, int horizontalScroll)
0256 {
0257     Q_D(KViewStateSerializer);
0258 
0259     if (!d->m_scrollArea) {
0260         return;
0261     }
0262 
0263     d->m_verticalScrollBarValue = verticalScoll;
0264     d->m_horizontalScrollBarValue = horizontalScroll;
0265 
0266     QTimer::singleShot(0, this, [d]() {
0267         d->restoreScrollBarState();
0268     });
0269 }
0270 
0271 void KViewStateSerializerPrivate::restoreSelection()
0272 {
0273     Q_Q(KViewStateSerializer);
0274 
0275     QSet<QString>::iterator it = m_pendingSelections.begin();
0276     for (; it != m_pendingSelections.end();) {
0277         QModelIndex idx = q->indexFromConfigString(m_selectionModel->model(), *it);
0278         if (idx.isValid()) {
0279             m_selectionModel->select(idx, QItemSelectionModel::Select);
0280             it = m_pendingSelections.erase(it);
0281         } else {
0282             ++it;
0283         }
0284     }
0285 }
0286 
0287 void KViewStateSerializer::restoreSelection(const QStringList &indexStrings)
0288 {
0289     Q_D(KViewStateSerializer);
0290 
0291     if (!d->m_selectionModel || !d->m_selectionModel->model()) {
0292         return;
0293     }
0294 
0295     if (indexStrings.isEmpty()) {
0296         return;
0297     }
0298 
0299     d->m_pendingSelections.unite(QSet<QString>(indexStrings.begin(), indexStrings.end()));
0300 
0301     d->restoreSelection();
0302     if (d->hasPendingChanges()) {
0303         d->listenToPendingChanges();
0304     }
0305 }
0306 
0307 QString KViewStateSerializer::currentIndexKey() const
0308 {
0309     Q_D(const KViewStateSerializer);
0310     if (!d->m_selectionModel) {
0311         return QString();
0312     }
0313     return indexToConfigString(d->m_selectionModel->currentIndex());
0314 }
0315 
0316 QStringList KViewStateSerializer::expansionKeys() const
0317 {
0318     Q_D(const KViewStateSerializer);
0319     if (!d->m_treeView || !d->m_treeView->model()) {
0320         return QStringList();
0321     }
0322 
0323     return d->getExpandedItems(QModelIndex());
0324 }
0325 
0326 QStringList KViewStateSerializer::selectionKeys() const
0327 {
0328     Q_D(const KViewStateSerializer);
0329     if (!d->m_selectionModel) {
0330         return QStringList();
0331     }
0332 
0333     const QModelIndexList selectedIndexes = d->m_selectionModel->selectedRows();
0334     QStringList selection;
0335     selection.reserve(selectedIndexes.count());
0336     for (const QModelIndex &index : selectedIndexes) {
0337         selection << indexToConfigString(index);
0338     }
0339 
0340     return selection;
0341 }
0342 
0343 QPair<int, int> KViewStateSerializer::scrollState() const
0344 {
0345     Q_D(const KViewStateSerializer);
0346     return qMakePair(d->m_scrollArea->verticalScrollBar()->value(), d->m_scrollArea->horizontalScrollBar()->value());
0347 }
0348 
0349 void KViewStateSerializer::restoreState()
0350 {
0351     Q_D(KViewStateSerializer);
0352     // Delete myself if not finished after 60 seconds
0353     QTimer::singleShot(60000, this, &KViewStateSerializer::deleteLater);
0354 
0355     d->processPendingChanges();
0356     if (d->hasPendingChanges()) {
0357         d->listenToPendingChanges();
0358     }
0359 }
0360 
0361 #include "moc_kviewstateserializer.cpp"