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 <jaroslaw.kobus@qt.io>
This commit is contained in:
Christian Stenger
2024-08-05 09:01:49 +02:00
parent fd94f03c3d
commit 56f143eb82
3 changed files with 58 additions and 31 deletions

View File

@@ -701,13 +701,18 @@ IssueListSearch IssuesWidget::searchFromUi() const
search.state = "added"; search.state = "added";
else if (m_removedFilter->isChecked()) else if (m_removedFilter->isChecked())
search.state = "removed"; 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<QPair<int, Qt::SortOrder>> 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; return search;
} }

View File

@@ -38,16 +38,20 @@ static QIcon iconForSorted(std::optional<Qt::SortOrder> order)
void IssueHeaderView::setColumnInfoList(const QList<ColumnInfo> &infos) void IssueHeaderView::setColumnInfoList(const QList<ColumnInfo> &infos)
{ {
m_columnInfoList = infos; m_columnInfoList = infos;
int oldIndex = m_currentSortIndex; const QList<int> oldIndexes = m_currentSortIndexes;
m_currentSortIndex = -1; m_currentSortIndexes.clear();
m_currentSortOrder.reset(); for (int i = 0; i < infos.size(); ++i)
if (oldIndex != -1) m_columnInfoList[i].sortOrder.reset();
for (int oldIndex : oldIndexes)
headerDataChanged(Qt::Horizontal, oldIndex, oldIndex); headerDataChanged(Qt::Horizontal, oldIndex, oldIndex);
} }
int IssueHeaderView::currentSortColumn() const QList<QPair<int, Qt::SortOrder>> IssueHeaderView::currentSortColumns() const
{ {
return m_currentSortOrder ? m_currentSortIndex : -1; QList<QPair<int, Qt::SortOrder>> result;
for (int i : m_currentSortIndexes)
result.append({i, m_columnInfoList.at(i).sortOrder.value()});
return result;
} }
void IssueHeaderView::mousePressEvent(QMouseEvent *event) void IssueHeaderView::mousePressEvent(QMouseEvent *event)
@@ -63,6 +67,7 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin; const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin;
const int start = end - ICON_SIZE; const int start = end - ICON_SIZE;
m_maybeToggleSort = start < pos && end > pos; m_maybeToggleSort = start < pos && end > pos;
m_withShift = event->modifiers() == Qt::ShiftModifier;
} }
} }
QHeaderView::mousePressEvent(event); QHeaderView::mousePressEvent(event);
@@ -71,8 +76,10 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event) void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
{ {
bool dontSkip = !m_dragging && m_maybeToggleSort; bool dontSkip = !m_dragging && m_maybeToggleSort;
bool withShift = m_withShift && event->modifiers() == Qt::ShiftModifier;
m_dragging = false; m_dragging = false;
m_maybeToggleSort = false; m_maybeToggleSort = false;
m_withShift = false;
if (dontSkip) { if (dontSkip) {
const QPoint position = event->position().toPoint(); const QPoint position = event->position().toPoint();
@@ -82,9 +89,9 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
&& logical > -1 && logical < m_columnInfoList.size()) { && logical > -1 && logical < m_columnInfoList.size()) {
if (m_columnInfoList.at(logical).sortable) { // ignore non-sortable if (m_columnInfoList.at(logical).sortable) { // ignore non-sortable
if (y < height() / 2) // TODO improve if (y < height() / 2) // TODO improve
onToggleSort(logical, Qt::AscendingOrder); onToggleSort(logical, Qt::AscendingOrder, withShift);
else else
onToggleSort(logical, Qt::DescendingOrder); onToggleSort(logical, Qt::DescendingOrder, withShift);
} }
} }
} }
@@ -99,20 +106,34 @@ void IssueHeaderView::mouseMoveEvent(QMouseEvent *event)
QHeaderView::mouseMoveEvent(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) { QTC_ASSERT(index > -1 && index < m_columnInfoList.size(), return);
if (!m_currentSortOrder || m_currentSortOrder.value() != order) const QList<int> oldSortIndexes = m_currentSortIndexes;
m_currentSortOrder = order; std::optional<Qt::SortOrder> oldSortOrder = m_columnInfoList.at(index).sortOrder;
else int pos = m_currentSortIndexes.indexOf(index);
m_currentSortOrder.reset();
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 { } 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; for (int oldIndex : oldSortIndexes)
m_currentSortIndex = index;
if (oldIndex != -1)
headerDataChanged(Qt::Horizontal, oldIndex, oldIndex); headerDataChanged(Qt::Horizontal, oldIndex, oldIndex);
headerDataChanged(Qt::Horizontal, index, index); headerDataChanged(Qt::Horizontal, index, index);
emit sortTriggered(); emit sortTriggered();
@@ -137,11 +158,13 @@ void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int log
painter->restore(); painter->restore();
if (logicalIndex < 0 || logicalIndex >= m_columnInfoList.size()) if (logicalIndex < 0 || logicalIndex >= m_columnInfoList.size())
return; return;
if (!m_columnInfoList.at(logicalIndex).sortable) const ColumnInfo info = m_columnInfoList.at(logicalIndex);
if (!info.sortable)
return; return;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this); 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 offset = qMax((rect.height() - ICON_SIZE), 0) / 2;
const int left = rect.left() + rect.width() - ICON_SIZE - margin; const int left = rect.left() + rect.width() - ICON_SIZE - margin;
const QRect iconRect(left, offset, ICON_SIZE, ICON_SIZE); const QRect iconRect(left, offset, ICON_SIZE, ICON_SIZE);

View File

@@ -24,8 +24,7 @@ public:
explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {} explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {}
void setColumnInfoList(const QList<ColumnInfo> &infos); void setColumnInfoList(const QList<ColumnInfo> &infos);
std::optional<Qt::SortOrder> currentSortOrder() const { return m_currentSortOrder; } QList<QPair<int, Qt::SortOrder>> currentSortColumns() const;
int currentSortColumn() const;
signals: signals:
void sortTriggered(); void sortTriggered();
@@ -38,13 +37,13 @@ protected:
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
private: private:
void onToggleSort(int index, Qt::SortOrder order); void onToggleSort(int index, Qt::SortOrder order, bool multi);
bool m_dragging = false; bool m_dragging = false;
bool m_maybeToggleSort = false; bool m_maybeToggleSort = false;
bool m_withShift = false;
int m_lastToggleLogicalPos = -1; int m_lastToggleLogicalPos = -1;
int m_currentSortIndex = -1;
std::optional<Qt::SortOrder> m_currentSortOrder = std::nullopt;
QList<ColumnInfo> m_columnInfoList; QList<ColumnInfo> m_columnInfoList;
QList<int> m_currentSortIndexes;
}; };
} // namespace Axivion::Internal } // namespace Axivion::Internal