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 | |
40 | namespace Poppler { |
41 | RadioButtonGroup::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 | |
57 | RadioButtonGroup::~RadioButtonGroup() { } |
58 | |
59 | QSet<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 | |
72 | OptContentItem::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 | |
86 | OptContentItem::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 | |
96 | OptContentItem::OptContentItem() : m_parent(nullptr), m_enabled(true) { } |
97 | |
98 | OptContentItem::~OptContentItem() { } |
99 | |
100 | void OptContentItem::appendRBGroup(RadioButtonGroup *rbgroup) |
101 | { |
102 | m_rbGroups.append(t: rbgroup); |
103 | } |
104 | |
105 | void 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 | |
136 | void OptContentItem::addChild(OptContentItem *child) |
137 | { |
138 | m_children += child; |
139 | child->setParent(this); |
140 | } |
141 | |
142 | QSet<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 | |
154 | OptContentModelPrivate::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 | |
178 | OptContentModelPrivate::~OptContentModelPrivate() |
179 | { |
180 | qDeleteAll(c: m_optContentItems); |
181 | qDeleteAll(c: m_rbgroups); |
182 | qDeleteAll(c: m_headerOptContentItems); |
183 | delete m_rootNode; |
184 | } |
185 | |
186 | void 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 * = 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 | |
217 | void 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 | |
235 | OptContentModel::OptContentModel(OCGs *optContent, QObject *parent) : QAbstractItemModel(parent) |
236 | { |
237 | d = new OptContentModelPrivate(this, optContent); |
238 | } |
239 | |
240 | OptContentModel::~OptContentModel() |
241 | { |
242 | delete d; |
243 | } |
244 | |
245 | void OptContentModelPrivate::setRootNode(OptContentItem *node) |
246 | { |
247 | q->beginResetModel(); |
248 | delete m_rootNode; |
249 | m_rootNode = node; |
250 | q->endResetModel(); |
251 | } |
252 | |
253 | QModelIndex 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 | |
266 | QModelIndex 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 | |
275 | QModelIndex 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 | |
288 | int 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 | |
298 | int OptContentModel::columnCount(const QModelIndex &parent) const |
299 | { |
300 | return 1; |
301 | } |
302 | |
303 | QVariant 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 | |
333 | bool 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 | |
365 | Qt::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 | |
375 | QVariant OptContentModel::(int section, Qt::Orientation orientation, int role) const |
376 | { |
377 | return QAbstractItemModel::headerData(section, orientation, role); |
378 | } |
379 | |
380 | void 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 | |
420 | void OptContentModelPrivate::addChild(OptContentItem *parent, OptContentItem *child) |
421 | { |
422 | parent->addChild(child); |
423 | } |
424 | |
425 | OptContentItem *OptContentModelPrivate::itemFromRef(const QString &ref) const |
426 | { |
427 | return m_optContentItems.value(key: ref); |
428 | } |
429 | |
430 | OptContentItem *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 | |