diff --git a/ui/annotationmodel.cpp b/ui/annotationmodel.cpp index ef0f5b8f5..82ffdc8af 100644 --- a/ui/annotationmodel.cpp +++ b/ui/annotationmodel.cpp @@ -304,7 +304,7 @@ QVariant AnnotationModel::data( const QModelIndex &index, int role ) const switch ( role ) { case Qt::DisplayRole: - return item->annotation->author(); + return AnnotationGuiUtils::captionForAnnotation( item->annotation ); break; case Qt::DecorationRole: return KIcon( "okular" ); diff --git a/ui/annotationproxymodels.cpp b/ui/annotationproxymodels.cpp index cbcf66654..a3020314d 100644 --- a/ui/annotationproxymodels.cpp +++ b/ui/annotationproxymodels.cpp @@ -8,6 +8,9 @@ ***************************************************************************/ #include +#include + +#include #include "annotationmodel.h" @@ -217,4 +220,335 @@ void PageGroupProxyModel::groupByPage( bool value ) rebuildIndexes(); } + +class AuthorGroupItem +{ + public: + enum Type + { + Page, + Author, + Annotation + }; + + AuthorGroupItem( AuthorGroupItem *parent, Type type = Page, const QModelIndex &index = QModelIndex() ) + : mParent( parent ), mType( type ), mIndex( index ) + { + } + + ~AuthorGroupItem() + { + qDeleteAll( mChilds ); + } + + void appendChild( AuthorGroupItem *child ) { mChilds.append( child ); } + AuthorGroupItem* parent() const { return mParent; } + AuthorGroupItem* child( int row ) const { return mChilds.value( row ); } + int childCount() const { return mChilds.count(); } + + void dump( int level = 0 ) + { + QString prefix; + for ( int i = 0; i < level; ++i ) prefix += " "; + + qDebug( "%s%s", qPrintable( prefix ), ( mType == Page ? "Page" : (mType == Author ? "Author" : "Annotation") ) ); + + for ( int i = 0; i < mChilds.count(); ++i ) + mChilds[ i ]->dump( level + 2 ); + } + + const AuthorGroupItem* findIndex( const QModelIndex &index ) const + { + if ( index == mIndex ) + return this; + + for ( int i = 0; i < mChilds.count(); ++i ) { + const AuthorGroupItem *item = mChilds[ i ]->findIndex( index ); + if ( item ) + return item; + } + + return 0; + } + + int row() const + { + return ( mParent ? mParent->mChilds.indexOf( const_cast( this ) ) : 0 ); + } + + Type type() const { return mType; } + QModelIndex index() const { return mIndex; } + + void setAuthor( const QString &author ) { mAuthor = author; } + QString author() const { return mAuthor; } + + private: + AuthorGroupItem *mParent; + Type mType; + QModelIndex mIndex; + QList mChilds; + QString mAuthor; +}; + +class AuthorGroupProxyModel::Private +{ + public: + Private( AuthorGroupProxyModel *parent ) + : mParent( parent ), mRoot( 0 ), + mGroupByAuthor( false ) + { + } + + AuthorGroupProxyModel *mParent; + AuthorGroupItem *mRoot; + bool mGroupByAuthor; +}; + +AuthorGroupProxyModel::AuthorGroupProxyModel( QObject *parent ) + : QAbstractProxyModel( parent ), + d( new Private( this ) ) +{ +} + +AuthorGroupProxyModel::~AuthorGroupProxyModel() +{ +} + +int AuthorGroupProxyModel::columnCount( const QModelIndex& ) const +{ + return 1; +} + +int AuthorGroupProxyModel::rowCount( const QModelIndex &parentIndex ) const +{ + AuthorGroupItem *item = 0; + if ( !parentIndex.isValid() ) + item = d->mRoot; + else + item = static_cast( parentIndex.internalPointer() ); + + return item->childCount(); +} + +QModelIndex AuthorGroupProxyModel::index( int row, int column, const QModelIndex &parentIndex ) const +{ + if ( !hasIndex( row, column, parentIndex ) ) + return QModelIndex(); + + AuthorGroupItem *parentItem = 0; + if ( !parentIndex.isValid() ) + parentItem = d->mRoot; + else + parentItem = static_cast( parentIndex.internalPointer() ); + + AuthorGroupItem *child = parentItem->child( row ); + if ( child ) + return createIndex( row, column, child ); + else + return QModelIndex(); +} + +QModelIndex AuthorGroupProxyModel::parent( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return QModelIndex(); + + AuthorGroupItem *childItem = static_cast( index.internalPointer() ); + AuthorGroupItem *parentItem = childItem->parent(); + + if ( parentItem == d->mRoot ) + return QModelIndex(); + else + return createIndex( parentItem->row(), 0, parentItem ); +} + +QModelIndex AuthorGroupProxyModel::mapFromSource( const QModelIndex &sourceIndex ) const +{ + const AuthorGroupItem *item = d->mRoot->findIndex( sourceIndex ); + if ( !item ) + return QModelIndex(); + + return createIndex( item->row(), 0, const_cast( item ) ); +} + +QModelIndex AuthorGroupProxyModel::mapToSource( const QModelIndex &proxyIndex ) const +{ + if ( !proxyIndex.isValid() ) + return QModelIndex(); + + AuthorGroupItem *item = static_cast( proxyIndex.internalPointer() ); + + return item->index(); +} + +void AuthorGroupProxyModel::setSourceModel( QAbstractItemModel *model ) +{ + if ( sourceModel() ) { + disconnect( sourceModel(), SIGNAL( layoutChanged() ), this, SLOT( rebuildIndexes() ) ); + disconnect( sourceModel(), SIGNAL( modelReset() ), this, SLOT( rebuildIndexes() ) ); + disconnect( sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), this, SLOT( rebuildIndexes() ) ); + disconnect( sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), this, SLOT( rebuildIndexes() ) ); + } + + QAbstractProxyModel::setSourceModel( model ); + + connect( sourceModel(), SIGNAL( layoutChanged() ), this, SLOT( rebuildIndexes() ) ); + connect( sourceModel(), SIGNAL( modelReset() ), this, SLOT( rebuildIndexes() ) ); + connect( sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), this, SLOT( rebuildIndexes() ) ); + connect( sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), this, SLOT( rebuildIndexes() ) ); + + rebuildIndexes(); +} + +static bool isAuthorItem( const QModelIndex &index ) +{ + AuthorGroupItem *item = static_cast( index.internalPointer() ); + return (item->type() == AuthorGroupItem::Author); +} + +QItemSelection AuthorGroupProxyModel::mapSelectionToSource( const QItemSelection &selection ) const +{ + QModelIndexList proxyIndexes = selection.indexes(); + QItemSelection sourceSelection; + for ( int i = 0; i < proxyIndexes.size(); ++i ) { + if ( !isAuthorItem( proxyIndexes.at( i ) ) ) + sourceSelection << QItemSelectionRange( mapToSource( proxyIndexes.at( i ) ) ); + } + + return sourceSelection; +} + +QItemSelection AuthorGroupProxyModel::mapSelectionFromSource( const QItemSelection &selection ) const +{ + return QAbstractProxyModel::mapSelectionFromSource( selection ); +} + +QVariant AuthorGroupProxyModel::data( const QModelIndex &proxyIndex, int role ) const +{ + if ( isAuthorItem( proxyIndex ) ) { + AuthorGroupItem *item = static_cast( proxyIndex.internalPointer() ); + if ( role == Qt::DisplayRole ) + return item->author(); + else if ( role == Qt::DecorationRole ) + return KIcon( item->author().isEmpty() ? "precense_away" : "personal" ); + else + return QVariant(); + } else { + return QAbstractProxyModel::data( proxyIndex, role ); + } +} + +QMap AuthorGroupProxyModel::itemData( const QModelIndex &index ) const +{ + if ( isAuthorItem( index ) ) { + return QMap(); + } else { + return QAbstractProxyModel::itemData( index ); + } +} + +Qt::ItemFlags AuthorGroupProxyModel::flags( const QModelIndex &index ) const +{ + if ( isAuthorItem( index ) ) { + return Qt::ItemIsEnabled; + } else { + return QAbstractProxyModel::flags( index ); + } +} + +void AuthorGroupProxyModel::groupByAuthor( bool value ) +{ + if ( d->mGroupByAuthor == value ) + return; + + d->mGroupByAuthor = value; + + rebuildIndexes(); +} + +void AuthorGroupProxyModel::rebuildIndexes() +{ + delete d->mRoot; + d->mRoot = new AuthorGroupItem( 0 ); + + if ( d->mGroupByAuthor ) { + QMap authorMap; + + for ( int row = 0; row < sourceModel()->rowCount(); ++row ) { + const QModelIndex idx = sourceModel()->index( row, 0 ); + const QString author = sourceModel()->data( idx, AnnotationModel::AuthorRole ).toString(); + if ( !author.isEmpty() ) { + // We have the annotations as top-level, so introduce authors as new + // top-levels and append the annotations + if ( !authorMap.contains( author ) ) { + AuthorGroupItem *item = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Author ); + item->setAuthor( author ); + + // Add item to tree + d->mRoot->appendChild( item ); + + // Insert to lookup list + authorMap.insert( author, item ); + } + + AuthorGroupItem *authorItem = authorMap.value( author ); + + AuthorGroupItem *item = new AuthorGroupItem( authorItem, AuthorGroupItem::Annotation, idx ); + authorItem->appendChild( item ); + } else { + // We have the pages as top-level, so we use them as top-level, append the + // authors for all annotations of the page, and then the annotations themself + AuthorGroupItem *pageItem = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Page, idx ); + d->mRoot->appendChild( pageItem ); + + // First collect all authors... + QMap pageAuthorMap; + for ( int subRow = 0; subRow < sourceModel()->rowCount( idx ); ++subRow ) { + const QModelIndex annIdx = sourceModel()->index( subRow, 0, idx ); + const QString author = sourceModel()->data( annIdx, AnnotationModel::AuthorRole ).toString(); + + if ( !pageAuthorMap.contains( author ) ) { + AuthorGroupItem *item = new AuthorGroupItem( pageItem, AuthorGroupItem::Author ); + item->setAuthor( author ); + + // Add item to tree + pageItem->appendChild( item ); + + // Insert to lookup list + pageAuthorMap.insert( author, item ); + } + + AuthorGroupItem *authorItem = pageAuthorMap.value( author ); + + AuthorGroupItem *item = new AuthorGroupItem( authorItem, AuthorGroupItem::Annotation, annIdx ); + authorItem->appendChild( item ); + } + } + } + } else { + for ( int row = 0; row < sourceModel()->rowCount(); ++row ) { + const QModelIndex idx = sourceModel()->index( row, 0 ); + const QString author = sourceModel()->data( idx, AnnotationModel::AuthorRole ).toString(); + if ( !author.isEmpty() ) { + // We have the annotations as top-level items + AuthorGroupItem *item = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Annotation, idx ); + d->mRoot->appendChild( item ); + } else { + // We have the pages as top-level items + AuthorGroupItem *pageItem = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Page, idx ); + d->mRoot->appendChild( pageItem ); + + // Append all annotations as second-level + for ( int subRow = 0; subRow < sourceModel()->rowCount( idx ); ++subRow ) { + const QModelIndex subIdx = sourceModel()->index( subRow, 0, idx ); + AuthorGroupItem *item = new AuthorGroupItem( pageItem, AuthorGroupItem::Annotation, subIdx ); + pageItem->appendChild( item ); + } + } + } + } + + reset(); +} + #include "annotationproxymodels.moc" diff --git a/ui/annotationproxymodels.h b/ui/annotationproxymodels.h index d381aee53..e29da0d13 100644 --- a/ui/annotationproxymodels.h +++ b/ui/annotationproxymodels.h @@ -61,7 +61,7 @@ class PageGroupProxyModel : public QAbstractProxyModel public: /** - * Creates a new page group proxy model. + * Creates a new page group proxy model. * * @param parent The parent object. */ @@ -93,4 +93,53 @@ class PageGroupProxyModel : public QAbstractProxyModel QList mIndexes; QList > > mTreeIndexes; }; + +/** + * A proxy model which groups the annotations by author. + */ +class AuthorGroupProxyModel : public QAbstractProxyModel +{ + Q_OBJECT + + public: + /** + * Creates a new author group proxy model. + * + * @param parent The parent object. + */ + AuthorGroupProxyModel( QObject *parent = 0 ); + ~AuthorGroupProxyModel(); + + virtual int columnCount( const QModelIndex &parentIndex ) const; + virtual int rowCount( const QModelIndex &parentIndex ) const; + + virtual QModelIndex index( int row, int column, const QModelIndex &parentIndex = QModelIndex() ) const; + virtual QModelIndex parent( const QModelIndex &index ) const; + + virtual QModelIndex mapFromSource( const QModelIndex &sourceIndex ) const; + virtual QModelIndex mapToSource( const QModelIndex &proxyIndex ) const; + + virtual void setSourceModel( QAbstractItemModel *model ); + + virtual QItemSelection mapSelectionToSource(const QItemSelection &selection) const; + virtual QItemSelection mapSelectionFromSource(const QItemSelection &selection) const; + QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const; + QMap itemData(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + public Q_SLOTS: + /** + * Sets whether the proxy model shall group + * the annotations by author. + */ + void groupByAuthor( bool value ); + + private Q_SLOTS: + void rebuildIndexes(); + + private: + class Private; + Private* const d; +}; + #endif diff --git a/ui/side_reviews.cpp b/ui/side_reviews.cpp index 95ff961ee..680bee34b 100644 --- a/ui/side_reviews.cpp +++ b/ui/side_reviews.cpp @@ -103,11 +103,14 @@ Reviews::Reviews( QWidget * parent, Okular::Document * document ) m_filterProxy = new PageFilterProxyModel( m_view ); m_groupProxy = new PageGroupProxyModel( m_view ); + m_authorProxy = new AuthorGroupProxyModel( m_view ); m_filterProxy->setSourceModel( m_model ); m_groupProxy->setSourceModel( m_filterProxy ); + m_authorProxy->setSourceModel( m_groupProxy ); - m_view->setModel( m_groupProxy ); + + m_view->setModel( m_authorProxy ); vLayout->addWidget( new KTreeViewSearchLine( this, m_view ) ); vLayout->addWidget( m_view ); @@ -121,12 +124,11 @@ Reviews::Reviews( QWidget * parent, Okular::Document * document ) connect( groupByPageAction, SIGNAL( toggled( bool ) ), this, SLOT( slotPageEnabled( bool ) ) ); groupByPageAction->setChecked( Okular::Settings::groupByPage() ); // - add Author button -/* QAction * groupByAuthorAction = toolBar->addAction( KIcon( "user" ), i18n( "Group by Author" ) ); groupByAuthorAction->setCheckable( true ); connect( groupByAuthorAction, SIGNAL( toggled( bool ) ), this, SLOT( slotAuthorEnabled( bool ) ) ); groupByAuthorAction->setChecked( Okular::Settings::groupByAuthor() ); -*/ + // - add separator toolBar->addSeparator(); // - add Current Page Only button @@ -157,13 +159,17 @@ void Reviews::slotPageEnabled( bool on ) // store toggle state in Settings and update the listview Okular::Settings::setGroupByPage( on ); m_groupProxy->groupByPage( on ); + + m_view->expandAll(); } void Reviews::slotAuthorEnabled( bool on ) { // store toggle state in Settings and update the listview Okular::Settings::setGroupByAuthor( on ); - //m_proxy->groupByAuthor( on ); + m_authorProxy->groupByAuthor( on ); + + m_view->expandAll(); } void Reviews::slotCurrentPageOnly( bool on ) @@ -171,13 +177,16 @@ void Reviews::slotCurrentPageOnly( bool on ) // store toggle state in Settings and update the listview Okular::Settings::setCurrentPageOnly( on ); m_filterProxy->groupByCurrentPage( on ); + + m_view->expandAll(); } //END GUI Slots void Reviews::activated( const QModelIndex &index ) { - const QModelIndex filterIndex = m_groupProxy->mapToSource( index ); + const QModelIndex authorIndex = m_authorProxy->mapToSource( index ); + const QModelIndex filterIndex = m_groupProxy->mapToSource( authorIndex ); const QModelIndex annotIndex = m_filterProxy->mapToSource( filterIndex ); Okular::Annotation *annotation = m_model->annotationForIndex( annotIndex ); diff --git a/ui/side_reviews.h b/ui/side_reviews.h index 38d1a1990..bb843ea6f 100644 --- a/ui/side_reviews.h +++ b/ui/side_reviews.h @@ -23,6 +23,7 @@ class Document; } class AnnotationModel; +class AuthorGroupProxyModel; class PageFilterProxyModel; class PageGroupProxyModel; class TreeView; @@ -59,6 +60,7 @@ class Reviews : public QWidget, public Okular::DocumentObserver // internal storage Okular::Document * m_document; AnnotationModel * m_model; + AuthorGroupProxyModel * m_authorProxy; PageFilterProxyModel * m_filterProxy; PageGroupProxyModel * m_groupProxy; };