aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.org13
-rw-r--r--include/CommandParser.hpp1
-rw-r--r--include/InputMediator.hpp3
-rw-r--r--include/completion/CommandsModel.hpp6
-rw-r--r--include/completion/Completer.hpp31
-rw-r--r--include/completion/CompleterDelegate.hpp7
-rw-r--r--include/completion/FuzzySearchProxyModel.hpp15
-rw-r--r--include/completion/TabsAdapter.hpp19
-rw-r--r--include/completion/TabsModel.hpp25
-rw-r--r--include/widgets/InputLine.hpp5
-rw-r--r--spec/InputLineSpec.cpp40
-rw-r--r--src/CommandParser.cpp2
-rw-r--r--src/InputMediator.cpp23
-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
-rw-r--r--src/widgets/InputLine.cpp27
23 files changed, 375 insertions, 58 deletions
diff --git a/TODO.org b/TODO.org
index f342d15..b956c88 100644
--- a/TODO.org
+++ b/TODO.org
@@ -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)