// 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 . #include "pageitemmodel.h" #include #include #include namespace pdfdocpage { 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(); } switch (role) { case Qt::DisplayRole: return m_pageGroupItems.at(index.row()).groupName; 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; } 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 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 newPageGroupItems; std::vector 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 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 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 rows; rows.reserve(list.size()); std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); std::vector 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 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()); 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 PageItemModel::extractItems(std::vector& items, const QModelIndexList& selection) const { std::vector extractedItems; std::vector 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()); 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); 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); emit dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex())); } PageItemModel::SelectionInfo PageItemModel::getSelectionInfo(const QModelIndexList& list) const { SelectionInfo info; std::set documents; std::set 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 pdfdocpage::PT_DocumentPage: documents.insert(groupItem.documentIndex); break; case pdfdocpage::PT_Image: images.insert(groupItem.imageIndex); break; case pdfdocpage::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 pageGroupItems = m_pageGroupItems; std::vector extractedItems = extractItems(pageGroupItems, list); auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [](const auto& item) { return item.pageIndex % 2 == 1; }); std::vector oddItems(extractedItems.begin(), it); std::vector 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 pageGroupItems = m_pageGroupItems; std::vector 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::regroupBookmarks(const QModelIndexList& list, const std::vector& indices) { if (list.empty()) { return; } Modifier modifier(this); std::vector pageGroupItems = m_pageGroupItems; std::vector 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 pageGroupItems = m_pageGroupItems; std::vector 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 firstDocItems(extractedItems.begin(), it); std::vector 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& load, std::vector& 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 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 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 pdfdocpage::PT_DocumentPage: break; case pdfdocpage::PT_Image: hasImages = true; ++imageCount; break; case pdfdocpage::PT_Empty: hasEmptyPage = true; ++emptyPageCount; break; case pdfdocpage::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 PageGroupItem::getDocumentIndices() const { std::set 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 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 newItems = m_pageGroupItems; std::vector 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> PageItemModel::getAssembledPages(AssembleMode mode) const { std::vector> 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 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 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 pdfdocpage