diff --git a/akonadi/CMakeLists.txt b/akonadi/CMakeLists.txt index bde271b64..86c47c9a9 100644 --- a/akonadi/CMakeLists.txt +++ b/akonadi/CMakeLists.txt @@ -1,249 +1,251 @@ project(akonadi-kde) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) if(CMAKE_COMPILE_GCOV) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") endif(CMAKE_COMPILE_GCOV) if (KDE4_BUILD_TESTS) # only with this macro the AKONADI_TESTS_EXPORT macro will do something add_definitions(-DCOMPILING_TESTS) add_subdirectory( tests ) endif (KDE4_BUILD_TESTS) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_subdirectory( kabc ) add_subdirectory( kmime ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_QTDBUS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR}/akonadi/private ) # libakonadi-kde set( akonadikde_LIB_SRC entity.cpp # keep it at top to not break enable-final agentbase.cpp agentfilterproxymodel.cpp agentinstance.cpp agentinstancecreatejob.cpp agentinstancemodel.cpp agentinstancewidget.cpp agentmanager.cpp agenttype.cpp agenttypemodel.cpp agenttypewidget.cpp agenttypedialog.cpp attribute.cpp attributefactory.cpp cachepolicy.cpp cachepolicypage.cpp changerecorder.cpp collection.cpp collectioncopyjob.cpp collectioncreatejob.cpp collectiondeletejob.cpp collectiondialog.cpp collectionfilterproxymodel.cpp collectiongeneralpropertiespage.cpp collectionfetchjob.cpp collectionmodel.cpp collectionmodel_p.cpp collectionmodifyjob.cpp collectionmovejob.cpp collectionpathresolver.cpp collectionpropertiesdialog.cpp collectionpropertiespage.cpp collectionrequester.cpp collectionrightsattribute.cpp collectionselectjob.cpp collectionstatistics.cpp collectionstatisticsdelegate.cpp collectionstatisticsjob.cpp collectionstatisticsmodel.cpp collectionsync.cpp collectionview.cpp control.cpp descendantsproxymodel.cpp entitydisplayattribute.cpp entitytreemodel.cpp entitytreemodel_p.cpp entityfilterproxymodel.cpp + entitytreeviewstatesaver.cpp erroroverlay.cpp exception.cpp expungejob.cpp favoritecollectionsmodel.cpp firstrun.cpp flatcollectionproxymodel.cpp item.cpp itemcreatejob.cpp itemcopyjob.cpp itemdeletejob.cpp itemfetchjob.cpp itemfetchscope.cpp itemmodel.cpp itemmonitor.cpp itemmovejob.cpp itemserializer.cpp itemserializerplugin.cpp itemmodifyjob.cpp itemsync.cpp itemview.cpp job.cpp linkjob.cpp mimetypechecker.cpp monitor.cpp monitor_p.cpp pastehelper.cpp protocolhelper.cpp resourcebase.cpp resourcescheduler.cpp resourceselectjob.cpp searchcreatejob.cpp selectionproxymodel.cpp selftestdialog.cpp session.cpp servermanager.cpp standardactionmanager.cpp statisticsproxymodel.cpp statisticstooltipproxymodel.cpp subscriptionjob.cpp subscriptionchangeproxymodel.cpp subscriptiondialog.cpp subscriptionmodel.cpp transactionjobs.cpp transactionsequence.cpp unlinkjob.cpp # Temporary until ported to Qt-plugin framework pluginloader.cpp ) # DBus interfaces and adaptors set(akonadi_xml ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.NotificationManager.xml) set_source_files_properties(${akonadi_xml} PROPERTIES INCLUDE "notificationmessage_p.h") qt4_add_dbus_interface( akonadikde_LIB_SRC ${akonadi_xml} notificationmanagerinterface ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.AgentManager.xml ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Tracer.xml ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Resource.xml resourcebase.h Akonadi::ResourceBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Status.xml agentbase.h Akonadi::AgentBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) kde4_add_ui_files( akonadikde_LIB_SRC cachepolicypage.ui collectiongeneralpropertiespage.ui subscriptiondialog.ui controlprogressindicator.ui selftestdialog.ui ) kde4_add_library( akonadi-kde SHARED ${akonadikde_LIB_SRC} ) macro_ensure_version( "4.2.0" ${KDE_VERSION} KDE_IS_AT_LEAST_42 ) target_link_libraries( akonadi-kde ${KDE4_SOLID_LIBS} ${QT_QTNETWORK_LIBRARY} ${QT_QTDBUS_LIBRARY} ${QT_QTSQL_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${AKONADI_COMMON_LIBRARIES} ) set( AKONADI_KDE_DEPS ${KDE4_KDEUI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTCORE_LIBRARY} ) if(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde LINK_INTERFACE_LIBRARIES ${AKONADI_KDE_DEPS}) else(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde ${AKONADI_KDE_DEPS}) endif(${KDE_IS_AT_LEAST_42}) set_target_properties( akonadi-kde PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS akonadi-kde EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( FILES akonadi_export.h agentbase.h agentfilterproxymodel.h agentinstance.h agentinstancecreatejob.h agentinstancemodel.h agentinstancewidget.h agentmanager.h agenttype.h agenttypemodel.h agenttypewidget.h agenttypedialog.h attribute.h attributefactory.h cachepolicy.h changerecorder.h collection.h collectioncopyjob.h collectioncreatejob.h collectiondeletejob.h collectiondialog.h collectionfilterproxymodel.h collectionfetchjob.h collectionmodel.h collectionmodifyjob.h collectionpropertiesdialog.h collectionpropertiespage.h collectionrequester.h collectionstatisticsdelegate.h collectionstatisticsmodel.h collectionstatistics.h collectionstatisticsjob.h collectionview.h control.h descendantsproxymodel.h entity.h entitydisplayattribute.h entitytreemodel.h entityfilterproxymodel.h + entitytreeviewstatesaver.h exception.h favoritecollectionsmodel.h item.h itemcreatejob.h itemcopyjob.h itemdeletejob.h itemfetchjob.h itemfetchscope.h itemmodel.h itemmodifyjob.h itemmonitor.h itemmovejob.h itempayloadinternals_p.h itemserializerplugin.h itemsync.h itemview.h job.h linkjob.h mimetypechecker.h monitor.h qtest_akonadi.h resourcebase.h searchcreatejob.h selectionproxymodel.h session.h servermanager.h standardactionmanager.h statisticsproxymodel.h statisticstooltipproxymodel.h transactionjobs.h transactionsequence.h unlinkjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi COMPONENT Devel ) install( FILES collectionpathresolver_p.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/private COMPONENT Devel ) install( FILES kcfg2dbus.xsl DESTINATION ${DATA_INSTALL_DIR}/akonadi-kde ) diff --git a/akonadi/entitytreeviewstatesaver.cpp b/akonadi/entitytreeviewstatesaver.cpp new file mode 100644 index 000000000..43671dcc0 --- /dev/null +++ b/akonadi/entitytreeviewstatesaver.cpp @@ -0,0 +1,208 @@ +/* + Copyright (c) 2009 Volker Krause + + 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 "entitytreeviewstatesaver.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Akonadi { + +struct State +{ + State() : selected( false ), expanded( false ) {} + bool selected; + bool expanded; +}; + +class EntityTreeViewStateSaverPrivate +{ + public: + EntityTreeViewStateSaverPrivate( EntityTreeViewStateSaver *parent ) : + q( parent ), + view( 0 ), + horizontalScrollBarValue( -1 ), + verticalScrollBarValue( -1 ) + { + } + + inline bool hasChanges() const + { + return !pendingCollectionChanges.isEmpty() || !pendingItemChanges.isEmpty(); + } + + static inline QString key( const QModelIndex &index ) + { + const Collection c = index.data( EntityTreeModel::CollectionRole ).value(); + if ( c.isValid() ) + return QString::fromLatin1( "c%1" ).arg( c.id() ); + return QString::fromLatin1( "i%1" ).arg( index.data( EntityTreeModel::ItemIdRole ).value() ); + } + + void saveState( const QModelIndex &index, KConfigGroup &selectionGroup, KConfigGroup &expansionGroup ) + { + const QString cfgKey = key( index ); + if ( view->selectionModel()->isSelected( index ) ) + selectionGroup.writeEntry( cfgKey, true ); + if ( view->isExpanded( index ) ) + expansionGroup.writeEntry( cfgKey, true ); + for ( int i = 0; i < view->model()->rowCount( index ); ++i ) { + const QModelIndex child = view->model()->index( i, 0, index ); + saveState( child, selectionGroup, expansionGroup ); + } + } + + inline void restoreState( const QModelIndex &index, const State &state ) + { + if ( state.selected ) + view->selectionModel()->select( index, QItemSelectionModel::Select | QItemSelectionModel::Rows ); + if ( state.expanded ) + view->setExpanded( index, true ); + QTimer::singleShot( 0, q, SLOT(restoreScrollBarState()) ); + } + + void restoreState( const QModelIndex &index ) + { + const Collection c = index.data( EntityTreeModel::CollectionRole ).value(); + if ( c.isValid() ) { + if ( pendingCollectionChanges.contains( c.id() ) ) { + restoreState( index, pendingCollectionChanges.value( c.id() ) ); + pendingCollectionChanges.remove( c.id() ); + } + } else { + Entity::Id itemId = index.data( EntityTreeModel::ItemIdRole ).value(); + if ( pendingItemChanges.contains( itemId ) ) { + restoreState( index, pendingItemChanges.value( itemId ) ); + pendingItemChanges.remove( itemId ); + } + } + for ( int i = 0; i < view->model()->rowCount( index ) && hasChanges(); ++i ) { + const QModelIndex child = view->model()->index( i, 0, index ); + restoreState( child ); + } + } + + inline void restoreScrollBarState() + { + if ( horizontalScrollBarValue >= 0 && horizontalScrollBarValue <= view->horizontalScrollBar()->maximum() ) { + view->horizontalScrollBar()->setValue( horizontalScrollBarValue ); + horizontalScrollBarValue = -1; + } + if ( verticalScrollBarValue >= 0 && verticalScrollBarValue <= view->verticalScrollBar()->maximum() ) { + view->verticalScrollBar()->setValue( verticalScrollBarValue ); + verticalScrollBarValue = -1; + } + } + + void rowsInserted( const QModelIndex &index, int start, int end ) + { + if ( !hasChanges() ) { + QObject::disconnect( view->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(rowsInserted(QModelIndex,int,int)) ); + return; + } + + for ( int i = start; i <= end && hasChanges(); ++i ) { + const QModelIndex child = index.child( i, 0 ); + restoreState( child ); + } + } + + EntityTreeViewStateSaver *q; + QTreeView *view; + QHash pendingCollectionChanges, pendingItemChanges; + int horizontalScrollBarValue, verticalScrollBarValue; +}; + +EntityTreeViewStateSaver::EntityTreeViewStateSaver( QTreeView * view ) : + QObject( view ), + d( new EntityTreeViewStateSaverPrivate( this ) ) +{ + d->view = view; +} + +EntityTreeViewStateSaver::~EntityTreeViewStateSaver() +{ + delete d; +} + +void EntityTreeViewStateSaver::saveState( KConfigGroup &configGroup ) const +{ + configGroup.deleteGroup(); + KConfigGroup selectionGroup( &configGroup, "Selection" ); + KConfigGroup expansionGroup( &configGroup, "Expansion" ); + for ( int i = 0; i < d->view->model()->rowCount(); ++i ) { + const QModelIndex index = d->view->model()->index( i, 0 ); + d->saveState( index, selectionGroup, expansionGroup ); + } + KConfigGroup scrollBarGroup( &configGroup, "ScrollBar" ); + scrollBarGroup.writeEntry( "Horizontal", d->view->horizontalScrollBar()->value() ); + scrollBarGroup.writeEntry( "Vertical", d->view->verticalScrollBar()->value() ); +} + +void EntityTreeViewStateSaver::restoreState (const KConfigGroup & configGroup) const +{ + const KConfigGroup selectionGroup( &configGroup, "Selection" ); + foreach( const QString &key, selectionGroup.keyList() ) { + Entity::Id id = key.mid( 1 ).toLongLong(); + if ( id < 0 ) + continue; + if ( key.startsWith( QLatin1Char( 'c' ) ) ) + d->pendingCollectionChanges[id].selected = true; + else if ( key.startsWith( QLatin1Char( 'i' ) ) ) + d->pendingItemChanges[id].selected = true; + } + + const KConfigGroup expansionGroup( &configGroup, "Expansion" ); + foreach( const QString &key, expansionGroup.keyList() ) { + Entity::Id id = key.mid( 1 ).toLongLong(); + if ( id < 0 ) + continue; + if ( key.startsWith( QLatin1Char( 'c' ) ) ) + d->pendingCollectionChanges[id].expanded = true; + else if ( key.startsWith( QLatin1Char( 'i' ) ) ) + d->pendingItemChanges[id].expanded = true; + } + + const KConfigGroup scrollBarGroup( &configGroup, "ScrollBar" ); + d->horizontalScrollBarValue = scrollBarGroup.readEntry( "Horizontal", -1 ); + d->verticalScrollBarValue = scrollBarGroup.readEntry( "Vertical", -1 ); + + // initial restore run, for everything already loaded + for ( int i = 0; i < d->view->model()->rowCount() && d->hasChanges(); ++i ) { + const QModelIndex index = d->view->model()->index( i, 0 ); + d->restoreState( index ); + } + d->restoreScrollBarState(); + + // watch the model for stuff coming in delayed + if ( d->hasChanges() ) + connect( d->view->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int)) ); +} + +} // namespace Akonadi + +#include "entitytreeviewstatesaver.moc" diff --git a/akonadi/entitytreeviewstatesaver.h b/akonadi/entitytreeviewstatesaver.h new file mode 100644 index 000000000..90e24dcc4 --- /dev/null +++ b/akonadi/entitytreeviewstatesaver.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2009 Volker Krause + + 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. +*/ + +#ifndef AKONADI_ENTITYTREEVIEWSTATESAVER_H +#define AKONADI_ENTITYTREEVIEWSTATESAVER_H + +#include "akonadi_export.h" + +#include + +class QTreeView; +class KConfigGroup; + +namespace Akonadi { + +class EntityTreeViewStateSaverPrivate; + +/** + Saves/restores the state of a QTreeView showing an EntityTreeModel + (or whatever proxies you stacked on top of one). + State so far means selection and expansion. +*/ +class AKONADI_EXPORT EntityTreeViewStateSaver : public QObject +{ + Q_OBJECT + public: + /** + Create a new state saver, for saving or restoring. + @param view The QTreeView which state should be saved/restored. + */ + EntityTreeViewStateSaver( QTreeView* view ); + + /** + Destroys this state saver. + */ + ~EntityTreeViewStateSaver(); + + /** + Store the current state in the given config group. + @param configGroup Config file group into which the state is supposed to be stored. + */ + void saveState( KConfigGroup &configGroup ) const; + + /** + Restore the state stored in @p configGroup as soon as the corresponding entities + become available in the model (the model is populated asynchronously, which is the + main reason why you want to use this class). + @param configGroup Config file group containing a previously stored ETM state. + */ + void restoreState( const KConfigGroup &configGroup ) const; + + private: + EntityTreeViewStateSaverPrivate* const d; + Q_PRIVATE_SLOT( d, void rowsInserted( const QModelIndex&, int, int ) ) + Q_PRIVATE_SLOT( d, void restoreScrollBarState() ) +}; + +} + +#endif