blob: 56abf871b308babf5ca0484a0b82a3d3b47cbe9a [file] [log] [blame]
/* poppler-optcontent.cc: qt interface to poppler
*
* Copyright (C) 2007, Brad Hards <bradh@kde.org>
* Copyright (C) 2008, 2014, Pino Toscano <pino@kde.org>
* Copyright (C) 2008, Carlos Garcia Campos <carlosgc@gnome.org>
* Copyright (C) 2015-2019, Albert Astals Cid <aacid@kde.org>
* Copyright (C) 2017, Hubert Figuière <hub@figuiere.net>
* Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
* Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
* Copyright (C) 2019 Oliver Sander <oliver.sander@tu-dresden.de>
*
* 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, 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*/
#include "poppler-optcontent.h"
#include "poppler-optcontent-private.h"
#include "poppler-private.h"
#include "poppler-link-private.h"
#include <QtCore/QDebug>
#include <QtCore/QtAlgorithms>
#include "poppler/OptionalContent.h"
#include "poppler/Link.h"
namespace Poppler
{
RadioButtonGroup::RadioButtonGroup( OptContentModelPrivate *ocModel, Array *rbarray )
{
itemsInGroup.reserve(rbarray->getLength());
for (int i = 0; i < rbarray->getLength(); ++i) {
const Object &ref = rbarray->getNF( i );
if ( ! ref.isRef() ) {
qDebug() << "expected ref, but got:" << ref.getType();
}
OptContentItem *item = ocModel->itemFromRef( QString::number(ref.getRefNum() ) );
itemsInGroup.append( item );
}
for (int i = 0; i < itemsInGroup.size(); ++i) {
OptContentItem *item = itemsInGroup.at(i);
item->appendRBGroup( this );
}
}
RadioButtonGroup::~RadioButtonGroup()
{
}
QSet<OptContentItem *> RadioButtonGroup::setItemOn( OptContentItem *itemToSetOn )
{
QSet<OptContentItem *> changedItems;
for (int i = 0; i < itemsInGroup.size(); ++i) {
OptContentItem *thisItem = itemsInGroup.at(i);
if (thisItem != itemToSetOn) {
QSet<OptContentItem *> newChangedItems;
thisItem->setState(OptContentItem::Off, false /*obeyRadioGroups*/, newChangedItems);
changedItems += newChangedItems;
}
}
return changedItems;
}
OptContentItem::OptContentItem( OptionalContentGroup *group )
{
m_group = group;
m_parent = nullptr;
m_name = UnicodeParsedString( group->getName() );
if ( group->getState() == OptionalContentGroup::On ) {
m_state = OptContentItem::On;
} else {
m_state = OptContentItem::Off;
}
m_stateBackup = m_state;
m_enabled = true;
}
OptContentItem::OptContentItem( const QString &label )
{
m_parent = nullptr;
m_name = label;
m_group = nullptr;
m_state = OptContentItem::HeadingOnly;
m_stateBackup = m_state;
m_enabled = true;
}
OptContentItem::OptContentItem() :
m_parent( nullptr ), m_enabled(true)
{
}
OptContentItem::~OptContentItem()
{
}
void OptContentItem::appendRBGroup( RadioButtonGroup *rbgroup )
{
m_rbGroups.append( rbgroup );
}
void OptContentItem::setState(ItemState state, bool obeyRadioGroups, QSet<OptContentItem *> &changedItems)
{
if (state == m_state)
return;
m_state = state;
m_stateBackup = m_state;
changedItems.insert(this);
QSet<OptContentItem *> empty;
Q_FOREACH (OptContentItem *child, m_children) {
ItemState oldState = child->m_stateBackup;
child->setState(state == OptContentItem::On ? child->m_stateBackup : OptContentItem::Off, true /*obeyRadioGroups*/, empty);
child->m_enabled = state == OptContentItem::On;
child->m_stateBackup = oldState;
}
if (!m_group) {
return;
}
if ( state == OptContentItem::On ) {
m_group->setState( OptionalContentGroup::On );
if (obeyRadioGroups) {
for (int i = 0; i < m_rbGroups.size(); ++i) {
RadioButtonGroup *rbgroup = m_rbGroups.at(i);
changedItems += rbgroup->setItemOn( this );
}
}
} else if ( state == OptContentItem::Off ) {
m_group->setState( OptionalContentGroup::Off );
}
}
void OptContentItem::addChild( OptContentItem *child )
{
m_children += child;
child->setParent( this );
}
QSet<OptContentItem*> OptContentItem::recurseListChildren(bool includeMe) const
{
QSet<OptContentItem*> ret;
if (includeMe) {
ret.insert(const_cast<OptContentItem*>(this));
}
Q_FOREACH (OptContentItem *child, m_children) {
ret += child->recurseListChildren(true);
}
return ret;
}
OptContentModelPrivate::OptContentModelPrivate( OptContentModel *qq, OCGs *optContent )
: q(qq)
{
m_rootNode = new OptContentItem();
const auto &ocgs = optContent->getOCGs();
for (const auto& ocg : ocgs) {
OptContentItem *node = new OptContentItem( ocg.second.get() );
m_optContentItems.insert( QString::number( ocg.first.num ), node );
}
if ( optContent->getOrderArray() == nullptr ) {
// no Order array, so drop them all at the top level
QMapIterator<QString, OptContentItem*> i(m_optContentItems);
while ( i.hasNext() ) {
i.next();
addChild( m_rootNode, i.value() );
}
} else {
parseOrderArray( m_rootNode, optContent->getOrderArray() );
}
parseRBGroupsArray( optContent->getRBGroupsArray() );
}
OptContentModelPrivate::~OptContentModelPrivate()
{
qDeleteAll( m_optContentItems );
qDeleteAll( m_rbgroups );
qDeleteAll( m_headerOptContentItems );
delete m_rootNode;
}
void OptContentModelPrivate::parseOrderArray( OptContentItem *parentNode, Array *orderArray )
{
OptContentItem *lastItem = parentNode;
for (int i = 0; i < orderArray->getLength(); ++i) {
Object orderItem = orderArray->get(i);
if ( orderItem.isDict() ) {
const Object &item = orderArray->getNF(i);
if (item.isRef() ) {
OptContentItem *ocItem = m_optContentItems.value(QString::number(item.getRefNum()));
if (ocItem) {
addChild( parentNode, ocItem );
lastItem = ocItem;
} else {
qDebug() << "could not find group for object" << item.getRefNum();
}
}
} else if ( (orderItem.isArray()) && (orderItem.arrayGetLength() > 0) ) {
parseOrderArray(lastItem, orderItem.getArray());
} else if ( orderItem.isString() ) {
const GooString *label = orderItem.getString();
OptContentItem *header = new OptContentItem ( UnicodeParsedString ( label ) );
m_headerOptContentItems.append( header );
addChild( parentNode, header );
parentNode = header;
lastItem = header;
} else {
qDebug() << "something unexpected";
}
}
}
void OptContentModelPrivate::parseRBGroupsArray( Array *rBGroupArray )
{
if (! rBGroupArray) {
return;
}
// This is an array of array(s)
for (int i = 0; i < rBGroupArray->getLength(); ++i) {
Object rbObj = rBGroupArray->get(i);
if ( ! rbObj.isArray() ) {
qDebug() << "expected inner array, got:" << rbObj.getType();
return;
}
Array *rbarray = rbObj.getArray();
RadioButtonGroup *rbg = new RadioButtonGroup( this, rbarray );
m_rbgroups.append( rbg );
}
}
OptContentModel::OptContentModel( OCGs *optContent, QObject *parent)
: QAbstractItemModel(parent)
{
d = new OptContentModelPrivate( this, optContent );
}
OptContentModel::~OptContentModel()
{
delete d;
}
void OptContentModelPrivate::setRootNode(OptContentItem *node)
{
q->beginResetModel();
delete m_rootNode;
m_rootNode = node;
q->endResetModel();
}
QModelIndex OptContentModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0) {
return QModelIndex();
}
OptContentItem *parentNode = d->nodeFromIndex( parent );
if (row < parentNode->childList().count()) {
return createIndex(row, column, parentNode->childList().at(row));
}
return QModelIndex();
}
QModelIndex OptContentModel::parent(const QModelIndex &child) const
{
OptContentItem *childNode = d->nodeFromIndex( child );
if (!childNode) {
return QModelIndex();
}
return d->indexFromItem(childNode->parent(), child.column());
}
QModelIndex OptContentModelPrivate::indexFromItem(OptContentItem *node, int column) const
{
if (!node) {
return QModelIndex();
}
OptContentItem *parentNode = node->parent();
if (!parentNode) {
return QModelIndex();
}
const int row = parentNode->childList().indexOf(node);
return q->createIndex(row, column, node);
}
int OptContentModel::rowCount(const QModelIndex &parent) const
{
OptContentItem *parentNode = d->nodeFromIndex( parent );
if (!parentNode) {
return 0;
} else {
return parentNode->childList().count();
}
}
int OptContentModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
QVariant OptContentModel::data(const QModelIndex &index, int role) const
{
OptContentItem *node = d->nodeFromIndex(index, true);
if (!node) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return node->name();
break;
case Qt::EditRole:
if (node->state() == OptContentItem::On) {
return true;
} else if (node->state() == OptContentItem::Off) {
return false;
}
break;
case Qt::CheckStateRole:
if (node->state() == OptContentItem::On) {
return Qt::Checked;
} else if (node->state() == OptContentItem::Off) {
return Qt::Unchecked;
}
break;
}
return QVariant();
}
bool OptContentModel::setData ( const QModelIndex & index, const QVariant & value, int role )
{
OptContentItem *node = d->nodeFromIndex(index, true);
if (!node) {
return false;
}
switch (role) {
case Qt::CheckStateRole:
{
const bool newvalue = value.toBool();
QSet<OptContentItem *> changedItems;
node->setState(newvalue ? OptContentItem::On : OptContentItem::Off, true /*obeyRadioGroups*/, changedItems);
if (!changedItems.isEmpty()) {
changedItems += node->recurseListChildren(false);
QModelIndexList indexes;
Q_FOREACH (OptContentItem *item, changedItems) {
indexes.append(d->indexFromItem(item, 0));
}
std::stable_sort(indexes.begin(), indexes.end());
Q_FOREACH (const QModelIndex &changedIndex, indexes) {
emit dataChanged(changedIndex, changedIndex);
}
return true;
}
break;
}
}
return false;
}
Qt::ItemFlags OptContentModel::flags ( const QModelIndex & index ) const
{
OptContentItem *node = d->nodeFromIndex(index);
Qt::ItemFlags itemFlags = Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
if (node->isEnabled()) {
itemFlags |= Qt::ItemIsEnabled;
}
return itemFlags;
}
QVariant OptContentModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
return QAbstractItemModel::headerData( section, orientation, role );
}
void OptContentModel::applyLink( LinkOCGState *link )
{
::LinkOCGState *popplerLinkOCGState = static_cast<LinkOCGStatePrivate*>(link->d_ptr)->popplerLinkOCGState;
QSet<OptContentItem *> changedItems;
const std::vector<::LinkOCGState::StateList*> *statesList = popplerLinkOCGState->getStateList();
for (std::size_t i = 0; i < statesList->size(); ++i) {
::LinkOCGState::StateList *stateList = (*statesList)[i];
std::vector<Ref*> *refsList = stateList->list;
for (std::size_t j = 0; j < refsList->size(); ++j) {
Ref *ref = (*refsList)[j];
OptContentItem *item = d->itemFromRef(QString::number(ref->num));
if (stateList->st == ::LinkOCGState::On) {
item->setState(OptContentItem::On, popplerLinkOCGState->getPreserveRB(), changedItems);
} else if (stateList->st == ::LinkOCGState::Off) {
item->setState(OptContentItem::Off, popplerLinkOCGState->getPreserveRB(), changedItems);
} else {
OptContentItem::ItemState newState = item->state() == OptContentItem::On ? OptContentItem::Off : OptContentItem::On;
item->setState(newState, popplerLinkOCGState->getPreserveRB(), changedItems);
}
}
}
if (!changedItems.isEmpty()) {
QSet<OptContentItem *> aux;
Q_FOREACH (OptContentItem *item, aux) {
changedItems += item->recurseListChildren(false);
}
QModelIndexList indexes;
Q_FOREACH (OptContentItem *item, changedItems) {
indexes.append(d->indexFromItem(item, 0));
}
std::stable_sort(indexes.begin(), indexes.end());
Q_FOREACH (const QModelIndex &changedIndex, indexes) {
emit dataChanged(changedIndex, changedIndex);
}
}
}
void OptContentModelPrivate::addChild( OptContentItem *parent, OptContentItem *child )
{
parent->addChild( child );
}
OptContentItem* OptContentModelPrivate::itemFromRef( const QString &ref ) const
{
return m_optContentItems.value(ref);
}
OptContentItem* OptContentModelPrivate::nodeFromIndex(const QModelIndex &index, bool canBeNull) const
{
if (index.isValid()) {
return static_cast<OptContentItem *>(index.internalPointer());
} else {
return canBeNull ? nullptr : m_rootNode;
}
}
}