forked from qt-creator/qt-creator
Axivion: Handle filter for columns
Change-Id: I352d1e27e873b1fef593c9e5250c28a8aef56df4 Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
@@ -389,6 +389,8 @@ IssuesWidget::IssuesWidget(QWidget *parent)
|
|||||||
m_headerView->setSectionsMovable(true);
|
m_headerView->setSectionsMovable(true);
|
||||||
connect(m_headerView, &IssueHeaderView::sortTriggered,
|
connect(m_headerView, &IssueHeaderView::sortTriggered,
|
||||||
this, &IssuesWidget::onSearchParameterChanged);
|
this, &IssuesWidget::onSearchParameterChanged);
|
||||||
|
connect(m_headerView, &IssueHeaderView::filterChanged,
|
||||||
|
this, &IssuesWidget::onSearchParameterChanged);
|
||||||
m_issuesView->setHeader(m_headerView);
|
m_issuesView->setHeader(m_headerView);
|
||||||
m_issuesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
m_issuesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||||
m_issuesView->enableColumnHiding();
|
m_issuesView->enableColumnHiding();
|
||||||
@@ -716,6 +718,13 @@ IssueListSearch IssuesWidget::searchFromUi() const
|
|||||||
+ (pair.second == Qt::AscendingOrder ? " asc" : " desc"));
|
+ (pair.second == Qt::AscendingOrder ? " asc" : " desc"));
|
||||||
}
|
}
|
||||||
search.sort = sort;
|
search.sort = sort;
|
||||||
|
QMap<QString, QString> filter;
|
||||||
|
const QList<QPair<int, QString>> currentFilterColumns = m_headerView->currentFilterColumns();
|
||||||
|
for (const auto &pair : currentFilterColumns) {
|
||||||
|
QTC_ASSERT((ulong)pair.first < m_currentTableInfo->columns.size(), return search);
|
||||||
|
filter.insert("filter_" + m_currentTableInfo->columns.at(pair.first).key, pair.second);
|
||||||
|
}
|
||||||
|
search.filter = filter;
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -198,6 +198,10 @@ QUrlQuery IssueListSearch::toUrlQuery(QueryMode mode) const
|
|||||||
query.addQueryItem("computeTotalRowCount", "true");
|
query.addQueryItem("computeTotalRowCount", "true");
|
||||||
if (!sort.isEmpty())
|
if (!sort.isEmpty())
|
||||||
query.addQueryItem("sort", sort);
|
query.addQueryItem("sort", sort);
|
||||||
|
if (!filter.isEmpty()) {
|
||||||
|
for (auto f = filter.cbegin(), end = filter.cend(); f != end; ++f)
|
||||||
|
query.addQueryItem(f.key(), f.value());
|
||||||
|
}
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include <utils/id.h>
|
#include <utils/id.h>
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QMap>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QVersionNumber>
|
#include <QVersionNumber>
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ struct IssueListSearch
|
|||||||
QString owner;
|
QString owner;
|
||||||
QString filter_path;
|
QString filter_path;
|
||||||
QString sort;
|
QString sort;
|
||||||
|
QMap<QString, QString> filter;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
int limit = DefaultSearchLimit;
|
int limit = DefaultSearchLimit;
|
||||||
bool computeTotalRowCount = false;
|
bool computeTotalRowCount = false;
|
||||||
|
@@ -3,16 +3,125 @@
|
|||||||
|
|
||||||
#include "issueheaderview.h"
|
#include "issueheaderview.h"
|
||||||
|
|
||||||
#include <utils/icon.h>
|
#include "axiviontr.h"
|
||||||
|
|
||||||
|
#include <utils/fancylineedit.h>
|
||||||
|
#include <utils/icon.h>
|
||||||
|
#include <utils/layoutbuilder.h>
|
||||||
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
|
#include <QLabel>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
namespace Axivion::Internal {
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
constexpr int IconSize = 16;
|
constexpr int IconSize = 16;
|
||||||
constexpr int InnerMargin = 4;
|
constexpr int InnerMargin = 4;
|
||||||
|
|
||||||
|
static QString infoText()
|
||||||
|
{
|
||||||
|
return Tr::tr("Allows for filters combined with & as logical AND, | as logical OR and "
|
||||||
|
"! as logical NOT. The filters may contain * to match sequences of "
|
||||||
|
"arbitrary characters. If a single filter is quoted with double quotes "
|
||||||
|
"it will be matched on the complete string. Some filter characters "
|
||||||
|
"require quoting of the filter expression with double quotes. If inside "
|
||||||
|
"double quotes you need to escape \" and \\ with a backslash.\n"
|
||||||
|
"Some examples:\n\n"
|
||||||
|
"a matches issues where the value contains the letter 'a'\n"
|
||||||
|
"\"abc\" matches issues where the value is exactly 'abc'\n"
|
||||||
|
"!abc matches issues whose value does not contain 'abc'\n"
|
||||||
|
"(ab | cd) & !ef matches issues with values containing 'ab' or 'cd' but not 'ef'\n"
|
||||||
|
"\"\" matches issues having an empty value in this column\n"
|
||||||
|
"!\"\" matches issues having any non-empty value in this column");
|
||||||
|
}
|
||||||
|
class FilterPopupWidget : public QFrame
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FilterPopupWidget(QWidget *parent, const QString &filter)
|
||||||
|
: QFrame(parent)
|
||||||
|
{
|
||||||
|
setWindowFlags(Qt::Popup);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
Qt::FocusPolicy origPolicy = parent->focusPolicy();
|
||||||
|
setFocusPolicy(Qt::NoFocus);
|
||||||
|
parent->setFocusPolicy(origPolicy);
|
||||||
|
setFocusProxy(parent);
|
||||||
|
|
||||||
|
auto infoButton = new QToolButton(this);
|
||||||
|
infoButton->setIcon(Utils::Icons::INFO.icon());
|
||||||
|
infoButton->setCheckable(true);
|
||||||
|
infoButton->setChecked(true);
|
||||||
|
m_lineEdit = new Utils::FancyLineEdit(this);
|
||||||
|
m_lineEdit->setClearButtonEnabled(true);
|
||||||
|
m_lineEdit->setText(filter);
|
||||||
|
// TODO add some pre-check for validity of the expression
|
||||||
|
// or handle invalid filter exception correctly
|
||||||
|
auto apply = new QPushButton(Tr::tr("Apply"), this);
|
||||||
|
auto infoLabel = new QLabel(infoText());
|
||||||
|
infoLabel->setWordWrap(true);
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
Column layout {
|
||||||
|
Row { infoButton, m_lineEdit, apply},
|
||||||
|
infoLabel
|
||||||
|
};
|
||||||
|
layout.attachTo(this);
|
||||||
|
|
||||||
|
adjustSize();
|
||||||
|
setFixedWidth(size().width());
|
||||||
|
|
||||||
|
const auto onApply = [this] {
|
||||||
|
QTC_ASSERT(m_lineEdit, return);
|
||||||
|
if (m_applyHook)
|
||||||
|
m_applyHook(m_lineEdit->text());
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
connect(infoButton, &QToolButton::toggled, this, [this, infoLabel](bool checked){
|
||||||
|
QTC_ASSERT(infoLabel, return);
|
||||||
|
infoLabel->setVisible(checked);
|
||||||
|
adjustSize();
|
||||||
|
});
|
||||||
|
connect(m_lineEdit, &Utils::FancyLineEdit::editingFinished,
|
||||||
|
this, [this, apply, onApply] {
|
||||||
|
if (m_lineEdit->hasFocus() || apply->hasFocus()) // avoid triggering for focus lost
|
||||||
|
onApply();
|
||||||
|
else
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
connect(apply, &QPushButton::clicked, this, onApply);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnApply(const std::function<void(const QString &)> &hook) { m_applyHook = hook; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent *event) override
|
||||||
|
{
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
if (!event->spontaneous())
|
||||||
|
m_lineEdit->setFocus(Qt::PopupFocusReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resizeEvent(QResizeEvent *event) override
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
if (m_handleResizeEvent) { // ignore the first resize event (first layout)
|
||||||
|
const int oldHeight = event->oldSize().height();
|
||||||
|
const int newHeight = event->size().height();
|
||||||
|
const QPoint position = pos();
|
||||||
|
move(position.x(), position.y() + oldHeight - newHeight);
|
||||||
|
}
|
||||||
|
m_handleResizeEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_handleResizeEvent = false;
|
||||||
|
Utils::FancyLineEdit *m_lineEdit = nullptr;
|
||||||
|
std::function<void(const QString &)> m_applyHook;
|
||||||
|
};
|
||||||
|
|
||||||
static QIcon iconForSorted(std::optional<Qt::SortOrder> order)
|
static QIcon iconForSorted(std::optional<Qt::SortOrder> order)
|
||||||
{
|
{
|
||||||
static const Utils::Icon UNSORTED(
|
static const Utils::Icon UNSORTED(
|
||||||
@@ -68,6 +177,17 @@ QList<QPair<int, Qt::SortOrder>> IssueHeaderView::currentSortColumns() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QPair<int, QString>> IssueHeaderView::currentFilterColumns() const
|
||||||
|
{
|
||||||
|
QList<QPair<int, QString>> result;
|
||||||
|
for (int i = 0, end = m_columnInfoList.size(); i < end; ++i) {
|
||||||
|
const ColumnInfo ci = m_columnInfoList.at(i);
|
||||||
|
if (ci.filter.has_value())
|
||||||
|
result.append({i, ci.filter.value()});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void IssueHeaderView::mousePressEvent(QMouseEvent *event)
|
void IssueHeaderView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (event->button() == Qt::LeftButton) {
|
if (event->button() == Qt::LeftButton) {
|
||||||
@@ -125,6 +245,21 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
|
|||||||
} else if (toggleMode == Filter && m_columnInfoList.at(logical).filterable) {
|
} else if (toggleMode == Filter && m_columnInfoList.at(logical).filterable) {
|
||||||
// TODO we need some popup for text input (entering filter expression)
|
// TODO we need some popup for text input (entering filter expression)
|
||||||
// apply them to the columninfo, and use them for the search..
|
// apply them to the columninfo, and use them for the search..
|
||||||
|
const auto onApply = [this, logical](const QString &txt) {
|
||||||
|
if (txt.isEmpty())
|
||||||
|
m_columnInfoList[logical].filter.reset();
|
||||||
|
else
|
||||||
|
m_columnInfoList[logical].filter.emplace(txt);
|
||||||
|
headerDataChanged(Qt::Horizontal, logical, logical);
|
||||||
|
emit filterChanged();
|
||||||
|
};
|
||||||
|
auto popup = new FilterPopupWidget(this, m_columnInfoList.at(logical).filter.value_or(""));
|
||||||
|
popup->setOnApply(onApply);
|
||||||
|
const int right = sectionViewportPosition(logical) + sectionSize(logical);
|
||||||
|
const QSize size = popup->sizeHint();
|
||||||
|
popup->move(mapToGlobal(QPoint{x() + right - size.width(),
|
||||||
|
this->y() - size.height()}));
|
||||||
|
popup->show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,8 +27,10 @@ public:
|
|||||||
void setColumnInfoList(const QList<ColumnInfo> &infos);
|
void setColumnInfoList(const QList<ColumnInfo> &infos);
|
||||||
|
|
||||||
QList<QPair<int, Qt::SortOrder>> currentSortColumns() const;
|
QList<QPair<int, Qt::SortOrder>> currentSortColumns() const;
|
||||||
|
QList<QPair<int, QString>> currentFilterColumns() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void filterChanged();
|
||||||
void sortTriggered();
|
void sortTriggered();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
Reference in New Issue
Block a user