okular/ui/tocmodel.cpp

494 lines
14 KiB
C++
Raw Normal View History

/***************************************************************************
* Copyright (C) 2007 by Pino Toscano <pino@kde.org> *
* *
* 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 <qapplication.h>
#include <qdom.h>
#include <qlist.h>
#include <qtreeview.h>
2014-10-06 06:15:04 +00:00
#include <QIcon>
#include "pageitemdelegate.h"
#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;
TOCModel *m_oldModel;
QVector<QModelIndex> m_oldTocExpandedIndexes;
};
TOCItem::TOCItem()
: highlight( false ), parent( nullptr ), model( nullptr )
{
}
TOCItem::TOCItem( TOCItem *_parent, const QDomElement &e )
: highlight( false ), parent( _parent )
{
parent->children.append( this );
model = parent->model;
text = e.tagName();
// viewport loading
2015-10-29 12:37:11 +00:00
if ( e.hasAttribute( QStringLiteral("Viewport") ) )
{
// if the node has a viewport, set it
2015-10-29 12:37:11 +00:00
viewport = Okular::DocumentViewport( e.attribute( QStringLiteral("Viewport") ) );
}
2015-10-29 12:37:11 +00:00
else if ( e.hasAttribute( QStringLiteral("ViewportName") ) )
{
// if the node references a viewport, get the reference and set it
2015-10-29 12:37:11 +00:00
const QString & page = e.attribute( QStringLiteral("ViewportName") );
QString viewport_string = model->document->metaData( QStringLiteral("NamedViewport"), page ).toString();
if ( !viewport_string.isEmpty() )
viewport = Okular::DocumentViewport( viewport_string );
}
2015-10-29 12:37:11 +00:00
extFileName = e.attribute( QStringLiteral("ExternalFileName") );
url = e.attribute( QStringLiteral("URL") );
}
TOCItem::~TOCItem()
{
qDeleteAll( children );
}
TOCModelPrivate::TOCModelPrivate( TOCModel *qq )
: q( qq ), root( new TOCItem ), dirty( false ), m_oldModel( nullptr )
{
root->model = this;
}
TOCModelPrivate::~TOCModelPrivate()
{
delete root;
delete m_oldModel;
}
void TOCModelPrivate::addChildren( const QDomNode & parentNode, TOCItem * parentItem )
{
TOCItem * currentItem = nullptr;
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;
2015-10-29 12:37:11 +00:00
if ( e.hasAttribute( QStringLiteral("Open") ) )
isOpen = QVariant( e.attribute( QStringLiteral("Open") ) ).toBool();
if ( isOpen )
itemsToOpen.append( currentItem );
n = n.nextSibling();
emit q->countChanged();
}
}
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
{
TOCItem *todo = item;
while ( todo )
{
TOCItem *current = todo;
todo = nullptr;
TOCItem *pos = nullptr;
foreach ( TOCItem *child, current->children )
{
if ( child->viewport.isValid() )
{
if ( child->viewport.pageNumber <= viewport.pageNumber )
{
pos = child;
if ( child->viewport.pageNumber == viewport.pageNumber )
{
break;
}
}
else
{
break;
}
}
}
if ( pos )
{
list.append( pos );
todo = pos;
}
}
}
TOCModel::TOCModel( Okular::Document *document, QObject *parent )
: QAbstractItemModel( parent ), d( new TOCModelPrivate( this ) )
{
d->document = document;
qRegisterMetaType< QModelIndex >();
}
TOCModel::~TOCModel()
{
delete d;
}
QHash<int, QByteArray> TOCModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[(int)PageItemDelegate::PageRole] = "page";
roles[(int)PageItemDelegate::PageLabelRole] = "pageLabel";
return roles;
}
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 )
{
const QVariant icon = QIcon::fromTheme( QApplication::layoutDirection() == Qt::RightToLeft ? QStringLiteral("arrow-left") : QStringLiteral("arrow-right") );
TOCItem *lastHighlighted = d->currentPage.last();
// in the mobile version our parent is not a QTreeView; add icon to the last highlighted item
// TODO misusing parent() here, fix
QTreeView *view = dynamic_cast< QTreeView* > ( QObject::parent() );
if ( !view )
{
if ( item == lastHighlighted )
return icon;
return QVariant();
}
if ( view->isExpanded( index ) )
{
// if this is the last highlighted node, its child is on a page below, thus it needs icon
if ( item == lastHighlighted )
return icon;
}
else
{
return icon;
}
}
break;
case PageItemDelegate::PageRole:
if ( item->viewport.isValid() )
return item->viewport.pageNumber + 1;
break;
case PageItemDelegate::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 QStringLiteral("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();
}
static QModelIndex indexForIndex( const QModelIndex &oldModelIndex, QAbstractItemModel *newModel )
{
QModelIndex newModelIndex;
if ( oldModelIndex.parent().isValid() )
{
newModelIndex = newModel->index( oldModelIndex.row(), oldModelIndex.column(), indexForIndex( oldModelIndex.parent(), newModel ) );
}
else
{
newModelIndex = newModel->index( oldModelIndex.row(), oldModelIndex.column() );
}
return newModelIndex;
}
void TOCModel::fill( const Okular::DocumentSynopsis *toc )
{
if ( !toc )
return;
clear();
emit layoutAboutToBeChanged();
d->addChildren( *toc, d->root );
d->dirty = true;
emit layoutChanged();
if ( equals( d->m_oldModel ) )
{
foreach( const QModelIndex &oldIndex, d->m_oldTocExpandedIndexes )
{
const QModelIndex index = indexForIndex( oldIndex, this );
if ( !index.isValid() )
continue;
// TODO misusing parent() here, fix
QMetaObject::invokeMethod( QObject::parent(), "expand", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) );
}
}
else
{
foreach ( TOCItem *item, d->itemsToOpen )
{
const QModelIndex index = d->indexForItem( item );
if ( !index.isValid() )
continue;
// TODO misusing parent() here, fix
QMetaObject::invokeMethod( QObject::parent(), "expand", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) );
}
}
d->itemsToOpen.clear();
delete d->m_oldModel;
d->m_oldModel = nullptr;
d->m_oldTocExpandedIndexes.clear();
}
void TOCModel::clear()
{
if ( !d->dirty )
return;
2016-07-24 21:46:53 +00:00
beginResetModel();
qDeleteAll( d->root->children );
d->root->children.clear();
d->currentPage.clear();
2016-07-24 21:46:53 +00:00
endResetModel();
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 );
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();
}
bool TOCModel::equals( const TOCModel *model ) const
{
if ( model )
return checkequality( model );
else
return false;
}
void TOCModel::setOldModelData( TOCModel *model, const QVector<QModelIndex> &list )
{
delete d->m_oldModel;
d->m_oldModel = model;
d->m_oldTocExpandedIndexes = list;
}
bool TOCModel::hasOldModelData() const
{
return (d->m_oldModel != nullptr);
}
TOCModel *TOCModel::clearOldModelData() const
{
TOCModel *oldModel = d->m_oldModel;
d->m_oldModel = nullptr;
d->m_oldTocExpandedIndexes.clear();
return oldModel;
}
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;
}
bool TOCModel::checkequality( const TOCModel *model, const QModelIndex & parentA, const QModelIndex & parentB ) const
{
if ( rowCount( parentA ) != model->rowCount( parentB ) )
return false;
for ( int i = 0; i < rowCount( parentA ); i++ )
{
QModelIndex indxA = index( i, 0, parentA );
QModelIndex indxB = model->index( i, 0, parentB );
if ( indxA.data() != indxB.data() )
{
return false;
}
if ( hasChildren( indxA ) != model->hasChildren( indxB ) )
{
return false;
}
if ( !checkequality( model, indxA, indxB ) )
{
return false;
}
}
return true;
}
#include "moc_tocmodel.cpp"