aboutsummaryrefslogtreecommitdiff
path: root/src/completion
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2025-03-15 19:17:03 +0530
committerAkshay Nair <phenax5@gmail.com>2025-03-15 20:08:30 +0530
commit387cf24e389854ecc97f6236fdd6acbc2486dd52 (patch)
treea51b51de0064fb10685d2da85d11748c4a32ce73 /src/completion
parent05ae8976a8e1ab5411058de244c4e2fcc87ec369 (diff)
downloadnull-browser-387cf24e389854ecc97f6236fdd6acbc2486dd52.tar.gz
null-browser-387cf24e389854ecc97f6236fdd6acbc2486dd52.zip
Replaced QCompleter with custom implementation
Diffstat (limited to '')
-rw-r--r--src/completion/CommandsAdapter.cpp6
-rw-r--r--src/completion/CommandsModel.cpp5
-rw-r--r--src/completion/Completer.cpp92
-rw-r--r--src/completion/CompleterDelegate.cpp8
-rw-r--r--src/completion/FuzzySearchProxyModel.cpp24
-rw-r--r--src/completion/TabsAdapter.cpp20
-rw-r--r--src/completion/TabsModel.cpp47
-rw-r--r--src/completion/UrlAdapter.cpp6
-rw-r--r--src/completion/UrlModel.cpp8
9 files changed, 179 insertions, 37 deletions
diff --git a/src/completion/CommandsAdapter.cpp b/src/completion/CommandsAdapter.cpp
index df71661..020c115 100644
--- a/src/completion/CommandsAdapter.cpp
+++ b/src/completion/CommandsAdapter.cpp
@@ -8,8 +8,8 @@
const QString cmdPrompt = "[exec]";
CommandsAdapter::CommandsAdapter() : Adapter() {
- completerInstance = new Completer(this);
- completerInstance->setModel(new CommandsModel(this));
+ completerInstance = new Completer();
+ completerInstance->setSourceModel(new CommandsModel(this));
}
Completer *CommandsAdapter::completer() { return completerInstance; }
@@ -17,6 +17,6 @@ Completer *CommandsAdapter::completer() { return completerInstance; }
QString CommandsAdapter::prompt() { return cmdPrompt; }
CommandsAdapter::~CommandsAdapter() {
- delete completerInstance->model();
+ delete completerInstance->sourceModel();
delete completerInstance;
}
diff --git a/src/completion/CommandsModel.cpp b/src/completion/CommandsModel.cpp
index 0f66233..670c0e8 100644
--- a/src/completion/CommandsModel.cpp
+++ b/src/completion/CommandsModel.cpp
@@ -16,10 +16,10 @@ QVariant CommandsModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole)
return item.name;
- else if (role == Qt::ToolTipRole)
- return item.description;
else if (role == Qt::UserRole)
return item.description;
+ else if (role == Qt::AccessibleDescriptionRole)
+ return item.name + item.description;
return QVariant();
}
@@ -28,6 +28,7 @@ QHash<int, QByteArray> CommandsModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "name";
roles[Qt::UserRole] = "description";
+ roles[Qt::AccessibleDescriptionRole] = "full";
return roles;
}
diff --git a/src/completion/Completer.cpp b/src/completion/Completer.cpp
index a38d3f2..27a7b77 100644
--- a/src/completion/Completer.cpp
+++ b/src/completion/Completer.cpp
@@ -1,8 +1,13 @@
#include <QAbstractItemView>
#include <QHeaderView>
+#include <QKeyEvent>
#include <QLineEdit>
#include <QTreeView>
#include <QWidget>
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qitemselectionmodel.h>
+#include <QtCore/qnamespace.h>
+#include <QtCore/qsortfilterproxymodel.h>
#include <QtCore>
#include "completion/Completer.hpp"
@@ -12,33 +17,70 @@ const char *completerStyles = R"(
background-color: #111;
color: #fff;
border-radius: 0;
+ width: 100%;
)";
-Completer::Completer(QObject *parent) : QCompleter(parent) {
- setFilterMode(Qt::MatchContains);
- setCaseSensitivity(Qt::CaseInsensitive);
- setCompletionRole(Qt::DisplayRole);
- setCompletionMode(QCompleter::PopupCompletion);
+Completer::Completer() : QWidget() {
+ view = new QTreeView(this);
+ viewDelegate = new CompleterDelegate(view);
+ view->setStyleSheet(completerStyles);
+ view->setItemDelegate(viewDelegate);
+ view->setRootIsDecorated(false);
+ view->setUniformRowHeights(true);
+ view->header()->hide();
+ view->setColumnWidth(1, 500);
+ view->setModel(&proxyModel);
+ view->setFocusPolicy(Qt::NoFocus);
+ view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ view->setSelectionMode(QAbstractItemView::SingleSelection);
+}
+
+void Completer::setSourceModel(QAbstractItemModel *model) {
+ proxyModel.setSourceModel(model);
+}
+
+void Completer::onTextChange(QString text) {
+ proxyModel.setFilterWildcard(text);
+}
+
+void Completer::setHighlightedRow(uint32_t row) {
+ viewDelegate->setCurrentRow(row);
+ view->update();
+}
- auto treeView = new QTreeView();
- setPopup(treeView);
- treeView->setStyleSheet(completerStyles);
- treeView->setItemDelegate(new CompleterDelegate(treeView));
- treeView->setRootIsDecorated(false);
- treeView->setUniformRowHeights(true);
- treeView->header()->hide();
- treeView->setColumnWidth(1, 300);
+void Completer::acceptHighlighted() {
+ auto index = proxyModel.index(viewDelegate->currentRow(), 0);
+ auto text = proxyModel.data(index, Qt::DisplayRole).toString();
+ emit accepted(text);
}
-// bool Completer::eventFilter(QObject *watched, QEvent *event) {
-// auto isFocusIn = event->type() == QEvent::FocusIn;
-// auto isKeyRelease = event->type() == QEvent::KeyRelease;
-//
-// QLineEdit *lineEdit = qobject_cast<QLineEdit *>(watched);
-// bool isInputEmpty = lineEdit && lineEdit->text().isEmpty();
-//
-// if ((isFocusIn || isKeyRelease) && isInputEmpty)
-// complete();
-//
-// return QCompleter::eventFilter(watched, event);
-// }
+bool Completer::onKeyPressEvent(QKeyEvent *event) {
+ auto combo = event->keyCombination();
+ auto row = viewDelegate->currentRow();
+
+ // If there are no matches, nothing to handle here
+ if (proxyModel.rowCount() == 0)
+ return false;
+
+ if (combo.key() == Qt::Key_Up) {
+ setHighlightedRow(row <= 0 ? proxyModel.rowCount() - 1 : row - 1);
+ return true;
+ }
+
+ if (combo.key() == Qt::Key_Down) {
+ setHighlightedRow((row + 1) % proxyModel.rowCount());
+ return true;
+ }
+
+ if (combo.key() == Qt::Key_Tab) {
+ acceptHighlighted();
+ return true;
+ }
+
+ if (combo.key() == Qt::Key_Return) {
+ acceptHighlighted();
+ return false;
+ }
+
+ return false;
+}
diff --git a/src/completion/CompleterDelegate.cpp b/src/completion/CompleterDelegate.cpp
index a35305f..ce446b2 100644
--- a/src/completion/CompleterDelegate.cpp
+++ b/src/completion/CompleterDelegate.cpp
@@ -9,13 +9,19 @@
CompleterDelegate::CompleterDelegate(QObject *parent)
: QStyledItemDelegate(parent) {}
+void CompleterDelegate::setCurrentRow(uint32_t r) { row = r; }
+
+uint32_t CompleterDelegate::currentRow() { return row; }
+
void CompleterDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
painter->save();
+ bool selected = index.row() == row;
+
// Style
- if (option.state & QStyle::State_Selected) {
+ if (selected) {
painter->fillRect(option.rect, QColor("#aaa"));
painter->setPen(Qt::black);
} else {
diff --git a/src/completion/FuzzySearchProxyModel.cpp b/src/completion/FuzzySearchProxyModel.cpp
new file mode 100644
index 0000000..151f60e
--- /dev/null
+++ b/src/completion/FuzzySearchProxyModel.cpp
@@ -0,0 +1,24 @@
+#include <QWidget>
+#include <QtCore>
+
+#include "completion/FuzzySearchProxyModel.hpp"
+
+bool FuzzySearchProxyModel::filterAcceptsRow(
+ int sourceRow, const QModelIndex &sourceParent) const {
+ QAbstractItemModel *model = sourceModel();
+ QString filterText = filterRegularExpression().pattern();
+ qDebug() << "Searching: " << filterText;
+
+ for (int col = 0; col < model->columnCount(); ++col) {
+ QModelIndex index = model->index(sourceRow, col, sourceParent);
+ QString data = model->data(index, Qt::AccessibleDescriptionRole).toString();
+
+ qDebug() << " against: " << data;
+
+ // TODO: Fuzzy eet up
+ if (data.contains(filterText, Qt::CaseInsensitive)) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/completion/TabsAdapter.cpp b/src/completion/TabsAdapter.cpp
new file mode 100644
index 0000000..4854b7d
--- /dev/null
+++ b/src/completion/TabsAdapter.cpp
@@ -0,0 +1,20 @@
+#include <QWidget>
+#include <QtCore>
+
+#include "completion/Completer.hpp"
+#include "completion/TabsAdapter.hpp"
+#include "completion/TabsModel.hpp"
+
+TabsAdapter::TabsAdapter() : Adapter() {
+ completerInstance = new Completer();
+ completerInstance->setSourceModel(new TabsModel(this));
+}
+
+Completer *TabsAdapter::completer() { return completerInstance; }
+
+QString TabsAdapter::prompt() { return "[tabs]"; }
+
+TabsAdapter::~TabsAdapter() {
+ delete completerInstance->sourceModel();
+ delete completerInstance;
+}
diff --git a/src/completion/TabsModel.cpp b/src/completion/TabsModel.cpp
new file mode 100644
index 0000000..37eb1dd
--- /dev/null
+++ b/src/completion/TabsModel.cpp
@@ -0,0 +1,47 @@
+#include <QAbstractListModel>
+#include <QWidget>
+#include <QtCore>
+
+#include "completion/TabsModel.hpp"
+
+TabsModel::TabsModel(QObject *parent) : QAbstractListModel(parent) {
+ items = {
+ {.url = "https://ediblemonad.dev", .title = "EdibleMonad website"},
+ {.url = "https://lite.duckduckgo.com", .title = "DDG lite"},
+ {.url = "https://github.com/trending", .title = "Github trending"},
+ };
+}
+
+QVariant TabsModel::data(const QModelIndex &index, int role) const {
+ if (!index.isValid() || index.row() >= items.size())
+ return QVariant();
+
+ const Buffer &item = items.at(index.row());
+
+ if (role == Qt::DisplayRole)
+ return index.row();
+ else if (role == Qt::UserRole)
+ return item.title;
+ else if (role == Qt::AccessibleDescriptionRole)
+ return item.url + item.title;
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> TabsModel::roleNames() const {
+ QHash<int, QByteArray> roles;
+ roles[Qt::DisplayRole] = "url";
+ roles[Qt::UserRole] = "title";
+ roles[Qt::AccessibleDescriptionRole] = "full";
+ return roles;
+}
+
+int TabsModel::rowCount(const QModelIndex &parent) const {
+ Q_UNUSED(parent);
+ return items.size();
+}
+
+int TabsModel::columnCount(const QModelIndex &parent) const {
+ Q_UNUSED(parent);
+ return 2; // url + title
+}
diff --git a/src/completion/UrlAdapter.cpp b/src/completion/UrlAdapter.cpp
index 2f1bb08..54caa0d 100644
--- a/src/completion/UrlAdapter.cpp
+++ b/src/completion/UrlAdapter.cpp
@@ -6,8 +6,8 @@
#include "completion/UrlModel.hpp"
UrlAdapter::UrlAdapter() : Adapter() {
- completerInstance = new Completer(this);
- completerInstance->setModel(new UrlModel(this));
+ completerInstance = new Completer();
+ completerInstance->setSourceModel(new UrlModel(this));
}
Completer *UrlAdapter::completer() { return completerInstance; }
@@ -15,6 +15,6 @@ Completer *UrlAdapter::completer() { return completerInstance; }
QString UrlAdapter::prompt() { return "[url]"; }
UrlAdapter::~UrlAdapter() {
- delete completerInstance->model();
+ delete completerInstance->sourceModel();
delete completerInstance;
}
diff --git a/src/completion/UrlModel.cpp b/src/completion/UrlModel.cpp
index 7671fc3..8c5d75c 100644
--- a/src/completion/UrlModel.cpp
+++ b/src/completion/UrlModel.cpp
@@ -1,5 +1,6 @@
#include <QAbstractListModel>
#include <QWidget>
+#include <QtCore/qnamespace.h>
#include <QtCore>
#include "completion/UrlModel.hpp"
@@ -22,10 +23,10 @@ QVariant UrlModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole)
return item.url;
- else if (role == Qt::ToolTipRole)
- return item.description;
else if (role == Qt::UserRole)
return item.description;
+ else if (role == Qt::AccessibleDescriptionRole)
+ return item.url + " " + item.description;
return QVariant();
}
@@ -34,6 +35,7 @@ QHash<int, QByteArray> UrlModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "url";
roles[Qt::UserRole] = "description";
+ roles[Qt::AccessibleDescriptionRole] = "full";
return roles;
}
@@ -44,5 +46,5 @@ int UrlModel::rowCount(const QModelIndex &parent) const {
int UrlModel::columnCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
- return 2; // name + description
+ return 2; // url + description
}