Axivion: Prepare filtering of columns

This adds a filter icon on the column header if the column
is marked as filterable.
Beside this the header view now only adjusts the width when
necessary.

Change-Id: Ib25bc7ba64689209b113cd5a7b4d4e8a615b3282
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Christian Stenger
2024-08-07 15:49:23 +02:00
parent 56f143eb82
commit 86b179143d
3 changed files with 77 additions and 21 deletions

View File

@@ -477,6 +477,7 @@ void IssuesWidget::updateTable()
hiddenColumns << column.key;
IssueHeaderView::ColumnInfo info;
info.sortable = column.canSort;
info.filterable = column.canFilter;
info.width = column.width;
columnInfos.append(info);
alignments << alignmentFromString(column.alignment);

View File

@@ -10,7 +10,8 @@
namespace Axivion::Internal {
constexpr int ICON_SIZE = 16;
constexpr int IconSize = 16;
constexpr int InnerMargin = 4;
static QIcon iconForSorted(std::optional<Qt::SortOrder> order)
{
@@ -35,6 +36,19 @@ static QIcon iconForSorted(std::optional<Qt::SortOrder> order)
return order.value() == Qt::AscendingOrder ? sortedAsc : sortedDesc;
}
static QIcon iconForFilter(bool isActive)
{
const Utils::Icon INACTIVE(
{{":/utils/images/filtericon.png", Utils::Theme::IconsDisabledColor}},
Utils::Icon::MenuTintedStyle);
const Utils::Icon ACTIVE(
{{":/utils/images/filtericon.png", Utils::Theme::PaletteText}},
Utils::Icon::MenuTintedStyle);
static const QIcon inactive = INACTIVE.icon();
static const QIcon active = ACTIVE.icon();
return isActive ? active : inactive;
}
void IssueHeaderView::setColumnInfoList(const QList<ColumnInfo> &infos)
{
m_columnInfoList = infos;
@@ -62,11 +76,26 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
if (y > 1 && y < height() - 2) { // TODO improve
const int pos = position.x();
const int logical = logicalIndexAt(pos);
QTC_ASSERT(logical > -1 && logical < m_columnInfoList.size(),
QHeaderView::mousePressEvent(event); return);
m_lastToggleLogicalPos = logical;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin;
const int start = end - ICON_SIZE;
m_maybeToggleSort = start < pos && end > pos;
const int lastIconEnd = sectionViewportPosition(logical) + sectionSize(logical) - margin;
const int lastIconStart = lastIconEnd - IconSize;
const int firstIconStart = lastIconStart - InnerMargin - IconSize;
const ColumnInfo info = m_columnInfoList.at(logical);
if (info.sortable && info.filterable) {
if (firstIconStart < pos && firstIconStart + IconSize > pos)
m_maybeToggle.emplace(Sort);
else if (lastIconStart < pos && lastIconEnd > pos)
m_maybeToggle.emplace(Filter);
else
m_maybeToggle.reset();
} else {
if (lastIconStart < pos && lastIconEnd > pos)
m_maybeToggle.emplace(info.sortable ? Sort : Filter);
}
m_withShift = event->modifiers() == Qt::ShiftModifier;
}
}
@@ -75,10 +104,11 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
{
bool dontSkip = !m_dragging && m_maybeToggleSort;
bool dontSkip = !m_dragging && m_maybeToggle;
const int toggleMode = m_maybeToggle ? m_maybeToggle.value() : -1;
bool withShift = m_withShift && event->modifiers() == Qt::ShiftModifier;
m_dragging = false;
m_maybeToggleSort = false;
m_maybeToggle.reset();
m_withShift = false;
if (dontSkip) {
@@ -87,11 +117,14 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
const int logical = logicalIndexAt(position.x());
if (logical == m_lastToggleLogicalPos
&& logical > -1 && logical < m_columnInfoList.size()) {
if (m_columnInfoList.at(logical).sortable) { // ignore non-sortable
if (toggleMode == Sort && m_columnInfoList.at(logical).sortable) { // ignore non-sortable
if (y < height() / 2) // TODO improve
onToggleSort(logical, Qt::AscendingOrder, withShift);
else
onToggleSort(logical, Qt::DescendingOrder, withShift);
} else if (toggleMode == Filter && m_columnInfoList.at(logical).filterable) {
// TODO we need some popup for text input (entering filter expression)
// apply them to the columninfo, and use them for the search..
}
}
}
@@ -142,13 +175,20 @@ void IssueHeaderView::onToggleSort(int index, Qt::SortOrder order, bool multi)
QSize IssueHeaderView::sectionSizeFromContents(int logicalIndex) const
{
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
const QSize newSize = logicalIndex < m_columnInfoList.size()
? QSize(qMax(m_columnInfoList.at(logicalIndex).width, oldSize.width()), oldSize.height())
: oldSize;
QTC_ASSERT(logicalIndex > -1 && logicalIndex < m_columnInfoList.size(), return oldSize);
const ColumnInfo ci = m_columnInfoList.at(logicalIndex);
const QSize newSize = QSize(qMax(ci.width, oldSize.width()), oldSize.height());
if (!ci.filterable && !ci.sortable)
return newSize;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
// add icon size and margin (default resize handle margin)
return QSize{newSize.width() + ICON_SIZE + margin, qMax(newSize.height(), ICON_SIZE)};
// compute additional space for needed icon(s)
int additionalWidth = IconSize + margin;
if (ci.filterable && ci.sortable)
additionalWidth += IconSize + InnerMargin;
return QSize{newSize.width() + additionalWidth, qMax(newSize.height(), IconSize)};
}
void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
@@ -159,23 +199,35 @@ void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int log
if (logicalIndex < 0 || logicalIndex >= m_columnInfoList.size())
return;
const ColumnInfo info = m_columnInfoList.at(logicalIndex);
if (!info.sortable)
if (!info.sortable && !info.filterable)
return;
const int offset = qMax((rect.height() - IconSize), 0) / 2;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
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);
const QRect clearRect(left, 0, ICON_SIZE + margin, rect.height());
const int lastIconLeft = rect.left() + rect.width() - IconSize - margin;
const int firstIconLeft = lastIconLeft - InnerMargin - IconSize;
const bool bothIcons = info.sortable && info.filterable;
const QRect clearRect(bothIcons ? firstIconLeft : lastIconLeft, 0,
bothIcons ? IconSize + InnerMargin + IconSize + margin
: IconSize + margin, rect.height());
painter->save();
QStyleOptionHeader opt;
initStyleOption(&opt);
opt.rect = clearRect;
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
painter->restore();
icon.paint(painter, iconRect);
QRect iconRect(lastIconLeft, offset, IconSize, IconSize);
if (info.filterable) {
const QIcon icon = iconForFilter(info.filter.has_value());
icon.paint(painter, iconRect);
iconRect.setRect(firstIconLeft, offset, IconSize, IconSize);
}
if (info.sortable) {
const QIcon icon = iconForSorted(m_currentSortIndexes.contains(logicalIndex) ? info.sortOrder
: std::nullopt);
icon.paint(painter, iconRect);
}
}
} // namespace Axivion::Internal

View File

@@ -19,6 +19,8 @@ public:
int width = 0;
std::optional<Qt::SortOrder> sortOrder = std::nullopt;
bool sortable = false;
bool filterable = false;
std::optional<QString> filter = std::nullopt;
};
explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {}
@@ -39,7 +41,8 @@ protected:
private:
void onToggleSort(int index, Qt::SortOrder order, bool multi);
bool m_dragging = false;
bool m_maybeToggleSort = false;
enum ToggleMode {Sort, Filter};
std::optional<ToggleMode> m_maybeToggle = std::nullopt;
bool m_withShift = false;
int m_lastToggleLogicalPos = -1;
QList<ColumnInfo> m_columnInfoList;