|
| 1 | +/* |
| 2 | + * Copyright (c) 2022, Andreas Kling <[email protected]> |
| 3 | + * Copyright (c) 2022, Matthew Costa <[email protected]> |
| 4 | + * |
| 5 | + * SPDX-License-Identifier: BSD-2-Clause |
| 6 | + */ |
| 7 | + |
1 | 8 | #include "BrowserWindow.h"
|
2 | 9 | #include "WebView.h"
|
3 |
| -#include <QStatusBar> |
| 10 | +#include <LibCore/EventLoop.h> |
| 11 | +#include <QAction> |
| 12 | +#include <QPlainTextEdit> |
| 13 | + |
| 14 | +extern String s_serenity_resource_root; |
4 | 15 |
|
5 |
| -BrowserWindow::BrowserWindow() |
| 16 | +BrowserWindow::BrowserWindow(Core::EventLoop& event_loop) |
| 17 | + : m_event_loop(event_loop) |
6 | 18 | {
|
7 |
| - m_toolbar = new QToolBar; |
8 |
| - m_location_edit = new QLineEdit; |
9 |
| - m_toolbar->addWidget(m_location_edit); |
| 19 | + m_tabs_container = new QTabWidget; |
| 20 | + m_tabs_container->setElideMode(Qt::TextElideMode::ElideRight); |
| 21 | + m_tabs_container->setMovable(true); |
| 22 | + m_tabs_container->setTabsClosable(true); |
| 23 | + |
| 24 | + m_tabs_bar = m_tabs_container->findChild<QTabBar*>(); |
| 25 | + m_tabs_bar->hide(); |
| 26 | + |
| 27 | + auto* menu = menuBar()->addMenu("&File"); |
| 28 | + |
| 29 | + auto* new_tab_action = new QAction("New &Tab"); |
| 30 | + new_tab_action->setShortcut(QKeySequence("Ctrl+T")); |
| 31 | + menu->addAction(new_tab_action); |
| 32 | + |
| 33 | + auto* quit_action = new QAction("&Quit"); |
| 34 | + quit_action->setShortcut(QKeySequence("Ctrl+Q")); |
| 35 | + menu->addAction(quit_action); |
| 36 | + |
| 37 | + auto* inspect_menu = menuBar()->addMenu("&Inspect"); |
| 38 | + |
| 39 | + auto* view_source_action = new QAction("View &Source"); |
| 40 | + view_source_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-html.png").arg(s_serenity_resource_root.characters()))); |
| 41 | + view_source_action->setShortcut(QKeySequence("CTRL + U")); |
| 42 | + inspect_menu->addAction(view_source_action); |
| 43 | + QObject::connect(view_source_action, &QAction::triggered, this, [this] { |
| 44 | + if (m_current_tab) { |
| 45 | + auto source = m_current_tab->view().source(); |
| 46 | + |
| 47 | + auto* text_edit = new QPlainTextEdit; |
| 48 | + text_edit->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont)); |
| 49 | + text_edit->resize(800, 600); |
| 50 | + text_edit->setPlainText(source.characters()); |
| 51 | + text_edit->show(); |
| 52 | + } |
| 53 | + }); |
| 54 | + |
| 55 | + auto* debug_menu = menuBar()->addMenu("&Debug"); |
| 56 | + |
| 57 | + auto* dump_dom_tree_action = new QAction("Dump DOM Tree"); |
| 58 | + dump_dom_tree_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters()))); |
| 59 | + debug_menu->addAction(dump_dom_tree_action); |
| 60 | + QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] { |
| 61 | + debug_request("dump-dom-tree"); |
| 62 | + }); |
| 63 | + |
| 64 | + auto* dump_layout_tree_action = new QAction("Dump Layout Tree"); |
| 65 | + dump_layout_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layout.png").arg(s_serenity_resource_root.characters()))); |
| 66 | + debug_menu->addAction(dump_layout_tree_action); |
| 67 | + QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] { |
| 68 | + debug_request("dump-layout-tree"); |
| 69 | + }); |
| 70 | + |
| 71 | + auto* dump_stacking_context_tree_action = new QAction("Dump Stacking Context Tree"); |
| 72 | + dump_stacking_context_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layers.png").arg(s_serenity_resource_root.characters()))); |
| 73 | + debug_menu->addAction(dump_stacking_context_tree_action); |
| 74 | + QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] { |
| 75 | + debug_request("dump-stacking-context-tree"); |
| 76 | + }); |
| 77 | + |
| 78 | + auto* dump_style_sheets_action = new QAction("Dump Style Sheets"); |
| 79 | + dump_style_sheets_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-css.png").arg(s_serenity_resource_root.characters()))); |
| 80 | + debug_menu->addAction(dump_style_sheets_action); |
| 81 | + QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] { |
| 82 | + debug_request("dump-style-sheets"); |
| 83 | + }); |
| 84 | + |
| 85 | + auto* dump_history_action = new QAction("Dump History"); |
| 86 | + dump_history_action->setIcon(QIcon(QString("%1/res/icons/16x16/history.png").arg(s_serenity_resource_root.characters()))); |
| 87 | + debug_menu->addAction(dump_history_action); |
| 88 | + QObject::connect(dump_history_action, &QAction::triggered, this, [this] { |
| 89 | + debug_request("dump-history"); |
| 90 | + }); |
| 91 | + |
| 92 | + auto* dump_cookies_action = new QAction("Dump Cookies"); |
| 93 | + dump_cookies_action->setIcon(QIcon(QString("%1/res/icons/browser/cookie.png").arg(s_serenity_resource_root.characters()))); |
| 94 | + debug_menu->addAction(dump_cookies_action); |
| 95 | + QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] { |
| 96 | + debug_request("dump-cookies"); |
| 97 | + }); |
| 98 | + |
| 99 | + auto* dump_local_storage_action = new QAction("Dump Local Storage"); |
| 100 | + dump_local_storage_action->setIcon(QIcon(QString("%1/res/icons/browser/local-storage.png").arg(s_serenity_resource_root.characters()))); |
| 101 | + debug_menu->addAction(dump_local_storage_action); |
| 102 | + QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] { |
| 103 | + debug_request("dump-local-storage"); |
| 104 | + }); |
| 105 | + |
| 106 | + debug_menu->addSeparator(); |
| 107 | + |
| 108 | + auto* show_line_box_borders_action = new QAction("Show Line Box Borders"); |
| 109 | + show_line_box_borders_action->setCheckable(true); |
| 110 | + debug_menu->addAction(show_line_box_borders_action); |
| 111 | + QObject::connect(show_line_box_borders_action, &QAction::triggered, this, [this, show_line_box_borders_action] { |
| 112 | + bool state = show_line_box_borders_action->isChecked(); |
| 113 | + debug_request("set-line-box-borders", state ? "on" : "off"); |
| 114 | + }); |
| 115 | + |
| 116 | + debug_menu->addSeparator(); |
| 117 | + |
| 118 | + auto* collect_garbage_action = new QAction("Collect Garbage!!"); |
| 119 | + collect_garbage_action->setIcon(QIcon(QString("%1/res/icons/16x16/trash-can.png").arg(s_serenity_resource_root.characters()))); |
| 120 | + debug_menu->addAction(collect_garbage_action); |
| 121 | + QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] { |
| 122 | + debug_request("collect-garbage"); |
| 123 | + }); |
| 124 | + |
| 125 | + auto* clear_cache_action = new QAction("Clear Cache"); |
| 126 | + clear_cache_action->setIcon(QIcon(QString("%1/res/icons/browser/clear-cache.png").arg(s_serenity_resource_root.characters()))); |
| 127 | + debug_menu->addAction(clear_cache_action); |
| 128 | + QObject::connect(clear_cache_action, &QAction::triggered, this, [this] { |
| 129 | + debug_request("clear-cache"); |
| 130 | + }); |
| 131 | + |
| 132 | + debug_menu->addSeparator(); |
10 | 133 |
|
11 |
| - addToolBar(m_toolbar); |
| 134 | + auto* enable_scripting_action = new QAction("Enable Scripting"); |
| 135 | + enable_scripting_action->setCheckable(true); |
| 136 | + enable_scripting_action->setChecked(true); |
| 137 | + debug_menu->addAction(enable_scripting_action); |
| 138 | + QObject::connect(enable_scripting_action, &QAction::triggered, this, [this, enable_scripting_action] { |
| 139 | + bool state = enable_scripting_action->isChecked(); |
| 140 | + debug_request("scripting", state ? "on" : "off"); |
| 141 | + }); |
12 | 142 |
|
13 |
| - m_view = new WebView; |
14 |
| - setCentralWidget(m_view); |
| 143 | + auto* enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy"); |
| 144 | + enable_same_origin_policy_action->setCheckable(true); |
| 145 | + debug_menu->addAction(enable_same_origin_policy_action); |
| 146 | + QObject::connect(enable_same_origin_policy_action, &QAction::triggered, this, [this, enable_same_origin_policy_action] { |
| 147 | + bool state = enable_same_origin_policy_action->isChecked(); |
| 148 | + debug_request("same-origin-policy", state ? "on" : "off"); |
| 149 | + }); |
15 | 150 |
|
16 |
| - QObject::connect(m_view, &WebView::linkHovered, statusBar(), &QStatusBar::showMessage); |
17 |
| - QObject::connect(m_view, &WebView::linkUnhovered, statusBar(), &QStatusBar::clearMessage); |
| 151 | + QObject::connect(new_tab_action, &QAction::triggered, this, &BrowserWindow::new_tab); |
| 152 | + QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close); |
| 153 | + QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) { |
| 154 | + setWindowTitle(QString("%1 - Coccinellidae").arg(m_tabs_container->tabText(index))); |
| 155 | + setWindowIcon(m_tabs_container->tabIcon(index)); |
| 156 | + }); |
| 157 | + QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab); |
18 | 158 |
|
19 |
| - QObject::connect(m_view, &WebView::loadStarted, m_location_edit, &QLineEdit::setText); |
20 |
| - QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &BrowserWindow::location_edit_return_pressed); |
21 |
| - QObject::connect(m_view, &WebView::title_changed, this, &BrowserWindow::page_title_changed); |
| 159 | + new_tab(); |
| 160 | + |
| 161 | + setCentralWidget(m_tabs_container); |
| 162 | +} |
| 163 | + |
| 164 | +void BrowserWindow::debug_request(String const& request, String const& argument) |
| 165 | +{ |
| 166 | + if (!m_current_tab) |
| 167 | + return; |
| 168 | + m_current_tab->debug_request(request, argument); |
| 169 | +} |
| 170 | + |
| 171 | +void BrowserWindow::new_tab() |
| 172 | +{ |
| 173 | + auto tab = make<Tab>(this); |
| 174 | + auto tab_ptr = tab.ptr(); |
| 175 | + m_tabs.append(std::move(tab)); |
| 176 | + |
| 177 | + if (m_current_tab == nullptr) { |
| 178 | + m_current_tab = tab_ptr; |
| 179 | + } |
| 180 | + |
| 181 | + m_tabs_container->addTab(tab_ptr, "New Tab"); |
| 182 | + m_tabs_container->setCurrentWidget(tab_ptr); |
| 183 | + |
| 184 | + QObject::connect(tab_ptr, &Tab::title_changed, this, &BrowserWindow::tab_title_changed); |
| 185 | + QObject::connect(tab_ptr, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed); |
| 186 | + |
| 187 | + if (m_tabs_container->count() > 1) |
| 188 | + m_tabs_bar->show(); |
22 | 189 | }
|
23 | 190 |
|
24 |
| -void BrowserWindow::location_edit_return_pressed() |
| 191 | +void BrowserWindow::close_tab(int index) |
25 | 192 | {
|
26 |
| - view().load(m_location_edit->text().toUtf8().data()); |
| 193 | + auto* tab = m_tabs_container->widget(index); |
| 194 | + m_tabs_container->removeTab(index); |
| 195 | + m_tabs.remove_first_matching([&](auto& entry) { |
| 196 | + return entry == tab; |
| 197 | + }); |
| 198 | + |
| 199 | + if (m_tabs_container->count() <= 1) |
| 200 | + m_tabs_bar->hide(); |
| 201 | +} |
| 202 | + |
| 203 | +int BrowserWindow::tab_index(Tab* tab) |
| 204 | +{ |
| 205 | + return m_tabs_container->indexOf(tab); |
27 | 206 | }
|
28 | 207 |
|
29 |
| -void BrowserWindow::page_title_changed(QString title) |
| 208 | +void BrowserWindow::tab_title_changed(int index, QString const& title) |
30 | 209 | {
|
31 |
| - if (title.isEmpty()) |
32 |
| - setWindowTitle("Ladybird"); |
33 |
| - else |
34 |
| - setWindowTitle(QString("%1 - Ladybird").arg(title)); |
| 210 | + if (title.isEmpty()) { |
| 211 | + m_tabs_container->setTabText(index, "..."); |
| 212 | + setWindowTitle("Coccinellidae"); |
| 213 | + } else { |
| 214 | + m_tabs_container->setTabText(index, title); |
| 215 | + setWindowTitle(QString("%1 - Coccinellidae").arg(title)); |
| 216 | + } |
| 217 | +} |
| 218 | + |
| 219 | +void BrowserWindow::tab_favicon_changed(int index, QIcon icon) |
| 220 | +{ |
| 221 | + m_tabs_container->setTabIcon(index, icon); |
| 222 | + setWindowIcon(icon); |
| 223 | +} |
| 224 | + |
| 225 | +void BrowserWindow::closeEvent(QCloseEvent* event) |
| 226 | +{ |
| 227 | + QWidget::closeEvent(event); |
| 228 | + |
| 229 | + // FIXME: Coccinellidae only supports one window at the moment. When we support |
| 230 | + // multiple windows, we'll only want to fire off the quit event when |
| 231 | + // all of the browser windows have closed. |
| 232 | + m_event_loop.quit(0); |
35 | 233 | }
|
0 commit comments