Serenity Operating System
at master 540 lines 23 kB view raw
1/* 2 * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, Matthew Costa <ucosty@gmail.com> 4 * Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com> 5 * Copyright (c) 2023, Linus Groh <linusg@serenityos.org> 6 * 7 * SPDX-License-Identifier: BSD-2-Clause 8 */ 9 10#include "BrowserWindow.h" 11#include "Settings.h" 12#include "SettingsDialog.h" 13#include "Utilities.h" 14#include "WebContentView.h" 15#include <AK/TypeCasts.h> 16#include <Browser/CookieJar.h> 17#include <LibWeb/CSS/PreferredColorScheme.h> 18#include <LibWeb/Loader/ResourceLoader.h> 19#include <QAction> 20#include <QActionGroup> 21#include <QClipboard> 22#include <QGuiApplication> 23#include <QInputDialog> 24#include <QPlainTextEdit> 25#include <QTabBar> 26 27extern DeprecatedString s_serenity_resource_root; 28extern Browser::Settings* s_settings; 29 30BrowserWindow::BrowserWindow(Browser::CookieJar& cookie_jar, StringView webdriver_content_ipc_path) 31 : m_cookie_jar(cookie_jar) 32 , m_webdriver_content_ipc_path(webdriver_content_ipc_path) 33{ 34 m_tabs_container = new QTabWidget(this); 35 m_tabs_container->installEventFilter(this); 36 m_tabs_container->setElideMode(Qt::TextElideMode::ElideRight); 37 m_tabs_container->setMovable(true); 38 m_tabs_container->setTabsClosable(true); 39 m_tabs_container->setDocumentMode(true); 40 m_tabs_container->setTabBarAutoHide(true); 41 42 auto* menu = menuBar()->addMenu("&File"); 43 44 auto* new_tab_action = new QAction("New &Tab", this); 45 new_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::AddTab)); 46 menu->addAction(new_tab_action); 47 48 auto* settings_action = new QAction("&Settings", this); 49 settings_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Preferences)); 50 menu->addAction(settings_action); 51 52 auto* close_current_tab_action = new QAction("Close Current Tab", this); 53 close_current_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Close)); 54 menu->addAction(close_current_tab_action); 55 56 auto* quit_action = new QAction("&Quit", this); 57 quit_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Quit)); 58 menu->addAction(quit_action); 59 60 auto* edit_menu = menuBar()->addMenu("&Edit"); 61 62 auto* copy_action = new QAction("&Copy", this); 63 copy_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy)); 64 edit_menu->addAction(copy_action); 65 QObject::connect(copy_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text); 66 67 auto* select_all_action = new QAction("Select &All", this); 68 select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll)); 69 edit_menu->addAction(select_all_action); 70 QObject::connect(select_all_action, &QAction::triggered, this, &BrowserWindow::select_all); 71 72 auto* view_menu = menuBar()->addMenu("&View"); 73 74 auto* open_next_tab_action = new QAction("Open &Next Tab", this); 75 open_next_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageDown)); 76 view_menu->addAction(open_next_tab_action); 77 QObject::connect(open_next_tab_action, &QAction::triggered, this, &BrowserWindow::open_next_tab); 78 79 auto* open_previous_tab_action = new QAction("Open &Previous Tab", this); 80 open_previous_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageUp)); 81 view_menu->addAction(open_previous_tab_action); 82 QObject::connect(open_previous_tab_action, &QAction::triggered, this, &BrowserWindow::open_previous_tab); 83 84 view_menu->addSeparator(); 85 86 auto* zoom_menu = view_menu->addMenu("&Zoom"); 87 88 auto* zoom_in_action = new QAction("Zoom &In", this); 89 auto zoom_in_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomIn); 90 zoom_in_shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Equal)); 91 zoom_in_action->setShortcuts(zoom_in_shortcuts); 92 zoom_menu->addAction(zoom_in_action); 93 QObject::connect(zoom_in_action, &QAction::triggered, this, &BrowserWindow::zoom_in); 94 95 auto* zoom_out_action = new QAction("Zoom &Out", this); 96 zoom_out_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomOut)); 97 zoom_menu->addAction(zoom_out_action); 98 QObject::connect(zoom_out_action, &QAction::triggered, this, &BrowserWindow::zoom_out); 99 100 auto* reset_zoom_action = new QAction("&Reset Zoom", this); 101 reset_zoom_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0)); 102 zoom_menu->addAction(reset_zoom_action); 103 QObject::connect(reset_zoom_action, &QAction::triggered, this, &BrowserWindow::reset_zoom); 104 105 view_menu->addSeparator(); 106 107 auto* color_scheme_menu = view_menu->addMenu("&Color Scheme"); 108 109 auto* color_scheme_group = new QActionGroup(this); 110 111 auto* auto_color_scheme = new QAction("&Auto", this); 112 auto_color_scheme->setCheckable(true); 113 color_scheme_group->addAction(auto_color_scheme); 114 color_scheme_menu->addAction(auto_color_scheme); 115 QObject::connect(auto_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_auto_color_scheme); 116 117 auto* light_color_scheme = new QAction("&Light", this); 118 light_color_scheme->setCheckable(true); 119 color_scheme_group->addAction(light_color_scheme); 120 color_scheme_menu->addAction(light_color_scheme); 121 QObject::connect(light_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_light_color_scheme); 122 123 auto* dark_color_scheme = new QAction("&Dark", this); 124 dark_color_scheme->setCheckable(true); 125 color_scheme_group->addAction(dark_color_scheme); 126 color_scheme_menu->addAction(dark_color_scheme); 127 QObject::connect(dark_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_dark_color_scheme); 128 129 auto_color_scheme->setChecked(true); 130 131 auto* inspect_menu = menuBar()->addMenu("&Inspect"); 132 133 auto* view_source_action = new QAction("View &Source", this); 134 view_source_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-html.png").arg(s_serenity_resource_root.characters()))); 135 view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U)); 136 inspect_menu->addAction(view_source_action); 137 QObject::connect(view_source_action, &QAction::triggered, this, [this] { 138 if (m_current_tab) { 139 m_current_tab->view().get_source(); 140 } 141 }); 142 143 auto* js_console_action = new QAction("Show &JS Console", this); 144 js_console_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-javascript.png").arg(s_serenity_resource_root.characters()))); 145 js_console_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_J)); 146 inspect_menu->addAction(js_console_action); 147 QObject::connect(js_console_action, &QAction::triggered, this, [this] { 148 if (m_current_tab) { 149 m_current_tab->view().show_js_console(); 150 } 151 }); 152 153 auto* inspector_action = new QAction("Open &Inspector"); 154 inspector_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters()))); 155 inspector_action->setShortcut(QKeySequence("Ctrl+Shift+I")); 156 inspect_menu->addAction(inspector_action); 157 QObject::connect(inspector_action, &QAction::triggered, this, [this] { 158 if (m_current_tab) { 159 m_current_tab->view().show_inspector(); 160 } 161 }); 162 163 auto* debug_menu = menuBar()->addMenu("&Debug"); 164 165 auto* dump_dom_tree_action = new QAction("Dump DOM Tree", this); 166 dump_dom_tree_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters()))); 167 debug_menu->addAction(dump_dom_tree_action); 168 QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] { 169 debug_request("dump-dom-tree"); 170 }); 171 172 auto* dump_layout_tree_action = new QAction("Dump Layout Tree", this); 173 dump_layout_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layout.png").arg(s_serenity_resource_root.characters()))); 174 debug_menu->addAction(dump_layout_tree_action); 175 QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] { 176 debug_request("dump-layout-tree"); 177 }); 178 179 auto* dump_stacking_context_tree_action = new QAction("Dump Stacking Context Tree", this); 180 dump_stacking_context_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layers.png").arg(s_serenity_resource_root.characters()))); 181 debug_menu->addAction(dump_stacking_context_tree_action); 182 QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] { 183 debug_request("dump-stacking-context-tree"); 184 }); 185 186 auto* dump_style_sheets_action = new QAction("Dump Style Sheets", this); 187 dump_style_sheets_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-css.png").arg(s_serenity_resource_root.characters()))); 188 debug_menu->addAction(dump_style_sheets_action); 189 QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] { 190 debug_request("dump-style-sheets"); 191 }); 192 193 auto* dump_history_action = new QAction("Dump History", this); 194 dump_history_action->setIcon(QIcon(QString("%1/res/icons/16x16/history.png").arg(s_serenity_resource_root.characters()))); 195 debug_menu->addAction(dump_history_action); 196 QObject::connect(dump_history_action, &QAction::triggered, this, [this] { 197 debug_request("dump-history"); 198 }); 199 200 auto* dump_cookies_action = new QAction("Dump Cookies", this); 201 dump_cookies_action->setIcon(QIcon(QString("%1/res/icons/browser/cookie.png").arg(s_serenity_resource_root.characters()))); 202 debug_menu->addAction(dump_cookies_action); 203 QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] { 204 m_cookie_jar.dump_cookies(); 205 }); 206 207 auto* dump_local_storage_action = new QAction("Dump Local Storage", this); 208 dump_local_storage_action->setIcon(QIcon(QString("%1/res/icons/browser/local-storage.png").arg(s_serenity_resource_root.characters()))); 209 debug_menu->addAction(dump_local_storage_action); 210 QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] { 211 debug_request("dump-local-storage"); 212 }); 213 214 debug_menu->addSeparator(); 215 216 auto* show_line_box_borders_action = new QAction("Show Line Box Borders", this); 217 show_line_box_borders_action->setCheckable(true); 218 debug_menu->addAction(show_line_box_borders_action); 219 QObject::connect(show_line_box_borders_action, &QAction::triggered, this, [this, show_line_box_borders_action] { 220 bool state = show_line_box_borders_action->isChecked(); 221 debug_request("set-line-box-borders", state ? "on" : "off"); 222 }); 223 224 debug_menu->addSeparator(); 225 226 auto* collect_garbage_action = new QAction("Collect Garbage", this); 227 collect_garbage_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G)); 228 collect_garbage_action->setIcon(QIcon(QString("%1/res/icons/16x16/trash-can.png").arg(s_serenity_resource_root.characters()))); 229 debug_menu->addAction(collect_garbage_action); 230 QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] { 231 debug_request("collect-garbage"); 232 }); 233 234 auto* clear_cache_action = new QAction("Clear Cache", this); 235 clear_cache_action->setIcon(QIcon(QString("%1/res/icons/browser/clear-cache.png").arg(s_serenity_resource_root.characters()))); 236 debug_menu->addAction(clear_cache_action); 237 QObject::connect(clear_cache_action, &QAction::triggered, this, [this] { 238 debug_request("clear-cache"); 239 }); 240 241 auto* spoof_user_agent_menu = debug_menu->addMenu("Spoof User Agent"); 242 spoof_user_agent_menu->setIcon(QIcon(QString("%1/res/icons/16x16/spoof.png").arg(s_serenity_resource_root.characters()))); 243 244 auto* user_agent_group = new QActionGroup(this); 245 246 auto add_user_agent = [this, &user_agent_group, &spoof_user_agent_menu](auto& name, auto& user_agent) { 247 auto* action = new QAction(name); 248 action->setCheckable(true); 249 user_agent_group->addAction(action); 250 spoof_user_agent_menu->addAction(action); 251 QObject::connect(action, &QAction::triggered, this, [this, user_agent] { 252 debug_request("spoof-user-agent", user_agent); 253 debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent 254 }); 255 return action; 256 }; 257 258 auto* disable_spoofing = add_user_agent("Disabled", Web::default_user_agent); 259 disable_spoofing->setChecked(true); 260 add_user_agent("Chrome Linux Desktop", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"); 261 add_user_agent("Firefox Linux Desktop", "Mozilla/5.0 (X11; Linux i686; rv:87.0) Gecko/20100101 Firefox/87.0"); 262 add_user_agent("Safari macOS Desktop", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15"); 263 add_user_agent("Chrome Android Mobile", "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.66 Mobile Safari/537.36"); 264 add_user_agent("Firefox Android Mobile", "Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/86.0"); 265 add_user_agent("Safari iOS Mobile", "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"); 266 267 auto* custom_user_agent_action = new QAction("Custom..."); 268 custom_user_agent_action->setCheckable(true); 269 user_agent_group->addAction(custom_user_agent_action); 270 spoof_user_agent_menu->addAction(custom_user_agent_action); 271 QObject::connect(custom_user_agent_action, &QAction::triggered, this, [this, disable_spoofing] { 272 auto user_agent = QInputDialog::getText(this, "Custom User Agent", "Enter User Agent:"); 273 if (!user_agent.isEmpty()) { 274 debug_request("spoof-user-agent", ak_deprecated_string_from_qstring(user_agent)); 275 debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent 276 } else { 277 disable_spoofing->activate(QAction::Trigger); 278 } 279 }); 280 281 debug_menu->addSeparator(); 282 283 auto* enable_scripting_action = new QAction("Enable Scripting", this); 284 enable_scripting_action->setCheckable(true); 285 enable_scripting_action->setChecked(true); 286 debug_menu->addAction(enable_scripting_action); 287 QObject::connect(enable_scripting_action, &QAction::triggered, this, [this, enable_scripting_action] { 288 bool state = enable_scripting_action->isChecked(); 289 debug_request("scripting", state ? "on" : "off"); 290 }); 291 292 auto* block_pop_ups_action = new QAction("Block Pop-ups", this); 293 block_pop_ups_action->setCheckable(true); 294 block_pop_ups_action->setChecked(true); 295 debug_menu->addAction(block_pop_ups_action); 296 QObject::connect(block_pop_ups_action, &QAction::triggered, this, [this, block_pop_ups_action] { 297 bool state = block_pop_ups_action->isChecked(); 298 debug_request("block-pop-ups", state ? "on" : "off"); 299 }); 300 301 auto* enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy", this); 302 enable_same_origin_policy_action->setCheckable(true); 303 debug_menu->addAction(enable_same_origin_policy_action); 304 QObject::connect(enable_same_origin_policy_action, &QAction::triggered, this, [this, enable_same_origin_policy_action] { 305 bool state = enable_same_origin_policy_action->isChecked(); 306 debug_request("same-origin-policy", state ? "on" : "off"); 307 }); 308 309 QObject::connect(new_tab_action, &QAction::triggered, this, [this] { 310 new_tab(s_settings->new_tab_page(), Activate::Yes); 311 }); 312 QObject::connect(settings_action, &QAction::triggered, this, [this] { 313 new SettingsDialog(this); 314 }); 315 QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close); 316 QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) { 317 setWindowTitle(QString("%1 - Ladybird").arg(m_tabs_container->tabText(index))); 318 setWindowIcon(m_tabs_container->tabIcon(index)); 319 m_current_tab = verify_cast<Tab>(m_tabs_container->widget(index)); 320 }); 321 QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab); 322 QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab); 323 324 new_tab(s_settings->new_tab_page(), Activate::Yes); 325 326 setCentralWidget(m_tabs_container); 327} 328 329void BrowserWindow::debug_request(DeprecatedString const& request, DeprecatedString const& argument) 330{ 331 if (!m_current_tab) 332 return; 333 m_current_tab->debug_request(request, argument); 334} 335 336void BrowserWindow::new_tab(QString const& url, Activate activate) 337{ 338 auto tab = make<Tab>(this, m_webdriver_content_ipc_path); 339 auto tab_ptr = tab.ptr(); 340 m_tabs.append(std::move(tab)); 341 342 if (m_current_tab == nullptr) { 343 m_current_tab = tab_ptr; 344 } 345 346 m_tabs_container->addTab(tab_ptr, "New Tab"); 347 if (activate == Activate::Yes) 348 m_tabs_container->setCurrentWidget(tab_ptr); 349 350 QObject::connect(tab_ptr, &Tab::title_changed, this, &BrowserWindow::tab_title_changed); 351 QObject::connect(tab_ptr, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed); 352 353 QObject::connect(&tab_ptr->view(), &WebContentView::urls_dropped, this, [this](auto& urls) { 354 VERIFY(urls.size()); 355 m_current_tab->navigate(urls[0].toString()); 356 357 for (qsizetype i = 1; i < urls.size(); ++i) 358 new_tab(urls[i].toString(), Activate::No); 359 }); 360 361 tab_ptr->view().on_get_all_cookies = [this](auto const& url) { 362 return m_cookie_jar.get_all_cookies(url); 363 }; 364 365 tab_ptr->view().on_get_named_cookie = [this](auto const& url, auto const& name) { 366 return m_cookie_jar.get_named_cookie(url, name); 367 }; 368 369 tab_ptr->view().on_get_cookie = [this](auto& url, auto source) -> DeprecatedString { 370 return m_cookie_jar.get_cookie(url, source); 371 }; 372 373 tab_ptr->view().on_set_cookie = [this](auto& url, auto& cookie, auto source) { 374 m_cookie_jar.set_cookie(url, cookie, source); 375 }; 376 377 tab_ptr->view().on_update_cookie = [this](auto const& cookie) { 378 m_cookie_jar.update_cookie(cookie); 379 }; 380 381 tab_ptr->focus_location_editor(); 382 383 // We *don't* load the initial page if we are connected to a WebDriver, as the Set URL command may come in very 384 // quickly, and become replaced by this load. 385 if (m_webdriver_content_ipc_path.is_empty()) { 386 // We make it HistoryNavigation so that the initial page doesn't get added to the history. 387 tab_ptr->navigate(url, Tab::LoadType::HistoryNavigation); 388 } 389} 390 391void BrowserWindow::close_tab(int index) 392{ 393 auto* tab = m_tabs_container->widget(index); 394 m_tabs_container->removeTab(index); 395 m_tabs.remove_first_matching([&](auto& entry) { 396 return entry == tab; 397 }); 398} 399 400void BrowserWindow::close_current_tab() 401{ 402 auto count = m_tabs_container->count() - 1; 403 if (!count) 404 close(); 405 else 406 close_tab(m_tabs_container->currentIndex()); 407} 408 409int BrowserWindow::tab_index(Tab* tab) 410{ 411 return m_tabs_container->indexOf(tab); 412} 413 414void BrowserWindow::tab_title_changed(int index, QString const& title) 415{ 416 if (title.isEmpty()) { 417 m_tabs_container->setTabText(index, "..."); 418 if (m_tabs_container->currentIndex() == index) 419 setWindowTitle("Ladybird"); 420 } else { 421 m_tabs_container->setTabText(index, title); 422 if (m_tabs_container->currentIndex() == index) 423 setWindowTitle(QString("%1 - Ladybird").arg(title)); 424 } 425} 426 427void BrowserWindow::tab_favicon_changed(int index, QIcon icon) 428{ 429 m_tabs_container->setTabIcon(index, icon); 430 if (m_tabs_container->currentIndex() == index) 431 setWindowIcon(icon); 432} 433 434void BrowserWindow::open_next_tab() 435{ 436 if (m_tabs_container->count() <= 1) 437 return; 438 439 auto next_index = m_tabs_container->currentIndex() + 1; 440 if (next_index >= m_tabs_container->count()) 441 next_index = 0; 442 m_tabs_container->setCurrentIndex(next_index); 443} 444 445void BrowserWindow::open_previous_tab() 446{ 447 if (m_tabs_container->count() <= 1) 448 return; 449 450 auto next_index = m_tabs_container->currentIndex() - 1; 451 if (next_index < 0) 452 next_index = m_tabs_container->count() - 1; 453 m_tabs_container->setCurrentIndex(next_index); 454} 455 456void BrowserWindow::enable_auto_color_scheme() 457{ 458 for (auto& tab : m_tabs) { 459 tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Auto); 460 } 461} 462 463void BrowserWindow::enable_light_color_scheme() 464{ 465 for (auto& tab : m_tabs) { 466 tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Light); 467 } 468} 469 470void BrowserWindow::enable_dark_color_scheme() 471{ 472 for (auto& tab : m_tabs) { 473 tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Dark); 474 } 475} 476 477void BrowserWindow::zoom_in() 478{ 479 if (m_current_tab) 480 m_current_tab->view().zoom_in(); 481} 482 483void BrowserWindow::zoom_out() 484{ 485 if (m_current_tab) 486 m_current_tab->view().zoom_out(); 487} 488 489void BrowserWindow::reset_zoom() 490{ 491 if (m_current_tab) 492 m_current_tab->view().reset_zoom(); 493} 494 495void BrowserWindow::select_all() 496{ 497 if (auto* tab = m_current_tab) 498 tab->view().select_all(); 499} 500 501void BrowserWindow::copy_selected_text() 502{ 503 if (auto* tab = m_current_tab) { 504 auto text = tab->view().selected_text(); 505 auto* clipboard = QGuiApplication::clipboard(); 506 clipboard->setText(qstring_from_ak_deprecated_string(text)); 507 } 508} 509 510void BrowserWindow::resizeEvent(QResizeEvent* event) 511{ 512 QWidget::resizeEvent(event); 513 for (auto& tab : m_tabs) { 514 tab->view().set_window_size({ frameSize().width(), frameSize().height() }); 515 } 516} 517 518void BrowserWindow::moveEvent(QMoveEvent* event) 519{ 520 QWidget::moveEvent(event); 521 for (auto& tab : m_tabs) { 522 tab->view().set_window_position({ event->pos().x(), event->pos().y() }); 523 } 524} 525 526bool BrowserWindow::eventFilter(QObject* obj, QEvent* event) 527{ 528 if (event->type() == QEvent::MouseButtonRelease) { 529 auto const* const mouse_event = static_cast<QMouseEvent*>(event); 530 if (mouse_event->button() == Qt::MouseButton::MiddleButton) { 531 if (obj == m_tabs_container) { 532 auto const tab_index = m_tabs_container->tabBar()->tabAt(mouse_event->pos()); 533 close_tab(tab_index); 534 return true; 535 } 536 } 537 } 538 539 return QMainWindow::eventFilter(obj, event); 540}