diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 9fab7d0f6e..7489177875 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -1683,6 +1683,8 @@ void Application::create_bookmark_menu_items(Optional data) auto action = Action::create(bookmark.title.value_or({}), ActionID::BookmarkItem, [this, url = bookmark.url]() { if (auto view = active_web_view(); view.has_value()) view->load(url); + else + open_url_in_new_tab(url, Web::HTML::ActivateTab::Yes); }); action->set_base64_png_icon(bookmark.favicon_base64_png); diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 4c459943e8..3eea66249c 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -111,7 +111,7 @@ public: virtual Optional active_web_view() const { return {}; } virtual Optional open_blank_new_tab(Web::HTML::ActivateTab) const { return {}; } virtual bool activate_tab_with_url(URL::URL const&) const { return false; } - void open_url_in_new_tab(URL::URL const&, Web::HTML::ActivateTab) const; + virtual void open_url_in_new_tab(URL::URL const&, Web::HTML::ActivateTab) const; void open_bookmark_in_new_tab(String const& bookmark_id, Web::HTML::ActivateTab) const; Main::Arguments const& command_line_arguments() const { return m_arguments; } diff --git a/UI/Qt/Application.cpp b/UI/Qt/Application.cpp index 10db079bfc..133a1fc20a 100644 --- a/UI/Qt/Application.cpp +++ b/UI/Qt/Application.cpp @@ -6,10 +6,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -18,6 +20,7 @@ # include #endif +#include #include #include #include @@ -25,10 +28,14 @@ #include #include #include +#include #include +#include +#include #include #include #include +#include #if defined(AK_OS_WINDOWS) # include @@ -69,6 +76,7 @@ public: : QApplication(arguments.argc, arguments.argv) { #if defined(AK_OS_MACOS) + setQuitOnLastWindowClosed(false); install_appkit_event_capture(); #endif update_chrome_style(); @@ -111,11 +119,121 @@ public: return handled; } +#if defined(AK_OS_MACOS) + void update_reopen_recently_closed_action() + { + if (!m_reopen_recently_closed_tab_action) + return; + + auto recently_closed_entry = Application::history_store().most_recently_closed_entry(); + m_reopen_recently_closed_tab_action->setText("&Reopen Recently Closed Tab"); + m_reopen_recently_closed_tab_action->setEnabled(recently_closed_entry.has_value()); + } + +#endif + +#if defined(AK_OS_MACOS) + void create_application_menu_bar() + { + if (m_application_menu_bar) + return; + + m_application_menu_bar = new QMenuBar; + auto& application = Application::the(); + + auto* file_menu = m_application_menu_bar->addMenu("&File"); + + auto* new_tab_action = add_application_menu_action(*file_menu, "New &Tab", QKeySequence::keyBindings(QKeySequence::StandardKey::AddTab)); + QObject::connect(new_tab_action, &QAction::triggered, this, [] { + Application::the().open_new_tab(); + }); + + auto* new_window_action = add_application_menu_action(*file_menu, "New &Window", QKeySequence::keyBindings(QKeySequence::StandardKey::New)); + QObject::connect(new_window_action, &QAction::triggered, this, [] { + Application::the().open_new_window(); + }); + + m_reopen_recently_closed_tab_action = add_application_menu_action(*file_menu, {}, { QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_T) }); + QObject::connect(m_reopen_recently_closed_tab_action, &QAction::triggered, this, [] { + Application::the().reopen_recently_closed_tab(); + }); + + auto* open_file_action = add_application_menu_action(*file_menu, "&Open File...", QKeySequence::keyBindings(QKeySequence::StandardKey::Open)); + QObject::connect(open_file_action, &QAction::triggered, this, [] { + Application::the().open_file(); + }); + + auto* open_location_action = add_application_menu_action(*file_menu, "Open &Location", { QKeySequence("Ctrl+L"), QKeySequence("Alt+D") }); + QObject::connect(open_location_action, &QAction::triggered, this, [] { + Application::the().focus_location_editor(); + }); + + file_menu->addSeparator(); + + auto* quit_action = add_application_menu_action(*file_menu, "&Quit", QKeySequence::keyBindings(QKeySequence::StandardKey::Quit)); + QObject::connect(quit_action, &QAction::triggered, this, [] { + Application::the().quit(); + }); + + auto* edit_menu = m_application_menu_bar->addMenu("&Edit"); + edit_menu->addAction(create_application_action(*edit_menu, application.cut_selection_action(), IncludeActionIcon::No)); + edit_menu->addAction(create_application_action(*edit_menu, application.copy_selection_action(), IncludeActionIcon::No)); + edit_menu->addAction(create_application_action(*edit_menu, application.paste_action(), IncludeActionIcon::No)); + edit_menu->addAction(create_application_action(*edit_menu, application.select_all_action(), IncludeActionIcon::No)); + edit_menu->addSeparator(); + edit_menu->addAction(create_application_action(*edit_menu, application.open_settings_page_action(), IncludeActionIcon::No)); + + auto* view_menu = m_application_menu_bar->addMenu("&View"); + view_menu->addMenu(create_application_menu(*view_menu, application.color_scheme_menu())); + view_menu->addMenu(create_application_menu(*view_menu, application.contrast_menu())); + view_menu->addMenu(create_application_menu(*view_menu, application.motion_menu())); + + m_application_menu_bar->addMenu(bookmarks_menu()); + m_application_menu_bar->addMenu(create_application_menu(*m_application_menu_bar, application.history_menu())); + + auto* help_menu = m_application_menu_bar->addMenu("&Help"); + help_menu->addAction(create_application_action(*help_menu, application.open_about_page_action(), IncludeActionIcon::No)); + + update_reopen_recently_closed_action(); + } +#endif + +#if defined(AK_OS_MACOS) + QMenu* bookmarks_menu() + { + if (!m_bookmarks_menu) + m_bookmarks_menu = create_application_menu(*m_application_menu_bar, Application::the().bookmarks_menu()); + return m_bookmarks_menu; + } + + void rebuild_bookmarks_menu() + { + if (m_bookmarks_menu) + repopulate_application_menu(*m_bookmarks_menu, *m_application_menu_bar, Application::the().bookmarks_menu()); + } +#endif + private: +#if defined(AK_OS_MACOS) + QAction* add_application_menu_action(QMenu& menu, QString const& text, QList shortcuts) + { + auto* action = new QAction(text, m_application_menu_bar); + action->setShortcuts(shortcuts); + menu.addAction(action); + return action; + } +#endif + void update_chrome_style() { setStyleSheet(ChromeStyle::application_style_sheet(palette())); } + +#if defined(AK_OS_MACOS) + QMenuBar* m_application_menu_bar { nullptr }; + QMenu* m_bookmarks_menu { nullptr }; + QAction* m_reopen_recently_closed_tab_action { nullptr }; +#endif }; Application::Application() = default; @@ -145,12 +263,15 @@ BrowserWindow& Application::new_window(Vector const& initial_urls, Win { auto* window = new BrowserWindow(initial_urls, is_popup_window, parent_tab, move(page_index)); set_active_window(*window); + QObject::connect(window, &QObject::destroyed, m_application.ptr(), [this, window] { + if (m_active_window == window) + m_active_window = nullptr; + }); - if (initial_urls.size() == 1 && initial_urls.first() == URL::about_newtab()) { - if (auto* tab = window->current_tab()) { + auto should_focus_location_editor = initial_urls.size() == 1 && initial_urls.first() == WebView::Application::settings().new_tab_page_url(); + if (should_focus_location_editor) { + if (auto* tab = window->current_tab()) tab->set_url_is_hidden(true); - tab->focus_location_editor(); - } } window->set_window_rect(configuration.x, configuration.y, configuration.width, configuration.height); @@ -161,9 +282,107 @@ BrowserWindow& Application::new_window(Vector const& initial_urls, Win window->activateWindow(); window->raise(); + if (should_focus_location_editor) { + QTimer::singleShot(0, window, [window] { + if (auto* tab = window->current_tab()) + tab->focus_location_editor(); + }); + } return *window; } +void Application::open_new_tab() +{ + if (!m_active_window) { + new_window({ WebView::Application::settings().new_tab_page_url() }); + return; + } + + auto& tab = m_active_window->new_tab_from_url(WebView::Application::settings().new_tab_page_url(), Web::HTML::ActivateTab::Yes); + tab.set_url_is_hidden(true); + tab.focus_location_editor(); +} + +void Application::open_new_window() +{ + WindowConfiguration configuration {}; + if (auto* previous_active_window = active_window_if_any()) { + configuration.width = previous_active_window->width(); + configuration.height = previous_active_window->height(); + configuration.maximized = previous_active_window->isMaximized(); + } + new_window({ WebView::Application::settings().new_tab_page_url() }, configuration); +} + +void Application::focus_location_editor() +{ + if (!m_active_window) { + new_window({ WebView::Application::settings().new_tab_page_url() }); + return; + } + + if (auto* tab = m_active_window->current_tab()) + tab->focus_location_editor(); +} + +void Application::reopen_recently_closed_tab() +{ + auto recently_closed_entry = Application::history_store().pop_most_recently_closed_entry(); + if (recently_closed_entry.has_value()) { + if (recently_closed_entry->was_window) { + auto& window = new_window(recently_closed_entry->urls); + window.activate_tab(static_cast(recently_closed_entry->active_tab_index)); + } else if (!recently_closed_entry->urls.is_empty()) { + if (!m_active_window) + new_window({ recently_closed_entry->urls[0] }); + else + m_active_window->new_tab_from_url(recently_closed_entry->urls[0], Web::HTML::ActivateTab::Yes); + } + } + update_reopen_recently_closed_actions(); +} + +void Application::open_file() +{ + if (!m_active_window) { + auto filename = QFileDialog::getOpenFileUrl(nullptr, "Open file", QDir::homePath(), "All Files (*.*)"); + if (filename.isValid()) + new_window({ ak_url_from_qurl(filename) }); + return; + } + + m_active_window->open_file(); +} + +void Application::quit() +{ + QApplication::closeAllWindows(); + + for (auto* widget : QApplication::topLevelWidgets()) { + if (as_if(widget) && widget->isVisible()) + return; + } + + QApplication::quit(); +} + +void Application::initialize_macos_application_menu() +{ +#if defined(AK_OS_MACOS) + if (m_application) + static_cast(m_application.ptr())->create_application_menu_bar(); +#endif +} + +QMenu* Application::qt_bookmarks_menu() const +{ +#if defined(AK_OS_MACOS) + if (m_application) + return static_cast(m_application.ptr())->bookmarks_menu(); +#endif + return nullptr; +} + Optional Application::active_web_view() const { if (auto* active_tab = this->active_tab()) @@ -173,10 +392,27 @@ Optional Application::active_web_view() const Optional Application::open_blank_new_tab(Web::HTML::ActivateTab activate_tab) const { + if (!m_active_window) { + auto& window = const_cast(*this).new_window({ WebView::Application::settings().new_tab_page_url() }); + if (auto* tab = window.current_tab()) + return tab->view(); + return {}; + } + auto& tab = active_window().create_new_tab(activate_tab); return tab.view(); } +void Application::open_url_in_new_tab(URL::URL const& url, Web::HTML::ActivateTab activate_tab) const +{ + if (!m_active_window) { + const_cast(*this).new_window({ url }); + return; + } + + active_window().new_tab_from_url(url, activate_tab); +} + bool Application::activate_tab_with_url(URL::URL const& url) const { if (!m_active_window) @@ -322,6 +558,11 @@ void Application::update_tabs_display() const void Application::rebuild_bookmarks_menu() const { +#if defined(AK_OS_MACOS) + if (m_application) + static_cast(m_application.ptr())->rebuild_bookmarks_menu(); +#endif + for (auto* widget : QApplication::topLevelWidgets()) { if (auto* window = as_if(widget)) window->rebuild_bookmarks_menu(); @@ -330,6 +571,11 @@ void Application::rebuild_bookmarks_menu() const void Application::update_reopen_recently_closed_actions() const { +#if defined(AK_OS_MACOS) + if (m_application) + static_cast(m_application.ptr())->update_reopen_recently_closed_action(); +#endif + for (auto* widget : QApplication::topLevelWidgets()) { if (auto* window = as_if(widget)) window->update_reopen_recently_closed_action(); diff --git a/UI/Qt/Application.h b/UI/Qt/Application.h index d3e130dec4..46c15abc96 100644 --- a/UI/Qt/Application.h +++ b/UI/Qt/Application.h @@ -13,6 +13,8 @@ #include +class QMenu; + namespace Ladybird { struct WindowConfiguration { @@ -31,9 +33,18 @@ public: Function on_open_file; BrowserWindow& new_window(Vector const& initial_urls, WindowConfiguration const& = {}, BrowserWindow::IsPopupWindow is_popup_window = BrowserWindow::IsPopupWindow::No, Tab* parent_tab = nullptr, Optional page_index = {}); + void open_new_tab(); + void open_new_window(); + void focus_location_editor(); + void reopen_recently_closed_tab(); + void open_file(); + void quit(); + void initialize_macos_application_menu(); + QMenu* qt_bookmarks_menu() const; BrowserWindow& active_window() const { return *m_active_window; } void set_active_window(BrowserWindow& w) { m_active_window = &w; } + BrowserWindow* active_window_if_any() const { return m_active_window; } Tab* active_tab() const { return m_active_window ? m_active_window->current_tab() : nullptr; } void update_reopen_recently_closed_actions() const; @@ -46,6 +57,7 @@ private: virtual Optional active_web_view() const override; virtual Optional open_blank_new_tab(Web::HTML::ActivateTab) const override; + virtual void open_url_in_new_tab(URL::URL const&, Web::HTML::ActivateTab) const override; virtual bool activate_tab_with_url(URL::URL const&) const override; virtual void open_url_in_new_window(URL::URL const& url) override; diff --git a/UI/Qt/BrowserWindow.cpp b/UI/Qt/BrowserWindow.cpp index 297e1065c6..2b4f9fb4bd 100644 --- a/UI/Qt/BrowserWindow.cpp +++ b/UI/Qt/BrowserWindow.cpp @@ -90,14 +90,6 @@ static Optional display_id_for_screen(QScreen* screen) }); } -static QString reopen_recently_closed_action_text(Optional entry) -{ - if (entry.has_value() && entry->was_window) - return "&Reopen Recently Closed Window"; - - return "&Reopen Recently Closed Tab"; -} - static Vector recently_closed_urls_for_window(TabWidget const& tabs_container) { Vector urls; @@ -373,7 +365,9 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow if (show_menubar_option_available()) view_menu->addAction(create_application_action(*view_menu, application.toggle_menu_bar_action(), IncludeActionIcon::No)); - m_bookmarks_menu = create_application_menu(*this, application.bookmarks_menu()); + m_bookmarks_menu = Application::the().qt_bookmarks_menu(); + if (!m_bookmarks_menu) + m_bookmarks_menu = create_application_menu(*this, application.bookmarks_menu()); m_hamburger_menu->addMenu(m_bookmarks_menu); menuBar()->addMenu(m_bookmarks_menu); @@ -401,35 +395,26 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow quit_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Quit)); m_hamburger_menu->addAction(quit_action); file_menu->addAction(quit_action); +#if defined(AK_OS_MACOS) + QObject::connect(quit_action, &QAction::triggered, this, [] { + Application::the().quit(); + }); +#else QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close); +#endif - QObject::connect(m_new_tab_action, &QAction::triggered, this, [this] { - auto& tab = new_tab_from_url(WebView::Application::settings().new_tab_page_url(), Web::HTML::ActivateTab::Yes); - tab.set_url_is_hidden(true); - tab.focus_location_editor(); + QObject::connect(m_new_tab_action, &QAction::triggered, this, [] { + Application::the().open_new_tab(); }); QObject::connect(m_new_window_action, &QAction::triggered, this, [] { - auto const& previous_active_window = Application::the().active_window(); - WindowConfiguration configuration { - .width = previous_active_window.width(), - .height = previous_active_window.height(), - .maximized = previous_active_window.isMaximized(), - }; - Application::the().new_window({ WebView::Application::settings().new_tab_page_url() }, configuration); + Application::the().open_new_window(); }); - QObject::connect(m_reopen_recently_closed_tab_action, &QAction::triggered, this, [this] { - auto recently_closed_entry = Application::history_store().pop_most_recently_closed_entry(); - if (recently_closed_entry.has_value()) { - if (recently_closed_entry->was_window) { - auto& window = Application::the().new_window(recently_closed_entry->urls); - window.activate_tab(static_cast(recently_closed_entry->active_tab_index)); - } else if (!recently_closed_entry->urls.is_empty()) { - new_tab_from_url(recently_closed_entry->urls[0], Web::HTML::ActivateTab::Yes); - } - } - Application::the().update_reopen_recently_closed_actions(); + QObject::connect(m_reopen_recently_closed_tab_action, &QAction::triggered, this, [] { + Application::the().reopen_recently_closed_tab(); + }); + QObject::connect(open_file_action, &QAction::triggered, this, [] { + Application::the().open_file(); }); - QObject::connect(open_file_action, &QAction::triggered, this, &BrowserWindow::open_file); m_exit_button = new ExitFullscreenButton { this }; m_fullscreen_mode = new FullscreenMode { this, m_exit_button }; @@ -514,7 +499,8 @@ void BrowserWindow::update_tabs_display() void BrowserWindow::rebuild_bookmarks_menu() { - repopulate_application_menu(*m_bookmarks_menu, *this, Application::the().bookmarks_menu()); + if (m_bookmarks_menu != Application::the().qt_bookmarks_menu()) + repopulate_application_menu(*m_bookmarks_menu, *this, Application::the().bookmarks_menu()); for_each_tab([](Tab& tab) { tab.bookmarks_bar().rebuild(); @@ -751,7 +737,7 @@ void BrowserWindow::update_reopen_recently_closed_action() return; auto recently_closed_entry = Application::history_store().most_recently_closed_entry(); - m_reopen_recently_closed_tab_action->setText(reopen_recently_closed_action_text(recently_closed_entry)); + m_reopen_recently_closed_tab_action->setText("&Reopen Recently Closed Tab"); m_reopen_recently_closed_tab_action->setEnabled(recently_closed_entry.has_value()); } diff --git a/UI/Qt/main.cpp b/UI/Qt/main.cpp index 316009ce9c..3b8cf90748 100644 --- a/UI/Qt/main.cpp +++ b/UI/Qt/main.cpp @@ -53,6 +53,7 @@ ErrorOr ladybird_main(Main::Arguments arguments) #endif auto app = TRY(Ladybird::Application::create(arguments)); + app->initialize_macos_application_menu(); WebView::BrowserProcess browser_process; WebView::copy_default_config_files(Ladybird::Settings::the()->directory()); @@ -68,12 +69,20 @@ ErrorOr ladybird_main(Main::Arguments arguments) } app->on_open_file = [&](auto const& file_url) { - auto& window = app->active_window(); - window.view().load(file_url); + if (auto* window = app->active_window_if_any()) { + window->view().load(file_url); + return; + } + app->new_window({ file_url }); }; browser_process.on_new_tab = [&](auto const& urls) { - auto& window = app->active_window(); + if (!app->active_window_if_any()) { + app->new_window(urls); + return; + } + + auto& window = *app->active_window_if_any(); for (size_t i = 0; i < urls.size(); ++i) { window.new_tab_from_url(urls[i], (i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No); } @@ -83,12 +92,12 @@ ErrorOr ladybird_main(Main::Arguments arguments) }; browser_process.on_new_window = [&](auto const& urls) { - auto const& previous_active_window = app->active_window(); - Ladybird::WindowConfiguration configuration { - .width = previous_active_window.width(), - .height = previous_active_window.height(), - .maximized = previous_active_window.isMaximized(), - }; + Ladybird::WindowConfiguration configuration {}; + if (auto* previous_active_window = app->active_window_if_any()) { + configuration.width = previous_active_window->width(); + configuration.height = previous_active_window->height(); + configuration.maximized = previous_active_window->isMaximized(); + } app->new_window(urls, configuration); };