forked from qt-creator/qt-creator
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:
@@ -477,6 +477,7 @@ void IssuesWidget::updateTable()
|
|||||||
hiddenColumns << column.key;
|
hiddenColumns << column.key;
|
||||||
IssueHeaderView::ColumnInfo info;
|
IssueHeaderView::ColumnInfo info;
|
||||||
info.sortable = column.canSort;
|
info.sortable = column.canSort;
|
||||||
|
info.filterable = column.canFilter;
|
||||||
info.width = column.width;
|
info.width = column.width;
|
||||||
columnInfos.append(info);
|
columnInfos.append(info);
|
||||||
alignments << alignmentFromString(column.alignment);
|
alignments << alignmentFromString(column.alignment);
|
||||||
|
@@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
namespace Axivion::Internal {
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
constexpr int ICON_SIZE = 16;
|
constexpr int IconSize = 16;
|
||||||
|
constexpr int InnerMargin = 4;
|
||||||
|
|
||||||
static QIcon iconForSorted(std::optional<Qt::SortOrder> order)
|
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;
|
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)
|
void IssueHeaderView::setColumnInfoList(const QList<ColumnInfo> &infos)
|
||||||
{
|
{
|
||||||
m_columnInfoList = infos;
|
m_columnInfoList = infos;
|
||||||
@@ -62,11 +76,26 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
|
|||||||
if (y > 1 && y < height() - 2) { // TODO improve
|
if (y > 1 && y < height() - 2) { // TODO improve
|
||||||
const int pos = position.x();
|
const int pos = position.x();
|
||||||
const int logical = logicalIndexAt(pos);
|
const int logical = logicalIndexAt(pos);
|
||||||
|
QTC_ASSERT(logical > -1 && logical < m_columnInfoList.size(),
|
||||||
|
QHeaderView::mousePressEvent(event); return);
|
||||||
m_lastToggleLogicalPos = logical;
|
m_lastToggleLogicalPos = logical;
|
||||||
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
||||||
const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin;
|
const int lastIconEnd = sectionViewportPosition(logical) + sectionSize(logical) - margin;
|
||||||
const int start = end - ICON_SIZE;
|
const int lastIconStart = lastIconEnd - IconSize;
|
||||||
m_maybeToggleSort = start < pos && end > pos;
|
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;
|
m_withShift = event->modifiers() == Qt::ShiftModifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,10 +104,11 @@ 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_maybeToggle;
|
||||||
|
const int toggleMode = m_maybeToggle ? m_maybeToggle.value() : -1;
|
||||||
bool withShift = m_withShift && event->modifiers() == Qt::ShiftModifier;
|
bool withShift = m_withShift && event->modifiers() == Qt::ShiftModifier;
|
||||||
m_dragging = false;
|
m_dragging = false;
|
||||||
m_maybeToggleSort = false;
|
m_maybeToggle.reset();
|
||||||
m_withShift = false;
|
m_withShift = false;
|
||||||
|
|
||||||
if (dontSkip) {
|
if (dontSkip) {
|
||||||
@@ -87,11 +117,14 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
|
|||||||
const int logical = logicalIndexAt(position.x());
|
const int logical = logicalIndexAt(position.x());
|
||||||
if (logical == m_lastToggleLogicalPos
|
if (logical == m_lastToggleLogicalPos
|
||||||
&& logical > -1 && logical < m_columnInfoList.size()) {
|
&& 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
|
if (y < height() / 2) // TODO improve
|
||||||
onToggleSort(logical, Qt::AscendingOrder, withShift);
|
onToggleSort(logical, Qt::AscendingOrder, withShift);
|
||||||
else
|
else
|
||||||
onToggleSort(logical, Qt::DescendingOrder, withShift);
|
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
|
QSize IssueHeaderView::sectionSizeFromContents(int logicalIndex) const
|
||||||
{
|
{
|
||||||
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
|
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
|
||||||
const QSize newSize = logicalIndex < m_columnInfoList.size()
|
QTC_ASSERT(logicalIndex > -1 && logicalIndex < m_columnInfoList.size(), return oldSize);
|
||||||
? QSize(qMax(m_columnInfoList.at(logicalIndex).width, oldSize.width()), oldSize.height())
|
const ColumnInfo ci = m_columnInfoList.at(logicalIndex);
|
||||||
: oldSize;
|
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);
|
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
||||||
// add icon size and margin (default resize handle margin)
|
// compute additional space for needed icon(s)
|
||||||
return QSize{newSize.width() + ICON_SIZE + margin, qMax(newSize.height(), ICON_SIZE)};
|
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
|
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())
|
if (logicalIndex < 0 || logicalIndex >= m_columnInfoList.size())
|
||||||
return;
|
return;
|
||||||
const ColumnInfo info = m_columnInfoList.at(logicalIndex);
|
const ColumnInfo info = m_columnInfoList.at(logicalIndex);
|
||||||
if (!info.sortable)
|
if (!info.sortable && !info.filterable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const int offset = qMax((rect.height() - IconSize), 0) / 2;
|
||||||
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
||||||
const QIcon icon = iconForSorted(m_currentSortIndexes.contains(logicalIndex) ? info.sortOrder
|
const int lastIconLeft = rect.left() + rect.width() - IconSize - margin;
|
||||||
: std::nullopt);
|
const int firstIconLeft = lastIconLeft - InnerMargin - IconSize;
|
||||||
const int offset = qMax((rect.height() - ICON_SIZE), 0) / 2;
|
|
||||||
const int left = rect.left() + rect.width() - ICON_SIZE - margin;
|
const bool bothIcons = info.sortable && info.filterable;
|
||||||
const QRect iconRect(left, offset, ICON_SIZE, ICON_SIZE);
|
const QRect clearRect(bothIcons ? firstIconLeft : lastIconLeft, 0,
|
||||||
const QRect clearRect(left, 0, ICON_SIZE + margin, rect.height());
|
bothIcons ? IconSize + InnerMargin + IconSize + margin
|
||||||
|
: IconSize + margin, rect.height());
|
||||||
painter->save();
|
painter->save();
|
||||||
QStyleOptionHeader opt;
|
QStyleOptionHeader opt;
|
||||||
initStyleOption(&opt);
|
initStyleOption(&opt);
|
||||||
opt.rect = clearRect;
|
opt.rect = clearRect;
|
||||||
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
|
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
|
||||||
painter->restore();
|
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
|
} // namespace Axivion::Internal
|
||||||
|
@@ -19,6 +19,8 @@ public:
|
|||||||
int width = 0;
|
int width = 0;
|
||||||
std::optional<Qt::SortOrder> sortOrder = std::nullopt;
|
std::optional<Qt::SortOrder> sortOrder = std::nullopt;
|
||||||
bool sortable = false;
|
bool sortable = false;
|
||||||
|
bool filterable = false;
|
||||||
|
std::optional<QString> filter = std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {}
|
explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {}
|
||||||
@@ -39,7 +41,8 @@ protected:
|
|||||||
private:
|
private:
|
||||||
void onToggleSort(int index, Qt::SortOrder order, bool multi);
|
void onToggleSort(int index, Qt::SortOrder order, bool multi);
|
||||||
bool m_dragging = false;
|
bool m_dragging = false;
|
||||||
bool m_maybeToggleSort = false;
|
enum ToggleMode {Sort, Filter};
|
||||||
|
std::optional<ToggleMode> m_maybeToggle = std::nullopt;
|
||||||
bool m_withShift = false;
|
bool m_withShift = false;
|
||||||
int m_lastToggleLogicalPos = -1;
|
int m_lastToggleLogicalPos = -1;
|
||||||
QList<ColumnInfo> m_columnInfoList;
|
QList<ColumnInfo> m_columnInfoList;
|
||||||
|
Reference in New Issue
Block a user