| /* 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, 2020 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 (OptContentItem *item : std::as_const(itemsInGroup)) { |
| item->appendRBGroup(this); |
| } |
| } |
| |
| RadioButtonGroup::~RadioButtonGroup() { } |
| |
| QSet<OptContentItem *> RadioButtonGroup::setItemOn(OptContentItem *itemToSetOn) |
| { |
| QSet<OptContentItem *> changedItems; |
| for (OptContentItem *thisItem : std::as_const(itemsInGroup)) { |
| 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 (RadioButtonGroup *rbgroup : std::as_const(m_rbGroups)) { |
| 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) |
| { |
| LinkOCGStatePrivate *linkPrivate = link->d_func(); |
| |
| QSet<OptContentItem *> changedItems; |
| |
| const std::vector<::LinkOCGState::StateList> &statesList = linkPrivate->stateList; |
| for (const ::LinkOCGState::StateList &stateList : statesList) { |
| const std::vector<Ref> &refsList = stateList.list; |
| for (const Ref &ref : refsList) { |
| OptContentItem *item = d->itemFromRef(QString::number(ref.num)); |
| |
| if (stateList.st == ::LinkOCGState::On) { |
| item->setState(OptContentItem::On, linkPrivate->preserveRB, changedItems); |
| } else if (stateList.st == ::LinkOCGState::Off) { |
| item->setState(OptContentItem::Off, linkPrivate->preserveRB, changedItems); |
| } else { |
| OptContentItem::ItemState newState = item->state() == OptContentItem::On ? OptContentItem::Off : OptContentItem::On; |
| item->setState(newState, linkPrivate->preserveRB, 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; |
| } |
| } |
| } |