diff --git a/src/flamegraph.cpp b/src/flamegraph.cpp index 23daae2da..128624e53 100644 --- a/src/flamegraph.cpp +++ b/src/flamegraph.cpp @@ -546,14 +546,13 @@ struct SearchResults qint64 directCost = 0; }; -SearchResults applySearch(FrameGraphicsItem* item, const QString& searchValue) +SearchResults applySearch(FrameGraphicsItem* item, const QRegularExpression& expression) { SearchResults result; - if (searchValue.isEmpty()) { + if (expression.pattern().isEmpty()) { result.matchType = NoSearch; - } else if (item->symbol().symbol.contains(searchValue, Qt::CaseInsensitive) - || (searchValue == QLatin1String("??") && item->symbol().symbol.isEmpty()) - || item->symbol().binary.contains(searchValue, Qt::CaseInsensitive)) { + } else if (expression.match(item->symbol().symbol).hasMatch() || expression.match(item->symbol().binary).hasMatch() + || (expression.pattern() == QLatin1String("\\?\\?") && item->symbol().symbol.isEmpty())) { result.directCost += item->cost(); result.matchType = DirectMatch; } @@ -562,7 +561,7 @@ SearchResults applySearch(FrameGraphicsItem* item, const QString& searchValue) const auto children = item->childItems(); for (auto* child : children) { auto* childFrame = static_cast(child); - auto childMatch = applySearch(childFrame, searchValue); + auto childMatch = applySearch(childFrame, expression); if (result.matchType != DirectMatch && (childMatch.matchType == DirectMatch || childMatch.matchType == ChildMatch)) { result.matchType = ChildMatch; @@ -805,13 +804,26 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags) searchInput->setMinimumWidth(200); layout->addWidget(searchInput); + auto regexCheckBox = new QCheckBox(widget); + regexCheckBox->setText(tr("Regex Search")); + layout->addWidget(regexCheckBox); + searchInput->setPlaceholderText(i18n("Search...")); searchInput->setToolTip(i18n("Search the flame graph for a symbol.")); searchInput->setClearButtonEnabled(true); - connect(searchInput, &QLineEdit::textChanged, this, &FlameGraph::setSearchValue); - connect(this, &FlameGraph::uiResetRequested, this, [this, searchInput] { + connect(searchInput, &QLineEdit::textChanged, this, + [this](const QString& value) { this->setSearchValue(value, m_useRegex); }); + auto applyRegexCheckBox = [this](bool checked) { this->setSearchValue(m_search, checked); }; +#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) + connect(regexCheckBox, &QCheckBox::stateChanged, this, applyRegexCheckBox); +#else + connect(regexCheckBox, &QCheckBox::checkStateChanged, this, applyRegexCheckBox); +#endif + connect(this, &FlameGraph::uiResetRequested, this, [this, searchInput, regexCheckBox] { m_search.clear(); + m_useRegex = false; searchInput->clear(); + regexCheckBox->setChecked(false); }); }, this); @@ -1140,7 +1152,7 @@ void FlameGraph::setData(FrameGraphicsItem* rootItem) m_scene->addItem(rootItem); if (!m_search.isEmpty()) { - setSearchValue(m_search); + setSearchValue(m_search, m_useRegex); } if (!m_hoveredStacks.isEmpty()) { hoverStacks(rootItem, m_hoveredStacks); @@ -1204,15 +1216,16 @@ void FlameGraph::selectItem(FrameGraphicsItem* item) setTooltipItem(item); } -void FlameGraph::setSearchValue(const QString& value) +void FlameGraph::setSearchValue(const QString& value, bool useRegex) { if (!m_rootItem) { return; } m_search = value; - - auto match = applySearch(m_rootItem, value); + m_useRegex = useRegex; + auto regex = useRegex ? value : QRegularExpression::escape(value); + auto match = applySearch(m_rootItem, QRegularExpression(regex)); if (value.isEmpty()) { m_searchResultsLabel->hide(); diff --git a/src/flamegraph.h b/src/flamegraph.h index 2ac7e7471..538035170 100644 --- a/src/flamegraph.h +++ b/src/flamegraph.h @@ -45,7 +45,7 @@ class FlameGraph : public QWidget private slots: void setData(FrameGraphicsItem* rootItem); - void setSearchValue(const QString& value); + void setSearchValue(const QString& value, bool useRegex); void navigateBack(); void navigateForward(); @@ -88,6 +88,7 @@ private slots: bool m_collapseRecursion = false; bool m_buildingScene = false; QString m_search; + bool m_useRegex = false; // cost threshold in percent, items below that value will not be shown static const constexpr double DEFAULT_COST_THRESHOLD = 0.1; double m_costThreshold = DEFAULT_COST_THRESHOLD; diff --git a/src/recordpage.cpp b/src/recordpage.cpp index d58325f10..a0e9b73b7 100644 --- a/src/recordpage.cpp +++ b/src/recordpage.cpp @@ -415,7 +415,7 @@ RecordPage::RecordPage(QWidget* parent) m_recordHost->setPids(pids); }); - ResultsUtil::connectFilter(ui->processesFilterBox, m_processProxyModel); + ResultsUtil::connectFilter(ui->processesFilterBox, m_processProxyModel, ui->regexCheckBox); connect(m_watcher, &QFutureWatcher::finished, this, &RecordPage::updateProcessesFinished); diff --git a/src/recordpage.ui b/src/recordpage.ui index 0c53b30fe..0d92e35f5 100644 --- a/src/recordpage.ui +++ b/src/recordpage.ui @@ -144,16 +144,6 @@ Process Filter: - - processesFilterBox - - - - - - - Filter the process list by process name or process ID - @@ -185,6 +175,38 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter the process list by process name or process ID + + + + + + + Regex Search + + + + + + diff --git a/src/resultsbottomuppage.cpp b/src/resultsbottomuppage.cpp index f9729b444..943b47a3c 100644 --- a/src/resultsbottomuppage.cpp +++ b/src/resultsbottomuppage.cpp @@ -61,7 +61,8 @@ ResultsBottomUpPage::ResultsBottomUpPage(FilterAndZoomStack* filterStack, PerfPa ui->setupUi(this); auto bottomUpCostModel = new BottomUpModel(this); - ResultsUtil::setupTreeView(ui->bottomUpTreeView, contextMenu, ui->bottomUpSearch, bottomUpCostModel); + ResultsUtil::setupTreeView(ui->bottomUpTreeView, contextMenu, ui->bottomUpSearch, ui->regexCheckBox, + bottomUpCostModel); ResultsUtil::setupCostDelegate(bottomUpCostModel, ui->bottomUpTreeView); ResultsUtil::setupContextMenu(ui->bottomUpTreeView, contextMenu, bottomUpCostModel, filterStack, this); diff --git a/src/resultsbottomuppage.ui b/src/resultsbottomuppage.ui index f5c16c78a..27f85cb13 100644 --- a/src/resultsbottomuppage.ui +++ b/src/resultsbottomuppage.ui @@ -71,6 +71,13 @@ + + + + Regex Search + + + diff --git a/src/resultscallercalleepage.cpp b/src/resultscallercalleepage.cpp index 363dc95f6..fc2340a7d 100644 --- a/src/resultscallercalleepage.cpp +++ b/src/resultscallercalleepage.cpp @@ -84,7 +84,7 @@ ResultsCallerCalleePage::ResultsCallerCalleePage(FilterAndZoomStack* filterStack m_callerCalleeProxy = new CallerCalleeProxy(this); m_callerCalleeProxy->setSourceModel(m_callerCalleeCostModel); m_callerCalleeProxy->setSortRole(CallerCalleeModel::SortRole); - ResultsUtil::connectFilter(ui->callerCalleeFilter, m_callerCalleeProxy); + ResultsUtil::connectFilter(ui->callerCalleeFilter, m_callerCalleeProxy, ui->regexCheckBox); ui->callerCalleeTableView->setSortingEnabled(true); ui->callerCalleeTableView->setModel(m_callerCalleeProxy); ResultsUtil::setupContextMenu(ui->callerCalleeTableView, contextMenu, m_callerCalleeCostModel, filterStack, this, diff --git a/src/resultscallercalleepage.ui b/src/resultscallercalleepage.ui index f6119633d..e0034614d 100644 --- a/src/resultscallercalleepage.ui +++ b/src/resultscallercalleepage.ui @@ -71,6 +71,13 @@ + + + + Regex Search + + + diff --git a/src/resultstopdownpage.cpp b/src/resultstopdownpage.cpp index 9e1f403fe..cff526a42 100644 --- a/src/resultstopdownpage.cpp +++ b/src/resultstopdownpage.cpp @@ -22,7 +22,8 @@ ResultsTopDownPage::ResultsTopDownPage(FilterAndZoomStack* filterStack, PerfPars ui->setupUi(this); auto topDownCostModel = new TopDownModel(this); - ResultsUtil::setupTreeView(ui->topDownTreeView, contextMenu, ui->topDownSearch, topDownCostModel); + ResultsUtil::setupTreeView(ui->topDownTreeView, contextMenu, ui->topDownSearch, ui->regexCheckBox, + topDownCostModel); ResultsUtil::setupCostDelegate(topDownCostModel, ui->topDownTreeView); ResultsUtil::setupContextMenu(ui->topDownTreeView, contextMenu, topDownCostModel, filterStack, this); diff --git a/src/resultstopdownpage.ui b/src/resultstopdownpage.ui index 2287335bb..5480ec2ad 100644 --- a/src/resultstopdownpage.ui +++ b/src/resultstopdownpage.ui @@ -71,6 +71,13 @@ + + + + Regex Search + + + diff --git a/src/resultsutil.cpp b/src/resultsutil.cpp index 21a4a44fb..6fac24f78 100644 --- a/src/resultsutil.cpp +++ b/src/resultsutil.cpp @@ -8,6 +8,7 @@ #include "resultsutil.h" +#include #include #include #include @@ -33,8 +34,9 @@ void setupHeaderView(QTreeView* view, CostContextMenu* contextMenu) view->setHeader(new CostHeaderView(contextMenu, view)); } -void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy) +void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy, QCheckBox* regexCheckBox) { + Q_ASSERT(regexCheckBox); auto* timer = new QTimer(filter); timer->setSingleShot(true); @@ -44,17 +46,28 @@ void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy) proxy->setFilterKeyColumn(-1); proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - QObject::connect(timer, &QTimer::timeout, proxy, [filter, proxy]() { - proxy->setFilterRegularExpression(QRegularExpression::escape(filter->text())); - }); + auto setFilterNeedle = [filter, proxy, regexCheckBox]() { + auto useRegex = regexCheckBox->isChecked(); + const auto needle = filter->text(); + proxy->setFilterRegularExpression(useRegex ? needle : QRegularExpression::escape(needle)); + }; + + QObject::connect(timer, &QTimer::timeout, proxy, setFilterNeedle); + if (regexCheckBox) { +#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) + QObject::connect(regexCheckBox, &QCheckBox::stateChanged, proxy, setFilterNeedle); +#else + QObject::connect(regexCheckBox, &QCheckBox::checkStateChanged, proxy, setFilterNeedle); +#endif + } QObject::connect(filter, &QLineEdit::textChanged, timer, [timer]() { timer->start(300); }); } -void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QSortFilterProxyModel* model, - int initialSortColumn, int sortRole) +void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QCheckBox* regexSearchCheckbox, + QSortFilterProxyModel* model, int initialSortColumn, int sortRole) { model->setSortRole(sortRole); - connectFilter(filter, model); + connectFilter(filter, model, regexSearchCheckbox); view->setModel(model); setupHeaderView(view, contextMenu); diff --git a/src/resultsutil.h b/src/resultsutil.h index 1dffcf849..568d88709 100644 --- a/src/resultsutil.h +++ b/src/resultsutil.h @@ -19,6 +19,7 @@ class QComboBox; class QLineEdit; class QSortFilterProxyModel; class QAbstractItemModel; +class QCheckBox; namespace Data { class Costs; @@ -31,18 +32,19 @@ class CostContextMenu; namespace ResultsUtil { void setupHeaderView(QTreeView* view, CostContextMenu* contextMenu); -void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy); +void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy, QCheckBox* regexCheckBox); -void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QSortFilterProxyModel* model, - int initialSortColumn, int sortRole); +void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QCheckBox* regexSearchCheckBox, + QSortFilterProxyModel* model, int initialSortColumn, int sortRole); template -void setupTreeView(QTreeView* view, CostContextMenu* costContextMenu, QLineEdit* filter, Model* model) +void setupTreeView(QTreeView* view, CostContextMenu* costContextMenu, QLineEdit* filter, QCheckBox* regexSearchCheckBox, + Model* model) { auto* proxy = new CostProxy(view); proxy->setSourceModel(model); - setupTreeView(view, costContextMenu, filter, qobject_cast(proxy), Model::InitialSortColumn, - Model::SortRole); + setupTreeView(view, costContextMenu, filter, regexSearchCheckBox, qobject_cast(proxy), + Model::InitialSortColumn, Model::SortRole); } void setupCostDelegate(QAbstractItemModel* model, QTreeView* view, int sortRole, int totalCostRole, int numBaseColumns); diff --git a/src/timelinewidget.cpp b/src/timelinewidget.cpp index 16b95c43c..8da01028b 100644 --- a/src/timelinewidget.cpp +++ b/src/timelinewidget.cpp @@ -67,7 +67,7 @@ TimeLineWidget::TimeLineWidget(PerfParser* parser, QMenu* filterMenu, FilterAndZ timeLineProxy->setSortRole(EventModel::SortRole); timeLineProxy->setFilterKeyColumn(EventModel::ThreadColumn); timeLineProxy->setFilterRole(Qt::DisplayRole); - ResultsUtil::connectFilter(ui->timeLineSearch, timeLineProxy); + ResultsUtil::connectFilter(ui->timeLineSearch, timeLineProxy, ui->regexCheckBox); ui->timeLineView->setModel(timeLineProxy); ui->timeLineView->setSortingEnabled(true); // ensure the vertical scroll bar is always shown, otherwise the timeline diff --git a/src/timelinewidget.ui b/src/timelinewidget.ui index 8f25a04fc..487fd370f 100644 --- a/src/timelinewidget.ui +++ b/src/timelinewidget.ui @@ -64,6 +64,13 @@ + + + + Regex Search + + +