From 56f143eb824f554ecc74bec96ab5794289b3c0da Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 5 Aug 2024 09:01:49 +0200 Subject: [PATCH] Axivion: Implement multi sort for issues table The issue table can be sorted by multiple columns. The sorting itself happens on the server side, so it is only necessary to reflect the sort state and adapt the search according to the sort columns. Sorting may now happen the following way: * click sort ascending or descending marker to set the column as sort column or remove the marker if it is set * hold Shift while click appends the column to the current sort columns with the respective sort order or removes it from the list if this marker was set already Change-Id: I180f14bc6b4785d13a6fd4669830dc323c0c32a7 Reviewed-by: Jarek Kobus --- src/plugins/axivion/axivionoutputpane.cpp | 17 +++--- src/plugins/axivion/issueheaderview.cpp | 63 ++++++++++++++++------- src/plugins/axivion/issueheaderview.h | 9 ++-- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/plugins/axivion/axivionoutputpane.cpp b/src/plugins/axivion/axivionoutputpane.cpp index b72bc0e68fc..297f27e3138 100644 --- a/src/plugins/axivion/axivionoutputpane.cpp +++ b/src/plugins/axivion/axivionoutputpane.cpp @@ -701,13 +701,18 @@ IssueListSearch IssuesWidget::searchFromUi() const search.state = "added"; else if (m_removedFilter->isChecked()) search.state = "removed"; - if (int column = m_headerView->currentSortColumn() != -1) { - QTC_ASSERT(m_currentTableInfo, return search); - QTC_ASSERT((ulong)column < m_currentTableInfo->columns.size(), return search); - search.sort = m_currentTableInfo->columns.at(m_headerView->currentSortColumn()).key - + (m_headerView->currentSortOrder() == Qt::AscendingOrder ? " asc" : " desc"); - } + QTC_ASSERT(m_currentTableInfo, return search); + QString sort; + const QList> currentSortColumns = m_headerView->currentSortColumns(); + for (const auto &pair : currentSortColumns) { + QTC_ASSERT((ulong)pair.first < m_currentTableInfo->columns.size(), return search); + if (!sort.isEmpty()) + sort.append(','); + sort.append(m_currentTableInfo->columns.at(pair.first).key + + (pair.second == Qt::AscendingOrder ? " asc" : " desc")); + } + search.sort = sort; return search; } diff --git a/src/plugins/axivion/issueheaderview.cpp b/src/plugins/axivion/issueheaderview.cpp index 019763d548b..fd5375288d9 100644 --- a/src/plugins/axivion/issueheaderview.cpp +++ b/src/plugins/axivion/issueheaderview.cpp @@ -38,16 +38,20 @@ static QIcon iconForSorted(std::optional order) void IssueHeaderView::setColumnInfoList(const QList &infos) { m_columnInfoList = infos; - int oldIndex = m_currentSortIndex; - m_currentSortIndex = -1; - m_currentSortOrder.reset(); - if (oldIndex != -1) + const QList oldIndexes = m_currentSortIndexes; + m_currentSortIndexes.clear(); + for (int i = 0; i < infos.size(); ++i) + m_columnInfoList[i].sortOrder.reset(); + for (int oldIndex : oldIndexes) headerDataChanged(Qt::Horizontal, oldIndex, oldIndex); } -int IssueHeaderView::currentSortColumn() const +QList> IssueHeaderView::currentSortColumns() const { - return m_currentSortOrder ? m_currentSortIndex : -1; + QList> result; + for (int i : m_currentSortIndexes) + result.append({i, m_columnInfoList.at(i).sortOrder.value()}); + return result; } void IssueHeaderView::mousePressEvent(QMouseEvent *event) @@ -63,6 +67,7 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event) const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin; const int start = end - ICON_SIZE; m_maybeToggleSort = start < pos && end > pos; + m_withShift = event->modifiers() == Qt::ShiftModifier; } } QHeaderView::mousePressEvent(event); @@ -71,8 +76,10 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event) void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event) { bool dontSkip = !m_dragging && m_maybeToggleSort; + bool withShift = m_withShift && event->modifiers() == Qt::ShiftModifier; m_dragging = false; m_maybeToggleSort = false; + m_withShift = false; if (dontSkip) { const QPoint position = event->position().toPoint(); @@ -82,9 +89,9 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event) && logical > -1 && logical < m_columnInfoList.size()) { if (m_columnInfoList.at(logical).sortable) { // ignore non-sortable if (y < height() / 2) // TODO improve - onToggleSort(logical, Qt::AscendingOrder); + onToggleSort(logical, Qt::AscendingOrder, withShift); else - onToggleSort(logical, Qt::DescendingOrder); + onToggleSort(logical, Qt::DescendingOrder, withShift); } } } @@ -99,20 +106,34 @@ void IssueHeaderView::mouseMoveEvent(QMouseEvent *event) QHeaderView::mouseMoveEvent(event); } -void IssueHeaderView::onToggleSort(int index, Qt::SortOrder order) +void IssueHeaderView::onToggleSort(int index, Qt::SortOrder order, bool multi) { - if (m_currentSortIndex == index) { - if (!m_currentSortOrder || m_currentSortOrder.value() != order) - m_currentSortOrder = order; - else - m_currentSortOrder.reset(); + QTC_ASSERT(index > -1 && index < m_columnInfoList.size(), return); + const QList oldSortIndexes = m_currentSortIndexes; + std::optional oldSortOrder = m_columnInfoList.at(index).sortOrder; + int pos = m_currentSortIndexes.indexOf(index); + + if (oldSortOrder == order) + m_columnInfoList[index].sortOrder.reset(); + else + m_columnInfoList[index].sortOrder = order; + if (multi) { + if (pos == -1) + m_currentSortIndexes.append(index); + else if (oldSortOrder == order) + m_currentSortIndexes.remove(pos); } else { - m_currentSortOrder = order; + m_currentSortIndexes.clear(); + if (pos == -1 || oldSortOrder != order) + m_currentSortIndexes.append(index); + for (int oldIndex : oldSortIndexes) { + if (oldIndex == index) + continue; + m_columnInfoList[oldIndex].sortOrder.reset(); + } } - int oldIndex = m_currentSortIndex; - m_currentSortIndex = index; - if (oldIndex != -1) + for (int oldIndex : oldSortIndexes) headerDataChanged(Qt::Horizontal, oldIndex, oldIndex); headerDataChanged(Qt::Horizontal, index, index); emit sortTriggered(); @@ -137,11 +158,13 @@ void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int log painter->restore(); if (logicalIndex < 0 || logicalIndex >= m_columnInfoList.size()) return; - if (!m_columnInfoList.at(logicalIndex).sortable) + const ColumnInfo info = m_columnInfoList.at(logicalIndex); + if (!info.sortable) return; const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this); - const QIcon icon = iconForSorted(logicalIndex == m_currentSortIndex ? m_currentSortOrder : std::nullopt); + const QIcon icon = iconForSorted(m_currentSortIndexes.contains(logicalIndex) ? info.sortOrder + : std::nullopt); const int offset = qMax((rect.height() - ICON_SIZE), 0) / 2; const int left = rect.left() + rect.width() - ICON_SIZE - margin; const QRect iconRect(left, offset, ICON_SIZE, ICON_SIZE); diff --git a/src/plugins/axivion/issueheaderview.h b/src/plugins/axivion/issueheaderview.h index 748f2faa11d..e8fef8f62fa 100644 --- a/src/plugins/axivion/issueheaderview.h +++ b/src/plugins/axivion/issueheaderview.h @@ -24,8 +24,7 @@ public: explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {} void setColumnInfoList(const QList &infos); - std::optional currentSortOrder() const { return m_currentSortOrder; } - int currentSortColumn() const; + QList> currentSortColumns() const; signals: void sortTriggered(); @@ -38,13 +37,13 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; private: - void onToggleSort(int index, Qt::SortOrder order); + void onToggleSort(int index, Qt::SortOrder order, bool multi); bool m_dragging = false; bool m_maybeToggleSort = false; + bool m_withShift = false; int m_lastToggleLogicalPos = -1; - int m_currentSortIndex = -1; - std::optional m_currentSortOrder = std::nullopt; QList m_columnInfoList; + QList m_currentSortIndexes; }; } // namespace Axivion::Internal