mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Optional content activity settings
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
|
@ -87,6 +87,7 @@ private:
|
||||
QDockWidget* m_optionalContentDockWidget;
|
||||
QTreeView* m_optionalContentTreeView;
|
||||
pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel;
|
||||
pdf::PDFOptionalContentActivity* m_optionalContentActivity;
|
||||
};
|
||||
|
||||
} // namespace pdfviewer
|
||||
|
Reference in New Issue
Block a user