diff options
| author | Akshay Nair <phenax5@gmail.com> | 2025-03-15 19:17:03 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2025-03-15 20:08:30 +0530 |
| commit | 387cf24e389854ecc97f6236fdd6acbc2486dd52 (patch) | |
| tree | a51b51de0064fb10685d2da85d11748c4a32ce73 | |
| parent | 05ae8976a8e1ab5411058de244c4e2fcc87ec369 (diff) | |
| download | null-browser-387cf24e389854ecc97f6236fdd6acbc2486dd52.tar.gz null-browser-387cf24e389854ecc97f6236fdd6acbc2486dd52.zip | |
Replaced QCompleter with custom implementation
Diffstat (limited to '')
| -rw-r--r-- | TODO.org | 13 | ||||
| -rw-r--r-- | include/CommandParser.hpp | 1 | ||||
| -rw-r--r-- | include/InputMediator.hpp | 3 | ||||
| -rw-r--r-- | include/completion/CommandsModel.hpp | 6 | ||||
| -rw-r--r-- | include/completion/Completer.hpp | 31 | ||||
| -rw-r--r-- | include/completion/CompleterDelegate.hpp | 7 | ||||
| -rw-r--r-- | include/completion/FuzzySearchProxyModel.hpp | 15 | ||||
| -rw-r--r-- | include/completion/TabsAdapter.hpp | 19 | ||||
| -rw-r--r-- | include/completion/TabsModel.hpp | 25 | ||||
| -rw-r--r-- | include/widgets/InputLine.hpp | 5 | ||||
| -rw-r--r-- | spec/InputLineSpec.cpp | 40 | ||||
| -rw-r--r-- | src/CommandParser.cpp | 2 | ||||
| -rw-r--r-- | src/InputMediator.cpp | 23 | ||||
| -rw-r--r-- | src/completion/CommandsAdapter.cpp | 6 | ||||
| -rw-r--r-- | src/completion/CommandsModel.cpp | 5 | ||||
| -rw-r--r-- | src/completion/Completer.cpp | 92 | ||||
| -rw-r--r-- | src/completion/CompleterDelegate.cpp | 8 | ||||
| -rw-r--r-- | src/completion/FuzzySearchProxyModel.cpp | 24 | ||||
| -rw-r--r-- | src/completion/TabsAdapter.cpp | 20 | ||||
| -rw-r--r-- | src/completion/TabsModel.cpp | 47 | ||||
| -rw-r--r-- | src/completion/UrlAdapter.cpp | 6 | ||||
| -rw-r--r-- | src/completion/UrlModel.cpp | 8 | ||||
| -rw-r--r-- | src/widgets/InputLine.cpp | 27 |
23 files changed, 375 insertions, 58 deletions
@@ -7,6 +7,7 @@ - [X] Command/URL input executor - [X] Refactor MainWindow - [ ] Buffer list/search +- [ ] Keybindings - [ ] Modal keys - [ ] Tab history navigation - [ ] Press tab/shift-tab for next/prev in completion @@ -15,13 +16,21 @@ ** Next - [ ] History persistance - [ ] History completion -- [ ] Sketch lua api for managing ui - [ ] Open url parsing (add protocol if missing, remove quotes, etc) +- [ ] Destructor for completer instance ** Later -- [ ] Fullscreen +- [ ] downloading +- [ ] Search text in page +- [ ] Notifications - [ ] Dev tools +- [ ] Search engine search if its not a url / aliases + +** Later later +- [ ] Fullscreen - [ ] remote debugging with cdp (spider-repl) +- [ ] Pipe page contents to external program ** Maybe +- [ ] lua api for managing ui - [ ] Custom file picker diff --git a/include/CommandParser.hpp b/include/CommandParser.hpp index dea04c5..9197df5 100644 --- a/include/CommandParser.hpp +++ b/include/CommandParser.hpp @@ -10,6 +10,7 @@ enum CommandType { TabOpen, TabNext, TabPrev, + TabSelect, }; struct Cmd { diff --git a/include/InputMediator.hpp b/include/InputMediator.hpp index 660d39f..98ae6d2 100644 --- a/include/InputMediator.hpp +++ b/include/InputMediator.hpp @@ -24,6 +24,7 @@ public: void showURLInput(QString url = "", OpenType openType = OpenType::OpenUrl); void showCommandInput(QString command = ""); + void showBufferInput(QString command = ""); void hideInputLine(); DELEGATE(webViewStack, openUrl, openUrl) @@ -58,3 +59,5 @@ private: }; class CommandEval : public EvaluationType {}; + +class BufferEval : public EvaluationType {}; diff --git a/include/completion/CommandsModel.hpp b/include/completion/CommandsModel.hpp index 28836fe..4f64654 100644 --- a/include/completion/CommandsModel.hpp +++ b/include/completion/CommandsModel.hpp @@ -9,11 +9,13 @@ struct Command { QString description; }; +// TODO: Combine commands model completion and commands evaluation const QList<Command> commands = { - {.name = "open", .description = "Open a url in the current tab"}, + {.name = "open", .description = "Update current URL"}, + {.name = "tabs", .description = "Select a tab"}, {.name = "tabopen", .description = "Open a url in a new tab"}, {.name = "tabnext", .description = "Go to next tab"}, - {.name = "tabbprev", .description = "Go to previous tab"}, + {.name = "tabprev", .description = "Go to previous tab"}, }; class CommandsModel : public QAbstractListModel { diff --git a/include/completion/Completer.hpp b/include/completion/Completer.hpp index b67de61..9ad03d4 100644 --- a/include/completion/Completer.hpp +++ b/include/completion/Completer.hpp @@ -1,14 +1,39 @@ #pragma once #include <QCompleter> +#include <QSortFilterProxyModel> #include <QWidget> #include <QtCore> +#include <QtWidgets/qtreeview.h> +#include <cstdint> -class Completer : public QCompleter { +#include "completion/CompleterDelegate.hpp" +#include "completion/FuzzySearchProxyModel.hpp" +#include "utils.hpp" + +class Completer : public QWidget { Q_OBJECT public: - Completer(QObject *parent = nullptr); + Completer(); + void setSourceModel(QAbstractItemModel *model); + + DELEGATE((&proxyModel), sourceModel, sourceModel) + +signals: + void accepted(QString text); + +public slots: + void onTextChange(QString text); + bool onKeyPressEvent(QKeyEvent *event); + +protected: + void keyPressEvent(QKeyEvent *event) override { onKeyPressEvent(event); } + void setHighlightedRow(uint32_t); + void acceptHighlighted(); - // bool eventFilter(QObject *watched, QEvent *event) override; +private: + QTreeView *view; + FuzzySearchProxyModel proxyModel; + CompleterDelegate *viewDelegate; }; diff --git a/include/completion/CompleterDelegate.hpp b/include/completion/CompleterDelegate.hpp index fc7a321..8de22de 100644 --- a/include/completion/CompleterDelegate.hpp +++ b/include/completion/CompleterDelegate.hpp @@ -2,7 +2,9 @@ #include <QStyledItemDelegate> #include <QWidget> +#include <QtCore/qabstractitemmodel.h> #include <QtCore> +#include <cstdint> class CompleterDelegate : public QStyledItemDelegate { Q_OBJECT @@ -12,4 +14,9 @@ public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setCurrentRow(uint32_t); + uint32_t currentRow(); + +private: + uint32_t row; }; diff --git a/include/completion/FuzzySearchProxyModel.hpp b/include/completion/FuzzySearchProxyModel.hpp new file mode 100644 index 0000000..ee4bfc3 --- /dev/null +++ b/include/completion/FuzzySearchProxyModel.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include <QWidget> +#include <QtCore> + +class FuzzySearchProxyModel : public QSortFilterProxyModel { + Q_OBJECT + +public: + using QSortFilterProxyModel::QSortFilterProxyModel; + +protected: + bool filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const override; +}; diff --git a/include/completion/TabsAdapter.hpp b/include/completion/TabsAdapter.hpp new file mode 100644 index 0000000..089eefb --- /dev/null +++ b/include/completion/TabsAdapter.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <QWidget> +#include <QtCore> + +#include "completion/Adapter.hpp" + +class TabsAdapter : public Adapter { + Q_OBJECT + +public: + TabsAdapter(); + ~TabsAdapter(); + Completer *completer() override; + QString prompt() override; + +private: + Completer *completerInstance; +}; diff --git a/include/completion/TabsModel.hpp b/include/completion/TabsModel.hpp new file mode 100644 index 0000000..be1ea78 --- /dev/null +++ b/include/completion/TabsModel.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include <QAbstractListModel> +#include <QWidget> +#include <QtCore> + +struct Buffer { + QString url; + QString title; +}; + +class TabsModel : public QAbstractListModel { + Q_OBJECT + +public: + TabsModel(QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QHash<int, QByteArray> roleNames() const override; + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + +private: + QList<Buffer> items; +}; diff --git a/include/widgets/InputLine.hpp b/include/widgets/InputLine.hpp index 07b6e92..45aa559 100644 --- a/include/widgets/InputLine.hpp +++ b/include/widgets/InputLine.hpp @@ -21,7 +21,9 @@ public: DELEGATE(input, hasFocus, isInputFocussed) DELEGATE(input, setText, setInputText) DELEGATE(input, text, getInputText) - DELEGATE(adapterInstance, prompt, prompt) + DELEGATE(promptPrefix, text, prompt) + + // bool eventFilter(QObject *obj, QEvent *event) override; signals: void submitted(QString text); @@ -32,7 +34,6 @@ protected: void emitSubmit(); private: - QBoxLayout *layout; QLineEdit *input; QLabel *promptPrefix; Adapter *adapterInstance = nullptr; diff --git a/spec/InputLineSpec.cpp b/spec/InputLineSpec.cpp index 9a3115a..db6890c 100644 --- a/spec/InputLineSpec.cpp +++ b/spec/InputLineSpec.cpp @@ -1,16 +1,38 @@ +#include "completion/Adapter.hpp" +#include "completion/Completer.hpp" #include "testUtils.h" #include "widgets/InputLine.hpp" +#include <QAbstractItemView> +#include <QtCore/qsharedpointer.h> +#include <QtCore/qstringlistmodel.h> +#include <QtCore> +#include <QtTest/qtestkeyboard.h> class InputLineSpec : public QObject { Q_OBJECT + class FakeInputAdapter : public Adapter { + public: + FakeInputAdapter() : Adapter() { + completerInstance = new Completer; + QStringList list = {"one", "two", "three"}; + QStringListModel model(list); + completerInstance->setSourceModel(&model); + } + Completer *completer() { return completerInstance; } + QString prompt() { return "fake prompt"; } + + private: + Completer *completerInstance; + }; + private slots: void testWithInitialInput() { context("when initialized with some text"); it("sets the initial input text") { InputLine inputWidget("Initial content", new QWidget()); - QCOMPARE(inputWidget.getInputText(), QString("Initial content")); + QCOMPARE(inputWidget.getInputText(), "Initial content"); } } @@ -22,7 +44,7 @@ private slots: auto input = inputWidget.findChild<QLineEdit *>(); QTest::keyClicks(input, " updated"); - QCOMPARE(inputWidget.getInputText(), QString("Initial content updated")); + QCOMPARE(inputWidget.getInputText(), "Initial content updated"); } context("when setInputText is called"); @@ -31,7 +53,7 @@ private slots: inputWidget.setInputText("New content"); - QCOMPARE(inputWidget.getInputText(), QString("New content")); + QCOMPARE(inputWidget.getInputText(), "New content"); } } @@ -96,6 +118,18 @@ private slots: QVERIFY(!inputWidget.isInputFocussed()); } } + + void testInputPrompt() { + it("sets the prompt text using the adapter") { + InputLine inputWidget("Initial content", new QWidget()); + FakeInputAdapter inputAdapter; + inputWidget.setAdapter(&inputAdapter); + + QCOMPARE(inputWidget.prompt(), "fake prompt"); + } + } + + // TODO: completions test }; QTEST_REGISTER(InputLineSpec) diff --git a/src/CommandParser.cpp b/src/CommandParser.cpp index 773d37c..2b9b422 100644 --- a/src/CommandParser.cpp +++ b/src/CommandParser.cpp @@ -30,6 +30,8 @@ CommandType CommandParser::toCommandType(QString cmd) { return TabNext; if (cmd == "tp" || cmd == "tabprev") return TabPrev; + if (cmd == "tabs") + return TabSelect; return Noop; } diff --git a/src/InputMediator.cpp b/src/InputMediator.cpp index 8bcadd0..ee7a552 100644 --- a/src/InputMediator.cpp +++ b/src/InputMediator.cpp @@ -5,6 +5,7 @@ #include "InputMediator.hpp" #include "LuaRuntime.hpp" #include "completion/CommandsAdapter.hpp" +#include "completion/TabsAdapter.hpp" #include "completion/UrlAdapter.hpp" #include "widgets/WebViewStack.hpp" @@ -32,6 +33,9 @@ void InputMediator::onInputSubmit(QString input) { if (auto urlEval = dynamic_cast<UrlEval *>(currentEvaluationType)) return webViewStack->openUrl(input, urlEval->type()); + + if (dynamic_cast<BufferEval *>(currentEvaluationType)) + return webViewStack->focusWebView(1); } void InputMediator::hideInputLine() { @@ -52,6 +56,13 @@ void InputMediator::showCommandInput(QString cmd) { showInputLine(); } +void InputMediator::showBufferInput(QString url) { + setEvaluationType(new CommandEval()); + inputLine->setAdapter(new TabsAdapter); + inputLine->setInputText(url); + showInputLine(); +} + void InputMediator::setEvaluationType(EvaluationType *evalType) { if (currentEvaluationType) delete currentEvaluationType; @@ -77,19 +88,23 @@ void InputMediator::evaluateCommand(QString command) { if (cmd.argsString.trimmed().isEmpty()) showURLInput("", OpenType::OpenUrl); else - webViewStack->openUrl(cmd.argsString, OpenType::OpenUrl); + openUrl(cmd.argsString, OpenType::OpenUrl); break; case CommandType::TabOpen: if (cmd.argsString.trimmed().isEmpty()) showURLInput("", OpenType::OpenUrlInTab); else - webViewStack->openUrl(cmd.argsString, OpenType::OpenUrlInTab); + openUrl(cmd.argsString, OpenType::OpenUrlInTab); break; case CommandType::TabNext: - webViewStack->next(); + nextWebView(); break; case CommandType::TabPrev: - webViewStack->previous(); + previousWebView(); + break; + case CommandType::TabSelect: + showBufferInput(); + // focusWebView(long index) // TODO: parse index and select break; case CommandType::Noop: break; 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 } diff --git a/src/widgets/InputLine.cpp b/src/widgets/InputLine.cpp index 130732c..7e0a2dd 100644 --- a/src/widgets/InputLine.cpp +++ b/src/widgets/InputLine.cpp @@ -9,6 +9,8 @@ #include <QWidgetAction> #include <QWindow> #include <QtCore> +#include <QtWidgets/qboxlayout.h> +#include <QtWidgets/qlineedit.h> #include "completion/CommandsAdapter.hpp" #include "widgets/InputLine.hpp" @@ -29,17 +31,19 @@ InputLine::InputLine(QString defaultInput, QWidget *parentNode) : QWidget(parentNode) { setContentsMargins(0, 0, 0, 0); move(0, 0); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setStyleSheet(rootStyles); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); auto cmdLineBox = new QWidget(); layout->addWidget(cmdLineBox); auto cmdLineLayout = new QHBoxLayout(); cmdLineBox->setLayout(cmdLineLayout); cmdLineBox->layout()->setContentsMargins(0, 0, 0, 0); + cmdLineBox->setContentsMargins(0, 0, 0, 0); cmdLineLayout->setSpacing(0); promptPrefix = new QLabel(); @@ -47,31 +51,44 @@ InputLine::InputLine(QString defaultInput, QWidget *parentNode) promptPrefix->setContentsMargins(0, 0, 0, 0); input = new QLineEdit(defaultInput); input->setFocusPolicy(Qt::StrongFocus); + input->setContentsMargins(0, 0, 0, 0); cmdLineLayout->addWidget(promptPrefix); cmdLineLayout->addWidget(input); - setFixedHeight(input->sizeHint().height()); + cmdLineBox->setFixedHeight(input->sizeHint().height()); setAdapter(new CommandsAdapter()); } void InputLine::setAdapter(Adapter *adapter) { - if (this->adapterInstance) + if (this->adapterInstance) { + layout()->removeWidget(this->adapterInstance->completer()); delete this->adapterInstance; + } this->adapterInstance = adapter; promptPrefix->setText(adapter->prompt()); - input->setCompleter(adapter->completer()); + + auto completer = adapter->completer(); + layout()->addWidget(completer); + completer->move(0, 0); + connect(input, &QLineEdit::textChanged, completer, &Completer::onTextChange); + connect(completer, &Completer::accepted, input, &QLineEdit::setText); } void InputLine::emitSubmit() { emit submitted(getInputText()); } void InputLine::keyPressEvent(QKeyEvent *event) { auto combo = event->keyCombination(); + + // Forward events to completer first + bool shouldStop = adapterInstance->completer()->onKeyPressEvent(event); + if (shouldStop) + return; + auto ctrlL = combo.key() == Qt::Key_L && combo.keyboardModifiers().testFlag(Qt::ControlModifier); auto esc = combo.key() == Qt::Key_Escape; auto enter = combo.key() == Qt::Key_Return || combo.key() == Qt::Key_Enter; - if (esc || ctrlL) emit cancelled(); else if (enter) |
