From 0497a09c97248121b59ed939fdfadc314d319e17 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Fri, 9 Aug 2024 15:39:35 +0200 Subject: [PATCH] Axivion: Handle filter for columns Change-Id: I352d1e27e873b1fef593c9e5250c28a8aef56df4 Reviewed-by: Jarek Kobus --- src/plugins/axivion/axivionoutputpane.cpp | 9 ++ src/plugins/axivion/axivionplugin.cpp | 4 + src/plugins/axivion/axivionplugin.h | 2 + src/plugins/axivion/issueheaderview.cpp | 137 +++++++++++++++++++++- src/plugins/axivion/issueheaderview.h | 2 + 5 files changed, 153 insertions(+), 1 deletion(-) diff --git a/src/plugins/axivion/axivionoutputpane.cpp b/src/plugins/axivion/axivionoutputpane.cpp index bf5016d3dad..4c8ca4eaa9a 100644 --- a/src/plugins/axivion/axivionoutputpane.cpp +++ b/src/plugins/axivion/axivionoutputpane.cpp @@ -389,6 +389,8 @@ IssuesWidget::IssuesWidget(QWidget *parent) m_headerView->setSectionsMovable(true); connect(m_headerView, &IssueHeaderView::sortTriggered, this, &IssuesWidget::onSearchParameterChanged); + connect(m_headerView, &IssueHeaderView::filterChanged, + this, &IssuesWidget::onSearchParameterChanged); m_issuesView->setHeader(m_headerView); m_issuesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_issuesView->enableColumnHiding(); @@ -716,6 +718,13 @@ IssueListSearch IssuesWidget::searchFromUi() const + (pair.second == Qt::AscendingOrder ? " asc" : " desc")); } search.sort = sort; + QMap filter; + const QList> 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; } diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp index d0fabb5e2c2..475cca04dbb 100644 --- a/src/plugins/axivion/axivionplugin.cpp +++ b/src/plugins/axivion/axivionplugin.cpp @@ -198,6 +198,10 @@ QUrlQuery IssueListSearch::toUrlQuery(QueryMode mode) const query.addQueryItem("computeTotalRowCount", "true"); if (!sort.isEmpty()) 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; } diff --git a/src/plugins/axivion/axivionplugin.h b/src/plugins/axivion/axivionplugin.h index 25843090c02..2ee02b67a25 100644 --- a/src/plugins/axivion/axivionplugin.h +++ b/src/plugins/axivion/axivionplugin.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -41,6 +42,7 @@ struct IssueListSearch QString owner; QString filter_path; QString sort; + QMap filter; int offset = 0; int limit = DefaultSearchLimit; bool computeTotalRowCount = false; diff --git a/src/plugins/axivion/issueheaderview.cpp b/src/plugins/axivion/issueheaderview.cpp index 42e01cc648f..d2b049bd32a 100644 --- a/src/plugins/axivion/issueheaderview.cpp +++ b/src/plugins/axivion/issueheaderview.cpp @@ -3,16 +3,125 @@ #include "issueheaderview.h" -#include +#include "axiviontr.h" +#include +#include +#include +#include + +#include #include #include +#include +#include namespace Axivion::Internal { constexpr int IconSize = 16; 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 &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 m_applyHook; +}; + static QIcon iconForSorted(std::optional order) { static const Utils::Icon UNSORTED( @@ -68,6 +177,17 @@ QList> IssueHeaderView::currentSortColumns() const return result; } +QList> IssueHeaderView::currentFilterColumns() const +{ + QList> 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) { if (event->button() == Qt::LeftButton) { @@ -125,6 +245,21 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event) } 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.. + 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(); } } } diff --git a/src/plugins/axivion/issueheaderview.h b/src/plugins/axivion/issueheaderview.h index 23c7a85174b..4ccf74fb3df 100644 --- a/src/plugins/axivion/issueheaderview.h +++ b/src/plugins/axivion/issueheaderview.h @@ -27,8 +27,10 @@ public: void setColumnInfoList(const QList &infos); QList> currentSortColumns() const; + QList> currentFilterColumns() const; signals: + void filterChanged(); void sortTriggered(); protected: