mirror of https://github.com/JakubMelka/PDF4QT.git
1192 lines
33 KiB
C++
1192 lines
33 KiB
C++
// Copyright (C) 2019-2022 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 "pdfdbgheap.h"
|
|
#include "pdfdrawwidget.h"
|
|
|
|
#include <QFont>
|
|
#include <QStyle>
|
|
#include <QApplication>
|
|
#include <QMimeDatabase>
|
|
#include <QFileIconProvider>
|
|
|
|
namespace pdf
|
|
{
|
|
|
|
PDFTreeItem::~PDFTreeItem()
|
|
{
|
|
qDeleteAll(m_children);
|
|
}
|
|
|
|
PDFTreeItem* PDFTreeItem::takeChild(int index)
|
|
{
|
|
PDFTreeItem* item = m_children.at(index);
|
|
m_children.erase(m_children.begin() + index);
|
|
return item;
|
|
}
|
|
|
|
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:
|
|
case Qt::EditRole:
|
|
return outlineItem->getTitle();
|
|
|
|
case Qt::ForegroundRole:
|
|
return QBrush(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)
|
|
{
|
|
if (m_editable)
|
|
{
|
|
outlineRoot = outlineRoot->clone();
|
|
}
|
|
|
|
m_rootItem.reset(new PDFOutlineTreeItem(nullptr, qMove(outlineRoot)));
|
|
}
|
|
else
|
|
{
|
|
if (m_editable && m_document)
|
|
{
|
|
outlineRoot.reset(new pdf::PDFOutlineItem());
|
|
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())
|
|
{
|
|
if (m_editable)
|
|
{
|
|
flags = flags | Qt::ItemIsDropEnabled;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
if (m_editable)
|
|
{
|
|
flags = flags | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
const PDFOutlineItem* PDFOutlineTreeItemModel::getOutlineItem(const QModelIndex& index) const
|
|
{
|
|
if (index.isValid())
|
|
{
|
|
const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
|
|
const PDFOutlineItem* outlineItem = item->getOutlineItem();
|
|
return outlineItem;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
PDFOutlineItem* PDFOutlineTreeItemModel::getOutlineItem(const QModelIndex& index)
|
|
{
|
|
if (index.isValid())
|
|
{
|
|
PDFOutlineTreeItem* item = static_cast<PDFOutlineTreeItem*>(index.internalPointer());
|
|
PDFOutlineItem* outlineItem = item->getOutlineItem();
|
|
return outlineItem;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void PDFOutlineTreeItemModel::setFontBold(const QModelIndex& index, bool value)
|
|
{
|
|
if (PDFOutlineItem* outlineItem = getOutlineItem(index))
|
|
{
|
|
if (outlineItem->isFontBold() != value)
|
|
{
|
|
outlineItem->setFontBold(value);
|
|
Q_EMIT dataChanged(index, index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PDFOutlineTreeItemModel::setFontItalics(const QModelIndex& index, bool value)
|
|
{
|
|
if (PDFOutlineItem* outlineItem = getOutlineItem(index))
|
|
{
|
|
if (outlineItem->isFontItalics() != value)
|
|
{
|
|
outlineItem->setFontItalics(value);
|
|
Q_EMIT dataChanged(index, index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PDFOutlineTreeItemModel::setDestination(const QModelIndex& index, const PDFDestination& destination)
|
|
{
|
|
if (PDFOutlineItem* outlineItem = getOutlineItem(index))
|
|
{
|
|
outlineItem->setAction(PDFActionPtr(new PDFActionGoTo(destination, PDFDestination())));
|
|
Q_EMIT dataChanged(index, index);
|
|
}
|
|
}
|
|
|
|
const PDFOutlineItem* PDFOutlineTreeItemModel::getRootOutlineItem() const
|
|
{
|
|
PDFOutlineTreeItem* item = static_cast<PDFOutlineTreeItem*>(m_rootItem.get());
|
|
return item->getOutlineItem();
|
|
}
|
|
|
|
bool PDFOutlineTreeItemModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
if (!m_editable || !index.isValid() || role != Qt::EditRole)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PDFOutlineTreeItem* item = static_cast<PDFOutlineTreeItem*>(index.internalPointer());
|
|
PDFOutlineItem* outlineItem = item->getOutlineItem();
|
|
if (outlineItem->getTitle() != value.toString())
|
|
{
|
|
outlineItem->setTitle(value.toString());
|
|
Q_EMIT dataChanged(index, index);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Qt::DropActions PDFOutlineTreeItemModel::supportedDropActions() const
|
|
{
|
|
if (!m_editable)
|
|
{
|
|
return Qt::IgnoreAction;
|
|
}
|
|
|
|
return Qt::CopyAction | Qt::MoveAction;
|
|
}
|
|
|
|
Qt::DropActions PDFOutlineTreeItemModel::supportedDragActions() const
|
|
{
|
|
if (!m_editable)
|
|
{
|
|
return Qt::IgnoreAction;
|
|
}
|
|
|
|
return Qt::CopyAction | Qt::MoveAction;
|
|
}
|
|
|
|
QStringList PDFOutlineTreeItemModel::mimeTypes() const
|
|
{
|
|
return QStringList() << "application/PDF4QT_PDFOutlineTreeItemModel";
|
|
}
|
|
|
|
QMimeData* PDFOutlineTreeItemModel::mimeData(const QModelIndexList& indexes) const
|
|
{
|
|
QMimeData* mimeData = new QMimeData();
|
|
|
|
if (indexes.size() == 1)
|
|
{
|
|
QByteArray ba;
|
|
|
|
{
|
|
QModelIndex index = indexes.front();
|
|
const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
|
|
m_dragDropItem = item->getOutlineItem()->clone();
|
|
QDataStream stream(&ba, QDataStream::WriteOnly);
|
|
stream << reinterpret_cast<quintptr>(m_dragDropItem.data());
|
|
}
|
|
|
|
mimeData->setData(mimeTypes().front(), ba);
|
|
}
|
|
|
|
return mimeData;
|
|
}
|
|
|
|
bool PDFOutlineTreeItemModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
|
|
{
|
|
Q_UNUSED(row);
|
|
Q_UNUSED(column);
|
|
Q_UNUSED(parent);
|
|
|
|
return action == Qt::MoveAction && data->hasFormat(mimeTypes().front());
|
|
}
|
|
|
|
bool PDFOutlineTreeItemModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
|
|
{
|
|
if (!data || action != Qt::MoveAction)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QByteArray pointerData = data->data(mimeTypes().front());
|
|
QDataStream stream(pointerData);
|
|
quintptr pointer = 0;
|
|
stream >> pointer;
|
|
|
|
PDFOutlineItem* item = reinterpret_cast<PDFOutlineItem*>(pointer);
|
|
if (item == m_dragDropItem.data())
|
|
{
|
|
if (row == -1)
|
|
{
|
|
row = rowCount(parent);
|
|
}
|
|
|
|
if (column == -1)
|
|
{
|
|
column = 0;
|
|
}
|
|
|
|
if (insertRow(row, parent))
|
|
{
|
|
QModelIndex targetIndex = this->index(row, column, parent);
|
|
PDFOutlineTreeItem* targetTreeItem = static_cast<PDFOutlineTreeItem*>(targetIndex.internalPointer());
|
|
*targetTreeItem->getOutlineItem() = *item;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PDFOutlineTreeItemModel::insertRows(int row, int count, const QModelIndex& parent)
|
|
{
|
|
if (!m_editable || row < 0 || count <= 0 || row > rowCount(parent))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
beginInsertRows(parent, row, row + count - 1);
|
|
|
|
PDFOutlineTreeItem* item = parent.isValid() ? static_cast<PDFOutlineTreeItem*>(parent.internalPointer()) : static_cast<PDFOutlineTreeItem*>(m_rootItem.get());
|
|
while (count > 0)
|
|
{
|
|
QSharedPointer<PDFOutlineItem> outlineItem(new PDFOutlineItem());
|
|
outlineItem->setTitle(tr("Item %1").arg(row + 1));
|
|
PDFOutlineTreeItem* newTreeItem = new PDFOutlineTreeItem(item, outlineItem);
|
|
item->insertCreatedChild(row, newTreeItem);
|
|
|
|
PDFOutlineItem* parentItem = item->getOutlineItem();
|
|
parentItem->insertChild(row, outlineItem);
|
|
|
|
++row;
|
|
--count;
|
|
}
|
|
|
|
endInsertRows();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PDFOutlineTreeItemModel::removeRows(int row, int count, const QModelIndex& parent)
|
|
{
|
|
if (!m_editable || count <= 0 || row < 0 || row + count > rowCount(parent))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
beginRemoveRows(parent, row, row + count - 1);
|
|
|
|
PDFOutlineTreeItem* item = parent.isValid() ? static_cast<PDFOutlineTreeItem*>(parent.internalPointer()) : static_cast<PDFOutlineTreeItem*>(m_rootItem.get());
|
|
while (count > 0)
|
|
{
|
|
item->getOutlineItem()->removeChild(row);
|
|
delete item->takeChild(row);
|
|
--count;
|
|
}
|
|
|
|
endRemoveRows();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PDFOutlineTreeItemModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild)
|
|
{
|
|
if (sourceRow < 0 || count <= 0 || (sourceParent == destinationParent && (sourceRow == destinationChild || sourceRow + count <= destinationChild)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PDFOutlineTreeItem* sourceNode = nullptr;
|
|
PDFOutlineTreeItem* destNode = nullptr;
|
|
|
|
if (sourceParent.isValid())
|
|
{
|
|
sourceNode = static_cast<PDFOutlineTreeItem*>(sourceParent.internalPointer());
|
|
}
|
|
else
|
|
{
|
|
sourceNode = static_cast<PDFOutlineTreeItem*>(m_rootItem.get());
|
|
}
|
|
|
|
if (destinationParent.isValid())
|
|
{
|
|
destNode = static_cast<PDFOutlineTreeItem*>(destinationParent.internalPointer());
|
|
}
|
|
else
|
|
{
|
|
destNode = static_cast<PDFOutlineTreeItem*>(m_rootItem.get());
|
|
}
|
|
|
|
if (sourceRow + count > sourceNode->getChildCount())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (destinationChild < 0)
|
|
{
|
|
destinationChild = 0;
|
|
}
|
|
|
|
beginRemoveRows(sourceParent, sourceRow, sourceRow + count - 1);
|
|
QList<PDFOutlineTreeItem*> nodesToMove;
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
nodesToMove.append(static_cast<PDFOutlineTreeItem*>(sourceNode->takeChild(sourceRow)));
|
|
}
|
|
endRemoveRows();
|
|
|
|
beginInsertRows(destinationParent, destinationChild, destinationChild + count - 1);
|
|
for (PDFOutlineTreeItem* node : nodesToMove)
|
|
{
|
|
destNode->insertCreatedChild(destinationChild++, node);
|
|
}
|
|
endInsertRows();
|
|
|
|
return true;
|
|
}
|
|
|
|
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 pageIndex = index.row();
|
|
switch (role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
return QString::number(pageIndex + 1);
|
|
|
|
case Qt::DecorationRole:
|
|
{
|
|
QString key = getKey(index.row());
|
|
|
|
QPixmap pixmap;
|
|
if (!m_thumbnailCache.find(key, &pixmap))
|
|
{
|
|
const qreal devicePixelRatio = m_proxy->getWidget()->devicePixelRatioF();
|
|
QImage thumbnail = m_proxy->drawThumbnailImage(index.row(), m_thumbnailSize * devicePixelRatio);
|
|
if (!thumbnail.isNull())
|
|
{
|
|
thumbnail.setDevicePixelRatio(devicePixelRatio);
|
|
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);
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
void PDFThumbnailsItemModel::setThumbnailsSize(int size)
|
|
{
|
|
if (m_thumbnailSize != size)
|
|
{
|
|
Q_EMIT layoutAboutToBeChanged();
|
|
m_thumbnailSize = size;
|
|
m_thumbnailCache.clear();
|
|
Q_EMIT layoutChanged();
|
|
}
|
|
}
|
|
|
|
void PDFThumbnailsItemModel::setDocument(const PDFModifiedDocument& document)
|
|
{
|
|
if (m_document != document)
|
|
{
|
|
if (document.hasReset() || document.hasPageContentsChanged() || 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();
|
|
Q_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));
|
|
Q_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()
|
|
{
|
|
|
|
}
|
|
|
|
QVariant PDFSelectableOutlineTreeItemModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (role == Qt::CheckStateRole)
|
|
{
|
|
if (!index.isValid())
|
|
{
|
|
return QVariant();
|
|
}
|
|
|
|
const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
|
|
const PDFOutlineItem* outlineItem = item->getOutlineItem();
|
|
const bool isChecked = m_selectedItems.count(outlineItem);
|
|
return isChecked ? Qt::Checked : Qt::Unchecked;
|
|
}
|
|
|
|
return BaseClass::data(index, role);
|
|
}
|
|
|
|
void PDFSelectableOutlineTreeItemModel::update()
|
|
{
|
|
BaseClass::update();
|
|
m_selectedItems.clear();
|
|
}
|
|
|
|
Qt::ItemFlags PDFSelectableOutlineTreeItemModel::flags(const QModelIndex& index) const
|
|
{
|
|
Qt::ItemFlags flags = BaseClass::flags(index);
|
|
|
|
if (index.isValid())
|
|
{
|
|
flags |= Qt::ItemIsUserCheckable;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
bool PDFSelectableOutlineTreeItemModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
if (role != Qt::CheckStateRole || !index.isValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const PDFOutlineTreeItem* item = static_cast<const PDFOutlineTreeItem*>(index.internalPointer());
|
|
const PDFOutlineItem* outlineItem = item->getOutlineItem();
|
|
|
|
if (outlineItem)
|
|
{
|
|
if (value.toInt() == Qt::Checked)
|
|
{
|
|
m_selectedItems.insert(outlineItem);
|
|
}
|
|
else
|
|
{
|
|
m_selectedItems.erase(outlineItem);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::vector<const PDFOutlineItem*> PDFSelectableOutlineTreeItemModel::getSelectedItems() const
|
|
{
|
|
return std::vector<const PDFOutlineItem*>(m_selectedItems.cbegin(), m_selectedItems.cend());
|
|
}
|
|
|
|
} // namespace pdf
|