From 8af5b49cba99be94c93fb4dc2497a1b38c705845 Mon Sep 17 00:00:00 2001 From: Matthew Costa Date: Tue, 5 Jul 2022 23:18:21 +0100 Subject: [PATCH] Ladybird: Rudimentary tabbed browsing support This patch removes the browser WebView from the window and places it inside a Tab object, all wrapped up in a QT tab control. So far you can create tabs, but can't close them. --- Ladybird/BrowserWindow.cpp | 81 ++++++++++++++++++++++---------------- Ladybird/BrowserWindow.h | 30 +++++++++----- Ladybird/CMakeLists.txt | 1 + Ladybird/Tab.cpp | 72 +++++++++++++++++++++++++++++++++ Ladybird/Tab.h | 41 +++++++++++++++++++ 5 files changed, 182 insertions(+), 43 deletions(-) create mode 100644 Ladybird/Tab.cpp create mode 100644 Ladybird/Tab.h diff --git a/Ladybird/BrowserWindow.cpp b/Ladybird/BrowserWindow.cpp index f1bb77b3c1d..d9ab7edfa50 100644 --- a/Ladybird/BrowserWindow.cpp +++ b/Ladybird/BrowserWindow.cpp @@ -1,55 +1,75 @@ +/* + * Copyright (c) 2022, Andreas Kling + * Copyright (c) 2022, Matthew Costa + * + * SPDX-License-Identifier: BSD-2-Clause + */ + #include "BrowserWindow.h" #include "WebView.h" #include #include #include -extern String s_serenity_resource_root; - BrowserWindow::BrowserWindow(Core::EventLoop& event_loop) : m_event_loop(event_loop) { - m_toolbar = new QToolBar; + m_tabs_container = new QTabWidget; + m_tabs_container->setElideMode(Qt::TextElideMode::ElideRight); + m_tabs_container->setMovable(true); + m_tabs_container->setTabsClosable(true); - auto reload_icon_path = QString("%1/res/icons/16x16/reload.png").arg(s_serenity_resource_root.characters()); - auto* reload_action = new QAction(QIcon(reload_icon_path), "Reload"); - reload_action->setShortcut(QKeySequence("Ctrl+R")); - m_toolbar->addAction(reload_action); + auto menu = menuBar()->addMenu("File"); + auto new_tab_action = menu->addAction("New Tab", QKeySequence(Qt::CTRL | Qt::Key_T)); + auto quit_action = menu->addAction("Quit", QKeySequence(Qt::CTRL | Qt::Key_Q)); - m_location_edit = new QLineEdit; - m_toolbar->addWidget(m_location_edit); + QObject::connect(new_tab_action, &QAction::triggered, this, &BrowserWindow::new_tab); + QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close); + QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) { + setWindowTitle(m_tabs_container->tabText(index)); + setWindowIcon(m_tabs_container->tabIcon(index)); + }); - addToolBar(m_toolbar); + new_tab(); - m_view = new WebView; - setCentralWidget(m_view); - - QObject::connect(m_view, &WebView::linkHovered, statusBar(), &QStatusBar::showMessage); - QObject::connect(m_view, &WebView::linkUnhovered, statusBar(), &QStatusBar::clearMessage); - - QObject::connect(m_view, &WebView::loadStarted, m_location_edit, &QLineEdit::setText); - QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &BrowserWindow::location_edit_return_pressed); - QObject::connect(m_view, &WebView::title_changed, this, &BrowserWindow::page_title_changed); - QObject::connect(m_view, &WebView::favicon_changed, this, &BrowserWindow::page_favicon_changed); - - QObject::connect(reload_action, &QAction::triggered, this, &BrowserWindow::reload); + setCentralWidget(m_tabs_container); } -void BrowserWindow::location_edit_return_pressed() +void BrowserWindow::new_tab() { - view().load(m_location_edit->text().toUtf8().data()); + auto tab = make(this); + auto tab_ptr = tab.ptr(); + m_tabs.append(std::move(tab)); + + if (m_current_tab == nullptr) { + m_current_tab = tab_ptr; + } + + m_tabs_container->addTab(tab_ptr, "New Tab"); + + QObject::connect(tab_ptr, &Tab::title_changed, this, &BrowserWindow::tab_title_changed); + QObject::connect(tab_ptr, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed); } -void BrowserWindow::page_title_changed(QString title) +int BrowserWindow::tab_index(Tab* tab) { - if (title.isEmpty()) + return m_tabs_container->indexOf(tab); +} + +void BrowserWindow::tab_title_changed(int index, QString const& title) +{ + if (title.isEmpty()) { + m_tabs_container->setTabText(index, "..."); setWindowTitle("Ladybird"); - else + } else { + m_tabs_container->setTabText(index, title); setWindowTitle(QString("%1 - Ladybird").arg(title)); + } } -void BrowserWindow::page_favicon_changed(QIcon icon) +void BrowserWindow::tab_favicon_changed(int index, QIcon icon) { + m_tabs_container->setTabIcon(index, icon); setWindowIcon(icon); } @@ -62,8 +82,3 @@ void BrowserWindow::closeEvent(QCloseEvent* event) // all of the browser windows have closed. m_event_loop.quit(0); } - -void BrowserWindow::reload() -{ - view().reload(); -} diff --git a/Ladybird/BrowserWindow.h b/Ladybird/BrowserWindow.h index 1eadf9b8562..2324dd5e582 100644 --- a/Ladybird/BrowserWindow.h +++ b/Ladybird/BrowserWindow.h @@ -1,7 +1,17 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Tab.h" +#include #include #include #include #include +#include +#include #include #pragma once @@ -13,21 +23,21 @@ class BrowserWindow : public QMainWindow { public: explicit BrowserWindow(Core::EventLoop&); - WebView& view() { return *m_view; } + WebView& view() const { return m_current_tab->view(); } + + int tab_index(Tab*); virtual void closeEvent(QCloseEvent*) override; public slots: - void location_edit_return_pressed(); - void page_title_changed(QString); - void page_favicon_changed(QIcon); - -public slots: - void reload(); + void tab_title_changed(int index, QString const&); + void tab_favicon_changed(int index, QIcon icon); + void new_tab(); private: - QToolBar* m_toolbar { nullptr }; - QLineEdit* m_location_edit { nullptr }; - WebView* m_view { nullptr }; + QTabWidget* m_tabs_container { nullptr }; + Vector> m_tabs; + Tab* m_current_tab { nullptr }; + Core::EventLoop& m_event_loop; }; diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 5333aea599a..ccc78718fff 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -37,6 +37,7 @@ set(SOURCES RequestManagerQt.cpp main.cpp WebView.cpp + Tab.cpp ) add_executable(ladybird ${SOURCES}) diff --git a/Ladybird/Tab.cpp b/Ladybird/Tab.cpp new file mode 100644 index 00000000000..906e059173c --- /dev/null +++ b/Ladybird/Tab.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2022, Matthew Costa + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Tab.h" +#include "BrowserWindow.h" +#include +#include +#include + +extern String s_serenity_resource_root; + +Tab::Tab(QMainWindow* window) + : m_window(window) +{ + m_layout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, this); + m_layout->setContentsMargins(0, 0, 0, 0); + + m_view = new WebView; + m_toolbar = new QToolBar; + m_location_edit = new QLineEdit; + + m_layout->addWidget(m_toolbar); + m_layout->addWidget(m_view); + + auto reload_icon_path = QString("%1/res/icons/16x16/reload.png").arg(s_serenity_resource_root.characters()); + auto* reload_action = new QAction(QIcon(reload_icon_path), "Reload"); + reload_action->setShortcut(QKeySequence("Ctrl+R")); + m_toolbar->addAction(reload_action); + m_toolbar->addWidget(m_location_edit); + + QObject::connect(m_view, &WebView::linkHovered, m_window->statusBar(), &QStatusBar::showMessage); + QObject::connect(m_view, &WebView::linkUnhovered, m_window->statusBar(), &QStatusBar::clearMessage); + + QObject::connect(m_view, &WebView::loadStarted, m_location_edit, &QLineEdit::setText); + QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &Tab::location_edit_return_pressed); + QObject::connect(m_view, &WebView::title_changed, this, &Tab::page_title_changed); + QObject::connect(m_view, &WebView::favicon_changed, this, &Tab::page_favicon_changed); + + QObject::connect(reload_action, &QAction::triggered, this, &Tab::reload); +} + +void Tab::reload() +{ + view().reload(); +} + +void Tab::location_edit_return_pressed() +{ + view().load(m_location_edit->text().toUtf8().data()); +} + +void Tab::page_title_changed(QString title) +{ + emit title_changed(tab_index(), std::move(title)); +} + +void Tab::page_favicon_changed(QIcon icon) +{ + emit favicon_changed(tab_index(), std::move(icon)); +} + +int Tab::tab_index() +{ + // FIXME: I hear you like footguns... + // There has to be a better way of doing this + auto browser_window = reinterpret_cast(m_window); + return browser_window->tab_index(this); +} diff --git a/Ladybird/Tab.h b/Ladybird/Tab.h new file mode 100644 index 00000000000..9e01c9e4c0b --- /dev/null +++ b/Ladybird/Tab.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2022, Matthew Costa + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "WebView.h" +#include +#include +#include +#include + +class Tab final : public QWidget { + Q_OBJECT +public: + explicit Tab(QMainWindow* window); + + WebView& view() { return *m_view; } + +public slots: + void location_edit_return_pressed(); + void page_title_changed(QString); + void page_favicon_changed(QIcon); + void reload(); + +signals: + void title_changed(int id, QString); + void favicon_changed(int id, QIcon); + +private: + QBoxLayout* m_layout; + QToolBar* m_toolbar { nullptr }; + QLineEdit* m_location_edit { nullptr }; + WebView* m_view { nullptr }; + QMainWindow* m_window { nullptr }; + + int tab_index(); +};