From 9c000ce8b171c5b393dfe57d297aeff6fd51ec9e Mon Sep 17 00:00:00 2001 From: Marco Martin Date: Tue, 31 Jul 2012 16:28:54 +0200 Subject: [PATCH] add a model for the table of contents todo: license issues --- .../package/contents/ui/TableOfContents.qml | 35 ++ .../app/package/contents/ui/TreeDelegate.qml | 62 +++ active/components/CMakeLists.txt | 2 + active/components/documentitem.cpp | 13 + active/components/documentitem.h | 5 + active/components/tocmodel.cpp | 355 ++++++++++++++++++ active/components/tocmodel.h | 61 +++ 7 files changed, 533 insertions(+) create mode 100644 active/app/package/contents/ui/TableOfContents.qml create mode 100644 active/app/package/contents/ui/TreeDelegate.qml create mode 100644 active/components/tocmodel.cpp create mode 100644 active/components/tocmodel.h diff --git a/active/app/package/contents/ui/TableOfContents.qml b/active/app/package/contents/ui/TableOfContents.qml new file mode 100644 index 000000000..8a733d968 --- /dev/null +++ b/active/app/package/contents/ui/TableOfContents.qml @@ -0,0 +1,35 @@ +/* + * Copyright 2011 Marco Martin + * + * This program 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, or + * (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.core 0.1 as PlasmaCore + +Column { + id: treeView + Repeater { + model: VisualDataModel { + id: tocModel + model: documentItem.tableOfContents + delegate: TreeDelegate { + sourceModel: tocModel + } + } + } +} diff --git a/active/app/package/contents/ui/TreeDelegate.qml b/active/app/package/contents/ui/TreeDelegate.qml new file mode 100644 index 000000000..048e4ba81 --- /dev/null +++ b/active/app/package/contents/ui/TreeDelegate.qml @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Marco Martin + * + * This program 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, or + * (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.core 0.1 as PlasmaCore + + +Column { + id: treeDelegate + property variant sourceModel + property int rowIndex: index + Row { + spacing: 30 + PlasmaComponents.Label { + id: label + text: display + } + PlasmaComponents.Label { + id: pageNumber + text: page + } + } + Column { + id: col + x: 20 + property variant model: childrenModel + Repeater { + id: rep + model: VisualDataModel { + id: childrenModel + model: documentItem.tableOfContents + } + } + } + onParentChanged: { + if (treeDelegate.parent && treeDelegate.parent.model) { + sourceModel = treeDelegate.parent.model + } + childrenModel.rootIndex = sourceModel.modelIndex(index) + + if (model.hasModelChildren) { + childrenModel.delegate = Qt.createComponent("TreeDelegate.qml") + } + } +} diff --git a/active/components/CMakeLists.txt b/active/components/CMakeLists.txt index b08b081de..8ad3e7187 100644 --- a/active/components/CMakeLists.txt +++ b/active/components/CMakeLists.txt @@ -14,6 +14,7 @@ set(okular_SRCS pageitem.cpp documentitem.cpp thumbnailitem.cpp + tocmodel.cpp ) qt4_automoc(${okular_SRCS}) @@ -23,6 +24,7 @@ target_link_libraries(okularplugin ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTDECLARATIVE_LIBRARY} + ${QT_QTXML_LIBRARY} ${KDE4_KDECORE_LIBRARY} ${KDE4_KDEUI_LIBRARY} ${QIMAGEBLITZ_LIBRARIES} diff --git a/active/components/documentitem.cpp b/active/components/documentitem.cpp index 5b670a736..d37ff2242 100644 --- a/active/components/documentitem.cpp +++ b/active/components/documentitem.cpp @@ -19,15 +19,21 @@ #include "documentitem.h" +#include + #include +#include "tocmodel.h" DocumentItem::DocumentItem(QObject *parent) : QObject(parent), m_searchInProgress(false) { + qmlRegisterUncreatableType("org.kde.okular", 1, 0, "TOCModel", QLatin1String("Do not create objects of this type.")); Okular::Settings::instance("okularproviderrc"); m_document = new Okular::Document(0); + m_tocModel = new TOCModel(m_document, this); + connect(m_document, SIGNAL(searchFinished(int,Okular::Document::SearchStatus)), this, SLOT(searchFinished(int,Okular::Document::SearchStatus))); } @@ -43,6 +49,8 @@ void DocumentItem::setPath(const QString &path) //TODO: remote urls m_document->openDocument(path, KUrl(path), KMimeType::findByUrl(KUrl(path))); + m_tocModel->fill(m_document->documentSynopsis()); + m_matchingPages.clear(); for (uint i = 0; i < m_document->pages(); ++i) { m_matchingPages << (int)i; @@ -85,6 +93,11 @@ QList DocumentItem::matchingPages() const return m_matchingPages; } +TOCModel *DocumentItem::tableOfContents() const +{ + return m_tocModel; +} + bool DocumentItem::supportsSearching() const { return m_document->supportsSearching(); diff --git a/active/components/documentitem.h b/active/components/documentitem.h index 4b22501ac..0c52569f8 100644 --- a/active/components/documentitem.h +++ b/active/components/documentitem.h @@ -32,6 +32,7 @@ namespace Okular { } class Observer; +class TOCModel; class DocumentItem : public QObject { @@ -44,6 +45,7 @@ class DocumentItem : public QObject Q_PROPERTY(bool supportsSearching READ supportsSearching NOTIFY supportsSearchingChanged) Q_PROPERTY(bool searchInProgress READ isSearchInProgress NOTIFY searchInProgressChanged) Q_PROPERTY(QList matchingPages READ matchingPages NOTIFY matchingPagesChanged) + Q_PROPERTY(TOCModel *tableOfContents READ tableOfContents CONSTANT) public: @@ -66,6 +68,8 @@ public: QList matchingPages() const; + TOCModel *tableOfContents() const; + //Those could be a property, but maybe we want to have parameter for searchText Q_INVOKABLE void searchText(const QString &text); Q_INVOKABLE void resetSearch(); @@ -88,6 +92,7 @@ private Q_SLOTS: private: Okular::Document *m_document; + TOCModel *m_tocModel; QHash m_observers; QList m_matchingPages; bool m_searchInProgress; diff --git a/active/components/tocmodel.cpp b/active/components/tocmodel.cpp new file mode 100644 index 000000000..4d9becb68 --- /dev/null +++ b/active/components/tocmodel.cpp @@ -0,0 +1,355 @@ +/*************************************************************************** + * 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 "core/document.h" +#include "core/page.h" + +Q_DECLARE_METATYPE( QModelIndex ) + +struct TOCItem +{ + TOCItem(); + TOCItem( TOCItem *parent, const QDomElement &e ); + ~TOCItem(); + + QString text; + Okular::DocumentViewport viewport; + QString extFileName; + QString url; + 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* > itemsToOpen; + 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" ); + url = e.attribute( "URL" ); +} + +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 = false; + if ( e.hasAttribute( "Open" ) ) + isOpen = QVariant( e.attribute( "Open" ) ).toBool(); + if ( isOpen ) + itemsToOpen.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; + + qRegisterMetaType< QModelIndex >(); + + QHash roles = roleNames(); + roles.insert(PageRole, "page"); + roles.insert(PageLabelRole, "pageLabel"); + setRoleNames(roles); +} + +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: + case Qt::ToolTipRole: + return item->text; + break; + case Qt::DecorationRole: + if ( item->highlight ) + return KIcon( QApplication::layoutDirection() == Qt::RightToLeft ? "arrow-left" : "arrow-right" ); + break; + case PageRole: + if ( item->viewport.isValid() ) + return item->viewport.pageNumber + 1; + break; + case PageLabelRole: + if ( item->viewport.isValid() && item->viewport.pageNumber < int(d->document->pages()) ) + 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(); + foreach ( TOCItem *item, d->itemsToOpen ) + { + QModelIndex index = d->indexForItem( item ); + if ( !index.isValid() ) + continue; + + QMetaObject::invokeMethod( QObject::parent(), "expand", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) ); + } + d->itemsToOpen.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 ); + } +} + +bool TOCModel::isEmpty() const +{ + return d->root->children.isEmpty(); +} + +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; +} + +QString TOCModel::urlForIndex( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return QString(); + + TOCItem *item = static_cast< TOCItem* >( index.internalPointer() ); + return item->url; +} + +#include "tocmodel.moc" diff --git a/active/components/tocmodel.h b/active/components/tocmodel.h new file mode 100644 index 000000000..48bdee678 --- /dev/null +++ b/active/components/tocmodel.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * 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: + enum TOCRoles { + PageRole = Qt::UserRole + 1, + PageLabelRole = Qt::UserRole + 2 + }; + + explicit 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 ); + + bool isEmpty() const; + + QString externalFileNameForIndex( const QModelIndex &index ) const; + Okular::DocumentViewport viewportForIndex( const QModelIndex &index ) const; + QString urlForIndex( const QModelIndex &index ) const; + + private: + // storage + friend class TOCModelPrivate; + TOCModelPrivate *const d; +}; + +#endif