File attachments

This commit is contained in:
Jakub Melka 2019-12-01 18:10:11 +01:00
parent 939a011ca6
commit 159fc9f815
7 changed files with 711 additions and 7 deletions

View File

@ -234,6 +234,7 @@ public:
PageLayout getPageLayout() const { return m_pageLayout; }
PageMode getPageMode() const { return m_pageMode; }
const QByteArray& getBaseURI() const { return m_baseURI; }
const std::map<QByteArray, PDFFileSpecification>& getEmbeddedFiles() const { return m_embeddedFiles; }
/// Returns destination using the key. If destination with the key is not found,
/// then nullptr is returned.

View File

@ -32,6 +32,11 @@ public:
explicit PDFEmbeddedFile() = default;
bool isValid() const { return m_stream.isStream(); }
const QByteArray& getSubtype() const { return m_subtype; }
PDFInteger getSize() const { return m_size; }
const QDateTime& getCreationDate() const { return m_creationDate; }
const QDateTime& getModifiedDate() const { return m_modifiedDate; }
const QByteArray& getChecksum() const { return m_checksum; }
static PDFEmbeddedFile parse(const PDFDocument* document, PDFObject object);

View File

@ -19,7 +19,10 @@
#include "pdfdocument.h"
#include <QFont>
#include <QStyle>
#include <QApplication>
#include <QMimeDatabase>
#include <QFileIconProvider>
namespace pdf
{
@ -437,4 +440,152 @@ const PDFAction* PDFOutlineTreeItemModel::getAction(const QModelIndex& index) co
return nullptr;
}
int PDFAttachmentsTreeItemModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return EndColumn;
}
QVariant PDFAttachmentsTreeItemModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
const PDFAttachmentsTreeItem* item = static_cast<const PDFAttachmentsTreeItem*>(index.internalPointer());
switch (role)
{
case Qt::DisplayRole:
{
switch (index.column())
{
case Title:
return item->getTitle();
case Description:
return item->getDescription();
default:
Q_ASSERT(false);
break;
}
break;
}
case Qt::DecorationRole:
{
switch (index.column())
{
case Title:
return item->getIcon();
case Description:
return QVariant();
default:
Q_ASSERT(false);
break;
}
break;
}
default:
break;
}
return QVariant();
}
void PDFAttachmentsTreeItemModel::update()
{
beginResetModel();
m_rootItem.reset();
if (m_document)
{
const std::map<QByteArray, PDFFileSpecification>& embeddedFiles = m_document->getCatalog()->getEmbeddedFiles();
if (!embeddedFiles.empty())
{
QMimeDatabase mimeDatabase;
QFileIconProvider fileIconProvider;
fileIconProvider.setOptions(QFileIconProvider::DontUseCustomDirectoryIcons);
PDFAttachmentsTreeItem* root = new PDFAttachmentsTreeItem(nullptr, QIcon(), QString(), QString(), nullptr);
m_rootItem.reset(root);
std::map<QString, PDFAttachmentsTreeItem*> subroots;
for (const auto& embeddedFile : embeddedFiles)
{
const PDFFileSpecification* specification = &embeddedFile.second;
const PDFEmbeddedFile* file = specification->getPlatformFile();
if (!file)
{
continue;
}
QString fileName = specification->getPlatformFileName();
QString description = specification->getDescription();
// Jakub Melka: try to obtain mime type from subtype, if it fails, then form file name.
// We do not obtain type from data, because it can be slow (files can be large).
QMimeType type = mimeDatabase.mimeTypeForName(file->getSubtype());
if (!type.isValid())
{
type = mimeDatabase.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
}
// Get icon and select folder, to which file belongs
QIcon icon;
QString fileTypeName = "@GENERIC";
QString fileTypeDescription = tr("Files");
if (type.isValid())
{
icon = QIcon::fromTheme(type.iconName());
if (icon.isNull())
{
icon = QIcon::fromTheme(type.genericIconName());
}
fileTypeName = type.name();
fileTypeDescription = type.comment();
}
if (icon.isNull())
{
icon = fileIconProvider.icon(QFileInfo(fileName));
}
if (icon.isNull())
{
icon = QApplication::style()->standardIcon(QStyle::SP_FileIcon);
}
// Create subroot
PDFAttachmentsTreeItem* subroot = nullptr;
auto it = subroots.find(fileTypeName);
if (it == subroots.cend())
{
subroot = new PDFAttachmentsTreeItem(nullptr, icon, fileTypeDescription, QString(), nullptr);
root->addCreatedChild(subroot);
subroots[fileTypeName] = subroot;
}
else
{
subroot = it->second;
}
// Create item
subroot->addCreatedChild(new PDFAttachmentsTreeItem(nullptr, qMove(icon), qMove(fileName), qMove(description), specification));
}
}
}
endResetModel();
}
Qt::ItemFlags PDFAttachmentsTreeItemModel::flags(const QModelIndex& index) const
{
return PDFTreeItemModel::flags(index);
}
} // namespace pdf

View File

@ -29,6 +29,7 @@ namespace pdf
class PDFAction;
class PDFDocument;
class PDFOutlineItem;
class PDFFileSpecification;
class PDFOptionalContentActivity;
/// Represents tree item in the GUI tree
@ -166,6 +167,54 @@ private:
QIcon m_icon;
};
class PDFAttachmentsTreeItem : public PDFTreeItem
{
public:
explicit PDFAttachmentsTreeItem(PDFAttachmentsTreeItem* parent, QIcon icon, QString title, QString description, const PDFFileSpecification* fileSpecification) :
PDFTreeItem(parent),
m_icon(qMove(icon)),
m_title(qMove(title)),
m_description(qMove(description)),
m_fileSpecification(fileSpecification)
{
}
const QIcon& getIcon() const { return m_icon; }
const QString& getTitle() const { return m_title; }
const QString& getDescription() const { return m_description; }
const PDFFileSpecification* getFileSpecification() const { return m_fileSpecification; }
private:
QIcon m_icon;
QString m_title;
QString m_description;
const PDFFileSpecification* m_fileSpecification;
};
class PDFFORQTLIBSHARED_EXPORT PDFAttachmentsTreeItemModel : public PDFTreeItemModel
{
Q_OBJECT
public:
PDFAttachmentsTreeItemModel(QObject* parent) :
PDFTreeItemModel(parent)
{
}
enum Column
{
Title,
Description,
EndColumn
};
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;
};
} // namespace pdf
#endif // PDFITEMMODELS_H

View File

@ -30,7 +30,8 @@ PDFSidebarWidget::PDFSidebarWidget(QWidget* parent) :
m_outlineTreeModel(nullptr),
m_optionalContentTreeModel(nullptr),
m_document(nullptr),
m_optionalContentActivity(nullptr)
m_optionalContentActivity(nullptr),
m_attachmentsTreeModel(nullptr)
{
ui->setupUi(this);
@ -46,10 +47,18 @@ PDFSidebarWidget::PDFSidebarWidget(QWidget* parent) :
m_optionalContentTreeModel = new pdf::PDFOptionalContentTreeItemModel(this);
ui->optionalContentTreeView->setModel(m_optionalContentTreeModel);
// Attachments
ui->attachmentsTreeView->header()->hide();
m_attachmentsTreeModel = new pdf::PDFAttachmentsTreeItemModel(this);
ui->attachmentsTreeView->setModel(m_attachmentsTreeModel);
ui->attachmentsTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->attachmentsTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pageInfo[Invalid] = { nullptr, ui->emptyPage };
m_pageInfo[OptionalContent] = { ui->optionalContentButton, ui->optionalContentPage };
m_pageInfo[Bookmarks] = { ui->bookmarksButton, ui->bookmarksPage };
m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage };
m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage };
setAutoFillBackground(true);
selectPage(Invalid);
@ -74,6 +83,11 @@ void PDFSidebarWidget::setDocument(const pdf::PDFDocument* document, pdf::PDFOpt
m_optionalContentTreeModel->setActivity(m_optionalContentActivity);
ui->optionalContentTreeView->expandAll();
// Update attachments
m_attachmentsTreeModel->setDocument(document);
ui->attachmentsTreeView->expandAll();
ui->attachmentsTreeView->resizeColumnToContents(0);
Page preferred = Invalid;
if (m_document)
{
@ -92,6 +106,10 @@ void PDFSidebarWidget::setDocument(const pdf::PDFDocument* document, pdf::PDFOpt
preferred = OptionalContent;
break;
case pdf::PageMode::UseAttachments:
preferred = Attachments;
break;
default:
break;
}
@ -122,15 +140,18 @@ bool PDFSidebarWidget::isEmpty(Page page) const
case Invalid:
return true;
case OptionalContent:
return m_optionalContentTreeModel->isEmpty();
case Bookmarks:
return m_outlineTreeModel->isEmpty();
case Thumbnails:
return true;
case OptionalContent:
return m_optionalContentTreeModel->isEmpty();
case Attachments:
return m_attachmentsTreeModel->isEmpty();
default:
Q_ASSERT(false);
break;

View File

@ -34,6 +34,7 @@ namespace pdf
class PDFAction;
class PDFDocument;
class PDFOutlineTreeItemModel;
class PDFAttachmentsTreeItemModel;
class PDFOptionalContentActivity;
class PDFOptionalContentTreeItemModel;
}
@ -53,9 +54,10 @@ public:
{
Invalid,
_BEGIN,
OptionalContent = _BEGIN,
Bookmarks,
Bookmarks = _BEGIN,
Thumbnails,
OptionalContent,
Attachments,
_END
};
@ -93,6 +95,7 @@ private:
pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel;
const pdf::PDFDocument* m_document;
pdf::PDFOptionalContentActivity* m_optionalContentActivity;
pdf::PDFAttachmentsTreeItemModel* m_attachmentsTreeModel;
std::map<Page, PageInfo> m_pageInfo;
};

View File

@ -509,6 +509,19 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="attachmentsButton">
<property name="text">
<string>Attachments</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="buttonSpacer">
<property name="orientation">
@ -527,7 +540,7 @@
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>3</number>
<number>4</number>
</property>
<widget class="QWidget" name="emptyPage"/>
<widget class="QWidget" name="bookmarksPage">
@ -1461,6 +1474,467 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="attachmentsPage">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>127</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>170</red>
<green>170</green>
<blue>170</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="SolidPattern">
<color alpha="128">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>127</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>170</red>
<green>170</green>
<blue>170</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="SolidPattern">
<color alpha="128">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>127</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>127</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>170</red>
<green>170</green>
<blue>170</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>127</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>127</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="SolidPattern">
<color alpha="128">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<layout class="QVBoxLayout" name="attachmentsPageLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="attachmentsTreeView"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>