Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/core/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,11 +761,6 @@ void MainWindow::showProjectSaveError(RzProjectErr err)
tr("Failed to save project: %1").arg(QString::fromUtf8(s)));
}

void MainWindow::refreshOmniBar(const QStringList &flags)
{
omnibar->refresh(flags);
}

void MainWindow::setFilename(const QString &fn)
{
// Add file name to window title
Expand Down
1 change: 0 additions & 1 deletion src/core/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ class CUTTER_EXPORT MainWindow : public QMainWindow
void readSettings();
void saveSettings();
void setFilename(const QString &fn);
void refreshOmniBar(const QStringList &flags);

void addWidget(CutterDockWidget *widget);
void addMemoryDockWidget(MemoryDockWidget *widget);
Expand Down
8 changes: 8 additions & 0 deletions src/shortcuts/DefaultShortcuts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ const QHash<QString, Shortcut> &getDefaultShortcuts()
{ { QKeySequence(Qt::Key_Escape) },
QT_TRANSLATE_NOOP("Omnibar", "Clear Omnibar"),
"Omnibar" } },
{ "Omnibar.showMore",
{ { QKeySequence(Qt::ControlModifier | Qt::Key_Return) },
QT_TRANSLATE_NOOP("Omnibar", "Show More Completions"),
"Omnibar" } },
{ "Omnibar.showAll",
{ { QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Return) },
QT_TRANSLATE_NOOP("Omnibar", "Show All Completions"),
"Omnibar" } },

// Graph Overview
{ "Overview.zoomIn",
Expand Down
6 changes: 0 additions & 6 deletions src/widgets/FlagsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,12 +268,6 @@ void FlagsWidget::refreshFlags()
flags_model->endResetModel();

tree->showItemsNumber(flags_proxy_model->rowCount());

// TODO: this is not a very good place for the following:
QStringList flagNames;
for (const FlagDescription &i : flags_model->flags)
flagNames.append(i.name);
main->refreshOmniBar(flagNames);
}

void FlagsWidget::setScrollMode()
Expand Down
225 changes: 199 additions & 26 deletions src/widgets/Omnibar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,39 @@
#include <QCompleter>
#include <QShortcut>
#include <QAbstractItemView>
#include <QStringListModel>
#include <QCollator>
#include <QStandardItemModel>
#include <QTimer>
#include <QEvent>
#include <QKeyEvent>

namespace {
constexpr int DEFAULT_ITEM_COUNT = 100;
Copy link
Copy Markdown
Member

@wargio wargio Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this should be configurable as if i use the screen in portrait mode, i may want to see more results.

Copy link
Copy Markdown
Collaborator Author

@PremadeS PremadeS Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I mentioned that in the PR description

There should be a new tab for options similar to this, If I add it right now it would conflict with #3580 as the configurable options from QuickFilterView should be moved to this "new tab" as well.
I'll add the option as soon as #3580 is merged

}

OmnibarCompleter::OmnibarCompleter(QObject *parent) : QCompleter(parent) {}

Omnibar::Omnibar(MainWindow *main, QWidget *parent) : QLineEdit(parent), main(main)
QStringList OmnibarCompleter::splitPath(const QString &) const
{
return QStringList("");
}

QString OmnibarCompleter::pathFromIndex(const QModelIndex &index) const
{
int type = index.data(Qt::UserRole).toInt();

if (type == Omnibar::ShowMore || type == Omnibar::ShowAll) {
if (auto edit = qobject_cast<QLineEdit *>(widget())) {
return edit->text();
}
}

return QCompleter::pathFromIndex(index);
}

Omnibar::Omnibar(MainWindow *main, QWidget *parent)
: QLineEdit(parent), main(main), m_completerModel(new QStandardItemModel(this))
{
// QLineEdit basic features
this->setMinimumHeight(16);
Expand All @@ -18,53 +49,108 @@ Omnibar::Omnibar(MainWindow *main, QWidget *parent) : QLineEdit(parent), main(ma
this->setTextMargins(10, 0, 0, 0);
this->setClearButtonEnabled(true);

connect(this, &QLineEdit::returnPressed, this, &Omnibar::on_gotoEntry_returnPressed);
connect(this, &QLineEdit::textEdited, this, [this](const QString &text) {
m_maxShownItems = DEFAULT_ITEM_COUNT; // reset the entries count to 100
handleSearch(text);
});

// Esc clears omnibar
QShortcut *clear_shortcut = Shortcuts()->makeQShortcut("Omnibar.clear", this);
connect(clear_shortcut, &QShortcut::activated, this, &Omnibar::clear);
clear_shortcut->setContext(Qt::WidgetWithChildrenShortcut);
}

void Omnibar::setupCompleter()
{
// Set gotoEntry completer for jump history
QCompleter *completer = new QCompleter(flags, this);
completer->setMaxVisibleItems(20);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setFilterMode(Qt::MatchContains);

this->setCompleter(completer);
m_completer = new OmnibarCompleter(this);
m_completer->setModel(m_completerModel);
m_completer->setMaxVisibleItems(20);
m_completer->setFilterMode(Qt::MatchContains);
m_completer->setCompletionMode(QCompleter::PopupCompletion);
m_completer->popup()->installEventFilter(this);
m_completer->popup()->viewport()->installEventFilter(this);

this->setCompleter(m_completer);
this->installEventFilter(this);

connect(Core(), &CutterCore::flagsChanged, this, &Omnibar::refresh);
connect(Core(), &CutterCore::codeRebased, this, &Omnibar::refresh);
connect(Core(), &CutterCore::refreshAll, this, &Omnibar::refresh);
}

void Omnibar::refresh(const QStringList &flagList)
bool Omnibar::eventFilter(QObject *obj, QEvent *event)
{
flags = flagList;
QAbstractItemView *popup = m_completer->popup();

if (((obj == popup || obj == this) && (event->type() == QEvent::KeyPress))
|| (obj == popup->viewport() && event->type() == QEvent::MouseButtonPress)) {

QModelIndex index;

if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::LeftButton) {
index = popup->indexAt(mouseEvent->pos());
}
} else if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

int keyInt = keyEvent->modifiers() | keyEvent->key();
QKeySequence eventSeq(keyInt);
if (eventSeq == Shortcuts()->getKeySequence("Omnibar.showMore")) {
handleShowMore(popup->currentIndex().row());
return true;
} else if (eventSeq == Shortcuts()->getKeySequence("Omnibar.showAll")) {
handleShowAll(popup->currentIndex().row());
return true;
} else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
index = popup->currentIndex();
}
}

if (index.isValid()) {
int row = index.row();
int type = index.data(Qt::UserRole).toInt();

setupCompleter();
if (type == ItemType::ShowMore) {
handleShowMore(row);
} else if (type == ItemType::ShowAll) {
handleShowAll(row);
} else {
goToEntry();
popup->hide();
}
return true;
}
}
return QObject::eventFilter(obj, event);
}

void Omnibar::restoreCompleter()
void Omnibar::refresh()
{
QCompleter *completer = this->completer();
if (!completer) {
return;
m_flags.clear();
const auto flags = Core()->getAllFlags();
for (const auto &f : flags) {
m_flags.append(f.name);
}
completer->setFilterMode(Qt::MatchContains);

QCollator collator;
collator.setNumericMode(true);
collator.setCaseSensitivity(Qt::CaseInsensitive);
std::sort(m_flags.begin(), m_flags.end(), [&collator](const QString &a, const QString &b) {
return collator.compare(a, b) < 0;
});

handleSearch(this->text());
}

void Omnibar::clear()
{
QLineEdit::clear();

// Close the potential shown completer popup
clearFocus();
setFocus();
m_completerModel->clear();
m_completer->popup()->hide();
}

void Omnibar::on_gotoEntry_returnPressed()
void Omnibar::goToEntry()
{
QString str = this->text();
if (!str.isEmpty()) {
Expand All @@ -79,5 +165,92 @@ void Omnibar::on_gotoEntry_returnPressed()

this->setText("");
this->clearFocus();
this->restoreCompleter();
}

void Omnibar::handleSearch(const QString &Text, bool append)
{
m_searchedText = Text;
m_matchCount = 0;

if (!append) {
m_completerModel->clear();
m_lastIndex = 0;
} else {
// remove "show more" and "show all" rows
m_completerModel->removeRows(m_completerModel->rowCount() - 2, 2);
}

if (Text.isEmpty()) {
return;
}

bool hasMore = false;
int itemsInModel = m_completerModel->rowCount();

for (int i = 0; i < m_flags.size(); ++i) {
const QString &flag = m_flags[i];
if (flag.contains(Text, Qt::CaseInsensitive)) {
if (itemsInModel < m_maxShownItems) {
if (!append || i > m_lastIndex) {
QStandardItem *item = new QStandardItem(flag);
item->setData(ItemType::Standard, Qt::UserRole);
m_completerModel->appendRow(item);
++itemsInModel;
m_lastIndex = i;
}
} else {
hasMore = true;
}
++m_matchCount;
}
}

if (hasMore) {
auto addActionRow = [&](const QString &text, ItemType type) {
QStandardItem *actionItem = new QStandardItem(text);
actionItem->setData(type, Qt::UserRole);

QPalette palette = this->palette();
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
QColor placeholderColor = palette.color(QPalette::PlaceholderText);
#else
QColor placeholderColor = palette.color(QPalette::Text);
placeholderColor.setAlpha(128);
#endif
actionItem->setForeground(placeholderColor);

QFont font = actionItem->font();
font.setItalic(true);
actionItem->setFont(font);

m_completerModel->appendRow(actionItem);
};

addActionRow(tr("Show More"), ItemType::ShowMore);
addActionRow(tr("Show All (%1 of %2)").arg(m_maxShownItems).arg(m_matchCount),
ItemType::ShowAll);
}

m_completer->setCompletionPrefix("");
m_completer->complete();
}

void Omnibar::handleShowMore(int currentRow)
{
if (m_maxShownItems >= m_matchCount) {
return;
}
m_maxShownItems = std::min(m_maxShownItems + DEFAULT_ITEM_COUNT, m_matchCount);
handleSearch(m_searchedText, true);
m_completer->popup()->setCurrentIndex(m_completerModel->index(currentRow, 0));
}

void Omnibar::handleShowAll(int currentRow)
{
if (m_maxShownItems >= m_matchCount) {
return;
}
m_maxShownItems = m_matchCount;
handleSearch(m_searchedText, true);
m_completer->popup()->setCurrentIndex(m_completerModel->index(currentRow, 0));
}
57 changes: 53 additions & 4 deletions src/widgets/Omnibar.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,79 @@
#define OMNIBAR_H

#include <QLineEdit>
#include <QCompleter>

class MainWindow;
class QStandardItemModel;

class OmnibarCompleter : public QCompleter
{
public:
explicit OmnibarCompleter(QObject *parent = nullptr);

/**
* @brief Force the completer to match everything, since we are handling filtering on our own
*/
QStringList splitPath(const QString &) const override;

/**
* @brief Do not auto-complete when selecting "Show More" or "Show All" entries
*
* Keeps the text the same for "Show More" and "Show All", auto-completes for other entries
*/
QString pathFromIndex(const QModelIndex &index) const override;
};

class Omnibar : public QLineEdit
{
Q_OBJECT
public:
explicit Omnibar(MainWindow *main, QWidget *parent = nullptr);

void refresh(const QStringList &flagList);
void refresh();

enum ItemType { Standard = 0, ShowMore = 1, ShowAll = 2 };

private slots:
void on_gotoEntry_returnPressed();
/**
* @brief Seeks to the address of selected entry/flag and shows it in the last memory widget
*/
void goToEntry();

/**
* @brief Filters or searches entries "containing" the text
*
* Appends "Show More" and "Show All" entries at the end if there are more found matches then
* currently shown
*
* @param text The string to match against
* @param append If false, resets the search to the beginning. If true, continues from the last
* found position
*/
void handleSearch(const QString &text, bool append = false);

void restoreCompleter();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;

public slots:
void clear();

private:
void setupCompleter();
void handleShowMore(int currentRow);
void handleShowAll(int currentRow);

MainWindow *main;
QStringList flags;
QStringList m_flags;

QStringList m_filteredFlags;
QStandardItemModel *m_completerModel;
QCompleter *m_completer;

QString m_searchedText;
int m_maxShownItems;
int m_lastIndex;
int m_matchCount;
};

#endif // OMNIBAR_H
Loading