Group/Ungroup functionality

This commit is contained in:
Jakub Melka
2021-07-09 19:50:00 +02:00
parent c8be46a7b3
commit ee587d0809
4 changed files with 168 additions and 3 deletions

View File

@ -253,6 +253,7 @@ bool MainWindow::canPerformOperation(Operation operation) const
{ {
QModelIndexList selection = ui->documentItemsView->selectionModel()->selection().indexes(); QModelIndexList selection = ui->documentItemsView->selectionModel()->selection().indexes();
const bool isSelected = !selection.isEmpty(); const bool isSelected = !selection.isEmpty();
const bool isMultiSelected = selection.size() > 1;
const bool isModelEmpty = m_model->rowCount(QModelIndex()) == 0; const bool isModelEmpty = m_model->rowCount(QModelIndex()) == 0;
switch (operation) switch (operation)
@ -263,7 +264,7 @@ bool MainWindow::canPerformOperation(Operation operation) const
return isSelected; return isSelected;
case Operation::RestoreRemovedItems: case Operation::RestoreRemovedItems:
return !m_trashBin.empty(); return !m_model->isTrashBinEmpty();
case Operation::Cut: case Operation::Cut:
case Operation::Copy: case Operation::Copy:
@ -277,7 +278,7 @@ bool MainWindow::canPerformOperation(Operation operation) const
return isSelected; return isSelected;
case Operation::Group: case Operation::Group:
return isSelected; return isMultiSelected;
case Operation::Ungroup: case Operation::Ungroup:
return m_model->isGrouped(selection); return m_model->isGrouped(selection);
@ -323,10 +324,15 @@ void MainWindow::performOperation(Operation operation)
case Operation::Paste: case Operation::Paste:
case Operation::RotateLeft: case Operation::RotateLeft:
case Operation::RotateRight: case Operation::RotateRight:
Q_ASSERT(false);
break; break;
case Operation::Group: case Operation::Group:
m_model->group(ui->documentItemsView->selectionModel()->selection().indexes());
break;
case Operation::Ungroup: case Operation::Ungroup:
m_model->ungroup(ui->documentItemsView->selectionModel()->selection().indexes());
break; break;
case Operation::SelectNone: case Operation::SelectNone:

View File

@ -102,7 +102,6 @@ private:
PageItemDelegate* m_delegate; PageItemDelegate* m_delegate;
Settings m_settings; Settings m_settings;
QSignalMapper m_mapper; QSignalMapper m_mapper;
std::vector<PageGroupItem> m_trashBin;
}; };
} // namespace pdfdocpage } // namespace pdfdocpage

View File

@ -125,6 +125,19 @@ const PageGroupItem* PageItemModel::getItem(const QModelIndex& index) const
return nullptr; 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 bool PageItemModel::isGrouped(const QModelIndexList& indices) const
{ {
for (const QModelIndex& index : indices) 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(); }); 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<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;
}
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();
}
}
QItemSelection PageItemModel::getSelectionImpl(std::function<bool (const PageGroupItem::GroupItem&)> filter) const QItemSelection PageItemModel::getSelectionImpl(std::function<bool (const PageGroupItem::GroupItem&)> filter) const
{ {
QItemSelection result; QItemSelection result;
@ -235,5 +330,50 @@ QString PageItemModel::getGroupNameFromDocument(int index) const
return fileInfo.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)
{
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<int> PageGroupItem::getDocumentIndices() const
{
std::set<int> result;
for (const auto& groupItem : groups)
{
result.insert(groupItem.documentIndex);
}
return result;
}
} // namespace pdfdocpage } // namespace pdfdocpage

View File

@ -34,6 +34,8 @@ struct PageGroupItem
struct GroupItem struct GroupItem
{ {
auto operator<=>(const GroupItem&) const = default;
int documentIndex = 0; int documentIndex = 0;
pdf::PDFInteger pageIndex; pdf::PDFInteger pageIndex;
QSizeF rotatedPageDimensionsMM; QSizeF rotatedPageDimensionsMM;
@ -41,7 +43,11 @@ struct PageGroupItem
std::vector<GroupItem> groups; std::vector<GroupItem> groups;
auto operator<=>(const PageGroupItem&) const = default;
bool isGrouped() const { return groups.size() > 1; } bool isGrouped() const { return groups.size() > 1; }
std::set<int> getDocumentIndices() const;
}; };
struct DocumentItem struct DocumentItem
@ -77,21 +83,35 @@ public:
/// \param index Index /// \param index Index
const PageGroupItem* getItem(const QModelIndex& index) const; 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 /// Returns true, if grouped item exists in the indices
bool isGrouped(const QModelIndexList& indices) const; bool isGrouped(const QModelIndexList& indices) const;
/// Returns true, if trash bin is empty
bool isTrashBinEmpty() const { return m_trashBin.empty(); }
QItemSelection getSelectionEven() const; QItemSelection getSelectionEven() const;
QItemSelection getSelectionOdd() const; QItemSelection getSelectionOdd() const;
QItemSelection getSelectionPortrait() const; QItemSelection getSelectionPortrait() const;
QItemSelection getSelectionLandscape() const; QItemSelection getSelectionLandscape() const;
void group(const QModelIndexList& list);
void ungroup(const QModelIndexList& list);
private: private:
void createDocumentGroup(int index); void createDocumentGroup(int index);
QString getGroupNameFromDocument(int index) const; QString getGroupNameFromDocument(int index) const;
void updateItemCaptionAndTags(PageGroupItem& item) const;
QItemSelection getSelectionImpl(std::function<bool(const PageGroupItem::GroupItem&)> filter) const; QItemSelection getSelectionImpl(std::function<bool(const PageGroupItem::GroupItem&)> filter) const;
std::vector<PageGroupItem> m_pageGroupItems; std::vector<PageGroupItem> m_pageGroupItems;
std::map<int, DocumentItem> m_documents; std::map<int, DocumentItem> m_documents;
std::vector<PageGroupItem> m_trashBin;
}; };
} // namespace pdfdocpage } // namespace pdfdocpage