From e04222fb0b652969766e5b14b0e133cd1940f77e Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Tue, 2 Jul 2019 16:20:12 +0200 Subject: [PATCH] Optional content activity settings --- PdfForQtLib/sources/pdfitemmodels.cpp | 54 +++++++- PdfForQtLib/sources/pdfitemmodels.h | 10 +- PdfForQtLib/sources/pdfobject.h | 3 +- PdfForQtLib/sources/pdfoptionalcontent.cpp | 151 +++++++++++++++++++++ PdfForQtLib/sources/pdfoptionalcontent.h | 55 ++++++++ PdfForQtViewer/pdfviewermainwindow.cpp | 18 ++- PdfForQtViewer/pdfviewermainwindow.h | 1 + 7 files changed, 287 insertions(+), 5 deletions(-) diff --git a/PdfForQtLib/sources/pdfitemmodels.cpp b/PdfForQtLib/sources/pdfitemmodels.cpp index 513ec94..0d3d519 100644 --- a/PdfForQtLib/sources/pdfitemmodels.cpp +++ b/PdfForQtLib/sources/pdfitemmodels.cpp @@ -111,6 +111,11 @@ Qt::ItemFlags PDFTreeItemModel::flags(const QModelIndex& index) const } +void PDFOptionalContentTreeItemModel::setActivity(PDFOptionalContentActivity* activity) +{ + m_activity = activity; +} + int PDFOptionalContentTreeItemModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); @@ -131,7 +136,33 @@ QVariant PDFOptionalContentTreeItemModel::data(const QModelIndex& index, int rol return item->getText(); case Qt::CheckStateRole: - return Qt::Checked; + { + if (item->getReference() != PDFObjectReference()) + { + if (m_activity) + { + switch (m_activity->getState(item->getReference())) + { + case OCState::ON: + return Qt::Checked; + case OCState::OFF: + return Qt::Unchecked; + case OCState::Unknown: + return Qt::PartiallyChecked; + + default: + { + Q_ASSERT(false); + break; + } + } + } + + return Qt::PartiallyChecked; + } + + break; + } default: break; @@ -272,4 +303,25 @@ QString PDFOptionalContentTreeItem::getText() const return QString(); } +bool PDFOptionalContentTreeItemModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid()) + { + return false; + } + + if (m_activity && role == Qt::CheckStateRole) + { + const PDFOptionalContentTreeItem* item = static_cast(index.internalPointer()); + if (item->getReference() != PDFObjectReference() && !item->isLocked()) + { + Qt::CheckState newState = static_cast(value.toInt()); + m_activity->setState(item->getReference(), (newState == Qt::Checked) ? OCState::ON : OCState::OFF); + return true; + } + } + + return false; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfitemmodels.h b/PdfForQtLib/sources/pdfitemmodels.h index f4bc239..904ac21 100644 --- a/PdfForQtLib/sources/pdfitemmodels.h +++ b/PdfForQtLib/sources/pdfitemmodels.h @@ -26,6 +26,7 @@ namespace pdf { class PDFDocument; +class PDFOptionalContentActivity; /// Represents tree item in the GUI tree class PDFTreeItem @@ -107,15 +108,22 @@ class PDFFORQTLIBSHARED_EXPORT PDFOptionalContentTreeItemModel : public PDFTreeI { public: inline explicit PDFOptionalContentTreeItemModel(QObject* parent) : - PDFTreeItemModel(parent) + PDFTreeItemModel(parent), + m_activity(nullptr) { } + void setActivity(PDFOptionalContentActivity* activity); + virtual int columnCount(const QModelIndex& parent) const override; virtual QVariant data(const QModelIndex& index, int role) const override; virtual void update() override; virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; + +private: + PDFOptionalContentActivity* m_activity; }; } // namespace pdf diff --git a/PdfForQtLib/sources/pdfobject.h b/PdfForQtLib/sources/pdfobject.h index 22d81ea..86adcab 100644 --- a/PdfForQtLib/sources/pdfobject.h +++ b/PdfForQtLib/sources/pdfobject.h @@ -167,8 +167,7 @@ private: }; /// Represents raw string in the PDF file. No conversions are performed, this is -/// reason, that we do not use QString, but QByteArray instead. QString is -/// encoded int UTF-8. +/// reason, that we do not use QString, but QByteArray instead. class PDFString : public PDFObjectContent { public: diff --git a/PdfForQtLib/sources/pdfoptionalcontent.cpp b/PdfForQtLib/sources/pdfoptionalcontent.cpp index 25d1715..4ab8dd9 100644 --- a/PdfForQtLib/sources/pdfoptionalcontent.cpp +++ b/PdfForQtLib/sources/pdfoptionalcontent.cpp @@ -170,6 +170,24 @@ PDFOptionalContentConfiguration PDFOptionalContentConfiguration::create(const PD return configuration; } +OCUsage PDFOptionalContentConfiguration::getUsageFromName(const QByteArray& name) +{ + if (name == "View") + { + return OCUsage::View; + } + else if (name == "Print") + { + return OCUsage::Print; + } + else if (name == "Export") + { + return OCUsage::Export; + } + + return OCUsage::Invalid; +} + PDFOptionalContentConfiguration::UsageApplication PDFOptionalContentConfiguration::createUsageApplication(const PDFDocument* document, const PDFObject& object) { UsageApplication result; @@ -274,4 +292,137 @@ PDFOptionalContentGroup PDFOptionalContentGroup::create(const PDFDocument* docum return result; } +OCState PDFOptionalContentGroup::getUsageState(OCUsage usage) const +{ + switch (usage) + { + case OCUsage::View: + return getUsageViewState(); + + case OCUsage::Print: + return getUsagePrintState(); + + case OCUsage::Export: + return getUsageExportState(); + + case OCUsage::Invalid: + break; + + default: + break; + } + + return OCState::Unknown; +} + +PDFOptionalContentActivity::PDFOptionalContentActivity(const PDFDocument* document, OCUsage usage, QObject* parent) : + QObject(parent), + m_document(document), + m_properties(document->getCatalog()->getOptionalContentProperties()), + m_usage(usage) +{ + if (m_properties->isValid()) + { + for (const PDFObjectReference& reference : m_properties->getAllOptionalContentGroups()) + { + m_states[reference] = OCState::Unknown; + } + + applyConfiguration(m_properties->getDefaultConfiguration()); + } +} + +OCState PDFOptionalContentActivity::getState(PDFObjectReference ocg) const +{ + auto it = m_states.find(ocg); + if (it != m_states.cend()) + { + return it->second; + } + + return OCState::Unknown; +} + +void PDFOptionalContentActivity::setState(PDFObjectReference ocg, OCState state) +{ + auto it = m_states.find(ocg); + if (it != m_states.cend() && it->second != state) + { + // We are changing the state. If new state is ON, then we must check radio button groups. + if (state == OCState::ON) + { + for (const std::vector& radioButtonGroup : m_properties->getDefaultConfiguration().getRadioButtonGroups()) + { + if (std::find(radioButtonGroup.cbegin(), radioButtonGroup.cend(), ocg) != radioButtonGroup.cend()) + { + // We must set all states of this radio button group to OFF + for (const PDFObjectReference& ocgRadioButtonGroup : radioButtonGroup) + { + setState(ocgRadioButtonGroup, OCState::OFF); + } + } + } + } + + it->second = state; + emit optionalContentGroupStateChanged(ocg, state); + } +} + +void PDFOptionalContentActivity::applyConfiguration(const PDFOptionalContentConfiguration& configuration) +{ + // Step 1: Apply base state to all states + if (configuration.getBaseState() != PDFOptionalContentConfiguration::BaseState::Unchanged) + { + const OCState newState = (configuration.getBaseState() == PDFOptionalContentConfiguration::BaseState::ON) ? OCState::ON : OCState::OFF; + for (auto& item : m_states) + { + item.second = newState; + } + } + + auto setOCGState = [this](PDFObjectReference ocg, OCState state) + { + auto it = m_states.find(ocg); + if (it != m_states.cend()) + { + it->second = state; + } + }; + + // Step 2: Process 'ON' entry + for (PDFObjectReference ocg : configuration.getOnArray()) + { + setOCGState(ocg, OCState::ON); + } + + // Step 3: Process 'OFF' entry + for (PDFObjectReference ocg : configuration.getOffArray()) + { + setOCGState(ocg, OCState::OFF); + } + + // Step 4: Apply usage + for (const PDFOptionalContentConfiguration::UsageApplication& usageApplication : configuration.getUsageApplications()) + { + // We will use usage from the events name. We ignore category, as it should duplicate the events name. + const OCUsage usage = PDFOptionalContentConfiguration::getUsageFromName(usageApplication.event); + + if (usage == m_usage) + { + for (PDFObjectReference ocg : usageApplication.optionalContengGroups) + { + if (!m_properties->hasOptionalContentGroup(ocg)) + { + continue; + } + + const PDFOptionalContentGroup& optionalContentGroup = m_properties->getOptionalContentGroup(ocg); + const OCState newState = optionalContentGroup.getUsageState(usage); + setOCGState(ocg, newState); + } + } + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfoptionalcontent.h b/PdfForQtLib/sources/pdfoptionalcontent.h index dd63598..503e6ae 100644 --- a/PdfForQtLib/sources/pdfoptionalcontent.h +++ b/PdfForQtLib/sources/pdfoptionalcontent.h @@ -25,6 +25,8 @@ namespace pdf { class PDFDocument; +class PDFOptionalContentProperties; +class PDFOptionalContentConfiguration; /// State of the optional content group, or result of expression enum class OCState @@ -34,6 +36,15 @@ enum class OCState Unknown }; +/// Type of optional content usage +enum class OCUsage +{ + View, + Print, + Export, + Invalid +}; + constexpr OCState operator &(OCState left, OCState right) { if (left == OCState::Unknown) @@ -62,6 +73,42 @@ constexpr OCState operator |(OCState left, OCState right) return (left == OCState::ON || right == OCState::ON) ? OCState::ON : OCState::OFF; } +/// Activeness of the optional content +class PDFFORQTLIBSHARED_EXPORT PDFOptionalContentActivity : public QObject +{ + Q_OBJECT + +public: + explicit PDFOptionalContentActivity(const PDFDocument* document, OCUsage usage, QObject* parent); + + /// Gets the optional content groups state. If optional content group doesn't exist, + /// then it returns Unknown state. + /// \param ocg Optional conteng group + OCState getState(PDFObjectReference ocg) const; + + /// Sets the state of optional content group. If optional content group doesn't exist, + /// then nothing happens. If optional content group is contained in radio button group, then + /// all other optional content groups in the group are switched off, if we are + /// switching this one to ON state. If we are switching it off, then nothing happens (as all + /// optional content groups in radio button group can be switched off). + /// \param ocg Optional content group + /// \param state New state of the optional content group + /// \note If something changed, then signalp \p optionalContentGroupStateChanged is emitted. + void setState(PDFObjectReference ocg, OCState state); + + /// Applies configuration to the current state of optional content groups + void applyConfiguration(const PDFOptionalContentConfiguration& configuration); + +signals: + void optionalContentGroupStateChanged(PDFObjectReference ocg, OCState state); + +private: + const PDFDocument* m_document; + const PDFOptionalContentProperties* m_properties; + OCUsage m_usage; + std::map m_states; +}; + /// Configuration of optional content configuration. class PDFOptionalContentConfiguration { @@ -93,6 +140,10 @@ public: /// \param object Object containing documents optional content configuration static PDFOptionalContentConfiguration create(const PDFDocument* document, const PDFObject& object); + /// Converts usage name to the enum. If value can't be converted, then Invalid usage is returned. + /// \param name Name of the usage + static OCUsage getUsageFromName(const QByteArray& name); + const QString& getName() const { return m_name; } const QString& getCreator() const { return m_creator; } BaseState getBaseState() const { return m_baseState; } @@ -147,6 +198,7 @@ public: OCState getUsagePrintState() const { return m_usagePrintState; } OCState getUsageViewState() const { return m_usageViewState; } OCState getUsageExportState() const { return m_usageExportState; } + OCState getUsageState(OCUsage usage) const; private: PDFObjectReference m_reference; @@ -181,6 +233,9 @@ public: const PDFOptionalContentConfiguration& getDefaultConfiguration() const { return m_defaultConfiguration; } const PDFOptionalContentGroup& getOptionalContentGroup(PDFObjectReference reference) const { return m_optionalContentGroups.at(reference); } + /// Returns true, if optional content group exists + bool hasOptionalContentGroup(PDFObjectReference reference) const { return m_optionalContentGroups.count(reference); } + private: std::vector m_allOptionalContentGroups; PDFOptionalContentConfiguration m_defaultConfiguration; diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 8cd6a55..de717fb 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -48,7 +48,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget *parent) : m_pdfWidget(nullptr), m_optionalContentDockWidget(nullptr), m_optionalContentTreeView(nullptr), - m_optionalContentTreeModel(nullptr) + m_optionalContentTreeModel(nullptr), + m_optionalContentActivity(nullptr) { ui->setupUi(this); @@ -94,6 +95,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget *parent) : m_optionalContentTreeView->setModel(m_optionalContentTreeModel); m_optionalContentDockWidget->setWidget(m_optionalContentTreeView); addDockWidget(Qt::LeftDockWidgetArea, m_optionalContentDockWidget); + m_optionalContentDockWidget->hide(); ui->menuView->addSeparator(); ui->menuView->addAction(m_optionalContentDockWidget->toggleViewAction()); @@ -254,8 +256,22 @@ void PDFViewerMainWindow::openDocument(const QString& fileName) void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document) { + if (m_optionalContentActivity) + { + // We use deleteLater, because we want to avoid consistency problem with model + // (we set document to the model before activity). + m_optionalContentActivity->deleteLater(); + m_optionalContentActivity = nullptr; + } + + if (document) + { + m_optionalContentActivity = new pdf::PDFOptionalContentActivity(document, pdf::OCUsage::View, this); + } + m_pdfWidget->setDocument(document); m_optionalContentTreeModel->setDocument(document); + m_optionalContentTreeModel->setActivity(m_optionalContentActivity); m_optionalContentTreeView->expandAll(); if (m_optionalContentTreeModel->isEmpty()) diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index 2f0baf3..4399be4 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -87,6 +87,7 @@ private: QDockWidget* m_optionalContentDockWidget; QTreeView* m_optionalContentTreeView; pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel; + pdf::PDFOptionalContentActivity* m_optionalContentActivity; }; } // namespace pdfviewer