PDF4QT/Pdf4QtPageMaster/pageitemmodel.cpp
2024-03-16 17:02:44 +01:00

1344 lines
35 KiB
C++

// Copyright (C) 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 "pageitemmodel.h"
#include <QFileInfo>
#include <QImageReader>
#include <QMimeData>
#include <iterator>
namespace pdfpagemaster
{
PageItemModel::PageItemModel(QObject* parent) :
QAbstractItemModel(parent)
{
}
QVariant PageItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(section);
Q_UNUSED(orientation);
Q_UNUSED(role);
return QVariant();
}
QModelIndex PageItemModel::index(int row, int column, const QModelIndex& parent) const
{
if (hasIndex(row, column, parent))
{
return createIndex(row, column, nullptr);
}
return QModelIndex();
}
QModelIndex PageItemModel::parent(const QModelIndex& index) const
{
Q_UNUSED(index);
return QModelIndex();
}
int PageItemModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
{
return 0;
}
return int(m_pageGroupItems.size());
}
int PageItemModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
{
return 0;
}
return 1;
}
QVariant PageItemModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
const PageGroupItem* item = getItem(index);
switch (role)
{
case Qt::DisplayRole:
return item->groupName;
case Qt::ToolTipRole:
{
QStringList texts;
texts << QString("<b>%1</b>").arg(item->groupName);
if (item->isGrouped())
{
texts << QString("%1 pages").arg(item->groups.size());
}
return texts.join("<br>");
}
default:
break;
}
return QVariant();
}
int PageItemModel::insertDocument(QString fileName, pdf::PDFDocument document, const QModelIndex& index)
{
Modifier modifier(this);
auto it = std::find_if(m_documents.cbegin(), m_documents.cend(), [&](const auto& item) { return item.second.fileName == fileName; });
if (it != m_documents.cend())
{
return -1;
}
int newIndex = 1;
if (!m_documents.empty())
{
newIndex = (m_documents.rbegin()->first) + 1;
}
m_documents[newIndex] = { qMove(fileName), qMove(document) };
createDocumentGroup(newIndex, index);
return newIndex;
}
int PageItemModel::insertImage(QString fileName, const QModelIndex& index)
{
Modifier modifier(this);
QFile file(fileName);
if (file.open(QFile::ReadOnly))
{
ImageItem item;
item.imageData = file.readAll();
QImageReader reader(fileName);
item.image = reader.read();
file.close();
if (!item.image.isNull())
{
int newIndex = 1;
if (!m_images.empty())
{
newIndex = (m_images.rbegin()->first) + 1;
}
m_images[newIndex] = qMove(item);
// Insert image item
PageGroupItem newItem;
newItem.groups.reserve(1);
PageGroupItem::GroupItem groupItem;
groupItem.imageIndex = newIndex;
groupItem.rotatedPageDimensionsMM = m_images[newIndex].image.size() * 0.1;
groupItem.pageType = PT_Image;
newItem.groups.push_back(qMove(groupItem));
updateItemCaptionAndTags(newItem);
int insertRow = index.isValid() ? index.row() + 1 : int(m_pageGroupItems.size());
beginInsertRows(QModelIndex(), insertRow, insertRow);
m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem));
endInsertRows();
return newIndex;
}
}
return -1;
}
int PageItemModel::insertImage(QImage image, const QModelIndex& index)
{
Modifier modifier(this);
if (!image.isNull())
{
ImageItem item;
item.image = image;
int newIndex = 1;
if (!m_images.empty())
{
newIndex = (m_images.rbegin()->first) + 1;
}
m_images[newIndex] = qMove(item);
// Insert image item
PageGroupItem newItem;
newItem.groups.reserve(1);
PageGroupItem::GroupItem groupItem;
groupItem.imageIndex = newIndex;
groupItem.rotatedPageDimensionsMM = m_images[newIndex].image.size() * 0.1;
groupItem.pageType = PT_Image;
newItem.groups.push_back(qMove(groupItem));
updateItemCaptionAndTags(newItem);
int insertRow = index.isValid() ? index.row() + 1 : int(m_pageGroupItems.size());
beginInsertRows(QModelIndex(), insertRow, insertRow);
m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem));
endInsertRows();
return newIndex;
}
return -1;
}
const PageGroupItem* PageItemModel::getItem(const QModelIndex& index) const
{
if (index.isValid())
{
if (index.row() < m_pageGroupItems.size())
{
return &m_pageGroupItems.at(index.row());
}
}
return nullptr;
}
PageGroupItem* PageItemModel::getItem(const QModelIndex& index)
{
if (index.isValid())
{
if (index.row() < m_pageGroupItems.size())
{
return &m_pageGroupItems.at(index.row());
}
}
return nullptr;
}
bool PageItemModel::isGrouped(const QModelIndexList& indices) const
{
for (const QModelIndex& index : indices)
{
if (const PageGroupItem* item = getItem(index))
{
if (item->isGrouped())
{
return true;
}
}
}
return false;
}
QItemSelection PageItemModel::getSelectionEven() const
{
return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.pageIndex % 2 == 1; });
}
QItemSelection PageItemModel::getSelectionOdd() const
{
return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.pageIndex % 2 == 0; });
}
QItemSelection PageItemModel::getSelectionPortrait() const
{
return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.rotatedPageDimensionsMM.width() <= groupItem.rotatedPageDimensionsMM.height(); });
}
QItemSelection PageItemModel::getSelectionLandscape() const
{
return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.rotatedPageDimensionsMM.width() >= groupItem.rotatedPageDimensionsMM.height(); });
}
void PageItemModel::group(const QModelIndexList& list)
{
if (list.isEmpty())
{
return;
}
Modifier modifier(this);
std::vector<size_t> groupedIndices;
groupedIndices.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(groupedIndices), [](const auto& index) { return index.row(); });
std::sort(groupedIndices.begin(), groupedIndices.end());
std::vector<PageGroupItem> newPageGroupItems;
std::vector<PageGroupItem::GroupItem> newGroups;
newPageGroupItems.reserve(m_pageGroupItems.size());
for (size_t i = 0; i < m_pageGroupItems.size(); ++i)
{
const PageGroupItem& item = m_pageGroupItems[i];
if (std::binary_search(groupedIndices.cbegin(), groupedIndices.cend(), i))
{
newGroups.insert(newGroups.end(), item.groups.begin(), item.groups.end());
}
else
{
newPageGroupItems.push_back(item);
}
}
PageGroupItem newItem;
newItem.groups = qMove(newGroups);
updateItemCaptionAndTags(newItem);
newPageGroupItems.insert(std::next(newPageGroupItems.begin(), groupedIndices.front()), qMove(newItem));
if (newPageGroupItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(newPageGroupItems);
endResetModel();
}
}
void PageItemModel::ungroup(const QModelIndexList& list)
{
if (list.isEmpty())
{
return;
}
Modifier modifier(this);
std::vector<size_t> ungroupedIndices;
ungroupedIndices.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(ungroupedIndices), [](const auto& index) { return index.row(); });
std::sort(ungroupedIndices.begin(), ungroupedIndices.end());
std::vector<PageGroupItem> newPageGroupItems;
newPageGroupItems.reserve(m_pageGroupItems.size());
for (size_t i = 0; i < m_pageGroupItems.size(); ++i)
{
const PageGroupItem& item = m_pageGroupItems[i];
if (item.isGrouped() && std::binary_search(ungroupedIndices.cbegin(), ungroupedIndices.cend(), i))
{
for (const PageGroupItem::GroupItem& groupItem : item.groups)
{
PageGroupItem newItem;
newItem.groups = { groupItem };
updateItemCaptionAndTags(newItem);
newPageGroupItems.push_back(qMove(newItem));
}
}
else
{
newPageGroupItems.push_back(item);
}
}
if (newPageGroupItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(newPageGroupItems);
endResetModel();
}
}
QModelIndexList PageItemModel::restoreRemovedItems()
{
QModelIndexList result;
if (m_trashBin.empty())
{
// Jakub Melka: nothing to do
return result;
}
Modifier modifier(this);
const int trashBinSize = int(m_trashBin.size());
const int rowCount = this->rowCount(QModelIndex());
beginInsertRows(QModelIndex(), rowCount, rowCount + trashBinSize - 1);
m_pageGroupItems.insert(m_pageGroupItems.end(), std::make_move_iterator(m_trashBin.begin()), std::make_move_iterator(m_trashBin.end()));
m_trashBin.clear();
endInsertRows();
result.reserve(trashBinSize);
for (int i = rowCount; i < rowCount + trashBinSize; ++i)
{
result << index(i, 0, QModelIndex());
}
return result;
}
QModelIndexList PageItemModel::cloneSelection(const QModelIndexList& list)
{
QModelIndexList result;
if (list.empty())
{
// Jakub Melka: nothing to do
return result;
}
Modifier modifier(this);
std::vector<int> rows;
rows.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); });
std::vector<PageGroupItem> clonedGroups;
clonedGroups.reserve(rows.size());
for (int row : rows)
{
clonedGroups.push_back(m_pageGroupItems[row]);
}
const int insertRow = rows.back() + 1;
const int lastRow = insertRow + int(rows.size());
beginResetModel();
m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), clonedGroups.begin(), clonedGroups.end());
endResetModel();
result.reserve(int(rows.size()));
for (int i = insertRow; i < lastRow; ++i)
{
result << index(i, 0, QModelIndex());
}
return result;
}
void PageItemModel::removeSelection(const QModelIndexList& list)
{
if (list.empty())
{
// Jakub Melka: nothing to do
return;
}
Modifier modifier(this);
std::vector<int> rows;
rows.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); });
std::sort(rows.begin(), rows.end(), std::greater<int>());
beginResetModel();
for (int row : rows)
{
m_trashBin.emplace_back(qMove(m_pageGroupItems[row]));
m_pageGroupItems.erase(std::next(m_pageGroupItems.begin(), row));
}
endResetModel();
}
void PageItemModel::insertEmptyPage(const QModelIndexList& list)
{
Modifier modifier(this);
if (list.isEmpty())
{
insertEmptyPage(QModelIndex());
}
else
{
QModelIndexList listCopy = list;
std::sort(listCopy.begin(), listCopy.end());
std::reverse(listCopy.begin(), listCopy.end());
for (const QModelIndex& index: listCopy)
{
insertEmptyPage(index);
}
}
}
void PageItemModel::insertEmptyPage(const QModelIndex& index)
{
int insertRow = index.isValid()? index.row() + 1 : int(m_pageGroupItems.size());
const int templateRow = index.isValid() ? index.row() : int(m_pageGroupItems.size()) - 1;
const bool isTemplateRowValid = templateRow > -1;
PageGroupItem::GroupItem groupItem;
groupItem.pageAdditionalRotation = isTemplateRowValid ? m_pageGroupItems[templateRow].groups.back().pageAdditionalRotation : pdf::PageRotation::None;
groupItem.pageType = PT_Empty;
groupItem.rotatedPageDimensionsMM = isTemplateRowValid ? m_pageGroupItems[templateRow].groups.back().rotatedPageDimensionsMM : QSizeF(210, 297);
PageGroupItem blankPageItem;
blankPageItem.groups.push_back(groupItem);
updateItemCaptionAndTags(blankPageItem);
beginInsertRows(QModelIndex(), insertRow, insertRow);
m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), std::move(blankPageItem));
endInsertRows();
}
std::vector<PageGroupItem::GroupItem> PageItemModel::extractItems(std::vector<PageGroupItem>& items,
const QModelIndexList& selection) const
{
std::vector<PageGroupItem::GroupItem> extractedItems;
std::vector<int> rows;
rows.reserve(selection.size());
std::transform(selection.cbegin(), selection.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); });
std::sort(rows.begin(), rows.end(), std::greater<int>());
for (int row : rows)
{
extractedItems.insert(extractedItems.begin(), items[row].groups.cbegin(), items[row].groups.cend());
items.erase(std::next(items.begin(), row));
}
return extractedItems;
}
void PageItemModel::rotateLeft(const QModelIndexList& list)
{
if (list.isEmpty())
{
return;
}
Modifier modifier(this);
int rowMin = list.front().row();
int rowMax = list.front().row();
for (const QModelIndex& index : list)
{
if (PageGroupItem* item = getItem(index))
{
item->rotateLeft();
}
rowMin = qMin(rowMin, index.row());
rowMax = qMax(rowMax, index.row());
}
rowMin = qMax(rowMin, 0);
rowMax = qMin(rowMax, rowCount(QModelIndex()) - 1);
Q_EMIT dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex()));
}
void PageItemModel::rotateRight(const QModelIndexList& list)
{
if (list.isEmpty())
{
return;
}
Modifier modifier(this);
int rowMin = list.front().row();
int rowMax = list.front().row();
for (const QModelIndex& index : list)
{
if (PageGroupItem* item = getItem(index))
{
item->rotateRight();
}
rowMin = qMin(rowMin, index.row());
rowMax = qMax(rowMax, index.row());
}
rowMin = qMax(rowMin, 0);
rowMax = qMin(rowMax, rowCount(QModelIndex()) - 1);
Q_EMIT dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex()));
}
PageItemModel::SelectionInfo PageItemModel::getSelectionInfo(const QModelIndexList& list) const
{
SelectionInfo info;
std::set<int> documents;
std::set<int> images;
for (const QModelIndex& index : list)
{
const PageGroupItem* item = getItem(index);
if (!item)
{
continue;
}
for (const PageGroupItem::GroupItem& groupItem : item->groups)
{
switch (groupItem.pageType)
{
case pdfpagemaster::PT_DocumentPage:
documents.insert(groupItem.documentIndex);
break;
case pdfpagemaster::PT_Image:
images.insert(groupItem.imageIndex);
break;
case pdfpagemaster::PT_Empty:
++info.blankPageCount;
break;
default:
Q_ASSERT(false);
break;
}
}
}
info.documentCount = int(documents.size());
info.imageCount = int(images.size());
info.firstDocumentIndex = !documents.empty() ? *documents.begin() : 0;
return info;
}
void PageItemModel::regroupEvenOdd(const QModelIndexList& list)
{
if (list.empty())
{
return;
}
Modifier modifier(this);
std::vector<PageGroupItem> pageGroupItems = m_pageGroupItems;
std::vector<PageGroupItem::GroupItem> extractedItems = extractItems(pageGroupItems, list);
auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [](const auto& item) { return item.pageIndex % 2 == 1; });
std::vector<PageGroupItem::GroupItem> oddItems(extractedItems.begin(), it);
std::vector<PageGroupItem::GroupItem> evenItems(it, extractedItems.end());
if (!oddItems.empty())
{
PageGroupItem item;
item.groups = std::move(oddItems);
updateItemCaptionAndTags(item);
pageGroupItems.emplace_back(std::move(item));
}
if (!evenItems.empty())
{
PageGroupItem item;
item.groups = std::move(evenItems);
updateItemCaptionAndTags(item);
pageGroupItems.emplace_back(std::move(item));
}
if (pageGroupItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(pageGroupItems);
endResetModel();
}
}
void PageItemModel::regroupPaired(const QModelIndexList& list)
{
if (list.empty())
{
return;
}
Modifier modifier(this);
std::vector<PageGroupItem> pageGroupItems = m_pageGroupItems;
std::vector<PageGroupItem::GroupItem> extractedItems = extractItems(pageGroupItems, list);
auto it = extractedItems.begin();
while (it != extractedItems.cend())
{
PageGroupItem item;
item.groups = { *it++ };
if (it != extractedItems.cend())
{
item.groups.emplace_back(std::move(*it++));
}
updateItemCaptionAndTags(item);
pageGroupItems.emplace_back(std::move(item));
}
if (pageGroupItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(pageGroupItems);
endResetModel();
}
}
void PageItemModel::regroupOutline(const QModelIndexList& list, const std::vector<pdf::PDFInteger>& indices)
{
if (list.empty())
{
return;
}
Modifier modifier(this);
std::vector<PageGroupItem> pageGroupItems = m_pageGroupItems;
std::vector<PageGroupItem::GroupItem> extractedItems = extractItems(pageGroupItems, list);
std::sort(extractedItems.begin(), extractedItems.end(), [](const auto& l, const auto& r) { return l.pageIndex < r.pageIndex; });
PageGroupItem item;
for (auto it = extractedItems.begin(); it != extractedItems.cend(); ++it)
{
PageGroupItem::GroupItem groupItem = *it;
if (std::binary_search(indices.cbegin(), indices.cend(), groupItem.pageIndex) &&
!item.groups.empty())
{
updateItemCaptionAndTags(item);
pageGroupItems.emplace_back(std::move(item));
item = PageGroupItem();
}
item.groups.push_back(groupItem);
}
if (!item.groups.empty())
{
updateItemCaptionAndTags(item);
pageGroupItems.emplace_back(std::move(item));
item = PageGroupItem();
}
if (pageGroupItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(pageGroupItems);
endResetModel();
}
}
void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool reversed)
{
if (list.empty())
{
return;
}
Modifier modifier(this);
std::vector<PageGroupItem> pageGroupItems = m_pageGroupItems;
std::vector<PageGroupItem::GroupItem> extractedItems = extractItems(pageGroupItems, list);
const int documentIndex = extractedItems.front().documentIndex;
auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [documentIndex](const auto& item) { return item.documentIndex == documentIndex; });
std::vector<PageGroupItem::GroupItem> firstDocItems(extractedItems.begin(), it);
std::vector<PageGroupItem::GroupItem> secondDocItems(it, extractedItems.end());
if (reversed)
{
std::reverse(secondDocItems.begin(), secondDocItems.end());
}
auto itF = firstDocItems.begin();
auto itS = secondDocItems.begin();
PageGroupItem item;
while (itF != firstDocItems.cend() || itS != secondDocItems.cend())
{
if (itF != firstDocItems.cend())
{
item.groups.emplace_back(std::move(*itF++));
}
if (itS != secondDocItems.cend())
{
item.groups.emplace_back(std::move(*itS++));
}
}
updateItemCaptionAndTags(item);
pageGroupItems.emplace_back(std::move(item));
if (pageGroupItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(pageGroupItems);
endResetModel();
}
}
void PageItemModel::undo()
{
performUndoRedo(m_undoSteps, m_redoSteps);
}
void PageItemModel::performUndoRedo(std::vector<PageItemModel::UndoRedoStep>& load,
std::vector<PageItemModel::UndoRedoStep>& save)
{
if (load.empty())
{
return;
}
save.emplace_back(getCurrentStep());
UndoRedoStep step = std::move(load.back());
load.pop_back();
updateUndoRedoSteps();
beginResetModel();
m_pageGroupItems = std::move(step.pageGroupItems);
m_trashBin = std::move(step.trashBin);
endResetModel();
}
void PageItemModel::redo()
{
performUndoRedo(m_redoSteps, m_undoSteps);
}
QItemSelection PageItemModel::getSelectionImpl(std::function<bool (const PageGroupItem::GroupItem&)> filter) const
{
QItemSelection result;
for (int i = 0; i < rowCount(QModelIndex()); ++i)
{
QModelIndex rowIndex = index(i, 0, QModelIndex());
if (const PageGroupItem* item = getItem(rowIndex))
{
bool isApplied = false;
for (const auto& groupItem : item->groups)
{
if (filter(groupItem))
{
isApplied = true;
break;
}
}
if (isApplied)
{
result.select(rowIndex, rowIndex);
}
}
}
return result;
}
void PageItemModel::updateUndoRedoSteps()
{
while (m_undoSteps.size() > MAX_UNDO_REDO_STEPS)
{
m_undoSteps.erase(m_undoSteps.begin());
}
while (m_redoSteps.size() > MAX_UNDO_REDO_STEPS)
{
m_redoSteps.erase(m_redoSteps.begin());
}
}
void PageItemModel::clearUndoRedo()
{
m_undoSteps.clear();
m_redoSteps.clear();
}
void PageItemModel::createDocumentGroup(int index, const QModelIndex& insertIndex)
{
const DocumentItem& item = m_documents.at(index);
const pdf::PDFInteger pageCount = item.document.getCatalog()->getPageCount();
pdf::PDFClosedIntervalSet pageSet;
pageSet.addInterval(1, pageCount);
PageGroupItem newItem;
newItem.groupName = getGroupNameFromDocument(index);
newItem.pagesCaption = pageSet.toText(true);
if (pageCount > 1)
{
newItem.tags = QStringList() << QString("#00CC00@+%1").arg(pageCount - 1);
}
newItem.groups.reserve(pageCount);
for (pdf::PDFInteger i = 1; i <= pageCount; ++i)
{
PageGroupItem::GroupItem groupItem;
groupItem.documentIndex = index;
groupItem.pageIndex = i;
groupItem.rotatedPageDimensionsMM = item.document.getCatalog()->getPage(i - 1)->getRotatedMediaBoxMM().size();
newItem.groups.push_back(qMove(groupItem));
}
int insertRow = rowCount(QModelIndex());
if (insertIndex.isValid())
{
insertRow = insertIndex.row() + 1;
}
beginInsertRows(QModelIndex(), insertRow, insertRow);
m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem));
endInsertRows();
}
QString PageItemModel::getGroupNameFromDocument(int index) const
{
if (index == -1)
{
return tr("Page Group");
}
const DocumentItem& item = m_documents.at(index);
QString title = item.document.getInfo()->title;
if (!title.isEmpty())
{
return title;
}
QFileInfo fileInfo(item.fileName);
return fileInfo.fileName();
}
void PageItemModel::updateItemCaptionAndTags(PageGroupItem& item) const
{
std::set<int> documentIndices = item.getDocumentIndices();
const size_t pageCount = item.groups.size();
if (documentIndices.size() == 1)
{
pdf::PDFClosedIntervalSet pageSet;
for (const auto& groupItem : item.groups)
{
if (groupItem.pageIndex != -1)
{
pageSet.addInterval(groupItem.pageIndex, groupItem.pageIndex);
}
}
item.groupName = getGroupNameFromDocument(*documentIndices.begin());
item.pagesCaption = pageSet.toText(true);
}
else
{
item.groupName = tr("Document collection");
item.pagesCaption = tr("Page Count: %1").arg(item.groups.size());
}
bool hasImages = false;
bool hasEmptyPage = false;
size_t imageCount = 0;
size_t emptyPageCount = 0;
for (const PageGroupItem::GroupItem& group : item.groups)
{
switch (group.pageType)
{
case pdfpagemaster::PT_DocumentPage:
break;
case pdfpagemaster::PT_Image:
hasImages = true;
++imageCount;
break;
case pdfpagemaster::PT_Empty:
hasEmptyPage = true;
++emptyPageCount;
break;
case pdfpagemaster::PT_Last:
Q_ASSERT(false);
break;
}
}
if (imageCount == pageCount)
{
item.groupName = imageCount == 1 ? tr("Image") : tr("Images");
}
if (emptyPageCount == pageCount)
{
item.groupName = emptyPageCount == 1 ? tr("Blank Page") : tr("Blank Pages");
}
item.tags.clear();
if (pageCount > 1)
{
item.tags << QString("#00CC00@+%1").arg(pageCount - 1);
}
if (documentIndices.size() > 1)
{
item.tags << tr("#BBBB00@Collection");
}
if (hasEmptyPage)
{
item.tags << tr("#D98335@Blank");
}
if (hasImages)
{
item.tags << tr("#24A5EA@Image");
}
}
std::set<int> PageGroupItem::getDocumentIndices() const
{
std::set<int> result;
for (const auto& groupItem : groups)
{
result.insert(groupItem.documentIndex);
}
return result;
}
void PageGroupItem::rotateLeft()
{
for (auto& groupItem : groups)
{
groupItem.pageAdditionalRotation = pdf::getPageRotationRotatedLeft(groupItem.pageAdditionalRotation);
}
}
void PageGroupItem::rotateRight()
{
for (auto& groupItem : groups)
{
groupItem.pageAdditionalRotation = pdf::getPageRotationRotatedRight(groupItem.pageAdditionalRotation);
}
}
QStringList PageItemModel::mimeTypes() const
{
return { getMimeDataType() };
}
QMimeData* PageItemModel::mimeData(const QModelIndexList& indexes) const
{
if (indexes.isEmpty())
{
return nullptr;
}
QMimeData* mimeData = new QMimeData;
QByteArray serializedData;
{
QDataStream stream(&serializedData, QIODevice::WriteOnly);
for (const QModelIndex& index : indexes)
{
stream << index.row();
}
}
mimeData->setData(getMimeDataType(), serializedData);
return mimeData;
}
bool PageItemModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(row);
Q_UNUSED(column);
Q_UNUSED(parent);
switch (action)
{
case Qt::CopyAction:
case Qt::MoveAction:
case Qt::IgnoreAction:
break;
case Qt::LinkAction:
case Qt::TargetMoveAction:
return false;
default:
Q_ASSERT(false);
break;
}
return data && data->hasFormat(getMimeDataType());
}
bool PageItemModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
{
if (action == Qt::IgnoreAction)
{
return true;
}
if (!data->hasFormat(getMimeDataType()))
{
return false;
}
if (column > 0)
{
return false;
}
Modifier modifier(this);
int insertRow = rowCount(QModelIndex());
if (row > -1)
{
insertRow = row;
}
else if (parent.isValid())
{
insertRow = parent.row();
}
std::vector<int> rows;
QByteArray serializedData = data->data(getMimeDataType());
QDataStream stream(&serializedData, QIODevice::ReadOnly);
while (!stream.atEnd())
{
int currentRow = -1;
stream >> currentRow;
rows.push_back(currentRow);
}
std::sort(rows.begin(), rows.end());
// Sanity checks on rows
if (rows.empty())
{
return false;
}
if (rows.front() < 0 || rows.back() >= rowCount(QModelIndex()))
{
return false;
}
std::vector<PageGroupItem> newItems = m_pageGroupItems;
std::vector<PageGroupItem> workItems;
// When moving, update insert row
if (action == Qt::MoveAction)
{
const int originalInsertRow = insertRow;
if (rows.front() < originalInsertRow)
{
++insertRow;
}
for (int currentRow : rows)
{
if (currentRow < originalInsertRow)
{
--insertRow;
}
}
}
workItems.reserve(rows.size());
for (int currentRow : rows)
{
workItems.push_back(m_pageGroupItems[currentRow]);
}
// When move, delete old page items
if (action == Qt::MoveAction)
{
for (auto it = rows.rbegin(); it != rows.rend(); ++it)
{
newItems.erase(std::next(newItems.begin(), *it));
}
}
// Insert work items at a given position
newItems.insert(std::next(newItems.begin(), insertRow), workItems.begin(), workItems.end());
if (newItems != m_pageGroupItems)
{
beginResetModel();
m_pageGroupItems = std::move(newItems);
endResetModel();
}
return true;
}
Qt::DropActions PageItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions PageItemModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags PageItemModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags flags = BaseClass::flags(index);
if (index.isValid())
{
flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
return flags;
}
std::vector<std::vector<pdf::PDFDocumentManipulator::AssembledPage>> PageItemModel::getAssembledPages(AssembleMode mode) const
{
std::vector<std::vector<pdf::PDFDocumentManipulator::AssembledPage>> result;
auto createAssembledPage = [this](const PageGroupItem::GroupItem& item)
{
pdf::PDFDocumentManipulator::AssembledPage assembledPage;
assembledPage.documentIndex = item.documentIndex;
assembledPage.imageIndex = item.imageIndex;
assembledPage.pageIndex = item.pageIndex;
if (assembledPage.pageIndex > 0)
{
--assembledPage.pageIndex;
}
pdf::PageRotation originalPageRotation = pdf::PageRotation::None;
if (item.pageType == PT_DocumentPage)
{
auto it = m_documents.find(item.documentIndex);
if (it != m_documents.cend())
{
const pdf::PDFPage* page = it->second.document.getCatalog()->getPage(item.pageIndex - 1);
originalPageRotation = page->getPageRotation();
}
}
assembledPage.pageRotation = pdf::getPageRotationCombined(originalPageRotation, item.pageAdditionalRotation);
assembledPage.pageSize = item.rotatedPageDimensionsMM;
return assembledPage;
};
switch (mode)
{
case AssembleMode::Unite:
{
std::vector<pdf::PDFDocumentManipulator::AssembledPage> unitedDocument;
for (const auto& pageGroupItem : m_pageGroupItems)
{
for (const auto& groupItem : pageGroupItem.groups)
{
unitedDocument.emplace_back(createAssembledPage(groupItem));
}
}
result.emplace_back(std::move(unitedDocument));
break;
}
case AssembleMode::Separate:
{
for (const auto& pageGroupItem : m_pageGroupItems)
{
for (const auto& groupItem : pageGroupItem.groups)
{
result.emplace_back().emplace_back(createAssembledPage(groupItem));
}
}
break;
}
case AssembleMode::SeparateGrouped:
{
for (const auto& pageGroupItem : m_pageGroupItems)
{
std::vector<pdf::PDFDocumentManipulator::AssembledPage> groupDocument;
for (const auto& groupItem : pageGroupItem.groups)
{
groupDocument.emplace_back(createAssembledPage(groupItem));
}
result.emplace_back(std::move(groupDocument));
}
break;
}
default:
{
Q_ASSERT(false);
break;
}
}
// Remove empty documents
result.erase(std::remove_if(result.begin(), result.end(), [](const auto& pages) { return pages.empty(); }), result.end());
return result;
}
void PageItemModel::clear()
{
beginResetModel();
m_pageGroupItems.clear();
m_documents.clear();
m_trashBin.clear();
clearUndoRedo();
endResetModel();
}
PageItemModel::Modifier::Modifier(PageItemModel* model) :
m_model(model)
{
Q_ASSERT(model);
m_stateBeforeModification = m_model->getCurrentStep();
}
PageItemModel::Modifier::~Modifier()
{
UndoRedoStep stateAfterModification = m_model->getCurrentStep();
if (m_stateBeforeModification != stateAfterModification)
{
m_model->m_undoSteps.emplace_back(std::move(m_stateBeforeModification));
m_model->m_redoSteps.clear();
m_model->updateUndoRedoSteps();
}
}
} // namespace pdfpagemaster