diff --git a/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro b/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro index 69d1e45..b8faee2 100644 --- a/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro +++ b/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro @@ -24,7 +24,7 @@ VERSION = 1.0.0 RC_ICONS = $$PWD/app-icon.ico -QMAKE_TARGET_DESCRIPTION = "PDF document page organizer" +QMAKE_TARGET_DESCRIPTION = "PDF Document Page Organizer" QMAKE_TARGET_COPYRIGHT = "(c) Jakub Melka 2018-2021" DEFINES += QT_DEPRECATED_WARNINGS diff --git a/Pdf4QtDocPageOrganizer/mainwindow.cpp b/Pdf4QtDocPageOrganizer/mainwindow.cpp index 0456c2d..4e3fd2b 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.cpp +++ b/Pdf4QtDocPageOrganizer/mainwindow.cpp @@ -83,6 +83,12 @@ MainWindow::MainWindow(QWidget* parent) : ui->actionInsert_PDF->setData(int(Operation::InsertPDF)); ui->actionGet_Source->setData(int(Operation::GetSource)); ui->actionAbout->setData(int(Operation::About)); + ui->actionInvert_Selection->setData(int(Operation::InvertSelection)); + ui->actionRegroup_Even_Odd->setData(int(Operation::RegroupEvenOdd)); + ui->actionRegroup_by_Page_Pairs->setData(int(Operation::RegroupPaired)); + ui->actionRegroup_by_Bookmarks->setData(int(Operation::RegroupBookmarks)); + ui->actionRegroup_by_Alternating_Pages->setData(int(Operation::RegroupAlternatingPages)); + ui->actionRegroup_by_Alternating_Pages_Reversed_Order->setData(int(Operation::RegroupAlternatingPagesReversed)); QToolBar* mainToolbar = addToolBar(tr("Main")); mainToolbar->setObjectName("main_toolbar"); @@ -98,7 +104,10 @@ MainWindow::MainWindow(QWidget* parent) : insertToolbar->addActions({ ui->actionInsert_PDF, ui->actionInsert_Image, ui->actionInsert_Empty_Page }); QToolBar* selectToolbar = addToolBar(tr("Select")); selectToolbar->setObjectName("select_toolbar"); - selectToolbar->addActions({ ui->actionSelect_None, ui->actionSelect_All, ui->actionSelect_Even, ui->actionSelect_Odd, ui->actionSelect_Portrait, ui->actionSelect_Landscape }); + selectToolbar->addActions({ ui->actionSelect_None, ui->actionSelect_All, ui->actionSelect_Even, ui->actionSelect_Odd, ui->actionSelect_Portrait, ui->actionSelect_Landscape, ui->actionInvert_Selection }); + QToolBar* regroupToolbar = addToolBar(tr("Regroup")); + regroupToolbar->setObjectName("regroup_toolbar"); + regroupToolbar->addActions({ ui->actionRegroup_Even_Odd, ui->actionRegroup_by_Page_Pairs, ui->actionRegroup_by_Bookmarks, ui->actionRegroup_by_Alternating_Pages, ui->actionRegroup_by_Alternating_Pages_Reversed_Order }); QToolBar* zoomToolbar = addToolBar(tr("Zoom")); zoomToolbar->setObjectName("zoom_toolbar"); zoomToolbar->addActions({ ui->actionZoom_In, ui->actionZoom_Out }); @@ -111,7 +120,7 @@ MainWindow::MainWindow(QWidget* parent) : for (QToolBar* toolbar : toolbars) { toolbar->setIconSize(iconSize); - ui->menuWindow->addAction(toolbar->toggleViewAction()); + ui->menuToolbars->addAction(toolbar->toggleViewAction()); } connect(&m_mapper, QOverload::of(&QSignalMapper::mapped), this, &MainWindow::onMappedActionTriggered); @@ -341,6 +350,31 @@ bool MainWindow::canPerformOperation(Operation operation) const case Operation::About: return true; + case Operation::InvertSelection: + return !isModelEmpty; + + case Operation::RegroupEvenOdd: + { + PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); + return info.isDocumentOnly(); + } + + case Operation::RegroupPaired: + return !isModelEmpty && !selection.isEmpty(); + + case Operation::RegroupBookmarks: + { + PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); + return info.isSingleDocument(); + } + + case Operation::RegroupAlternatingPages: + case Operation::RegroupAlternatingPagesReversed: + { + PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); + return info.isTwoDocuments(); + } + default: Q_ASSERT(false); break; @@ -695,9 +729,58 @@ void MainWindow::performOperation(Operation operation) break; } + case Operation::InvertSelection: + { + QModelIndex rootIndex = ui->documentItemsView->rootIndex(); + + QModelIndex firstIndex = rootIndex.child(0, 0); + QModelIndex lastIndex = rootIndex.child(rootIndex.model()->rowCount() - 1, 0); + QItemSelection selection(firstIndex, lastIndex); + + ui->documentItemsView->selectionModel()->select(selection, QItemSelectionModel::Toggle); + break; + } + + case Operation::RegroupEvenOdd: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupEvenOdd(indexes); + break; + } + + case Operation::RegroupPaired: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupPaired(indexes); + break; + } + + case Operation::RegroupBookmarks: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupBookmarks(indexes); + break; + } + + case Operation::RegroupAlternatingPages: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupAlternatingPages(indexes, false); + break; + } + + case Operation::RegroupAlternatingPagesReversed: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupAlternatingPages(indexes, true); + break; + } + default: + { Q_ASSERT(false); break; + } } updateActions(); diff --git a/Pdf4QtDocPageOrganizer/mainwindow.h b/Pdf4QtDocPageOrganizer/mainwindow.h index 61f3456..d8dc717 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.h +++ b/Pdf4QtDocPageOrganizer/mainwindow.h @@ -69,6 +69,7 @@ public: SelectOdd, SelectPortrait, SelectLandscape, + InvertSelection, ZoomIn, ZoomOut, @@ -81,6 +82,12 @@ public: InsertEmptyPage, InsertPDF, + RegroupEvenOdd, + RegroupPaired, + RegroupBookmarks, + RegroupAlternatingPages, + RegroupAlternatingPagesReversed, + GetSource, About }; diff --git a/Pdf4QtDocPageOrganizer/mainwindow.ui b/Pdf4QtDocPageOrganizer/mainwindow.ui index 8061783..55f142d 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.ui +++ b/Pdf4QtDocPageOrganizer/mainwindow.ui @@ -96,6 +96,7 @@ + @@ -115,17 +116,28 @@ - + - Window + Toolbars + + + Regroup + + + + + + + + - + @@ -489,6 +501,60 @@ Ctrl+W + + + + :/pdfdocpage/resources/regroup-even-odd.svg:/pdfdocpage/resources/regroup-even-odd.svg + + + Regroup by Even/Odd Pages + + + + + + :/pdfdocpage/resources/regroup-pairs.svg:/pdfdocpage/resources/regroup-pairs.svg + + + Regroup by Page Pairs + + + + + + :/pdfdocpage/resources/regroup-bookmarks.svg:/pdfdocpage/resources/regroup-bookmarks.svg + + + Regroup by Bookmarks + + + + + + :/pdfdocpage/resources/regroup-alternating.svg:/pdfdocpage/resources/regroup-alternating.svg + + + Regroup by Alternating Pages + + + + + + :/pdfdocpage/resources/regroup-alternating-reversed.svg:/pdfdocpage/resources/regroup-alternating-reversed.svg + + + Regroup by Alternating Pages (Reversed Order) + + + + + + :/pdfdocpage/resources/invert-selection.svg:/pdfdocpage/resources/invert-selection.svg + + + Invert Selection + + diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp index 02332dc..834e526 100644 --- a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp +++ b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp @@ -435,6 +435,25 @@ void PageItemModel::insertEmptyPage(const QModelIndex& index) 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()) @@ -489,6 +508,176 @@ void PageItemModel::rotateRight(const QModelIndexList& list) 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()); + + return info; +} + +void PageItemModel::regroupEvenOdd(const QModelIndexList& list) +{ + if (list.empty()) + { + return; + } + + 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; + } + + 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) +{ + Q_ASSERT(false); +} + +void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool reversed) +{ + if (list.empty()) + { + return; + } + + 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(); + } +} + QItemSelection PageItemModel::getSelectionImpl(std::function filter) const { QItemSelection result; diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.h b/Pdf4QtDocPageOrganizer/pageitemmodel.h index e7cde6f..72c0347 100644 --- a/Pdf4QtDocPageOrganizer/pageitemmodel.h +++ b/Pdf4QtDocPageOrganizer/pageitemmodel.h @@ -32,7 +32,8 @@ enum PageType { PT_DocumentPage, PT_Image, - PT_Empty + PT_Empty, + PT_Last }; struct PageGroupItem @@ -167,12 +168,32 @@ public: const std::map& getDocuments() const { return m_documents; } const std::map& getImages() const { return m_images; } + struct SelectionInfo + { + int documentCount = 0; + int imageCount = 0; + int blankPageCount = 0; + + bool isDocumentOnly() const { return documentCount > 0 && imageCount == 0 && blankPageCount == 0; } + bool isSingleDocument() const { return isDocumentOnly() && documentCount == 1; } + bool isTwoDocuments() const { return isDocumentOnly() && documentCount == 2; } + }; + + SelectionInfo getSelectionInfo(const QModelIndexList& list) const; + + void regroupEvenOdd(const QModelIndexList& list); + void regroupPaired(const QModelIndexList& list); + void regroupBookmarks(const QModelIndexList& list); + void regroupAlternatingPages(const QModelIndexList& list, bool reversed); + private: void createDocumentGroup(int index, const QModelIndex& insertIndex); QString getGroupNameFromDocument(int index) const; void updateItemCaptionAndTags(PageGroupItem& item) const; void insertEmptyPage(const QModelIndex& index); + std::vector extractItems(std::vector& items, const QModelIndexList& selection) const; + QItemSelection getSelectionImpl(std::function filter) const; std::vector m_pageGroupItems; diff --git a/Pdf4QtDocPageOrganizer/resources.qrc b/Pdf4QtDocPageOrganizer/resources.qrc index 5d92f56..de380d4 100644 --- a/Pdf4QtDocPageOrganizer/resources.qrc +++ b/Pdf4QtDocPageOrganizer/resources.qrc @@ -30,5 +30,11 @@ resources/zoom-in.svg resources/zoom-out.svg resources/clear.svg + resources/invert-selection.svg + resources/regroup-alternating.svg + resources/regroup-alternating-reversed.svg + resources/regroup-bookmarks.svg + resources/regroup-even-odd.svg + resources/regroup-pairs.svg diff --git a/Pdf4QtDocPageOrganizer/resources/invert-selection.svg b/Pdf4QtDocPageOrganizer/resources/invert-selection.svg new file mode 100644 index 0000000..b78dde2 --- /dev/null +++ b/Pdf4QtDocPageOrganizer/resources/invert-selection.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtDocPageOrganizer/resources/regroup-alternating-reversed.svg b/Pdf4QtDocPageOrganizer/resources/regroup-alternating-reversed.svg new file mode 100644 index 0000000..950c57c --- /dev/null +++ b/Pdf4QtDocPageOrganizer/resources/regroup-alternating-reversed.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtDocPageOrganizer/resources/regroup-alternating.svg b/Pdf4QtDocPageOrganizer/resources/regroup-alternating.svg new file mode 100644 index 0000000..950c57c --- /dev/null +++ b/Pdf4QtDocPageOrganizer/resources/regroup-alternating.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtDocPageOrganizer/resources/regroup-bookmarks.svg b/Pdf4QtDocPageOrganizer/resources/regroup-bookmarks.svg new file mode 100644 index 0000000..950c57c --- /dev/null +++ b/Pdf4QtDocPageOrganizer/resources/regroup-bookmarks.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtDocPageOrganizer/resources/regroup-even-odd.svg b/Pdf4QtDocPageOrganizer/resources/regroup-even-odd.svg new file mode 100644 index 0000000..950c57c --- /dev/null +++ b/Pdf4QtDocPageOrganizer/resources/regroup-even-odd.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtDocPageOrganizer/resources/regroup-pairs.svg b/Pdf4QtDocPageOrganizer/resources/regroup-pairs.svg new file mode 100644 index 0000000..950c57c --- /dev/null +++ b/Pdf4QtDocPageOrganizer/resources/regroup-pairs.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp index 12603d7..176e5c1 100644 --- a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp +++ b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp @@ -343,7 +343,7 @@ PDFDocumentManipulator::ProcessedPages PDFDocumentManipulator::collectObjectsAnd auto referenceIt = references.begin(); for (auto currentIt = it; currentIt != itEnd; ++currentIt, ++referenceIt) { - it->second = *referenceIt; + currentIt->second = *referenceIt; } }