diff --git a/Pdf4QtDocPageOrganizer/mainwindow.cpp b/Pdf4QtDocPageOrganizer/mainwindow.cpp index 3db4880..2763033 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.cpp +++ b/Pdf4QtDocPageOrganizer/mainwindow.cpp @@ -253,6 +253,7 @@ bool MainWindow::canPerformOperation(Operation operation) const { QModelIndexList selection = ui->documentItemsView->selectionModel()->selection().indexes(); const bool isSelected = !selection.isEmpty(); + const bool isMultiSelected = selection.size() > 1; const bool isModelEmpty = m_model->rowCount(QModelIndex()) == 0; switch (operation) @@ -263,7 +264,7 @@ bool MainWindow::canPerformOperation(Operation operation) const return isSelected; case Operation::RestoreRemovedItems: - return !m_trashBin.empty(); + return !m_model->isTrashBinEmpty(); case Operation::Cut: case Operation::Copy: @@ -277,7 +278,7 @@ bool MainWindow::canPerformOperation(Operation operation) const return isSelected; case Operation::Group: - return isSelected; + return isMultiSelected; case Operation::Ungroup: return m_model->isGrouped(selection); @@ -323,10 +324,15 @@ void MainWindow::performOperation(Operation operation) case Operation::Paste: case Operation::RotateLeft: case Operation::RotateRight: + Q_ASSERT(false); break; case Operation::Group: + m_model->group(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + case Operation::Ungroup: + m_model->ungroup(ui->documentItemsView->selectionModel()->selection().indexes()); break; case Operation::SelectNone: diff --git a/Pdf4QtDocPageOrganizer/mainwindow.h b/Pdf4QtDocPageOrganizer/mainwindow.h index 1f179f0..6cfad83 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.h +++ b/Pdf4QtDocPageOrganizer/mainwindow.h @@ -102,7 +102,6 @@ private: PageItemDelegate* m_delegate; Settings m_settings; QSignalMapper m_mapper; - std::vector m_trashBin; }; } // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp index ce448ff..00b982a 100644 --- a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp +++ b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp @@ -125,6 +125,19 @@ const PageGroupItem* PageItemModel::getItem(const QModelIndex& index) const 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) @@ -161,6 +174,88 @@ 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; + } + + 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; + } + + 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(); + } +} + QItemSelection PageItemModel::getSelectionImpl(std::function filter) const { QItemSelection result; @@ -235,5 +330,50 @@ QString PageItemModel::getGroupNameFromDocument(int index) const 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) + { + 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()); + } + + item.tags.clear(); + if (pageCount > 1) + { + item.tags << QString("#00CC00@+%1").arg(pageCount - 1); + } + if (documentIndices.size() > 1) + { + item.tags << QString("#BBBB00@Collection"); + } +} + +std::set PageGroupItem::getDocumentIndices() const +{ + std::set result; + + for (const auto& groupItem : groups) + { + result.insert(groupItem.documentIndex); + } + + return result; +} + } // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.h b/Pdf4QtDocPageOrganizer/pageitemmodel.h index 570d4bc..95876b8 100644 --- a/Pdf4QtDocPageOrganizer/pageitemmodel.h +++ b/Pdf4QtDocPageOrganizer/pageitemmodel.h @@ -34,6 +34,8 @@ struct PageGroupItem struct GroupItem { + auto operator<=>(const GroupItem&) const = default; + int documentIndex = 0; pdf::PDFInteger pageIndex; QSizeF rotatedPageDimensionsMM; @@ -41,7 +43,11 @@ struct PageGroupItem std::vector groups; + auto operator<=>(const PageGroupItem&) const = default; + bool isGrouped() const { return groups.size() > 1; } + + std::set getDocumentIndices() const; }; struct DocumentItem @@ -77,21 +83,35 @@ public: /// \param index Index const PageGroupItem* getItem(const QModelIndex& index) const; + /// Returns item at a given index. If item doesn't exist, + /// then nullptr is returned. + /// \param index Index + PageGroupItem* getItem(const QModelIndex& index); + /// Returns true, if grouped item exists in the indices bool isGrouped(const QModelIndexList& indices) const; + /// Returns true, if trash bin is empty + bool isTrashBinEmpty() const { return m_trashBin.empty(); } + QItemSelection getSelectionEven() const; QItemSelection getSelectionOdd() const; QItemSelection getSelectionPortrait() const; QItemSelection getSelectionLandscape() const; + void group(const QModelIndexList& list); + void ungroup(const QModelIndexList& list); + private: void createDocumentGroup(int index); QString getGroupNameFromDocument(int index) const; + void updateItemCaptionAndTags(PageGroupItem& item) const; + QItemSelection getSelectionImpl(std::function filter) const; std::vector m_pageGroupItems; std::map m_documents; + std::vector m_trashBin; }; } // namespace pdfdocpage