mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-23 00:19:18 +00:00
440 lines
14 KiB
Text
440 lines
14 KiB
Text
/*
|
|
* Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibWebView/Application.h>
|
|
|
|
#import <Application/ApplicationDelegate.h>
|
|
#import <Interface/InfoBar.h>
|
|
#import <Interface/LadybirdWebView.h>
|
|
#import <Interface/Menu.h>
|
|
#import <Interface/Tab.h>
|
|
#import <Interface/TabController.h>
|
|
#import <Utilities/Conversions.h>
|
|
|
|
#if !__has_feature(objc_arc)
|
|
# error "This project requires ARC"
|
|
#endif
|
|
|
|
@interface ApplicationDelegate ()
|
|
|
|
@property (nonatomic, strong) NSMutableArray<TabController*>* managed_tabs;
|
|
@property (nonatomic, weak) Tab* active_tab;
|
|
|
|
@property (nonatomic, strong) InfoBar* info_bar;
|
|
|
|
- (NSMenuItem*)createApplicationMenu;
|
|
- (NSMenuItem*)createFileMenu;
|
|
- (NSMenuItem*)createEditMenu;
|
|
- (NSMenuItem*)createViewMenu;
|
|
- (NSMenuItem*)createHistoryMenu;
|
|
- (NSMenuItem*)createInspectMenu;
|
|
- (NSMenuItem*)createDebugMenu;
|
|
- (NSMenuItem*)createWindowMenu;
|
|
- (NSMenuItem*)createHelpMenu;
|
|
|
|
@end
|
|
|
|
@implementation ApplicationDelegate
|
|
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
[NSApp setMainMenu:[[NSMenu alloc] init]];
|
|
|
|
[[NSApp mainMenu] addItem:[self createApplicationMenu]];
|
|
[[NSApp mainMenu] addItem:[self createFileMenu]];
|
|
[[NSApp mainMenu] addItem:[self createEditMenu]];
|
|
[[NSApp mainMenu] addItem:[self createViewMenu]];
|
|
[[NSApp mainMenu] addItem:[self createHistoryMenu]];
|
|
[[NSApp mainMenu] addItem:[self createInspectMenu]];
|
|
[[NSApp mainMenu] addItem:[self createDebugMenu]];
|
|
[[NSApp mainMenu] addItem:[self createWindowMenu]];
|
|
[[NSApp mainMenu] addItem:[self createHelpMenu]];
|
|
|
|
self.managed_tabs = [[NSMutableArray alloc] init];
|
|
|
|
// Reduce the tooltip delay, as the default delay feels quite long.
|
|
[[NSUserDefaults standardUserDefaults] setObject:@100 forKey:@"NSInitialToolTipDelay"];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Public methods
|
|
|
|
- (nonnull TabController*)createNewTab:(Web::HTML::ActivateTab)activate_tab
|
|
fromTab:(nullable Tab*)tab
|
|
{
|
|
auto* controller = [[TabController alloc] init];
|
|
[self initializeTabController:controller
|
|
activateTab:activate_tab
|
|
fromTab:tab];
|
|
|
|
return controller;
|
|
}
|
|
|
|
- (TabController*)createNewTab:(Optional<URL::URL> const&)url
|
|
fromTab:(Tab*)tab
|
|
activateTab:(Web::HTML::ActivateTab)activate_tab
|
|
{
|
|
auto* controller = [self createNewTab:activate_tab fromTab:tab];
|
|
|
|
if (url.has_value()) {
|
|
[controller loadURL:*url];
|
|
}
|
|
|
|
return controller;
|
|
}
|
|
|
|
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
|
|
fromTab:(nonnull Tab*)tab
|
|
activateTab:(Web::HTML::ActivateTab)activate_tab
|
|
pageIndex:(u64)page_index
|
|
{
|
|
auto* controller = [self createChildTab:activate_tab fromTab:tab pageIndex:page_index];
|
|
|
|
if (url.has_value()) {
|
|
[controller loadURL:*url];
|
|
}
|
|
|
|
return controller;
|
|
}
|
|
|
|
- (void)setActiveTab:(Tab*)tab
|
|
{
|
|
self.active_tab = tab;
|
|
|
|
if (self.info_bar) {
|
|
[self.info_bar tabBecameActive:self.active_tab];
|
|
}
|
|
}
|
|
|
|
- (Tab*)activeTab
|
|
{
|
|
return self.active_tab;
|
|
}
|
|
|
|
- (void)removeTab:(TabController*)controller
|
|
{
|
|
[self.managed_tabs removeObject:controller];
|
|
}
|
|
|
|
- (void)onDevtoolsEnabled
|
|
{
|
|
if (!self.info_bar) {
|
|
self.info_bar = [[InfoBar alloc] init];
|
|
}
|
|
|
|
auto message = MUST(String::formatted("DevTools is enabled on port {}", WebView::Application::browser_options().devtools_port));
|
|
|
|
[self.info_bar showWithMessage:Ladybird::string_to_ns_string(message)
|
|
dismissButtonTitle:@"Disable"
|
|
dismissButtonClicked:^{
|
|
MUST(WebView::Application::the().toggle_devtools_enabled());
|
|
}
|
|
activeTab:self.active_tab];
|
|
}
|
|
|
|
- (void)onDevtoolsDisabled
|
|
{
|
|
if (self.info_bar) {
|
|
[self.info_bar hide];
|
|
self.info_bar = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Private methods
|
|
|
|
- (void)openLocation:(id)sender
|
|
{
|
|
auto* current_tab = [NSApp keyWindow];
|
|
|
|
if (![current_tab isKindOfClass:[Tab class]]) {
|
|
return;
|
|
}
|
|
|
|
auto* controller = (TabController*)[current_tab windowController];
|
|
[controller focusLocationToolbarItem];
|
|
}
|
|
|
|
- (nonnull TabController*)createChildTab:(Web::HTML::ActivateTab)activate_tab
|
|
fromTab:(nonnull Tab*)tab
|
|
pageIndex:(u64)page_index
|
|
{
|
|
auto* controller = [[TabController alloc] initAsChild:tab pageIndex:page_index];
|
|
[self initializeTabController:controller
|
|
activateTab:activate_tab
|
|
fromTab:tab];
|
|
|
|
return controller;
|
|
}
|
|
|
|
- (void)initializeTabController:(TabController*)controller
|
|
activateTab:(Web::HTML::ActivateTab)activate_tab
|
|
fromTab:(nullable Tab*)tab
|
|
{
|
|
[controller showWindow:nil];
|
|
|
|
if (tab) {
|
|
[[tab tabGroup] addWindow:controller.window];
|
|
|
|
// FIXME: Can we create the tabbed window above without it becoming active in the first place?
|
|
if (activate_tab == Web::HTML::ActivateTab::No) {
|
|
[tab orderFront:nil];
|
|
}
|
|
}
|
|
|
|
if (activate_tab == Web::HTML::ActivateTab::Yes) {
|
|
[[controller window] orderFrontRegardless];
|
|
}
|
|
|
|
[self.managed_tabs addObject:controller];
|
|
}
|
|
|
|
- (void)closeCurrentTab:(id)sender
|
|
{
|
|
auto* current_window = [NSApp keyWindow];
|
|
[current_window close];
|
|
}
|
|
|
|
- (void)clearHistory:(id)sender
|
|
{
|
|
for (TabController* controller in self.managed_tabs) {
|
|
[controller clearHistory];
|
|
}
|
|
}
|
|
|
|
- (NSMenuItem*)createApplicationMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* process_name = [[NSProcessInfo processInfo] processName];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:process_name];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().open_about_page_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().open_settings_page_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Hide %@", process_name]
|
|
action:@selector(hide:)
|
|
keyEquivalent:@"h"]];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Quit %@", process_name]
|
|
action:@selector(terminate:)
|
|
keyEquivalent:@"q"]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createFileMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"File"];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"New Tab"
|
|
action:@selector(createNewTab:)
|
|
keyEquivalent:@"t"]];
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Close Tab"
|
|
action:@selector(closeCurrentTab:)
|
|
keyEquivalent:@"w"]];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Location"
|
|
action:@selector(openLocation:)
|
|
keyEquivalent:@"l"]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createEditMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"Edit"];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Undo"
|
|
action:@selector(undo:)
|
|
keyEquivalent:@"z"]];
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Redo"
|
|
action:@selector(redo:)
|
|
keyEquivalent:@"y"]];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Cut"
|
|
action:@selector(cut:)
|
|
keyEquivalent:@"x"]];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().copy_selection_action())];
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().paste_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().select_all_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find..."
|
|
action:@selector(find:)
|
|
keyEquivalent:@"f"]];
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find Next"
|
|
action:@selector(findNextMatch:)
|
|
keyEquivalent:@"g"]];
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find Previous"
|
|
action:@selector(findPreviousMatch:)
|
|
keyEquivalent:@"G"]];
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Use Selection for Find"
|
|
action:@selector(useSelectionForFind:)
|
|
keyEquivalent:@"e"]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createViewMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"View"];
|
|
|
|
auto* zoom_menu = Ladybird::create_application_menu(WebView::Application::the().zoom_menu());
|
|
auto* zoom_menu_item = [[NSMenuItem alloc] initWithTitle:[zoom_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[zoom_menu_item setSubmenu:zoom_menu];
|
|
|
|
auto* color_scheme_menu = Ladybird::create_application_menu(WebView::Application::the().color_scheme_menu());
|
|
auto* color_scheme_menu_item = [[NSMenuItem alloc] initWithTitle:[color_scheme_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[color_scheme_menu_item setSubmenu:color_scheme_menu];
|
|
|
|
auto* contrast_menu = Ladybird::create_application_menu(WebView::Application::the().contrast_menu());
|
|
auto* contrast_menu_item = [[NSMenuItem alloc] initWithTitle:[contrast_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[contrast_menu_item setSubmenu:contrast_menu];
|
|
|
|
auto* motion_menu = Ladybird::create_application_menu(WebView::Application::the().motion_menu());
|
|
auto* motion_menu_item = [[NSMenuItem alloc] initWithTitle:[motion_menu title]
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[motion_menu_item setSubmenu:motion_menu];
|
|
|
|
[submenu addItem:zoom_menu_item];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
[submenu addItem:color_scheme_menu_item];
|
|
[submenu addItem:contrast_menu_item];
|
|
[submenu addItem:motion_menu_item];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createHistoryMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"History"];
|
|
[submenu setAutoenablesItems:NO];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().reload_action())];
|
|
[submenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History"
|
|
action:@selector(clearHistory:)
|
|
keyEquivalent:@""]];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createInspectMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"];
|
|
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().view_source_action())];
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().toggle_devtools_action())];
|
|
[submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().open_processes_page_action())];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createDebugMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
|
|
auto* submenu = Ladybird::create_application_menu(WebView::Application::the().debug_menu());
|
|
[menu setSubmenu:submenu];
|
|
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createWindowMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"Window"];
|
|
|
|
[NSApp setWindowsMenu:submenu];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
- (NSMenuItem*)createHelpMenu
|
|
{
|
|
auto* menu = [[NSMenuItem alloc] init];
|
|
auto* submenu = [[NSMenu alloc] initWithTitle:@"Help"];
|
|
|
|
[NSApp setHelpMenu:submenu];
|
|
|
|
[menu setSubmenu:submenu];
|
|
return menu;
|
|
}
|
|
|
|
#pragma mark - NSApplicationDelegate
|
|
|
|
- (void)applicationDidFinishLaunching:(NSNotification*)notification
|
|
{
|
|
auto const& browser_options = WebView::Application::browser_options();
|
|
|
|
if (browser_options.devtools_port.has_value())
|
|
[self onDevtoolsEnabled];
|
|
|
|
Tab* tab = nil;
|
|
|
|
for (auto const& url : browser_options.urls) {
|
|
auto activate_tab = tab == nil ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No;
|
|
|
|
auto* controller = [self createNewTab:url
|
|
fromTab:tab
|
|
activateTab:activate_tab];
|
|
|
|
tab = (Tab*)[controller window];
|
|
}
|
|
}
|
|
|
|
- (void)applicationWillTerminate:(NSNotification*)notification
|
|
{
|
|
}
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)applicationDidChangeScreenParameters:(NSNotification*)notification
|
|
{
|
|
for (TabController* controller in self.managed_tabs) {
|
|
auto* tab = (Tab*)[controller window];
|
|
[[tab web_view] handleDisplayRefreshRateChange];
|
|
}
|
|
}
|
|
|
|
@end
|