summaryrefslogtreecommitdiff
path: root/plugins/warp/ui/shared/search.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/warp/ui/shared/search.cpp')
-rw-r--r--plugins/warp/ui/shared/search.cpp496
1 files changed, 254 insertions, 242 deletions
diff --git a/plugins/warp/ui/shared/search.cpp b/plugins/warp/ui/shared/search.cpp
index 1431e9b6..a852605e 100644
--- a/plugins/warp/ui/shared/search.cpp
+++ b/plugins/warp/ui/shared/search.cpp
@@ -2,300 +2,312 @@
#include "misc.h"
#include "../../../../../ui/mainwindow.h"
-QVariant WarpSearchModel::data(const QModelIndex &index, int role) const
+QVariant WarpSearchModel::data(const QModelIndex& index, int role) const
{
- if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
- return {};
- const auto &it = m_items[index.row()];
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
+ return {};
+ const auto& it = m_items[index.row()];
- // Provide tokenized type for functions via UserRole for TokenDataDelegate
- if (role == Qt::UserRole && index.column() == DisplayCol)
- {
- if (it && it->GetKind() == WARPContainerSearchItemKindFunction)
- if (auto itemType = it->GetType(nullptr))
- return QVariant::fromValue(TokenData(*itemType, it->GetName()));
- return {};
- }
+ // Provide tokenized type for functions via UserRole for TokenDataDelegate
+ if (role == Qt::UserRole && index.column() == DisplayCol)
+ {
+ if (it && it->GetKind() == WARPContainerSearchItemKindFunction)
+ if (auto itemType = it->GetType(nullptr))
+ return QVariant::fromValue(TokenData(*itemType, it->GetName()));
+ return {};
+ }
- if (role == Qt::DisplayRole)
- {
- // TODO: We might want to run the demangler here on the name.
- switch (index.column())
- {
- case DisplayCol: return QString::fromStdString(it->GetName());
- case KindCol:
- {
- switch (it->GetKind())
- {
- case WARPContainerSearchItemKindFunction: return "Function";
- case WARPContainerSearchItemKindType: return "Type";
- case WARPContainerSearchItemKindSource: return "Source";
- case WARPContainerSearchItemKindSymbol: return "Symbol";
- default: return {};
- }
- }
- case SourceCol: return QString::fromStdString(it->GetSource().ToString());
- default: return {};
- }
- }
- return {};
+ if (role == Qt::DisplayRole)
+ {
+ // TODO: We might want to run the demangler here on the name.
+ switch (index.column())
+ {
+ case DisplayCol:
+ return QString::fromStdString(it->GetName());
+ case KindCol:
+ {
+ switch (it->GetKind())
+ {
+ case WARPContainerSearchItemKindFunction:
+ return "Function";
+ case WARPContainerSearchItemKindType:
+ return "Type";
+ case WARPContainerSearchItemKindSource:
+ return "Source";
+ case WARPContainerSearchItemKindSymbol:
+ return "Symbol";
+ default:
+ return {};
+ }
+ }
+ case SourceCol:
+ return QString::fromStdString(it->GetSource().ToString());
+ default:
+ return {};
+ }
+ }
+ return {};
}
QVariant WarpSearchModel::headerData(int section, Qt::Orientation orientation, int role) const
{
- if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
- {
- switch (section)
- {
- case DisplayCol: return "Item";
- case KindCol: return "Kind";
- case SourceCol: return "Source";
- default: return {};
- }
- }
- return {};
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
+ {
+ switch (section)
+ {
+ case DisplayCol:
+ return "Item";
+ case KindCol:
+ return "Kind";
+ case SourceCol:
+ return "Source";
+ default:
+ return {};
+ }
+ }
+ return {};
}
-void WarpSearchDelegate::paint(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
+void WarpSearchDelegate::paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
- // TODO: We actually still want the function icon, I think? So this should not early return and instead replace the name.
- // If model provided TokenData (e.g., for function types), render via TokenDataDelegate
- if (index.data(Qt::UserRole).canConvert<TokenData>())
- {
- TokenDataDelegate(parent()).paint(p, option, index);
- return;
- }
+ // TODO: We actually still want the function icon, I think? So this should not early return and instead replace the
+ // name. If model provided TokenData (e.g., for function types), render via TokenDataDelegate
+ if (index.data(Qt::UserRole).canConvert<TokenData>())
+ {
+ TokenDataDelegate(parent()).paint(p, option, index);
+ return;
+ }
- QStyleOptionViewItem opt(option);
- initStyleOption(&opt, index);
+ QStyleOptionViewItem opt(option);
+ initStyleOption(&opt, index);
- // We will draw the background/selection via style, and custom-draw icon (left) + text.
- const QWidget *w = option.widget;
- QStyle *style = w ? w->style() : QApplication::style();
+ // We will draw the background/selection via style, and custom-draw icon (left) + text.
+ const QWidget* w = option.widget;
+ QStyle* style = w ? w->style() : QApplication::style();
- // Let style draw the item background/selection etc., but not the default text
- QString originalText = opt.text;
- opt.text.clear();
- style->drawControl(QStyle::CE_ItemViewItem, &opt, p, w);
+ // Let style draw the item background/selection etc., but not the default text
+ QString originalText = opt.text;
+ opt.text.clear();
+ style->drawControl(QStyle::CE_ItemViewItem, &opt, p, w);
- // Retrieve text for DisplayCol
- const QString &text = originalText;
+ // Retrieve text for DisplayCol
+ const QString& text = originalText;
- // Retrieve kind text from the hidden KindCol
- const QModelIndex kindIndex = index.sibling(index.row(), WarpSearchModel::KindCol);
- const QString kind = kindIndex.isValid() ? kindIndex.data(Qt::DisplayRole).toString() : QString();
+ // Retrieve kind text from the hidden KindCol
+ const QModelIndex kindIndex = index.sibling(index.row(), WarpSearchModel::KindCol);
+ const QString kind = kindIndex.isValid() ? kindIndex.data(Qt::DisplayRole).toString() : QString();
- // Map kind -> icon (emoji for simplicity; replace with QIcon/QPixmap if desired)
- const QString icon = kindToIcon(kind);
+ // Map kind -> icon (emoji for simplicity; replace with QIcon/QPixmap if desired)
+ const QString icon = kindToIcon(kind);
- // Layout rects
- const QRect r = opt.rect;
- const int marginH = 8;
- const int iconSide = 16;
- const int spacing = 8;
+ // Layout rects
+ const QRect r = opt.rect;
+ const int marginH = 8;
+ const int iconSide = 16;
+ const int spacing = 8;
- // Reserve space on the left for the icon if we have one
- QRect iconRect = QRect(r.left() + marginH, r.center().y() - iconSide / 2, iconSide, iconSide);
- QRect textRect = r.adjusted((icon.isEmpty() ? marginH : (marginH + iconSide + spacing)), 0, -marginH, 0);
+ // Reserve space on the left for the icon if we have one
+ QRect iconRect = QRect(r.left() + marginH, r.center().y() - iconSide / 2, iconSide, iconSide);
+ QRect textRect = r.adjusted((icon.isEmpty() ? marginH : (marginH + iconSide + spacing)), 0, -marginH, 0);
- // Draw icon first (without clipping), then text with clipping
- p->save();
- if (!icon.isEmpty())
- {
- QFont iconFont = opt.font;
- iconFont.setPointSizeF(iconFont.pointSizeF() + 2);
- p->setFont(iconFont);
- p->setPen(
- opt.palette.color(opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
- p->drawText(iconRect, Qt::AlignCenter, icon);
- }
- p->restore();
+ // Draw icon first (without clipping), then text with clipping
+ p->save();
+ if (!icon.isEmpty())
+ {
+ QFont iconFont = opt.font;
+ iconFont.setPointSizeF(iconFont.pointSizeF() + 2);
+ p->setFont(iconFont);
+ p->setPen(opt.palette.color(opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
+ p->drawText(iconRect, Qt::AlignCenter, icon);
+ }
+ p->restore();
- // Draw elided text, vertically centered (with clipping so long strings don't overflow)
- p->save();
- p->setClipRect(textRect);
- const QFontMetrics fm(opt.font);
- const QString elided = fm.elidedText(text, Qt::ElideRight, textRect.width());
- p->setPen(opt.palette.color(opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
- p->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, elided);
- p->restore();
+ // Draw elided text, vertically centered (with clipping so long strings don't overflow)
+ p->save();
+ p->setClipRect(textRect);
+ const QFontMetrics fm(opt.font);
+ const QString elided = fm.elidedText(text, Qt::ElideRight, textRect.width());
+ p->setPen(opt.palette.color(opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
+ p->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, elided);
+ p->restore();
}
void WarpSearchRunner::fetchPage(std::size_t offset, std::size_t limit)
{
- const auto session = currentSession();
- auto container = m_container;
- auto q = m_queryText;
- auto src = m_source;
+ const auto session = currentSession();
+ auto container = m_container;
+ auto q = m_queryText;
+ auto src = m_source;
- // Runs search on Qt threadpool then moves the result to the main thread to update the UI.
- using TaskResult = std::pair<std::optional<Warp::ContainerSearchResponse>, QString>;
- QFutureWatcher<TaskResult> *watcher = new QFutureWatcher<TaskResult>(this);
- const auto future = QtConcurrent::run([container, q, src, offset, limit]() -> TaskResult {
- const Warp::ContainerSearchQuery query(q.toStdString(), offset, limit, src);
- auto respOpt = container->Search(query);
- // TODO: We may want to provide better errors in the future, e.g. BNWARPGetError
- if (!respOpt.has_value())
- return {std::nullopt, QStringLiteral("No response from container")};
- return {std::move(respOpt), QString()};
- });
- connect(watcher, &QFutureWatcher<TaskResult>::finished, this, [this, watcher, session]() {
- const TaskResult result = watcher->result();
- watcher->deleteLater();
- if (const auto &err = result.second; !err.isEmpty())
- emit pageError(session, err);
- else if (const auto &resp = result.first; resp.has_value())
- emit pageReady(session, *resp);
- });
- watcher->setFuture(future);
+ // Runs search on Qt threadpool then moves the result to the main thread to update the UI.
+ using TaskResult = std::pair<std::optional<Warp::ContainerSearchResponse>, QString>;
+ QFutureWatcher<TaskResult>* watcher = new QFutureWatcher<TaskResult>(this);
+ const auto future = QtConcurrent::run([container, q, src, offset, limit]() -> TaskResult {
+ const Warp::ContainerSearchQuery query(q.toStdString(), offset, limit, src);
+ auto respOpt = container->Search(query);
+ // TODO: We may want to provide better errors in the future, e.g. BNWARPGetError
+ if (!respOpt.has_value())
+ return {std::nullopt, QStringLiteral("No response from container")};
+ return {std::move(respOpt), QString()};
+ });
+ connect(watcher, &QFutureWatcher<TaskResult>::finished, this, [this, watcher, session]() {
+ const TaskResult result = watcher->result();
+ watcher->deleteLater();
+ if (const auto& err = result.second; !err.isEmpty())
+ emit pageError(session, err);
+ else if (const auto& resp = result.first; resp.has_value())
+ emit pageReady(session, *resp);
+ });
+ watcher->setFuture(future);
}
-WarpSearchWidget::WarpSearchWidget(Warp::Ref<Warp::Container> container, QWidget *parent) : QWidget(parent)
+WarpSearchWidget::WarpSearchWidget(Warp::Ref<Warp::Container> container, QWidget* parent) : QWidget(parent)
{
- m_model = new WarpSearchModel(this);
- m_runner = new WarpSearchRunner(container, this);
- auto *layout = new QVBoxLayout(this);
- m_query = new QLineEdit(this);
- m_status = new QLabel(this);
- m_view = new QTableView(this);
+ m_model = new WarpSearchModel(this);
+ m_runner = new WarpSearchRunner(container, this);
+ auto* layout = new QVBoxLayout(this);
+ m_query = new QLineEdit(this);
+ m_status = new QLabel(this);
+ m_view = new QTableView(this);
- // TODO: I am not necessarily a fan with how this looks.
- m_query->setPlaceholderText("Search… (Optionally filter with source:<uuid>)");
+ // TODO: I am not necessarily a fan with how this looks.
+ m_query->setPlaceholderText("Search… (Optionally filter with source:<uuid>)");
- // This is where we make the table not look like a table but instead a list.
- m_view->setShowGrid(false);
- m_view->verticalHeader()->setVisible(false);
- m_view->horizontalHeader()->setVisible(false);
- m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
- m_view->setSelectionMode(QAbstractItemView::SingleSelection);
- m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
- m_view->setFocusPolicy(Qt::NoFocus);
+ // This is where we make the table not look like a table but instead a list.
+ m_view->setShowGrid(false);
+ m_view->verticalHeader()->setVisible(false);
+ m_view->horizontalHeader()->setVisible(false);
+ m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_view->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_view->setFocusPolicy(Qt::NoFocus);
- layout->addWidget(m_query);
- layout->addWidget(m_status);
- layout->addWidget(m_view);
+ layout->addWidget(m_query);
+ layout->addWidget(m_status);
+ layout->addWidget(m_view);
- m_view->setModel(m_model);
+ m_view->setModel(m_model);
- // Use a delegate to draw the DisplayCol text with an icon (optionally) aligned to the left.
- m_view->setItemDelegateForColumn(WarpSearchModel::DisplayCol, new WarpSearchDelegate(m_view));
+ // Use a delegate to draw the DisplayCol text with an icon (optionally) aligned to the left.
+ m_view->setItemDelegateForColumn(WarpSearchModel::DisplayCol, new WarpSearchDelegate(m_view));
- // Hide extra columns: only show the combined "Item" column; do not display source GUID.
- m_view->setColumnHidden(WarpSearchModel::KindCol, true);
- m_view->setColumnHidden(WarpSearchModel::SourceCol, true);
- m_view->horizontalHeader()->setSectionResizeMode(WarpSearchModel::DisplayCol, QHeaderView::Stretch);
+ // Hide extra columns: only show the combined "Item" column; do not display source GUID.
+ m_view->setColumnHidden(WarpSearchModel::KindCol, true);
+ m_view->setColumnHidden(WarpSearchModel::SourceCol, true);
+ m_view->horizontalHeader()->setSectionResizeMode(WarpSearchModel::DisplayCol, QHeaderView::Stretch);
- // Debounce user input
- m_debounce.setSingleShot(true);
- m_debounce.setInterval(SEARCH_DEBOUNCE_MS);
- connect(m_query, &QLineEdit::textChanged, this, [this](const QString &) { m_debounce.start(); });
+ // Debounce user input
+ m_debounce.setSingleShot(true);
+ m_debounce.setInterval(SEARCH_DEBOUNCE_MS);
+ connect(m_query, &QLineEdit::textChanged, this, [this](const QString&) { m_debounce.start(); });
- connect(&m_debounce, &QTimer::timeout, this, [this]() {
- const QString raw = m_query->text();
+ connect(&m_debounce, &QTimer::timeout, this, [this]() {
+ const QString raw = m_query->text();
- // Parse generic qualifiers and clean the free-text query
- const auto parsed = ParsedQuery(raw);
+ // Parse generic qualifiers and clean the free-text query
+ const auto parsed = ParsedQuery(raw);
- std::optional<Warp::Source> inlineSource = std::nullopt;
- if (const auto val = parsed.GetValue("source"); val.has_value())
- inlineSource = Warp::Source::FromString(val.value().toStdString());
- const auto effectiveSource = inlineSource.has_value() ? inlineSource : m_source;
- // TODO: Filter for tags and function guid.
+ std::optional<Warp::Source> inlineSource = std::nullopt;
+ if (const auto val = parsed.GetValue("source"); val.has_value())
+ inlineSource = Warp::Source::FromString(val.value().toStdString());
+ const auto effectiveSource = inlineSource.has_value() ? inlineSource : m_source;
+ // TODO: Filter for tags and function guid.
- m_runner->startSession(parsed.query, effectiveSource);
- m_model->beginNewSearch(SEARCH_PAGE_SIZE);
- });
+ m_runner->startSession(parsed.query, effectiveSource);
+ m_model->beginNewSearch(SEARCH_PAGE_SIZE);
+ });
- // Infinite scroll trigger
- connect(m_model, &WarpSearchModel::fetchMoreRequested, this, [this](std::size_t offset, std::size_t limit) {
- m_status->setText(QStringLiteral("Loading… (%1/%2)").arg(m_model->currentCount()).arg(m_model->total()));
- m_runner->fetchPage(offset, limit);
- });
+ // Infinite scroll trigger
+ connect(m_model, &WarpSearchModel::fetchMoreRequested, this, [this](std::size_t offset, std::size_t limit) {
+ m_status->setText(QStringLiteral("Loading… (%1/%2)").arg(m_model->currentCount()).arg(m_model->total()));
+ m_runner->fetchPage(offset, limit);
+ });
- // Append results if session still valid
- connect(m_runner, &WarpSearchRunner::pageReady, this,
- [this](std::uint64_t, const Warp::ContainerSearchResponse &resp) {
- m_model->appendResponse(resp);
- m_status->setText(
- QStringLiteral("Showing %1 of %2").arg(m_model->currentCount()).arg(m_model->total()));
- });
+ // Append results if session still valid
+ connect(
+ m_runner, &WarpSearchRunner::pageReady, this, [this](std::uint64_t, const Warp::ContainerSearchResponse& resp) {
+ m_model->appendResponse(resp);
+ m_status->setText(QStringLiteral("Showing %1 of %2").arg(m_model->currentCount()).arg(m_model->total()));
+ });
- // In cases like if the network container server goes down.
- connect(m_runner, &WarpSearchRunner::pageError, this,
- [this](std::uint64_t, const QString &msg) {
- m_status->setText(QStringLiteral("Error: %1").arg(msg));
- });
+ // In cases like if the network container server goes down.
+ connect(m_runner, &WarpSearchRunner::pageError, this, [this](std::uint64_t, const QString& msg) {
+ m_status->setText(QStringLiteral("Error: %1").arg(msg));
+ });
- // Add a context menu so that we can actually do actionable things with what we find.
- m_view->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(m_view, &QTableView::customContextMenuRequested, this, [this](const QPoint &pos) {
- const QModelIndex idx = m_view->indexAt(pos);
- if (!idx.isValid())
- return;
+ // Add a context menu so that we can actually do actionable things with what we find.
+ m_view->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(m_view, &QTableView::customContextMenuRequested, this, [this](const QPoint& pos) {
+ const QModelIndex idx = m_view->indexAt(pos);
+ if (!idx.isValid())
+ return;
- // TODO: Getting the current view here is really awful, but i dont care right now.
- auto ctx = MainWindow::activeContext();
- auto view = ctx->getCurrentView();
- auto binaryView = view->getData();
- auto viewFrame = ctx->getCurrentViewFrame();
- auto viewLocation = viewFrame->getViewLocation();
- auto func = viewLocation.getFunction();
+ // TODO: Getting the current view here is really awful, but i dont care right now.
+ auto ctx = MainWindow::activeContext();
+ auto view = ctx->getCurrentView();
+ auto binaryView = view->getData();
+ auto viewFrame = ctx->getCurrentViewFrame();
+ auto viewLocation = viewFrame->getViewLocation();
+ auto func = viewLocation.getFunction();
- // Retrieve the current architecture from the current function or try the current view.
- auto arch = binaryView->GetDefaultArchitecture();
- if (func)
- arch = func->GetArchitecture();
+ // Retrieve the current architecture from the current function or try the current view.
+ auto arch = binaryView->GetDefaultArchitecture();
+ if (func)
+ arch = func->GetArchitecture();
- const int row = idx.row();
- const auto item = m_model->itemAt(row);
- const auto itemType = item->GetType(arch);
- const auto itemFunc = item->GetFunction();
+ const int row = idx.row();
+ const auto item = m_model->itemAt(row);
+ const auto itemType = item->GetType(arch);
+ const auto itemFunc = item->GetFunction();
- QMenu menu(this);
- QAction *copySourceId = menu.addAction(tr("Copy Source"));
- QAction *searchSource = menu.addAction(tr("Search Source"));
- QAction *applyType = menu.addAction(tr("Apply Type"));
- QAction *applyFunction = menu.addAction(tr("Apply to Current Function"));
+ QMenu menu(this);
+ QAction* copySourceId = menu.addAction(tr("Copy Source"));
+ QAction* searchSource = menu.addAction(tr("Search Source"));
+ QAction* applyType = menu.addAction(tr("Apply Type"));
+ QAction* applyFunction = menu.addAction(tr("Apply to Current Function"));
- // We let users apply the type for types and functions (assuming the function has one)
- // if the user applies a function, we actually will set the user type for the current view location function.
- // For types, we will just throw it in the user types.
- applyType->setEnabled(itemType != nullptr);
- applyType->setVisible(applyType->isEnabled());
+ // We let users apply the type for types and functions (assuming the function has one)
+ // if the user applies a function, we actually will set the user type for the current view location function.
+ // For types, we will just throw it in the user types.
+ applyType->setEnabled(itemType != nullptr);
+ applyType->setVisible(applyType->isEnabled());
- applyFunction->setEnabled(func != nullptr && itemFunc);
- applyFunction->setVisible(applyFunction->isEnabled());
+ applyFunction->setEnabled(func != nullptr && itemFunc);
+ applyFunction->setVisible(applyFunction->isEnabled());
- QAction *chosen = menu.exec(m_view->viewport()->mapToGlobal(pos));
- if (!chosen)
- return;
- if (chosen == copySourceId)
- QApplication::clipboard()->setText(QString::fromStdString(item->GetSource().ToString()));
- else if (chosen == searchSource)
- {
- const QString sourceId = QString::fromStdString(item->GetSource().ToString());
- // TODO: This does not preserve the query that existed prior, doing so may result in duplicate source qualifiers.
- m_query->setText(QStringLiteral("source:\"%1\"").arg(sourceId));
- } else if (chosen == applyType)
- {
- if (func && item->GetKind() == WARPContainerSearchItemKindFunction)
- {
- func->SetUserType(itemType);
- binaryView->UpdateAnalysis();
- }
- else
- binaryView->DefineUserType(item->GetName(), itemType);
- } else if (chosen == applyFunction)
- {
- itemFunc->Apply(*func);
- binaryView->UpdateAnalysis();
- }
- });
+ QAction* chosen = menu.exec(m_view->viewport()->mapToGlobal(pos));
+ if (!chosen)
+ return;
+ if (chosen == copySourceId)
+ QApplication::clipboard()->setText(QString::fromStdString(item->GetSource().ToString()));
+ else if (chosen == searchSource)
+ {
+ const QString sourceId = QString::fromStdString(item->GetSource().ToString());
+ // TODO: This does not preserve the query that existed prior, doing so may result in duplicate source
+ // qualifiers.
+ m_query->setText(QStringLiteral("source:\"%1\"").arg(sourceId));
+ }
+ else if (chosen == applyType)
+ {
+ if (func && item->GetKind() == WARPContainerSearchItemKindFunction)
+ {
+ func->SetUserType(itemType);
+ binaryView->UpdateAnalysis();
+ }
+ else
+ binaryView->DefineUserType(item->GetName(), itemType);
+ }
+ else if (chosen == applyFunction)
+ {
+ itemFunc->Apply(*func);
+ binaryView->UpdateAnalysis();
+ }
+ });
- // This will start searching with an empty query once the widget is constructed. This is a decent behavior considering
- // we should be heavily caching queries such as an empty one.
- m_debounce.start();
+ // This will start searching with an empty query once the widget is constructed. This is a decent behavior
+ // considering we should be heavily caching queries such as an empty one.
+ m_debounce.start();
}