From 112ba7beb9e6e9b267538f2b907b85771c636e27 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 28 Nov 2020 17:50:14 +0100 Subject: [PATCH] Mapper (continuation) --- PdfForQtLib/PdfForQtLib.pro | 1 + PdfForQtLib/sources/pdfobjecteditormodel.cpp | 29 +- PdfForQtLib/sources/pdfobjecteditormodel.h | 7 + PdfForQtLib/sources/pdfobjecteditorwidget.cpp | 321 +++++++++++++++++- .../sources/pdfobjecteditorwidget_impl.h | 173 ++++++++++ 5 files changed, 509 insertions(+), 22 deletions(-) create mode 100644 PdfForQtLib/sources/pdfobjecteditorwidget_impl.h diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index f76cf84..9d1fd5e 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -122,6 +122,7 @@ HEADERS += \ sources/pdfobject.h \ sources/pdfobjecteditormodel.h \ sources/pdfobjecteditorwidget.h \ + sources/pdfobjecteditorwidget_impl.h \ sources/pdfobjectutils.h \ sources/pdfoptimizer.h \ sources/pdfoptionalcontent.h \ diff --git a/PdfForQtLib/sources/pdfobjecteditormodel.cpp b/PdfForQtLib/sources/pdfobjecteditormodel.cpp index a868efd..e60b875 100644 --- a/PdfForQtLib/sources/pdfobjecteditormodel.cpp +++ b/PdfForQtLib/sources/pdfobjecteditormodel.cpp @@ -40,6 +40,11 @@ size_t PDFObjectEditorAbstractModel::getAttributeCount() const return m_attributes.size(); } +ObjectEditorAttributeType PDFObjectEditorAbstractModel::getAttributeType(size_t index) const +{ + return m_attributes.at(index).type; +} + const QString& PDFObjectEditorAbstractModel::getAttributeCategory(size_t index) const { return m_attributes.at(index).category; @@ -55,6 +60,11 @@ const QString& PDFObjectEditorAbstractModel::getAttributeName(size_t index) cons return m_attributes.at(index).name; } +const PDFObjectEditorModelAttributeEnumItems& PDFObjectEditorAbstractModel::getAttributeEnumItems(size_t index) const +{ + return m_attributes.at(index).enumItems; +} + bool PDFObjectEditorAbstractModel::queryAttribute(size_t index, Question question) const { const PDFObjectEditorModelAttribute& attribute = m_attributes.at(index); @@ -87,7 +97,9 @@ bool PDFObjectEditorAbstractModel::queryAttribute(size_t index, Question questio return true; } - s + + case Question::IsAttributeEditable: + return queryAttribute(index, Question::HasAttribute) && !attribute.attributeFlags.testFlag(PDFObjectEditorModelAttribute::Readonly); default: break; @@ -96,6 +108,11 @@ bool PDFObjectEditorAbstractModel::queryAttribute(size_t index, Question questio return false; } +bool PDFObjectEditorAbstractModel::getSelectorValue(size_t index) const +{ + return m_attributes.at(index).selectorAttributeValue; +} + PDFObject PDFObjectEditorAbstractModel::getValue(size_t index) const { const QByteArrayList& dictionaryAttribute = m_attributes.at(index).dictionaryAttribute; @@ -112,7 +129,7 @@ PDFObject PDFObjectEditorAbstractModel::getValue(size_t index) const for (int i = 0; i < pathDepth; ++i) { - dictionary = m_storage->getDictionaryFromObject(dictionaryAttribute[i]); + dictionary = m_storage->getDictionaryFromObject(dictionary->get(dictionaryAttribute[i])); if (!dictionary) { return PDFObject(); @@ -141,9 +158,11 @@ size_t PDFObjectEditorAbstractModel::createAttribute(ObjectEditorAttributeType t { size_t index = m_attributes.size(); + QByteArrayList attributes; + attributes.push_back(attributeName); PDFObjectEditorModelAttribute attribute; attribute.type = type; - attribute.dictionaryAttribute = QByteArrayList(qMove(attributeName)); + attribute.dictionaryAttribute = attributes; attribute.category = qMove(category); attribute.subcategory = qMove(subcategory); attribute.name = qMove(name); @@ -162,14 +181,14 @@ size_t PDFObjectEditorAbstractModel::createAttribute(ObjectEditorAttributeType t size_t PDFObjectEditorAbstractModel::createSelectorAttribute(QString category, QString subcategory, QString name) { - return createAttribute(ObjectEditorAttributeType::Selector, QString(), qMove(category), qMove(subcategory), qMove(name)); + return createAttribute(ObjectEditorAttributeType::Selector, QByteArray(), qMove(category), qMove(subcategory), qMove(name)); } uint32_t PDFObjectEditorAbstractModel::getCurrentTypeFlags() const { PDFObject value = getValue(m_typeAttribute); - for (const PDFObjectEditorModelAttributeEnumItem& item : m_attributes.at(index).enumItems) + for (const PDFObjectEditorModelAttributeEnumItem& item : m_attributes.at(m_typeAttribute).enumItems) { if (item.value == value) { diff --git a/PdfForQtLib/sources/pdfobjecteditormodel.h b/PdfForQtLib/sources/pdfobjecteditormodel.h index dca4512..8b50855 100644 --- a/PdfForQtLib/sources/pdfobjecteditormodel.h +++ b/PdfForQtLib/sources/pdfobjecteditormodel.h @@ -103,6 +103,9 @@ struct PDFObjectEditorModelAttribute /// Enum items PDFObjectEditorModelAttributeEnumItems enumItems; + + /// Value for selector attribute + bool selectorAttributeValue = false; }; class PDFFORQTLIBSHARED_EXPORT PDFObjectEditorAbstractModel : public QObject @@ -117,9 +120,11 @@ public: virtual ~PDFObjectEditorAbstractModel(); size_t getAttributeCount() const; + ObjectEditorAttributeType getAttributeType(size_t index) const; const QString& getAttributeCategory(size_t index) const; const QString& getAttributeSubcategory(size_t index) const; const QString& getAttributeName(size_t index) const; + const PDFObjectEditorModelAttributeEnumItems& getAttributeEnumItems(size_t index) const; enum class Question { @@ -134,6 +139,8 @@ public: PDFObject getValue(size_t index) const; PDFObject getDefaultValue(size_t index) const; + const PDFObjectStorage* getStorage() const { return m_storage; } + protected: size_t createAttribute(ObjectEditorAttributeType type, QByteArray attributeName, diff --git a/PdfForQtLib/sources/pdfobjecteditorwidget.cpp b/PdfForQtLib/sources/pdfobjecteditorwidget.cpp index cb111b9..1df3760 100644 --- a/PdfForQtLib/sources/pdfobjecteditorwidget.cpp +++ b/PdfForQtLib/sources/pdfobjecteditorwidget.cpp @@ -16,30 +16,23 @@ // along with PDFForQt. If not, see . #include "pdfobjecteditorwidget.h" +#include "pdfobjecteditorwidget_impl.h" +#include "pdfdocumentbuilder.h" #include #include #include +#include +#include +#include +#include +#include +#include +#include namespace pdf { -class PDFObjectEditorWidgetMapper : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - explicit PDFObjectEditorWidgetMapper(PDFObjectEditorAbstractModel* model, QObject* parent); - - void initialize(); - -private: - PDFObjectEditorAbstractModel* m_model; -}; - PDFObjectEditorWidget::PDFObjectEditorWidget(PDFObjectEditorAbstractModel* model, QWidget* parent) : BaseClass(parent), m_mapper(nullptr), @@ -50,6 +43,7 @@ PDFObjectEditorWidget::PDFObjectEditorWidget(PDFObjectEditorAbstractModel* model layout->addWidget(m_tabWidget); m_mapper = new PDFObjectEditorWidgetMapper(model, this); + m_mapper->initialize(m_tabWidget); } PDFObjectEditorWidgetMapper::PDFObjectEditorWidgetMapper(PDFObjectEditorAbstractModel* model, QObject* parent) : @@ -59,10 +53,303 @@ PDFObjectEditorWidgetMapper::PDFObjectEditorWidgetMapper(PDFObjectEditorAbstract } -void PDFObjectEditorWidgetMapper::initialize() +void PDFObjectEditorWidgetMapper::initialize(QTabWidget* tabWidget) { size_t attributeCount = m_model->getAttributeCount(); + + for (size_t i = 0; i < attributeCount; ++i) + { + // Unmapped attributes are ignored + if (!m_model->queryAttribute(i, PDFObjectEditorAbstractModel::Question::IsMapped)) + { + continue; + } + + QString categoryName = m_model->getAttributeCategory(i); + QString subcategoryName = m_model->getAttributeSubcategory(i); + + Category* category = getOrCreateCategory(categoryName); + Subcategory* subcategory = category->getOrCreateSubcategory(subcategoryName); + subcategory->attributes.push_back(i); + } + + // Create GUI + for (Category& category : m_categories) + { + category.page = new QWidget(tabWidget); + tabWidget->addTab(category.page, category.name); + category.page->setLayout(new QVBoxLayout()); + + // Create subcategory GUI + for (Subcategory& subcategory : category.subcategories) + { + QGroupBox* groupBox = new QGroupBox(category.page); + category.page->layout()->addWidget(groupBox); + + QGridLayout* layout = new QGridLayout(); + groupBox->setLayout(layout); + + for (size_t attribute : subcategory.attributes) + { + createMappedAdapter(groupBox, layout, attribute); + } + } + + category.page->layout()->addItem(new QSpacerItem(0, 0)); + } +} + +void PDFObjectEditorWidgetMapper::createMappedAdapter(QGroupBox* groupBox, QGridLayout* layout, size_t attribute) +{ + auto setAdapter = [this, attribute](PDFObjectEditorMappedWidgetAdapter* adapter) + { + Q_ASSERT(!m_adapters.count(attribute)); + m_adapters[attribute] = adapter; + }; + + ObjectEditorAttributeType type = m_model->getAttributeType(attribute); + switch (type) + { + case ObjectEditorAttributeType::Type: + case ObjectEditorAttributeType::ComboBox: + { + int row = layout->rowCount(); + + QLabel* label = new QLabel(groupBox); + QComboBox* comboBox = new QComboBox(groupBox); + layout->addWidget(label, row, 0); + layout->addWidget(comboBox, row, 1); + + setAdapter(new PDFObjectEditorMappedComboBoxAdapter(label, comboBox, m_model, attribute, this)); + break; + } + + case ObjectEditorAttributeType::TextLine: + { + int row = layout->rowCount(); + + QLabel* label = new QLabel(groupBox); + QLineEdit* lineEdit = new QLineEdit(groupBox); + layout->addWidget(label, row, 0); + layout->addWidget(lineEdit, row, 1); + + setAdapter(new PDFObjectEditorMappedLineEditAdapter(label, lineEdit, m_model, attribute, this)); + break; + } + + case ObjectEditorAttributeType::TextBrowser: + { + int row = layout->rowCount(); + + QLabel* label = new QLabel(groupBox); + QTextBrowser* textBrowser = new QTextBrowser(groupBox); + layout->addWidget(label, row, 0, 1, -1); + layout->addWidget(textBrowser, row + 1, 0, 1, -1); + + setAdapter(new PDFObjectEditorMappedTextBrowserAdapter(label, textBrowser, m_model, attribute, this)); + break; + } + + case ObjectEditorAttributeType::Rectangle: + { + int row = layout->rowCount(); + + QLabel* label = new QLabel(groupBox); + QPushButton* pushButton = new QPushButton(groupBox); + pushButton->setFlat(true); + + layout->addWidget(label, row, 0); + layout->addWidget(pushButton, row, 1); + + setAdapter(new PDFObjectEditorMappedRectangleAdapter(label, pushButton, m_model, attribute, this)); + break; + } + + default: + Q_ASSERT(false); + } +x + /* + DateTime, ///< Date/time + Flags, ///< Flags + Selector, ///< Selector attribute, it is not persisted + Color, ///< Color + Boolean, ///< Check box*/ +} + +PDFObjectEditorWidgetMapper::Category* PDFObjectEditorWidgetMapper::getOrCreateCategory(QString categoryName) +{ + auto categoryIt = std::find_if(m_categories.begin(), m_categories.end(), [&categoryName](const auto& category) { return category.name == categoryName; }); + if (categoryIt != m_categories.end()) + { + return &*categoryIt; + } + + Category category; + category.name = qMove(categoryName); + m_categories.emplace_back(qMove(category)); + return &m_categories.back(); +} + +PDFObjectEditorWidgetMapper::Subcategory* PDFObjectEditorWidgetMapper::Category::getOrCreateSubcategory(QString subcategoryName) +{ + auto subcategoryIt = std::find_if(subcategories.begin(), subcategories.end(), [&subcategoryName](const auto& subcategory) { return subcategory.name == subcategoryName; }); + if (subcategoryIt != subcategories.end()) + { + return &*subcategoryIt; + } + + Subcategory subcategory; + subcategory.name = qMove(subcategoryName); + subcategories.emplace_back(qMove(subcategory)); + return &subcategories.back(); +} + +PDFObjectEditorMappedWidgetAdapter::PDFObjectEditorMappedWidgetAdapter(PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent) : + BaseClass(parent), + m_model(model), + m_attribute(attribute) +{ + +} + +void PDFObjectEditorMappedWidgetAdapter::initLabel(QLabel* label) +{ + label->setText(m_model->getAttributeName(m_attribute)); +} + +PDFObjectEditorMappedComboBoxAdapter::PDFObjectEditorMappedComboBoxAdapter(QLabel* label, + QComboBox* comboBox, + PDFObjectEditorAbstractModel* model, + size_t attribute, + QObject* parent) : + BaseClass(model, attribute, parent), + m_label(label), + m_comboBox(comboBox) +{ + initLabel(label); + + comboBox->clear(); + for (const PDFObjectEditorModelAttributeEnumItem& item : m_model->getAttributeEnumItems(attribute)) + { + comboBox->addItem(item.name, item.flags); + } + + connect(comboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this, attribute](){ emit commitRequested(attribute); }); +} + +PDFObject PDFObjectEditorMappedComboBoxAdapter::getValue() const +{ + QVariant currentData = m_comboBox->currentData(); + if (!currentData.isValid()) + { + return PDFObject(); + } + + const uint32_t flags = currentData.toUInt(); + for (const PDFObjectEditorModelAttributeEnumItem& item : m_model->getAttributeEnumItems(m_attribute)) + { + if (item.flags == flags) + { + return item.value; + } + } + + return PDFObject(); +} + +void PDFObjectEditorMappedComboBoxAdapter::setValue(PDFObject object) +{ + for (const PDFObjectEditorModelAttributeEnumItem& item : m_model->getAttributeEnumItems(m_attribute)) + { + if (item.value == object) + { + m_comboBox->setCurrentIndex(m_comboBox->findData(int(item.flags))); + return; + } + } + + m_comboBox->setCurrentIndex(-1); +} + +PDFObjectEditorMappedLineEditAdapter::PDFObjectEditorMappedLineEditAdapter(QLabel* label, + QLineEdit* lineEdit, + PDFObjectEditorAbstractModel* model, + size_t attribute, + QObject* parent) : + BaseClass(model, attribute, parent), + m_label(label), + m_lineEdit(lineEdit) +{ + initLabel(label); + lineEdit->setClearButtonEnabled(true); + + connect(lineEdit, &QLineEdit::editingFinished, this, [this, attribute](){ emit commitRequested(attribute); }); +} + +PDFObject PDFObjectEditorMappedLineEditAdapter::getValue() const +{ + PDFObjectFactory factory; + factory << m_lineEdit->text(); + return factory.takeObject(); +} + +void PDFObjectEditorMappedLineEditAdapter::setValue(PDFObject object) +{ + PDFDocumentDataLoaderDecorator loader(m_model->getStorage()); + m_lineEdit->setText(loader.readTextString(object, QString())); } +PDFObjectEditorMappedTextBrowserAdapter::PDFObjectEditorMappedTextBrowserAdapter(QLabel* label, + QTextBrowser* textBrowser, + PDFObjectEditorAbstractModel* model, + size_t attribute, + QObject* parent) : + BaseClass(model, attribute, parent), + m_label(label), + m_textBrowser(textBrowser) +{ + initLabel(label); + textBrowser->setUndoRedoEnabled(true); + textBrowser->setTextInteractionFlags(Qt::TextEditorInteraction); + + connect(textBrowser, &QTextBrowser::textChanged, this, [this, attribute](){ emit commitRequested(attribute); }); +} + +PDFObject PDFObjectEditorMappedTextBrowserAdapter::getValue() const +{ + PDFObjectFactory factory; + factory << m_textBrowser->toPlainText(); + return factory.takeObject(); +} + +void PDFObjectEditorMappedTextBrowserAdapter::setValue(PDFObject object) +{ + PDFDocumentDataLoaderDecorator loader(m_model->getStorage()); + m_textBrowser->setText(loader.readTextString(object, QString())); +} + +PDFObjectEditorMappedRectangleAdapter::PDFObjectEditorMappedRectangleAdapter(QLabel* label, + QPushButton* pushButton, + PDFObjectEditorAbstractModel* model, + size_t attribute, + QObject* parent): + BaseClass(model, attribute, parent), + m_label(label), + m_pushButton(pushButton) +{ + initLabel(label); +} + +PDFObject PDFObjectEditorMappedRectangleAdapter::getValue() const +{ + return m_rectangle; +} + +void PDFObjectEditorMappedRectangleAdapter::setValue(PDFObject object) +{ + m_rectangle = qMove(object); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfobjecteditorwidget_impl.h b/PdfForQtLib/sources/pdfobjecteditorwidget_impl.h new file mode 100644 index 0000000..7da3044 --- /dev/null +++ b/PdfForQtLib/sources/pdfobjecteditorwidget_impl.h @@ -0,0 +1,173 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#ifndef PDFOBJECTEDITORWIDGET_IMPL_H +#define PDFOBJECTEDITORWIDGET_IMPL_H + +#include "pdfobjecteditormodel.h" + +class QLabel; +class QGroupBox; +class QComboBox; +class QTabWidget; +class QGridLayout; +class QLineEdit; +class QTextBrowser; +class QPushButton; + +namespace pdf +{ + +class PDFObjectEditorMappedWidgetAdapter : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFObjectEditorMappedWidgetAdapter(PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent); + + /// Returns PDFObject value currently present in the widget + virtual PDFObject getValue() const = 0; + /// Sets PDFObject value to the widget. If data are incompatible, + /// then no data are set to the widget. + virtual void setValue(PDFObject object) = 0; + +signals: + void commitRequested(size_t attribute); + +protected: + // Initializes label text with attributes name + void initLabel(QLabel* label); + + PDFObjectEditorAbstractModel* m_model; + size_t m_attribute; +}; + +class PDFObjectEditorMappedComboBoxAdapter : public PDFObjectEditorMappedWidgetAdapter +{ + Q_OBJECT + +private: + using BaseClass = PDFObjectEditorMappedWidgetAdapter; + +public: + explicit PDFObjectEditorMappedComboBoxAdapter(QLabel* label, QComboBox* comboBox, PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent); + + virtual PDFObject getValue() const override; + virtual void setValue(PDFObject object) override; + +private: + QLabel* m_label; + QComboBox* m_comboBox; +}; + +class PDFObjectEditorMappedLineEditAdapter : public PDFObjectEditorMappedWidgetAdapter +{ + Q_OBJECT + +private: + using BaseClass = PDFObjectEditorMappedWidgetAdapter; + +public: + explicit PDFObjectEditorMappedLineEditAdapter(QLabel* label, QLineEdit* lineEdit, PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent); + + virtual PDFObject getValue() const override; + virtual void setValue(PDFObject object) override; + +private: + QLabel* m_label; + QLineEdit* m_lineEdit; +}; + +class PDFObjectEditorMappedTextBrowserAdapter : public PDFObjectEditorMappedWidgetAdapter +{ + Q_OBJECT + +private: + using BaseClass = PDFObjectEditorMappedWidgetAdapter; + +public: + explicit PDFObjectEditorMappedTextBrowserAdapter(QLabel* label, QTextBrowser* textBrowser, PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent); + + virtual PDFObject getValue() const override; + virtual void setValue(PDFObject object) override; + +private: + QLabel* m_label; + QTextBrowser* m_textBrowser; +}; + +class PDFObjectEditorWidgetMapper : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFObjectEditorWidgetMapper(PDFObjectEditorAbstractModel* model, QObject* parent); + + void initialize(QTabWidget* tabWidget); + +private: + struct Subcategory + { + QString name; + std::vector attributes; + }; + + struct Category + { + QString name; + std::vector subcategories; + QWidget* page = nullptr; + + Subcategory* getOrCreateSubcategory(QString name); + }; + + void createMappedAdapter(QGroupBox* groupBox, QGridLayout* layout, size_t attribute); + Category* getOrCreateCategory(QString categoryName); + + PDFObjectEditorAbstractModel* m_model; + std::vector m_categories; + std::map m_adapters; +}; + +class PDFObjectEditorMappedRectangleAdapter : public PDFObjectEditorMappedWidgetAdapter +{ + Q_OBJECT + +private: + using BaseClass = PDFObjectEditorMappedWidgetAdapter; + +public: + explicit PDFObjectEditorMappedRectangleAdapter(QLabel* label, QPushButton* pushButton, PDFObjectEditorAbstractModel* model, size_t attribute, QObject* parent); + + virtual PDFObject getValue() const override; + virtual void setValue(PDFObject object) override; + +private: + QLabel* m_label; + QPushButton* m_pushButton; + PDFObject m_rectangle; +}; + +} // namespace pdf + +#endif // PDFOBJECTEDITORWIDGET_IMPL_H