mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			815 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			815 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //    Copyright (C) 2019-2021 Jakub Melka
 | |
| //
 | |
| //    This file is part of Pdf4Qt.
 | |
| //
 | |
| //    Pdf4Qt 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
 | |
| //    with the written consent of the copyright owner, any later version.
 | |
| //
 | |
| //    Pdf4Qt 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 Pdf4Qt.  If not, see <https://www.gnu.org/licenses/>.
 | |
| 
 | |
| #include "pdfitemmodels.h"
 | |
| #include "pdfdocument.h"
 | |
| #include "pdfdrawspacecontroller.h"
 | |
| 
 | |
| #include <QFont>
 | |
| #include <QStyle>
 | |
| #include <QApplication>
 | |
| #include <QMimeDatabase>
 | |
| #include <QFileIconProvider>
 | |
| 
 | |
| namespace pdf
 | |
| {
 | |
| 
 | |
| PDFTreeItem::~PDFTreeItem()
 | |
| {
 | |
|     qDeleteAll(m_children);
 | |
| }
 | |
| 
 | |
| PDFTreeItemModel::PDFTreeItemModel(QObject* parent) :
 | |
|     QAbstractItemModel(parent),
 | |
|     m_document(nullptr)
 | |
| {
 | |
| 
 | |
| }
 | |
| 
 | |
| void PDFTreeItemModel::setDocument(const PDFModifiedDocument& document)
 | |
| {
 | |
|     if (m_document != document)
 | |
|     {
 | |
|         m_document = document;
 | |
| 
 | |
|         // We must only update only, when document has been reset, i.e.
 | |
|         // all document content has been changed.
 | |
|         if (document.hasReset())
 | |
|         {
 | |
|             update();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool PDFTreeItemModel::isEmpty() const
 | |
| {
 | |
|     return rowCount(QModelIndex()) == 0;
 | |
| }
 | |
| 
 | |
| QModelIndex PDFTreeItemModel::index(int row, int column, const QModelIndex& parent) const
 | |
| {
 | |
|     if (hasIndex(row, column, parent))
 | |
|     {
 | |
|         const PDFTreeItem* parentItem = nullptr;
 | |
| 
 | |
|         if (!parent.isValid())
 | |
|         {
 | |
|             parentItem = m_rootItem.get();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             parentItem = static_cast<PDFTreeItem*>(parent.internalPointer());
 | |
|         }
 | |
| 
 | |
|         return createIndex(row, column, const_cast<PDFTreeItem*>(parentItem->getChild(row)));
 | |
|     }
 | |
| 
 | |
|     return QModelIndex();
 | |
| }
 | |
| 
 | |
| QModelIndex PDFTreeItemModel::parent(const QModelIndex& child) const
 | |
| {
 | |
|     if (child.isValid())
 | |
|     {
 | |
|         const PDFTreeItem* childItem = static_cast<PDFTreeItem*>(child.internalPointer());
 | |
|         const PDFTreeItem* parentItem = childItem->getParent();
 | |
| 
 | |
|         if (parentItem != m_rootItem.get())
 | |
|         {
 | |
|             return createIndex(parentItem->getRow(), child.column(), const_cast<PDFTreeItem*>(parentItem));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return QModelIndex();
 | |
| }
 | |
| 
 | |
| int PDFTreeItemModel::rowCount(const QModelIndex& parent) const
 | |
| {
 | |
|     if (parent.isValid())
 | |
|     {
 | |
|         const PDFTreeItem* parentItem = static_cast<PDFTreeItem*>(parent.internalPointer());
 | |
|         return parentItem->getChildCount();
 | |
|     }
 | |
| 
 | |
|     return m_rootItem ? m_rootItem->getChildCount() : 0;
 | |
| }
 | |
| 
 | |
| bool PDFTreeItemModel::hasChildren(const QModelIndex& parent) const
 | |
| {
 | |
|     return rowCount(parent) > 0;
 | |
| }
 | |
| 
 | |
| Qt::ItemFlags PDFTreeItemModel::flags(const QModelIndex& index) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|     {
 | |
|         return Qt::NoItemFlags;
 | |
|     }
 | |
| 
 | |
|     return QAbstractItemModel::flags(index);
 | |
| }
 | |
| 
 | |
| 
 | |
| void PDFOptionalContentTreeItemModel::setActivity(PDFOptionalContentActivity* activity)
 | |
| {
 | |
|     m_activity = activity;
 | |
| }
 | |
| 
 | |
| int PDFOptionalContentTreeItemModel::columnCount(const QModelIndex& parent) const
 | |
| {
 | |
|     Q_UNUSED(parent);
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| QVariant PDFOptionalContentTreeItemModel::data(const QModelIndex& index, int role) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|     {
 | |
|         return QVariant();
 | |
|     }
 | |
| 
 | |
|     const PDFOptionalContentTreeItem* item = static_cast<const PDFOptionalContentTreeItem*>(index.internalPointer());
 | |
|     switch (role)
 | |
|     {
 | |
|         case Qt::DisplayRole:
 | |
|             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;
 | |
|     }
 | |
| 
 | |
|     return QVariant();
 | |
| }
 | |
| 
 | |
| void PDFOptionalContentTreeItemModel::update()
 | |
| {
 | |
|     beginResetModel();
 | |
| 
 | |
|     PDFOptionalContentTreeItem* root = new PDFOptionalContentTreeItem(nullptr, PDFObjectReference(), QString(), false);
 | |
|     m_rootItem.reset(root);
 | |
| 
 | |
|     if (m_document)
 | |
|     {
 | |
|         const PDFOptionalContentProperties* optionalContentProperties = m_document->getCatalog()->getOptionalContentProperties();
 | |
|         if (optionalContentProperties->isValid())
 | |
|         {
 | |
|             const PDFOptionalContentConfiguration& configuration = optionalContentProperties->getDefaultConfiguration();
 | |
|             const PDFObject& orderObject = m_document->getObject(configuration.getOrder());
 | |
|             const std::vector<PDFObjectReference>& ocgs = optionalContentProperties->getAllOptionalContentGroups();
 | |
|             const std::vector<PDFObjectReference>& locked = configuration.getLocked();
 | |
| 
 | |
|             // We must detect cycles in the reference array
 | |
|             std::set<PDFObjectReference> lockedOptionalContentGroups(locked.cbegin(), locked.cend());
 | |
|             std::set<PDFObjectReference> optionalContentGroups(ocgs.cbegin(), ocgs.cend());
 | |
|             std::set<PDFObjectReference> processedReferences;
 | |
| 
 | |
|             PDFDocumentDataLoaderDecorator loader(m_document);
 | |
|             std::function<PDFOptionalContentTreeItem*(const PDFObject&)> processObject = [&, this](const PDFObject& object) -> PDFOptionalContentTreeItem*
 | |
|             {
 | |
|                 PDFObject dereferencedObject = object;
 | |
|                 if (object.isReference())
 | |
|                 {
 | |
|                     PDFObjectReference reference = object.getReference();
 | |
|                     if (optionalContentGroups.count(reference))
 | |
|                     {
 | |
|                         const PDFOptionalContentGroup& ocg = optionalContentProperties->getOptionalContentGroup(reference);
 | |
|                         return new PDFOptionalContentTreeItem(nullptr, reference, ocg.getName(), lockedOptionalContentGroups.count(reference));
 | |
|                     }
 | |
|                     else if (!processedReferences.count(reference))
 | |
|                     {
 | |
|                         processedReferences.insert(reference);
 | |
|                         dereferencedObject = m_document->getStorage().getObject(reference);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         // Error - we have cyclic references
 | |
|                         return nullptr;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (dereferencedObject.isArray())
 | |
|                 {
 | |
|                     const PDFArray* array = dereferencedObject.getArray();
 | |
|                     const size_t arraySize = array->getCount();
 | |
| 
 | |
|                     // We must have at least one item!
 | |
|                     if (arraySize == 0)
 | |
|                     {
 | |
|                         return nullptr;
 | |
|                     }
 | |
| 
 | |
|                     QString text;
 | |
|                     size_t i = 0;
 | |
| 
 | |
|                     // Try to retrieve group name
 | |
|                     const PDFObject& firstItem = m_document->getObject(array->getItem(0));
 | |
|                     if (firstItem.isString())
 | |
|                     {
 | |
|                         text = loader.readTextString(firstItem, QString());
 | |
|                         ++i;
 | |
|                     }
 | |
| 
 | |
|                     std::unique_ptr<PDFOptionalContentTreeItem> parentItem(new PDFOptionalContentTreeItem(nullptr, PDFObjectReference(), text, false));
 | |
|                     for (; i < arraySize; ++i)
 | |
|                     {
 | |
|                         if (PDFOptionalContentTreeItem* item = processObject(array->getItem(i)))
 | |
|                         {
 | |
|                             parentItem->addCreatedChild(item);
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             // Item cannot be parsed properly
 | |
|                             return nullptr;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     return parentItem.release();
 | |
|                 }
 | |
| 
 | |
|                 return nullptr;
 | |
|             };
 | |
| 
 | |
|             m_rootItem.reset(processObject(orderObject));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     endResetModel();
 | |
| }
 | |
| 
 | |
| Qt::ItemFlags PDFOptionalContentTreeItemModel::flags(const QModelIndex& index) const
 | |
| {
 | |
|     Qt::ItemFlags flags = PDFTreeItemModel::flags(index);
 | |
| 
 | |
|     if (!index.isValid())
 | |
|     {
 | |
|         return flags;
 | |
|     }
 | |
| 
 | |
|     const PDFOptionalContentTreeItem* item = static_cast<const PDFOptionalContentTreeItem*>(index.internalPointer());
 | |
|     if (item->getReference() != PDFObjectReference())
 | |
|     {
 | |
|         flags = flags | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren;
 | |
| 
 | |
|         if (item->isLocked())
 | |
|         {
 | |
|             flags &= ~Qt::ItemIsEnabled;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return flags;
 | |
| }
 | |
| 
 | |
| QString PDFOptionalContentTreeItem::getText() const
 | |
| {
 | |
|     if (!m_text.isEmpty())
 | |
|     {
 | |
|         return m_text;
 | |
|     }
 | |
|     else if (getParent())
 | |
|     {
 | |
|         return static_cast<const PDFOptionalContentTreeItem*>(getParent())->getText();
 | |
|     }
 | |
| 
 | |
|     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;
 | |
| }
 | |
| 
 | |
| PDFOutlineTreeItem::PDFOutlineTreeItem(PDFOutlineTreeItem* parent, QSharedPointer<PDFOutlineItem> outlineItem) :
 | |
|     PDFTreeItem(parent),
 | |
|     m_outlineItem(qMove(outlineItem))
 | |
| {
 | |
|     size_t childCount = m_outlineItem->getChildCount();
 | |
|     for (size_t i = 0; i < childCount; ++i)
 | |
|     {
 | |
|         addCreatedChild(new PDFOutlineTreeItem(nullptr, m_outlineItem->getChildPtr(i)));
 | |
|     }
 | |
| }
 | |
| 
 | |
| int PDFOutlineTreeItemModel::columnCount(const QModelIndex& parent) const
 | |
| {
 | |
|     Q_UNUSED(parent);
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| QVariant PDFOutlineTreeItemModel::data(const QModelIndex& index, int role) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|     {
 | |
|         return QVariant();
 | |
|     }
 | |
| 
 | |
|     const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
 | |
|     const PDFOutlineItem* outlineItem = item->getOutlineItem();
 | |
|     switch (role)
 | |
|     {
 | |
|         case Qt::DisplayRole:
 | |
|             return outlineItem->getTitle();
 | |
| 
 | |
|         case Qt::TextColorRole:
 | |
|             return outlineItem->getTextColor();
 | |
| 
 | |
|         case Qt::FontRole:
 | |
|         {
 | |
|             QFont font = QApplication::font();
 | |
|             font.setPointSize(10);
 | |
|             font.setBold(outlineItem->isFontBold());
 | |
|             font.setItalic(outlineItem->isFontItalics());
 | |
|             return font;
 | |
|         }
 | |
| 
 | |
|         case Qt::DecorationRole:
 | |
|         {
 | |
|             if (!m_icon.isNull())
 | |
|             {
 | |
|                 return m_icon;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         default:
 | |
|             break;
 | |
|     }
 | |
| 
 | |
|     return QVariant();
 | |
| }
 | |
| 
 | |
| void PDFOutlineTreeItemModel::update()
 | |
| {
 | |
|     beginResetModel();
 | |
| 
 | |
|     QSharedPointer<PDFOutlineItem> outlineRoot;
 | |
|     if (m_document)
 | |
|     {
 | |
|         outlineRoot = m_document->getCatalog()->getOutlineRootPtr();
 | |
|     }
 | |
|     if (outlineRoot)
 | |
|     {
 | |
|         m_rootItem.reset(new PDFOutlineTreeItem(nullptr, qMove(outlineRoot)));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         m_rootItem.reset();
 | |
|     }
 | |
| 
 | |
|     endResetModel();
 | |
| }
 | |
| 
 | |
| Qt::ItemFlags PDFOutlineTreeItemModel::flags(const QModelIndex& index) const
 | |
| {
 | |
|     Qt::ItemFlags flags = PDFTreeItemModel::flags(index);
 | |
| 
 | |
|     if (!index.isValid())
 | |
|     {
 | |
|         return flags;
 | |
|     }
 | |
| 
 | |
|     const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
 | |
|     if (item->getChildCount() == 0)
 | |
|     {
 | |
|         flags = flags | Qt::ItemNeverHasChildren;
 | |
|     }
 | |
| 
 | |
|     return flags;
 | |
| }
 | |
| 
 | |
| const PDFAction* PDFOutlineTreeItemModel::getAction(const QModelIndex& index) const
 | |
| {
 | |
|     if (index.isValid())
 | |
|     {
 | |
|         const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
 | |
|         const PDFOutlineItem* outlineItem = item->getOutlineItem();
 | |
|         return outlineItem->getAction();
 | |
|     }
 | |
| 
 | |
|     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())
 | |
|                 {
 | |
|                     QIcon folderIcon = QApplication::style()->standardIcon(QStyle::SP_DirIcon);
 | |
|                     subroot = new PDFAttachmentsTreeItem(nullptr, qMove(folderIcon), 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
 | |
| {
 | |
|     if (!index.isValid())
 | |
|     {
 | |
|         return Qt::NoItemFlags;
 | |
|     }
 | |
| 
 | |
|     if (rowCount(index) > 0)
 | |
|     {
 | |
|         return Qt::ItemIsEnabled;
 | |
|     }
 | |
| 
 | |
|     if (index.column() == Title)
 | |
|     {
 | |
|         return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
 | |
|     }
 | |
| 
 | |
|     return Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
 | |
| }
 | |
| 
 | |
| const PDFFileSpecification* PDFAttachmentsTreeItemModel::getFileSpecification(const QModelIndex& index) const
 | |
| {
 | |
|     if (index.isValid())
 | |
|     {
 | |
|         return static_cast<const PDFAttachmentsTreeItem*>(index.internalPointer())->getFileSpecification();
 | |
|     }
 | |
| 
 | |
|     return nullptr;
 | |
| }
 | |
| 
 | |
| PDFThumbnailsItemModel::PDFThumbnailsItemModel(const PDFDrawWidgetProxy* proxy, QObject* parent) :
 | |
|     QAbstractItemModel(parent),
 | |
|     m_proxy(proxy),
 | |
|     m_thumbnailSize(100),
 | |
|     m_extraItemWidthHint(0),
 | |
|     m_extraItemHeighHint(0),
 | |
|     m_pageCount(0),
 | |
|     m_document(nullptr)
 | |
| {
 | |
|     connect(proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFThumbnailsItemModel::onPageImageChanged);
 | |
| }
 | |
| 
 | |
| bool PDFThumbnailsItemModel::isEmpty() const
 | |
| {
 | |
|     return rowCount(QModelIndex()) == 0;
 | |
| }
 | |
| 
 | |
| QModelIndex PDFThumbnailsItemModel::index(int row, int column, const QModelIndex& parent) const
 | |
| {
 | |
|     if (parent.isValid())
 | |
|     {
 | |
|         return QModelIndex();
 | |
|     }
 | |
| 
 | |
|     return createIndex(row, column, nullptr);
 | |
| }
 | |
| 
 | |
| QModelIndex PDFThumbnailsItemModel::parent(const QModelIndex& child) const
 | |
| {
 | |
|     Q_UNUSED(child);
 | |
|     return QModelIndex();
 | |
| }
 | |
| 
 | |
| int PDFThumbnailsItemModel::rowCount(const QModelIndex& parent) const
 | |
| {
 | |
|     if (parent.isValid())
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     return m_pageCount;
 | |
| }
 | |
| 
 | |
| int PDFThumbnailsItemModel::columnCount(const QModelIndex& parent) const
 | |
| {
 | |
|     Q_UNUSED(parent);
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| QVariant PDFThumbnailsItemModel::data(const QModelIndex& index, int role) const
 | |
| {
 | |
|     if (!index.isValid() || !m_document)
 | |
|     {
 | |
|         return QVariant();
 | |
|     }
 | |
| 
 | |
|     const int page = index.row();
 | |
|     switch (role)
 | |
|     {
 | |
|         case Qt::DisplayRole:
 | |
|             return QString::number(page + 1);
 | |
| 
 | |
|         case Qt::DecorationRole:
 | |
|         {
 | |
|             QString key = getKey(index.row());
 | |
| 
 | |
|             QPixmap pixmap;
 | |
|             if (!m_thumbnailCache.find(key, &pixmap))
 | |
|             {
 | |
|                 QImage thumbnail = m_proxy->drawThumbnailImage(index.row(), m_thumbnailSize);
 | |
|                 if (!thumbnail.isNull())
 | |
|                 {
 | |
|                     pixmap = QPixmap::fromImage(qMove(thumbnail));
 | |
|                     m_thumbnailCache.insert(key, pixmap);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return pixmap;
 | |
|         }
 | |
| 
 | |
|         case Qt::TextAlignmentRole:
 | |
|         {
 | |
|             return int(Qt::AlignHCenter | Qt::AlignBottom);
 | |
|         }
 | |
| 
 | |
|         case Qt::SizeHintRole:
 | |
|         {
 | |
|             const PDFPage* page = m_document->getCatalog()->getPage(index.row());
 | |
|             QSizeF pageSize = page->getRotatedMediaBox().size();
 | |
|             pageSize.scale(m_thumbnailSize, m_thumbnailSize, Qt::KeepAspectRatio);
 | |
|             return pageSize.toSize() + QSize(m_extraItemWidthHint, m_extraItemHeighHint);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         default:
 | |
|             break;
 | |
|     }
 | |
| 
 | |
|     return QVariant();
 | |
| }
 | |
| 
 | |
| void PDFThumbnailsItemModel::setThumbnailsSize(int size)
 | |
| {
 | |
|     if (m_thumbnailSize != size)
 | |
|     {
 | |
|         emit layoutAboutToBeChanged();
 | |
|         m_thumbnailSize = size;
 | |
|         m_thumbnailCache.clear();
 | |
|         emit layoutChanged();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void PDFThumbnailsItemModel::setDocument(const PDFModifiedDocument& document)
 | |
| {
 | |
|     if (m_document != document)
 | |
|     {
 | |
|         if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation))
 | |
|         {
 | |
|             beginResetModel();
 | |
|             m_thumbnailCache.clear();
 | |
|             m_document = document;
 | |
| 
 | |
|             m_pageCount = 0;
 | |
|             if (m_document)
 | |
|             {
 | |
|                 m_pageCount = static_cast<int>(m_document->getCatalog()->getPageCount());
 | |
|             }
 | |
|             endResetModel();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // Soft reset
 | |
|             m_document = document;
 | |
|             Q_ASSERT(!m_document || m_pageCount == static_cast<int>(m_document->getCatalog()->getPageCount()));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| PDFInteger PDFThumbnailsItemModel::getPageIndex(const QModelIndex& index) const
 | |
| {
 | |
|     return index.row();
 | |
| }
 | |
| 
 | |
| void PDFThumbnailsItemModel::onPageImageChanged(bool all, const std::vector<PDFInteger>& pages)
 | |
| {
 | |
|     Q_UNUSED(all);
 | |
|     Q_UNUSED(pages);
 | |
| 
 | |
|     if (all)
 | |
|     {
 | |
|         m_thumbnailCache.clear();
 | |
|         emit dataChanged(index(0, 0, QModelIndex()), index(rowCount(QModelIndex()) - 1, 0, QModelIndex()));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         const int rowCount = this->rowCount(QModelIndex());
 | |
|         for (const PDFInteger pageIndex : pages)
 | |
|         {
 | |
|             if (pageIndex < rowCount)
 | |
|             {
 | |
|                 m_thumbnailCache.remove(getKey(pageIndex));
 | |
|                 emit dataChanged(index(pageIndex, 0, QModelIndex()), index(pageIndex, 0, QModelIndex()));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| QString PDFThumbnailsItemModel::getKey(int pageIndex) const
 | |
| {
 | |
|     return QString("PDF_THUMBNAIL_%1").arg(pageIndex);
 | |
| }
 | |
| 
 | |
| PDFAttachmentsTreeItem::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(nullptr)
 | |
| {
 | |
|     if (fileSpecification)
 | |
|     {
 | |
|         m_fileSpecification = std::make_unique<PDFFileSpecification>(*fileSpecification);
 | |
|     }
 | |
| }
 | |
| 
 | |
| PDFAttachmentsTreeItem::~PDFAttachmentsTreeItem()
 | |
| {
 | |
| 
 | |
| }
 | |
| 
 | |
| }   // namespace pdf
 |