aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--TODO.org19
-rw-r--r--include/BrowserManager.hpp39
-rw-r--r--include/MainWindow.hpp6
-rw-r--r--spec/BrowserManagerSpec.cpp271
-rw-r--r--src/BrowserManager.cpp124
-rw-r--r--src/MainWindow.cpp58
6 files changed, 491 insertions, 26 deletions
diff --git a/TODO.org b/TODO.org
new file mode 100644
index 0000000..d101052
--- /dev/null
+++ b/TODO.org
@@ -0,0 +1,19 @@
+** Current
+- [X] Multi tabs
+- [X] Open in new tab/new window
+- [ ] Lua for command input
+- [ ] Modal keys
+- [ ] Tab history navigation
+
+** Next
+- [ ] History persistance
+- [ ] Sketch lua api for managing ui
+- [ ] Simpler shell-ey/vimscript-ey language for command input
+
+** Later
+- [ ] Fullscreen
+- [ ] File picker
+- [ ] Dev tools
+- [ ] remote debugging with cdp (spider-repl)
+
+** Maybe
diff --git a/include/BrowserManager.hpp b/include/BrowserManager.hpp
new file mode 100644
index 0000000..84d6f6a
--- /dev/null
+++ b/include/BrowserManager.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <QStackedLayout>
+#include <QWebEngineProfile>
+#include <QWebEngineView>
+#include <sys/types.h>
+
+class BrowserManager : public QWidget {
+ Q_OBJECT
+
+public:
+ inline static const QUrl NewtabURL = QUrl("about:blank");
+
+public:
+ BrowserManager(QWebEngineProfile *profile = new QWebEngineProfile);
+
+ QUrl currentUrl();
+ void setCurrentUrl(QUrl url);
+
+ QWebEngineView *createNewWebView(QUrl url = BrowserManager::NewtabURL,
+ bool focus = false);
+
+ std::vector<QUrl> webViewUrls();
+ u_int32_t currentWebViewIndex();
+ u_int32_t webViewCount();
+ void focusWebView(long index);
+ void nextWebView();
+ void previousWebView();
+
+ void closeWebView(long index);
+ void closeCurrentWebView();
+
+ void onNewWebViewRequest(QWebEngineNewWindowRequest &request);
+
+private:
+ QWebEngineProfile *profile;
+ QStackedLayout *layout;
+ QList<QWebEngineView *> webViewList;
+};
diff --git a/include/MainWindow.hpp b/include/MainWindow.hpp
index 552aa25..fde3178 100644
--- a/include/MainWindow.hpp
+++ b/include/MainWindow.hpp
@@ -4,6 +4,7 @@
#include <QObject>
#include <QWebEngineView>
+#include "BrowserManager.hpp"
#include "CommandInput.hpp"
class MainWindow : public QMainWindow {
@@ -14,9 +15,10 @@ protected:
void keyPressEvent(QKeyEvent *event) override;
void toggleURLInput();
void evaluateCommand(QString command);
- void hideInput();
+ void hideURLInput();
+ void showURLInput();
private:
- QWebEngineView *web;
+ BrowserManager *browserManager;
CommandInput *commandInput;
};
diff --git a/spec/BrowserManagerSpec.cpp b/spec/BrowserManagerSpec.cpp
new file mode 100644
index 0000000..f4af481
--- /dev/null
+++ b/spec/BrowserManagerSpec.cpp
@@ -0,0 +1,271 @@
+#include <QWebEngineNewWindowRequest>
+#include <QWebEngineProfile>
+#include <vector>
+
+#include "BrowserManager.hpp"
+#include "TestUtils.h"
+
+class BrowserManagerSpec : public QObject {
+ Q_OBJECT
+
+ class FakeNewWindowRequest : public QWebEngineNewWindowRequest {
+ public:
+ FakeNewWindowRequest(DestinationType t, const QRect &r, const QUrl &u,
+ bool b)
+ : QWebEngineNewWindowRequest(t, r, u, b, nullptr) {}
+ };
+
+private slots:
+ void testInitialState() {
+ context("when initialized");
+ it("opens a single tab") {
+ BrowserManager browserManager;
+
+ QCOMPARE(browserManager.webViewCount(), 1);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ browserManager.deleteLater();
+ }
+ }
+
+ void testCreateNewWebView() {
+ context("when creating a new background webview without a url");
+ it("opens new tab url as a background view") {
+ BrowserManager browserManager;
+
+ browserManager.createNewWebView();
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ std::vector<QUrl> urls = {QUrl(BrowserManager::NewtabURL),
+ QUrl(BrowserManager::NewtabURL)};
+ QCOMPARE(browserManager.webViewUrls(), urls);
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ }
+
+ context("when creating a new webview with a url and focus is false");
+ it("opens the given url in background") {
+ BrowserManager browserManager;
+
+ browserManager.createNewWebView(QUrl("https://duckduckgo.com"), false);
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ std::vector<QUrl> urls = {QUrl(BrowserManager::NewtabURL),
+ QUrl("https://duckduckgo.com")};
+ QCOMPARE(browserManager.webViewUrls(), urls);
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ }
+
+ context("when creating a new webview with a url and focus is true");
+ it("opens the given url as current view") {
+ BrowserManager browserManager;
+
+ browserManager.createNewWebView(QUrl("https://duckduckgo.com"), true);
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ std::vector<QUrl> urls = {QUrl(BrowserManager::NewtabURL),
+ QUrl("https://duckduckgo.com")};
+ QCOMPARE(browserManager.webViewUrls(), urls);
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+ QCOMPARE(browserManager.currentUrl(), QUrl("https://duckduckgo.com"));
+ }
+ }
+
+ void testNextNavigation() {
+ context("when nextWebView is called");
+ context("- and there is only 1 tab");
+ it("does nothing") {
+ BrowserManager browserManager;
+
+ browserManager.nextWebView();
+
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ }
+
+ context("when nextWebView is called");
+ context("- and there are tabs after the current tab");
+ it("goes to the next tab") {
+ BrowserManager browserManager;
+ browserManager.createNewWebView(QUrl("http://a1.com"));
+ browserManager.createNewWebView(QUrl("http://a2.com"));
+
+ browserManager.nextWebView();
+
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+ QCOMPARE(browserManager.currentUrl(), QUrl("http://a1.com"));
+ }
+
+ context("when nextWebView is called");
+ context("- and current tab is the last tab");
+ it("jumps to the first tab") {
+ BrowserManager browserManager;
+ browserManager.createNewWebView(QUrl("http://a1.com"));
+ browserManager.createNewWebView(QUrl("http://a2.com"), true);
+ QCOMPARE(browserManager.currentWebViewIndex(), 2);
+
+ browserManager.nextWebView();
+
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ }
+ }
+
+ void testPreviousNavigation() {
+ context("when previousWebView is called");
+ context("- and there is only 1 tab");
+ it("does nothing") {
+ BrowserManager browserManager;
+
+ browserManager.previousWebView();
+
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ }
+
+ context("when previousWebView is called");
+ context("- and there are tabs before the current tab");
+ it("goes to the next tab") {
+ BrowserManager browserManager;
+ browserManager.createNewWebView(QUrl("http://a1.com"));
+ browserManager.createNewWebView(QUrl("http://a2.com"), true);
+ QCOMPARE(browserManager.currentWebViewIndex(), 2);
+
+ browserManager.previousWebView();
+
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+ QCOMPARE(browserManager.currentUrl(), QUrl("http://a1.com"));
+ }
+
+ context("when previousWebView is called");
+ context("- and current tab is the last tab");
+ it("jumps to the last tab") {
+ BrowserManager browserManager;
+ browserManager.createNewWebView(QUrl("http://a1.com"));
+ browserManager.createNewWebView(QUrl("http://a2.com"));
+
+ browserManager.previousWebView();
+
+ QCOMPARE(browserManager.currentWebViewIndex(), 2);
+ QCOMPARE(browserManager.currentUrl(), QUrl("http://a2.com"));
+ }
+ }
+
+ void testCloseWebView() {
+ context("when closeWebView is called");
+ context("- with out of bounds index");
+ it("does nothing") {
+ BrowserManager browserManager;
+ browserManager.setCurrentUrl(QUrl("https://a.com"));
+
+ browserManager.closeWebView(1);
+
+ QCOMPARE(browserManager.webViewCount(), 1);
+ QCOMPARE(browserManager.webViewUrls(),
+ (std::vector<QUrl>{QUrl("https://a.com")}));
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), QUrl("https://a.com"));
+ }
+
+ context("when closeWebView is called");
+ context("- and there is only 1 tab");
+ it("closes the tab and opens empty tab in its place") {
+ BrowserManager browserManager;
+ browserManager.setCurrentUrl(QUrl("https://a.com"));
+ QCOMPARE(browserManager.webViewCount(), 1);
+
+ browserManager.closeWebView(0);
+
+ QCOMPARE(browserManager.webViewCount(), 1);
+ QCOMPARE(browserManager.webViewUrls(),
+ (std::vector<QUrl>{BrowserManager::NewtabURL}));
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), BrowserManager::NewtabURL);
+ }
+
+ context("when closeWebView is called");
+ context("- with the current tab index");
+ context("- and there are some tabs after");
+ it("closes the tab and focuses the next tab") {
+ BrowserManager browserManager;
+ browserManager.createNewWebView(QUrl("https://a1.com"), true);
+ browserManager.createNewWebView(QUrl("https://a2.com"));
+ QCOMPARE(browserManager.webViewCount(), 3);
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+
+ browserManager.closeWebView(1);
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ QCOMPARE(browserManager.webViewUrls(),
+ (std::vector<QUrl>{BrowserManager::NewtabURL,
+ QUrl("https://a2.com")}));
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+ QCOMPARE(browserManager.currentUrl(), QUrl("https://a2.com"));
+ }
+
+ context("when closeWebView is called");
+ context("- with the current tab index");
+ context("- which is the last tab");
+ it("closes the tab and focusses previous tab") {
+ BrowserManager browserManager;
+ browserManager.createNewWebView(QUrl("https://a1.com"));
+ browserManager.createNewWebView(QUrl("https://a2.com"), true);
+ QCOMPARE(browserManager.webViewCount(), 3);
+ QCOMPARE(browserManager.currentWebViewIndex(), 2);
+
+ browserManager.closeWebView(2);
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ QCOMPARE(browserManager.webViewUrls(),
+ (std::vector<QUrl>{BrowserManager::NewtabURL,
+ QUrl("https://a1.com")}));
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+ QCOMPARE(browserManager.currentUrl(), QUrl("https://a1.com"));
+ }
+ }
+
+ void testNewWindowRequestSignal() {
+ context("when webview emits a newWindowRequested signal");
+ context("- of type new tab");
+ it("opens a new web view and focusses it") {
+ BrowserManager browserManager;
+ browserManager.setCurrentUrl(QUrl("https://a.com"));
+ auto webview = browserManager.findChild<QWebEngineView *>();
+
+ FakeNewWindowRequest windowRequest(
+ FakeNewWindowRequest::DestinationType::InNewTab, QRect(0, 0, 0, 0),
+ QUrl("https://new.com"), true);
+ emit webview->page()->newWindowRequested(windowRequest);
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ QCOMPARE(
+ browserManager.webViewUrls(),
+ (std::vector<QUrl>{QUrl("https://a.com"), QUrl("https://new.com")}));
+ QCOMPARE(browserManager.currentWebViewIndex(), 1);
+ QCOMPARE(browserManager.currentUrl(), QUrl("https://new.com"));
+ }
+
+ context("when webview emits a newWindowRequested signal");
+ context("- of type new background tab");
+ it("opens a new web view in the background") {
+ BrowserManager browserManager;
+ browserManager.setCurrentUrl(QUrl("https://a.com"));
+ auto webview = browserManager.findChild<QWebEngineView *>();
+
+ FakeNewWindowRequest windowRequest(
+ FakeNewWindowRequest::DestinationType::InNewBackgroundTab,
+ QRect(0, 0, 0, 0), QUrl("https://new.com"), true);
+ emit webview->page()->newWindowRequested(windowRequest);
+
+ QCOMPARE(browserManager.webViewCount(), 2);
+ QCOMPARE(
+ browserManager.webViewUrls(),
+ (std::vector<QUrl>{QUrl("https://a.com"), QUrl("https://new.com")}));
+ QCOMPARE(browserManager.currentWebViewIndex(), 0);
+ QCOMPARE(browserManager.currentUrl(), QUrl("https://a.com"));
+ }
+ }
+};
+
+QTEST_REGISTER(BrowserManagerSpec)
+#include "BrowserManagerSpec.moc"
diff --git a/src/BrowserManager.cpp b/src/BrowserManager.cpp
new file mode 100644
index 0000000..ddc6105
--- /dev/null
+++ b/src/BrowserManager.cpp
@@ -0,0 +1,124 @@
+#include <QStackedLayout>
+#include <QWebEngineNewWindowRequest>
+#include <QWebEngineView>
+
+#include "BrowserManager.hpp"
+
+BrowserManager::BrowserManager(QWebEngineProfile *profile) : QWidget() {
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ layout = new QStackedLayout(this);
+ layout->setStackingMode(QStackedLayout::StackAll);
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ this->profile = profile;
+
+ createNewWebView(BrowserManager::NewtabURL, true);
+}
+
+QWebEngineView *BrowserManager::createNewWebView(QUrl url, bool focus) {
+ auto webview = new QWebEngineView(profile);
+ webview->setUrl(url);
+ layout->addWidget(webview);
+ webViewList.append(webview);
+
+ connect(webview->page(), &QWebEnginePage::newWindowRequested, this,
+ &BrowserManager::onNewWebViewRequest);
+
+ if (focus)
+ focusWebView(webViewList.length() - 1);
+
+ return webview;
+}
+
+void BrowserManager::onNewWebViewRequest(QWebEngineNewWindowRequest &request) {
+ switch (request.destination()) {
+ case QWebEngineNewWindowRequest::InNewTab:
+ createNewWebView(request.requestedUrl(), true);
+ break;
+ case QWebEngineNewWindowRequest::InNewBackgroundTab:
+ createNewWebView(request.requestedUrl(), false);
+ break;
+ case QWebEngineNewWindowRequest::InNewWindow:
+ // TODO: Impl
+ createNewWebView(request.requestedUrl(), true);
+ break;
+ case QWebEngineNewWindowRequest::InNewDialog:
+ // TODO: Impl
+ createNewWebView(request.requestedUrl(), true);
+ break;
+ }
+}
+
+void BrowserManager::nextWebView() {
+ if (webViewList.isEmpty())
+ return;
+ auto index = currentWebViewIndex() + 1;
+ auto total = webViewList.length();
+ index = index >= total ? index % total : index;
+ focusWebView(index);
+}
+
+void BrowserManager::previousWebView() {
+ if (webViewList.isEmpty())
+ return;
+ auto index = currentWebViewIndex() - 1;
+ auto total = webViewList.length();
+ index = index < 0 ? total + index : index;
+ focusWebView(index);
+}
+
+void BrowserManager::closeCurrentWebView() {
+ closeWebView(currentWebViewIndex());
+}
+
+void BrowserManager::closeWebView(long index) {
+ if (index < 0 || index >= webViewList.length())
+ return;
+
+ auto webview = webViewList.at(index);
+ layout->removeWidget(webview);
+ webViewList.removeAt(index);
+ disconnect(webview->page());
+ webview->deleteLater();
+
+ focusWebView(currentWebViewIndex());
+
+ if (webViewList.isEmpty()) {
+ createNewWebView(BrowserManager::NewtabURL, true);
+ }
+}
+
+std::vector<QUrl> BrowserManager::webViewUrls() {
+ std::vector<QUrl> urls;
+ for (auto &view : webViewList)
+ urls.push_back(view->url());
+ return urls;
+}
+
+u_int32_t BrowserManager::currentWebViewIndex() {
+ return layout->currentIndex();
+}
+u_int32_t BrowserManager::webViewCount() { return webViewList.length(); }
+
+void BrowserManager::focusWebView(long index) {
+ if (webViewList.isEmpty())
+ return;
+
+ index = std::max((long)0, std::min(index, (long)webViewList.length() - 1));
+ layout->setCurrentIndex(index);
+}
+
+QUrl BrowserManager::currentUrl() {
+ if (currentWebViewIndex() >= webViewList.length())
+ return QUrl("about:newtab");
+
+ return webViewList.at(currentWebViewIndex())->url();
+}
+
+void BrowserManager::setCurrentUrl(QUrl url) {
+ if (currentWebViewIndex() >= webViewList.length())
+ return;
+
+ webViewList.at(currentWebViewIndex())->setUrl(url);
+}
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 809e5de..ee8d128 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -2,49 +2,53 @@
#include <QStackedLayout>
#include <QVBoxLayout>
#include <QWebEngineView>
-#include <iostream>
-#include <lua.hpp>
+#include "BrowserManager.hpp"
#include "CommandInput.hpp"
#include "MainWindow.hpp"
MainWindow::MainWindow() {
setStyleSheet("background-color: #000; color: #fff;");
+ setCentralWidget(new QWidget());
- auto centralWidget = new QWidget();
- setCentralWidget(centralWidget);
-
+ // Root stacked layout
auto layout = new QStackedLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
- centralWidget->setLayout(layout);
layout->setStackingMode(QStackedLayout::StackAll);
+ centralWidget()->setLayout(layout);
- // Webengine
- web = new QWebEngineView();
- web->setUrl(QUrl("https://ediblemonad.dev"));
- layout->addWidget(web);
+ // Web engine
+ browserManager = new BrowserManager(new QWebEngineProfile("web-browser"));
+ layout->addWidget(browserManager);
// Command input
- commandInput = new CommandInput(web->url().toString());
- hideInput();
+ commandInput = new CommandInput(browserManager->currentUrl().toString());
+ hideURLInput();
commandInput->move(0, 0);
connect(commandInput, &CommandInput::submitted, this,
&MainWindow::evaluateCommand);
- connect(commandInput, &CommandInput::cancelled, this, &MainWindow::hideInput);
-
+ connect(commandInput, &CommandInput::cancelled, this,
+ &MainWindow::hideURLInput);
layout->addWidget(commandInput);
}
-void MainWindow::hideInput() {
+void MainWindow::hideURLInput() {
commandInput->setInputFocus(false);
commandInput->setHidden(true);
}
+void MainWindow::showURLInput() {
+ commandInput->setInputText(browserManager->currentUrl().toString());
+ commandInput->raise();
+ commandInput->setHidden(false);
+ commandInput->setInputFocus(true);
+}
+
void MainWindow::evaluateCommand(QString command) {
if (!command.isEmpty())
- web->setUrl(command);
- hideInput();
+ browserManager->setCurrentUrl(command);
+ hideURLInput();
}
void MainWindow::keyPressEvent(QKeyEvent *event) {
@@ -52,15 +56,21 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
if (combo.key() == Qt::Key_L &&
combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
toggleURLInput();
+ } else if (combo.key() == Qt::Key_T &&
+ combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
+ browserManager->createNewWebView(QUrl("https://duckduckgo.com"), true);
+ } else if (combo.key() == Qt::Key_J &&
+ combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
+ browserManager->nextWebView();
+ } else if (combo.key() == Qt::Key_K &&
+ combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
+ browserManager->previousWebView();
+ } else if (combo.key() == Qt::Key_W &&
+ combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
+ browserManager->closeCurrentWebView();
}
}
void MainWindow::toggleURLInput() {
- auto shouldShow = commandInput->isHidden();
- if (shouldShow) {
- commandInput->setInputText(web->url().toString());
- commandInput->raise();
- }
- commandInput->setHidden(!shouldShow);
- commandInput->setInputFocus(shouldShow);
+ commandInput->isHidden() ? showURLInput() : hideURLInput();
}