Optional content activity settings

This commit is contained in:
Jakub Melka
2019-07-02 16:20:12 +02:00
parent d4ef618c5d
commit e04222fb0b
7 changed files with 287 additions and 5 deletions

View File

@ -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:
{
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<const PDFOptionalContentTreeItem*>(index.internalPointer());
if (item->getReference() != PDFObjectReference() && !item->isLocked())
{
Qt::CheckState newState = static_cast<Qt::CheckState>(value.toInt());
m_activity->setState(item->getReference(), (newState == Qt::Checked) ? OCState::ON : OCState::OFF);
return true;
}
}
return false;
}
} // namespace pdf

View File

@ -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

View File

@ -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:

View File

@ -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<PDFObjectReference>& 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

View File

@ -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<PDFObjectReference, OCState> 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<PDFObjectReference> m_allOptionalContentGroups;
PDFOptionalContentConfiguration m_defaultConfiguration;

View File

@ -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())

View File

@ -87,6 +87,7 @@ private:
QDockWidget* m_optionalContentDockWidget;
QTreeView* m_optionalContentTreeView;
pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel;
pdf::PDFOptionalContentActivity* m_optionalContentActivity;
};
} // namespace pdfviewer