diff --git a/CMakeLists.txt b/CMakeLists.txt index 664a6ef97..196101c05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ set(okularpart_SRCS ui/side_reviews.cpp ui/thumbnaillist.cpp ui/toc.cpp + ui/tocmodel.cpp ) kde4_add_ui_files(okularpart_SRCS diff --git a/ui/toc.cpp b/ui/toc.cpp index 5ad3bab6e..bdc0fcbe5 100644 --- a/ui/toc.cpp +++ b/ui/toc.cpp @@ -13,105 +13,39 @@ #include #include #include -#include -#include -#include -#include -#include -#include +#include + +#include // local includes +#include "ktreeviewsearchline.h" #include "pageitemdelegate.h" +#include "tocmodel.h" #include "core/action.h" #include "core/document.h" -#include "core/page.h" -class TOCItem : public QTreeWidgetItem -{ - public: - TOCItem( QTreeWidget *parent, TOCItem *after, Okular::Document *document, const QDomElement & e ) - : QTreeWidgetItem( parent, after ) - { - init( document, e ); - } - - TOCItem( QTreeWidgetItem *parent, TOCItem *after, Okular::Document *document, const QDomElement & e ) - : QTreeWidgetItem( parent, after ) - { - init( document, e ); - } - - void init( Okular::Document *document, const QDomElement & e ) - { - // viewport loading - if ( e.hasAttribute( "Viewport" ) ) - { - // if the node has a viewport, set it - m_viewport = Okular::DocumentViewport( e.attribute( "Viewport" ) ); - } - else if ( e.hasAttribute( "ViewportName" ) ) - { - // if the node references a viewport, get the reference and set it - const QString & page = e.attribute( "ViewportName" ); - QString viewport = document->metaData( "NamedViewport", page ).toString(); - if ( !viewport.isNull() ) - m_viewport = Okular::DocumentViewport( viewport ); - } - - if ( m_viewport.isValid() ) - { - setData( 0, PageItemDelegate::PageRole, QString::number( m_viewport.pageNumber + 1 ) ); - QString label = document->page( m_viewport.pageNumber )->label(); - if ( !label.isEmpty() ) - setData( 0, PageItemDelegate::PageLabelRole, label ); - } - m_extFileName = e.attribute( "ExternalFileName" ); - setText( 0, e.tagName() ); - } - - QString externalFileName() const - { - return m_extFileName; - } - - const Okular::DocumentViewport& viewport() const - { - return m_viewport; - } - - void setCurrent( bool selected ) - { - setIcon( 0, selected ? KIcon( treeWidget()->layoutDirection() == Qt::RightToLeft ? "arrow-left" : "arrow-right" ) : QIcon() ); - } - - private: - Okular::DocumentViewport m_viewport; - QString m_extFileName; -}; - -TOC::TOC(QWidget *parent, Okular::Document *document) : QWidget(parent), m_document(document), m_current(0), m_currentPage(-1) +TOC::TOC(QWidget *parent, Okular::Document *document) : QWidget(parent), m_document(document), m_currentPage(-1) { QVBoxLayout *mainlay = new QVBoxLayout( this ); mainlay->setMargin( 0 ); mainlay->setSpacing( 6 ); - m_searchLine = new KTreeWidgetSearchLine( this ); + m_searchLine = new KTreeViewSearchLine( this ); mainlay->addWidget( m_searchLine ); - m_treeView = new QTreeWidget( this ); + m_treeView = new QTreeView( this ); mainlay->addWidget( m_treeView ); - QStringList cols; - cols.append( "Topics" ); - m_treeView->setHeaderLabels( cols ); + m_model = new TOCModel( document, m_treeView ); + m_treeView->setModel( m_model ); m_treeView->setSortingEnabled( false ); m_treeView->setRootIsDecorated( true ); m_treeView->setAlternatingRowColors( true ); m_treeView->setItemDelegate( new PageItemDelegate( m_treeView ) ); m_treeView->header()->hide(); m_treeView->setSelectionBehavior( QAbstractItemView::SelectRows ); - connect( m_treeView, SIGNAL( itemClicked( QTreeWidgetItem *, int ) ), this, SLOT( slotExecuted( QTreeWidgetItem * ) ) ); - connect( m_treeView, SIGNAL( itemActivated( QTreeWidgetItem *, int ) ), this, SLOT( slotExecuted( QTreeWidgetItem * ) ) ); - m_searchLine->addTreeWidget( m_treeView ); + connect( m_treeView, SIGNAL( clicked( const QModelIndex & ) ), this, SLOT( slotExecuted( const QModelIndex & ) ) ); + connect( m_treeView, SIGNAL( activated( const QModelIndex & ) ), this, SLOT( slotExecuted( const QModelIndex & ) ) ); + m_searchLine->addTreeView( m_treeView ); } TOC::~TOC() @@ -130,9 +64,7 @@ void TOC::notifySetup( const QVector< Okular::Page * > & /*pages*/, int setupFla return; // clear contents - m_treeView->clear(); - m_searchLine->clear(); - m_current = 0; + m_model->clear(); m_currentPage = -1; // request synopsis description (is a dom tree) @@ -146,7 +78,7 @@ void TOC::notifySetup( const QVector< Okular::Page * > & /*pages*/, int setupFla } // else populate the listview and enable the tab - addChildren( *syn ); + m_model->fill( syn ); emit hasTOC( true ); } @@ -158,25 +90,7 @@ void TOC::notifyViewportChanged( bool /*smoothMove*/ ) m_currentPage = newpage; - if ( m_current ) - { - m_current->setCurrent( false ); - m_current = 0; - } - - QTreeWidgetItemIterator it( m_treeView ); - while ( (*it) && !m_current ) - { - TOCItem *tmp = dynamic_cast( *it ); - int p = tmp ? tmp->viewport().pageNumber : -1; - if ( p == newpage ) - { - m_current = tmp; - if (m_current) - m_current->setCurrent( true ); - } - ++it; - } + m_model->setCurrentViewport( m_document->viewport() ); } @@ -186,52 +100,21 @@ void TOC::reparseConfig() } -void TOC::addChildren( const QDomNode & parentNode, QTreeWidgetItem * parentItem ) +void TOC::slotExecuted( const QModelIndex &index ) { - // keep track of the current listViewItem - TOCItem * currentItem = 0; - QDomNode n = parentNode.firstChild(); - while( !n.isNull() ) - { - // convert the node to an element (sure it is) - QDomElement e = n.toElement(); - - // insert the entry as top level (listview parented) or 2nd+ level - if ( !parentItem ) - currentItem = new TOCItem( m_treeView, currentItem, m_document, e ); - else - currentItem = new TOCItem( parentItem, currentItem, m_document, e ); - - // descend recursively and advance to the next node - if ( e.hasChildNodes() ) - addChildren( n, currentItem ); - - // open/keep close the item - bool isOpen = false; - if ( e.hasAttribute( "Open" ) ) - isOpen = QVariant( e.attribute( "Open" ) ).toBool(); - currentItem->setExpanded( isOpen ); - - n = n.nextSibling(); - } -} - -void TOC::slotExecuted( QTreeWidgetItem *i ) -{ - TOCItem* tocItem = dynamic_cast( i ); - // that filters clicks on [+] that for a strange reason don't seem to be TOCItem* - if (tocItem == NULL) + if ( !index.isValid() ) return; - QString externalFileName = tocItem->externalFileName(); + QString externalFileName = m_model->externalFileNameForIndex( index ); + Okular::DocumentViewport viewport = m_model->viewportForIndex( index ); if ( !externalFileName.isEmpty() ) { - Okular::GotoAction action( externalFileName, tocItem->viewport() ); + Okular::GotoAction action( externalFileName, viewport ); m_document->processAction( &action ); } else { - m_document->setViewport( tocItem->viewport() ); + m_document->setViewport( viewport ); } } diff --git a/ui/toc.h b/ui/toc.h index d52e0e9e7..aa7363e83 100644 --- a/ui/toc.h +++ b/ui/toc.h @@ -14,10 +14,10 @@ #include "core/observer.h" class QDomNode; -class QTreeWidget; -class QTreeWidgetItem; -class KTreeWidgetSearchLine; -class TOCItem; +class QModelIndex; +class QTreeView; +class KTreeViewSearchLine; +class TOCModel; namespace Okular { class Document; @@ -41,14 +41,13 @@ Q_OBJECT void hasTOC(bool has); private slots: - void slotExecuted(QTreeWidgetItem *i); + void slotExecuted( const QModelIndex & ); private: - void addChildren( const QDomNode & parentNode, QTreeWidgetItem * parentItem = 0 ); Okular::Document *m_document; - QTreeWidget *m_treeView; - KTreeWidgetSearchLine *m_searchLine; - TOCItem *m_current; + QTreeView *m_treeView; + KTreeViewSearchLine *m_searchLine; + TOCModel *m_model; int m_currentPage; }; diff --git a/ui/tocmodel.cpp b/ui/tocmodel.cpp new file mode 100644 index 000000000..dec18416c --- /dev/null +++ b/ui/tocmodel.cpp @@ -0,0 +1,331 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "tocmodel.h" + +#include +#include +#include + +#include + +#include "pageitemdelegate.h" +#include "core/document.h" +#include "core/page.h" + +struct TOCItem +{ + TOCItem(); + TOCItem( TOCItem *parent, const QDomElement &e ); + ~TOCItem(); + + QString text; + Okular::DocumentViewport viewport; + QString extFileName; + bool highlight : 1; + TOCItem *parent; + QList< TOCItem* > children; + TOCModelPrivate *model; +}; + + +class TOCModelPrivate +{ +public: + TOCModelPrivate( TOCModel *qq ); + ~TOCModelPrivate(); + + void addChildren( const QDomNode &parentNode, TOCItem * parentItem ); + QModelIndex indexForItem( TOCItem *item ) const; + void findViewport( const Okular::DocumentViewport &viewport, TOCItem *item, QList< TOCItem* > &list ) const; + + TOCModel *q; + TOCItem *root; + bool dirty : 1; + Okular::Document *document; + QList< TOCItem* > itemsToCollapse; + QList< TOCItem* > currentPage; +}; + + +TOCItem::TOCItem() + : highlight( false ), parent( 0 ), model( 0 ) +{ +} + +TOCItem::TOCItem( TOCItem *_parent, const QDomElement &e ) + : highlight( false ), parent( _parent ) +{ + parent->children.append( this ); + model = parent->model; + text = e.tagName(); + + // viewport loading + if ( e.hasAttribute( "Viewport" ) ) + { + // if the node has a viewport, set it + viewport = Okular::DocumentViewport( e.attribute( "Viewport" ) ); + } + else if ( e.hasAttribute( "ViewportName" ) ) + { + // if the node references a viewport, get the reference and set it + const QString & page = e.attribute( "ViewportName" ); + QString viewport_string = model->document->metaData( "NamedViewport", page ).toString(); + if ( !viewport_string.isEmpty() ) + viewport = Okular::DocumentViewport( viewport_string ); + } + + extFileName = e.attribute( "ExternalFileName" ); +} + +TOCItem::~TOCItem() +{ + qDeleteAll( children ); +} + + +TOCModelPrivate::TOCModelPrivate( TOCModel *qq ) + : q( qq ), root( new TOCItem ), dirty( false ) +{ + root->model = this; +} + +TOCModelPrivate::~TOCModelPrivate() +{ + delete root; +} + +void TOCModelPrivate::addChildren( const QDomNode & parentNode, TOCItem * parentItem ) +{ + TOCItem * currentItem = 0; + QDomNode n = parentNode.firstChild(); + while( !n.isNull() ) + { + // convert the node to an element (sure it is) + QDomElement e = n.toElement(); + + // insert the entry as top level (listview parented) or 2nd+ level + currentItem = new TOCItem( parentItem, e ); + + // descend recursively and advance to the next node + if ( e.hasChildNodes() ) + addChildren( n, currentItem ); + + // open/keep close the item + bool isOpen = true; + if ( e.hasAttribute( "Open" ) ) + isOpen = QVariant( e.attribute( "Open" ) ).toBool(); + if ( !isOpen ) + itemsToCollapse.append( currentItem ); + + n = n.nextSibling(); + } +} + +QModelIndex TOCModelPrivate::indexForItem( TOCItem *item ) const +{ + if ( item->parent ) + { + int id = item->parent->children.indexOf( item ); + if ( id >= 0 && id < item->parent->children.count() ) + return q->createIndex( id, 0, item ); + } + return QModelIndex(); +} + +void TOCModelPrivate::findViewport( const Okular::DocumentViewport &viewport, TOCItem *item, QList< TOCItem* > &list ) const +{ + if ( item->viewport.isValid() && item->viewport.pageNumber == viewport.pageNumber ) + list.append( item ); + + foreach ( TOCItem *child, item->children ) + findViewport( viewport, child, list ); +} + + +TOCModel::TOCModel( Okular::Document *document, QObject *parent ) + : QAbstractItemModel( parent ), d( new TOCModelPrivate( this ) ) +{ + d->document = document; +} + +TOCModel::~TOCModel() +{ + delete d; +} + +int TOCModel::columnCount( const QModelIndex &parent ) const +{ + Q_UNUSED( parent ) + return 1; +} + +QVariant TOCModel::data( const QModelIndex &index, int role ) const +{ + if ( !index.isValid() ) + return QVariant(); + + TOCItem *item = static_cast< TOCItem* >( index.internalPointer() ); + switch ( role ) + { + case Qt::DisplayRole: + return item->text; + break; + case Qt::DecorationRole: + if ( item->highlight ) + return KIcon( QApplication::layoutDirection() == Qt::RightToLeft ? "arrow-left" : "arrow-right" ); + break; + case PageItemDelegate::PageRole: + if ( item->viewport.isValid() ) + return item->viewport.pageNumber + 1; + break; + case PageItemDelegate::PageLabelRole: + if ( item->viewport.isValid() ) + return d->document->page( item->viewport.pageNumber )->label(); + break; + } + return QVariant(); +} + +bool TOCModel::hasChildren( const QModelIndex &parent ) const +{ + if ( !parent.isValid() ) + return true; + + TOCItem *item = static_cast< TOCItem* >( parent.internalPointer() ); + return !item->children.isEmpty(); +} + +QVariant TOCModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( orientation != Qt::Horizontal ) + return QVariant(); + + if ( section == 0 && role == Qt::DisplayRole ) + return "Topics"; + + return QVariant(); +} + +QModelIndex TOCModel::index( int row, int column, const QModelIndex &parent ) const +{ + if ( row < 0 || column != 0 ) + return QModelIndex(); + + TOCItem *item = parent.isValid() ? static_cast< TOCItem* >( parent.internalPointer() ) : d->root; + if ( row < item->children.count() ) + return createIndex( row, column, item->children.at( row ) ); + + return QModelIndex(); +} + +QModelIndex TOCModel::parent( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return QModelIndex(); + + TOCItem *item = static_cast< TOCItem* >( index.internalPointer() ); + return d->indexForItem( item->parent ); +} + +int TOCModel::rowCount( const QModelIndex &parent ) const +{ + TOCItem *item = parent.isValid() ? static_cast< TOCItem* >( parent.internalPointer() ) : d->root; + return item->children.count(); +} + +void TOCModel::fill( const Okular::DocumentSynopsis *toc ) +{ + if ( !toc ) + return; + + clear(); + emit layoutAboutToBeChanged(); + d->addChildren( *toc, d->root ); + d->dirty = true; + emit layoutChanged(); + QMetaObject::invokeMethod( QObject::parent(), "expandAll" ); + foreach ( TOCItem *item, d->itemsToCollapse ) + { + QModelIndex index = d->indexForItem( item ); + if ( !index.isValid() ) + continue; + + QMetaObject::invokeMethod( QObject::parent(), "collapse", Q_ARG( QModelIndex, index ) ); + } + d->itemsToCollapse.clear(); +} + +void TOCModel::clear() +{ + if ( !d->dirty ) + return; + + qDeleteAll( d->root->children ); + d->root->children.clear(); + d->currentPage.clear(); + reset(); + d->dirty = false; +} + +void TOCModel::setCurrentViewport( const Okular::DocumentViewport &viewport ) +{ + foreach ( TOCItem* item, d->currentPage ) + { + QModelIndex index = d->indexForItem( item ); + if ( !index.isValid() ) + continue; + + item->highlight = false; + emit dataChanged( index, index ); + } + d->currentPage.clear(); + + QList< TOCItem* > newCurrentPage; + d->findViewport( viewport, d->root, newCurrentPage ); + // HACK: for now, support only the first item found + if ( newCurrentPage.count() > 0 ) + { + TOCItem *first = newCurrentPage.first(); + newCurrentPage.clear(); + newCurrentPage.append( first ); + } + + d->currentPage = newCurrentPage; + + foreach ( TOCItem* item, d->currentPage ) + { + QModelIndex index = d->indexForItem( item ); + if ( !index.isValid() ) + continue; + + item->highlight = true; + emit dataChanged( index, index ); + } +} + +QString TOCModel::externalFileNameForIndex( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return QString(); + + TOCItem *item = static_cast< TOCItem* >( index.internalPointer() ); + return item->extFileName; +} + +Okular::DocumentViewport TOCModel::viewportForIndex( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return Okular::DocumentViewport(); + + TOCItem *item = static_cast< TOCItem* >( index.internalPointer() ); + return item->viewport; +} + +#include "tocmodel.moc" diff --git a/ui/tocmodel.h b/ui/tocmodel.h new file mode 100644 index 000000000..d07d6923a --- /dev/null +++ b/ui/tocmodel.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef TOCMODEL_H +#define TOCMODEL_H + +#include + +namespace Okular { +class Document; +class DocumentSynopsis; +class DocumentViewport; +} + +class TOCModelPrivate; + +class TOCModel : public QAbstractItemModel +{ + Q_OBJECT + + public: + TOCModel( Okular::Document *document, QObject *parent = 0 ); + virtual ~TOCModel(); + + // reimplementations from QAbstractItemModel + virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const; + virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const; + virtual bool hasChildren( const QModelIndex &parent = QModelIndex() ) const; + virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; + virtual QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const; + virtual QModelIndex parent( const QModelIndex &index ) const; + virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const; + + void fill( const Okular::DocumentSynopsis *toc ); + void clear(); + void setCurrentViewport( const Okular::DocumentViewport &viewport ); + + QString externalFileNameForIndex( const QModelIndex &index ) const; + Okular::DocumentViewport viewportForIndex( const QModelIndex &index ) const; + + private: + // storage + friend class TOCModelPrivate; + TOCModelPrivate *const d; +}; + +#endif