/* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net, author Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kviewstatesaver.h" #include #include #include #include #include "kdebug.h" #include "kconfiggroup.h" static const char * selectionKey = "Selection"; static const char * expansionKey = "Expansion"; static const char * currentKey = "Current"; static const char * scrollStateHorizontalKey = "HorizontalScroll"; static const char * scrollStateVerticalKey = "VerticalScroll"; class KViewStateSaverPrivate { public: KViewStateSaverPrivate(KViewStateSaver *qq) : q_ptr(qq), m_treeView(0), m_view(0), m_selectionModel(0), m_scrollArea(0), m_horizontalScrollBarValue(-1), m_verticalScrollBarValue(-1) { } Q_DECLARE_PUBLIC(KViewStateSaver) KViewStateSaver * const q_ptr; QStringList getExpandedItems(const QModelIndex &index) const; void listenToPendingChanges(); void processPendingChanges(); inline void restoreScrollBarState() { if ( !m_scrollArea ) return; if ( m_horizontalScrollBarValue >= 0 && m_horizontalScrollBarValue <= m_scrollArea->horizontalScrollBar()->maximum() ) { m_scrollArea->horizontalScrollBar()->setValue( m_horizontalScrollBarValue ); m_horizontalScrollBarValue = -1; } if ( m_verticalScrollBarValue >= 0 && m_verticalScrollBarValue <= m_scrollArea->verticalScrollBar()->maximum() ) { m_scrollArea->verticalScrollBar()->setValue( m_verticalScrollBarValue ); m_verticalScrollBarValue = -1; } } void restoreSelection(); void restoreCurrentItem(); void restoreExpanded(); inline bool hasPendingChanges() const { return !m_pendingCurrent.isEmpty() || !m_pendingExpansions.isEmpty() || !m_pendingSelections.isEmpty(); } const QAbstractItemModel* getModel() { if ( m_selectionModel && m_selectionModel->model() ) return m_selectionModel->model(); else if ( m_view && m_view->model() ) return m_view->model(); return 0; } void rowsInserted( const QModelIndex &/*index*/, int /*start*/, int /*end*/ ) { Q_Q(KViewStateSaver); processPendingChanges(); if ( !hasPendingChanges() ) { q->disconnect( getModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(rowsInserted(QModelIndex,int,int)) ); q->deleteLater(); } } QTreeView *m_treeView; QAbstractItemView *m_view; QItemSelectionModel *m_selectionModel; QAbstractScrollArea *m_scrollArea; int m_horizontalScrollBarValue; int m_verticalScrollBarValue; QSet m_pendingSelections; QSet m_pendingExpansions; QString m_pendingCurrent; }; KViewStateSaver::KViewStateSaver(QObject* parent) : QObject(0), d_ptr( new KViewStateSaverPrivate(this) ) { Q_UNUSED(parent); qRegisterMetaType( "QModelIndex" ); } KViewStateSaver::~KViewStateSaver() { delete d_ptr; } void KViewStateSaver::setView(QAbstractItemView* view) { Q_D(KViewStateSaver); d->m_scrollArea = view; if (view) { d->m_selectionModel = view->selectionModel(); d->m_treeView = qobject_cast(view); } else { d->m_selectionModel = 0; d->m_treeView = 0; } d->m_view = view; } QAbstractItemView* KViewStateSaver::view() const { Q_D(const KViewStateSaver); return d->m_view; } QItemSelectionModel* KViewStateSaver::selectionModel() const { Q_D(const KViewStateSaver); return d->m_selectionModel; } void KViewStateSaver::setSelectionModel(QItemSelectionModel* selectionModel) { Q_D(KViewStateSaver); d->m_selectionModel = selectionModel; } void KViewStateSaverPrivate::listenToPendingChanges() { Q_Q(KViewStateSaver); // watch the model for stuff coming in delayed if ( hasPendingChanges() ) { const QAbstractItemModel *model = getModel(); if ( model ) { q->disconnect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(rowsInserted(QModelIndex,int,int)) ); q->connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int)) ); return; } else { q->deleteLater(); } } else { q->deleteLater(); } } void KViewStateSaverPrivate::processPendingChanges() { Q_Q(KViewStateSaver); q->restoreCurrentItem(m_pendingCurrent); q->restoreSelection(m_pendingSelections.toList()); q->restoreExpanded(m_pendingExpansions.toList()); q->restoreScrollState(m_verticalScrollBarValue, m_horizontalScrollBarValue); } void KViewStateSaver::restoreState(const KConfigGroup& configGroup) { Q_D(KViewStateSaver); // Delete myself if not finished after 60 seconds. QTimer::singleShot(60000, this, SLOT(deleteLater())); d->m_pendingCurrent = configGroup.readEntry( currentKey, QString() ); d->m_pendingSelections = configGroup.readEntry( selectionKey, QStringList() ).toSet(); d->m_pendingExpansions = configGroup.readEntry( expansionKey, QStringList() ).toSet(); d->m_horizontalScrollBarValue = configGroup.readEntry( scrollStateHorizontalKey, -1 ); d->m_verticalScrollBarValue = configGroup.readEntry( scrollStateVerticalKey, -1 ); d->processPendingChanges(); if (d->hasPendingChanges()) d->listenToPendingChanges(); } QStringList KViewStateSaverPrivate::getExpandedItems(const QModelIndex &index) const { Q_Q(const KViewStateSaver); QStringList expansion; for ( int i = 0; i < m_treeView->model()->rowCount( index ); ++i ) { const QModelIndex child = m_treeView->model()->index( i, 0, index ); // http://bugreports.qt.nokia.com/browse/QTBUG-18039 if ( m_treeView->model()->hasChildren( child ) ) { if ( m_treeView->isExpanded( child ) ) expansion << q->indexToConfigString( child ); expansion << getExpandedItems( child ); } } return expansion; } void KViewStateSaver::saveState(KConfigGroup& configGroup) { Q_D(KViewStateSaver); if ( d->m_selectionModel ) { configGroup.writeEntry( selectionKey, selectionKeys() ); configGroup.writeEntry( currentKey, currentIndexKey() ); } if ( d->m_treeView ) { QStringList expansion = expansionKeys(); configGroup.writeEntry( expansionKey, expansion ); } if ( d->m_scrollArea ) { QPair _scrollState = scrollState(); configGroup.writeEntry( scrollStateVerticalKey, _scrollState.first ); configGroup.writeEntry( scrollStateHorizontalKey, _scrollState.second ); } } void KViewStateSaverPrivate::restoreCurrentItem() { Q_Q(KViewStateSaver); QModelIndex currentIndex = q->indexFromConfigString(m_selectionModel->model(), m_pendingCurrent); if ( currentIndex.isValid() ) { if (m_treeView) m_treeView->setCurrentIndex(currentIndex); else m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate); m_pendingCurrent.clear(); } } void KViewStateSaver::restoreCurrentItem(const QString& indexString) { Q_D(KViewStateSaver); if (!d->m_selectionModel || !d->m_selectionModel->model()) return; if (indexString.isEmpty()) { return; } d->m_pendingCurrent = indexString; d->restoreCurrentItem(); if (d->hasPendingChanges()) d->listenToPendingChanges(); } void KViewStateSaverPrivate::restoreExpanded() { Q_Q(KViewStateSaver); QSet::iterator it = m_pendingExpansions.begin(); for ( ; it != m_pendingExpansions.end(); ) { QModelIndex idx = q->indexFromConfigString( m_treeView->model(), *it); if ( idx.isValid() ) { m_treeView->expand( idx ); it = m_pendingExpansions.erase( it ); } else { ++it; } } } void KViewStateSaver::restoreExpanded(const QStringList& indexStrings) { Q_D(KViewStateSaver); if (!d->m_treeView || !d->m_treeView->model()) return; if (indexStrings.isEmpty()) return; d->m_pendingExpansions.unite(indexStrings.toSet()); d->restoreExpanded(); if (d->hasPendingChanges()) d->listenToPendingChanges(); } void KViewStateSaver::restoreScrollState(int verticalScoll, int horizontalScroll) { Q_D(KViewStateSaver); if ( !d->m_scrollArea ) return; d->m_verticalScrollBarValue = verticalScoll; d->m_horizontalScrollBarValue = horizontalScroll; QTimer::singleShot( 0, this, SLOT(restoreScrollBarState()) ); } void KViewStateSaverPrivate::restoreSelection() { Q_Q(KViewStateSaver); QSet::iterator it = m_pendingSelections.begin(); for ( ; it != m_pendingSelections.end(); ) { QModelIndex idx = q->indexFromConfigString( m_selectionModel->model(), *it); if ( idx.isValid() ) { m_selectionModel->select( idx, QItemSelectionModel::Select ); it = m_pendingSelections.erase( it ); } else { ++it; } } } void KViewStateSaver::restoreSelection(const QStringList& indexStrings) { Q_D(KViewStateSaver); if (!d->m_selectionModel || !d->m_selectionModel->model()) return; if (indexStrings.isEmpty()) return; d->m_pendingSelections.unite(indexStrings.toSet()); d->restoreSelection(); if (d->hasPendingChanges()) d->listenToPendingChanges(); } QString KViewStateSaver::currentIndexKey() const { Q_D(const KViewStateSaver); if (!d->m_selectionModel) return QString(); return indexToConfigString(d->m_selectionModel->currentIndex()); } QStringList KViewStateSaver::expansionKeys() const { Q_D(const KViewStateSaver); if (!d->m_treeView || !d->m_treeView->model()) return QStringList(); return d->getExpandedItems(QModelIndex()); } QStringList KViewStateSaver::selectionKeys() const { Q_D(const KViewStateSaver); if (!d->m_selectionModel) return QStringList(); QModelIndexList selectedIndexes = d->m_selectionModel->selectedRows(); QStringList selection; foreach ( const QModelIndex &index, selectedIndexes ) selection << indexToConfigString( index ); return selection; } QPair KViewStateSaver::scrollState() const { Q_D(const KViewStateSaver); return qMakePair(d->m_scrollArea->verticalScrollBar()->value(), d->m_scrollArea->horizontalScrollBar()->value()); } #include "moc_kviewstatesaver.cpp"