1/* poppler-optcontent.cc: qt interface to poppler
2 *
3 * Copyright (C) 2007, Brad Hards <bradh@kde.org>
4 * Copyright (C) 2008, 2014, Pino Toscano <pino@kde.org>
5 * Copyright (C) 2008, Carlos Garcia Campos <carlosgc@gnome.org>
6 * Copyright (C) 2015-2019, Albert Astals Cid <aacid@kde.org>
7 * Copyright (C) 2017, Hubert Figuière <hub@figuiere.net>
8 * 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
9 * Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
10 * Copyright (C) 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2, or (at your option)
15 * any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26
27#include "poppler-optcontent.h"
28
29#include "poppler-optcontent-private.h"
30
31#include "poppler-private.h"
32#include "poppler-link-private.h"
33
34#include <QtCore/QDebug>
35#include <QtCore/QtAlgorithms>
36
37#include "poppler/OptionalContent.h"
38#include "poppler/Link.h"
39
40namespace Poppler {
41RadioButtonGroup::RadioButtonGroup(OptContentModelPrivate *ocModel, Array *rbarray)
42{
43 itemsInGroup.reserve(asize: rbarray->getLength());
44 for (int i = 0; i < rbarray->getLength(); ++i) {
45 const Object &ref = rbarray->getNF(i);
46 if (!ref.isRef()) {
47 qDebug() << "expected ref, but got:" << ref.getType();
48 }
49 OptContentItem *item = ocModel->itemFromRef(ref: QString::number(ref.getRefNum()));
50 itemsInGroup.append(t: item);
51 }
52 for (OptContentItem *item : std::as_const(t&: itemsInGroup)) {
53 item->appendRBGroup(rbgroup: this);
54 }
55}
56
57RadioButtonGroup::~RadioButtonGroup() { }
58
59QSet<OptContentItem *> RadioButtonGroup::setItemOn(OptContentItem *itemToSetOn)
60{
61 QSet<OptContentItem *> changedItems;
62 for (OptContentItem *thisItem : std::as_const(t&: itemsInGroup)) {
63 if (thisItem != itemToSetOn) {
64 QSet<OptContentItem *> newChangedItems;
65 thisItem->setState(state: OptContentItem::Off, obeyRadioGroups: false /*obeyRadioGroups*/, changedItems&: newChangedItems);
66 changedItems += newChangedItems;
67 }
68 }
69 return changedItems;
70}
71
72OptContentItem::OptContentItem(OptionalContentGroup *group)
73{
74 m_group = group;
75 m_parent = nullptr;
76 m_name = UnicodeParsedString(s1: group->getName());
77 if (group->getState() == OptionalContentGroup::On) {
78 m_state = OptContentItem::On;
79 } else {
80 m_state = OptContentItem::Off;
81 }
82 m_stateBackup = m_state;
83 m_enabled = true;
84}
85
86OptContentItem::OptContentItem(const QString &label)
87{
88 m_parent = nullptr;
89 m_name = label;
90 m_group = nullptr;
91 m_state = OptContentItem::HeadingOnly;
92 m_stateBackup = m_state;
93 m_enabled = true;
94}
95
96OptContentItem::OptContentItem() : m_parent(nullptr), m_enabled(true) { }
97
98OptContentItem::~OptContentItem() { }
99
100void OptContentItem::appendRBGroup(RadioButtonGroup *rbgroup)
101{
102 m_rbGroups.append(t: rbgroup);
103}
104
105void OptContentItem::setState(ItemState state, bool obeyRadioGroups, QSet<OptContentItem *> &changedItems)
106{
107 if (state == m_state) {
108 return;
109 }
110
111 m_state = state;
112 m_stateBackup = m_state;
113 changedItems.insert(value: this);
114 QSet<OptContentItem *> empty;
115 Q_FOREACH (OptContentItem *child, m_children) {
116 ItemState oldState = child->m_stateBackup;
117 child->setState(state: state == OptContentItem::On ? child->m_stateBackup : OptContentItem::Off, obeyRadioGroups: true /*obeyRadioGroups*/, changedItems&: empty);
118 child->m_enabled = state == OptContentItem::On;
119 child->m_stateBackup = oldState;
120 }
121 if (!m_group) {
122 return;
123 }
124 if (state == OptContentItem::On) {
125 m_group->setState(OptionalContentGroup::On);
126 if (obeyRadioGroups) {
127 for (RadioButtonGroup *rbgroup : std::as_const(t&: m_rbGroups)) {
128 changedItems += rbgroup->setItemOn(this);
129 }
130 }
131 } else if (state == OptContentItem::Off) {
132 m_group->setState(OptionalContentGroup::Off);
133 }
134}
135
136void OptContentItem::addChild(OptContentItem *child)
137{
138 m_children += child;
139 child->setParent(this);
140}
141
142QSet<OptContentItem *> OptContentItem::recurseListChildren(bool includeMe) const
143{
144 QSet<OptContentItem *> ret;
145 if (includeMe) {
146 ret.insert(value: const_cast<OptContentItem *>(this));
147 }
148 Q_FOREACH (OptContentItem *child, m_children) {
149 ret += child->recurseListChildren(includeMe: true);
150 }
151 return ret;
152}
153
154OptContentModelPrivate::OptContentModelPrivate(OptContentModel *qq, OCGs *optContent) : q(qq)
155{
156 m_rootNode = new OptContentItem();
157 const auto &ocgs = optContent->getOCGs();
158
159 for (const auto &ocg : ocgs) {
160 OptContentItem *node = new OptContentItem(ocg.second.get());
161 m_optContentItems.insert(key: QString::number(ocg.first.num), value: node);
162 }
163
164 if (optContent->getOrderArray() == nullptr) {
165 // no Order array, so drop them all at the top level
166 QMapIterator<QString, OptContentItem *> i(m_optContentItems);
167 while (i.hasNext()) {
168 i.next();
169 addChild(parent: m_rootNode, child: i.value());
170 }
171 } else {
172 parseOrderArray(parentNode: m_rootNode, orderArray: optContent->getOrderArray());
173 }
174
175 parseRBGroupsArray(rBGroupArray: optContent->getRBGroupsArray());
176}
177
178OptContentModelPrivate::~OptContentModelPrivate()
179{
180 qDeleteAll(c: m_optContentItems);
181 qDeleteAll(c: m_rbgroups);
182 qDeleteAll(c: m_headerOptContentItems);
183 delete m_rootNode;
184}
185
186void OptContentModelPrivate::parseOrderArray(OptContentItem *parentNode, Array *orderArray)
187{
188 OptContentItem *lastItem = parentNode;
189 for (int i = 0; i < orderArray->getLength(); ++i) {
190 Object orderItem = orderArray->get(i);
191 if (orderItem.isDict()) {
192 const Object &item = orderArray->getNF(i);
193 if (item.isRef()) {
194 OptContentItem *ocItem = m_optContentItems.value(key: QString::number(item.getRefNum()));
195 if (ocItem) {
196 addChild(parent: parentNode, child: ocItem);
197 lastItem = ocItem;
198 } else {
199 qDebug() << "could not find group for object" << item.getRefNum();
200 }
201 }
202 } else if ((orderItem.isArray()) && (orderItem.arrayGetLength() > 0)) {
203 parseOrderArray(parentNode: lastItem, orderArray: orderItem.getArray());
204 } else if (orderItem.isString()) {
205 const GooString *label = orderItem.getString();
206 OptContentItem *header = new OptContentItem(UnicodeParsedString(s1: label));
207 m_headerOptContentItems.append(t: header);
208 addChild(parent: parentNode, child: header);
209 parentNode = header;
210 lastItem = header;
211 } else {
212 qDebug() << "something unexpected";
213 }
214 }
215}
216
217void OptContentModelPrivate::parseRBGroupsArray(Array *rBGroupArray)
218{
219 if (!rBGroupArray) {
220 return;
221 }
222 // This is an array of array(s)
223 for (int i = 0; i < rBGroupArray->getLength(); ++i) {
224 Object rbObj = rBGroupArray->get(i);
225 if (!rbObj.isArray()) {
226 qDebug() << "expected inner array, got:" << rbObj.getType();
227 return;
228 }
229 Array *rbarray = rbObj.getArray();
230 RadioButtonGroup *rbg = new RadioButtonGroup(this, rbarray);
231 m_rbgroups.append(t: rbg);
232 }
233}
234
235OptContentModel::OptContentModel(OCGs *optContent, QObject *parent) : QAbstractItemModel(parent)
236{
237 d = new OptContentModelPrivate(this, optContent);
238}
239
240OptContentModel::~OptContentModel()
241{
242 delete d;
243}
244
245void OptContentModelPrivate::setRootNode(OptContentItem *node)
246{
247 q->beginResetModel();
248 delete m_rootNode;
249 m_rootNode = node;
250 q->endResetModel();
251}
252
253QModelIndex OptContentModel::index(int row, int column, const QModelIndex &parent) const
254{
255 if (row < 0 || column != 0) {
256 return QModelIndex();
257 }
258
259 OptContentItem *parentNode = d->nodeFromIndex(index: parent);
260 if (row < parentNode->childList().count()) {
261 return createIndex(arow: row, acolumn: column, adata: parentNode->childList().at(i: row));
262 }
263 return QModelIndex();
264}
265
266QModelIndex OptContentModel::parent(const QModelIndex &child) const
267{
268 OptContentItem *childNode = d->nodeFromIndex(index: child);
269 if (!childNode) {
270 return QModelIndex();
271 }
272 return d->indexFromItem(node: childNode->parent(), column: child.column());
273}
274
275QModelIndex OptContentModelPrivate::indexFromItem(OptContentItem *node, int column) const
276{
277 if (!node) {
278 return QModelIndex();
279 }
280 OptContentItem *parentNode = node->parent();
281 if (!parentNode) {
282 return QModelIndex();
283 }
284 const int row = parentNode->childList().indexOf(t: node);
285 return q->createIndex(arow: row, acolumn: column, adata: node);
286}
287
288int OptContentModel::rowCount(const QModelIndex &parent) const
289{
290 OptContentItem *parentNode = d->nodeFromIndex(index: parent);
291 if (!parentNode) {
292 return 0;
293 } else {
294 return parentNode->childList().count();
295 }
296}
297
298int OptContentModel::columnCount(const QModelIndex &parent) const
299{
300 return 1;
301}
302
303QVariant OptContentModel::data(const QModelIndex &index, int role) const
304{
305 OptContentItem *node = d->nodeFromIndex(index, canBeNull: true);
306 if (!node) {
307 return QVariant();
308 }
309
310 switch (role) {
311 case Qt::DisplayRole:
312 return node->name();
313 break;
314 case Qt::EditRole:
315 if (node->state() == OptContentItem::On) {
316 return true;
317 } else if (node->state() == OptContentItem::Off) {
318 return false;
319 }
320 break;
321 case Qt::CheckStateRole:
322 if (node->state() == OptContentItem::On) {
323 return Qt::Checked;
324 } else if (node->state() == OptContentItem::Off) {
325 return Qt::Unchecked;
326 }
327 break;
328 }
329
330 return QVariant();
331}
332
333bool OptContentModel::setData(const QModelIndex &index, const QVariant &value, int role)
334{
335 OptContentItem *node = d->nodeFromIndex(index, canBeNull: true);
336 if (!node) {
337 return false;
338 }
339
340 switch (role) {
341 case Qt::CheckStateRole: {
342 const bool newvalue = value.toBool();
343 QSet<OptContentItem *> changedItems;
344 node->setState(state: newvalue ? OptContentItem::On : OptContentItem::Off, obeyRadioGroups: true /*obeyRadioGroups*/, changedItems);
345
346 if (!changedItems.isEmpty()) {
347 changedItems += node->recurseListChildren(includeMe: false);
348 QModelIndexList indexes;
349 Q_FOREACH (OptContentItem *item, changedItems) {
350 indexes.append(t: d->indexFromItem(node: item, column: 0));
351 }
352 std::stable_sort(first: indexes.begin(), last: indexes.end());
353 Q_FOREACH (const QModelIndex &changedIndex, indexes) {
354 emit dataChanged(topLeft: changedIndex, bottomRight: changedIndex);
355 }
356 return true;
357 }
358 break;
359 }
360 }
361
362 return false;
363}
364
365Qt::ItemFlags OptContentModel::flags(const QModelIndex &index) const
366{
367 OptContentItem *node = d->nodeFromIndex(index);
368 Qt::ItemFlags itemFlags = Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
369 if (node->isEnabled()) {
370 itemFlags |= Qt::ItemIsEnabled;
371 }
372 return itemFlags;
373}
374
375QVariant OptContentModel::headerData(int section, Qt::Orientation orientation, int role) const
376{
377 return QAbstractItemModel::headerData(section, orientation, role);
378}
379
380void OptContentModel::applyLink(LinkOCGState *link)
381{
382 LinkOCGStatePrivate *linkPrivate = link->d_func();
383
384 QSet<OptContentItem *> changedItems;
385
386 const std::vector<::LinkOCGState::StateList> &statesList = linkPrivate->stateList;
387 for (const ::LinkOCGState::StateList &stateList : statesList) {
388 const std::vector<Ref> &refsList = stateList.list;
389 for (const Ref &ref : refsList) {
390 OptContentItem *item = d->itemFromRef(ref: QString::number(ref.num));
391
392 if (stateList.st == ::LinkOCGState::On) {
393 item->setState(state: OptContentItem::On, obeyRadioGroups: linkPrivate->preserveRB, changedItems);
394 } else if (stateList.st == ::LinkOCGState::Off) {
395 item->setState(state: OptContentItem::Off, obeyRadioGroups: linkPrivate->preserveRB, changedItems);
396 } else {
397 OptContentItem::ItemState newState = item->state() == OptContentItem::On ? OptContentItem::Off : OptContentItem::On;
398 item->setState(state: newState, obeyRadioGroups: linkPrivate->preserveRB, changedItems);
399 }
400 }
401 }
402
403 if (!changedItems.isEmpty()) {
404 QSet<OptContentItem *> aux;
405 Q_FOREACH (OptContentItem *item, aux) {
406 changedItems += item->recurseListChildren(includeMe: false);
407 }
408
409 QModelIndexList indexes;
410 Q_FOREACH (OptContentItem *item, changedItems) {
411 indexes.append(t: d->indexFromItem(node: item, column: 0));
412 }
413 std::stable_sort(first: indexes.begin(), last: indexes.end());
414 Q_FOREACH (const QModelIndex &changedIndex, indexes) {
415 emit dataChanged(topLeft: changedIndex, bottomRight: changedIndex);
416 }
417 }
418}
419
420void OptContentModelPrivate::addChild(OptContentItem *parent, OptContentItem *child)
421{
422 parent->addChild(child);
423}
424
425OptContentItem *OptContentModelPrivate::itemFromRef(const QString &ref) const
426{
427 return m_optContentItems.value(key: ref);
428}
429
430OptContentItem *OptContentModelPrivate::nodeFromIndex(const QModelIndex &index, bool canBeNull) const
431{
432 if (index.isValid()) {
433 return static_cast<OptContentItem *>(index.internalPointer());
434 } else {
435 return canBeNull ? nullptr : m_rootNode;
436 }
437}
438}
439

source code of poppler/qt6/src/poppler-optcontent.cc