blob: 37e3ea73660cc18b3f67105a1634b0bd69d0432d [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/main_menu_builder.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/cocoa/accelerators_cocoa.h"
#include "chrome/browser/ui/cocoa/history_menu_bridge.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
#include "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
#include "ui/strings/grit/ui_strings.h"
namespace chrome {
namespace {
using Item = internal::MenuItemBuilder;
base::scoped_nsobject<NSMenuItem> BuildAppMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_APP_MENU_PRODUCT_NAME)
.tag(IDC_CHROME_MENU)
.submenu({
Item(IDS_ABOUT_MAC)
.string_format_1(product_name)
.tag(IDC_ABOUT)
.target(app_delegate)
.action(@selector(orderFrontStandardAboutPanel:)),
Item().is_separator(),
Item(IDS_PREFERENCES)
.tag(IDC_OPTIONS)
.target(app_delegate)
.action(@selector(showPreferences:))
.remove_if(is_pwa),
Item().is_separator().remove_if(is_pwa),
Item(IDS_CLEAR_BROWSING_DATA)
.command_id(IDC_CLEAR_BROWSING_DATA)
.remove_if(is_pwa),
Item(IDS_IMPORT_SETTINGS_MENU_MAC)
.command_id(IDC_IMPORT_SETTINGS)
.remove_if(is_pwa),
Item().is_separator(),
Item(IDS_SERVICES_MAC).tag(-1).submenu({}),
Item(IDS_HIDE_APP_MAC)
.string_format_1(product_name)
.tag(IDC_HIDE_APP)
.action(@selector(hide:)),
Item(IDS_HIDE_OTHERS_MAC)
.action(@selector(hideOtherApplications:))
.key_equivalent(@"h", NSEventModifierFlagCommand |
NSEventModifierFlagOption),
Item(IDS_SHOW_ALL_MAC)
.action(@selector(unhideAllApplications:)),
Item().is_separator(),
Item(IDS_CONFIRM_TO_QUIT_OPTION)
.target(app_delegate)
.action(@selector(toggleConfirmToQuit:))
.remove_if(is_pwa),
Item().is_separator(),
// AppKit inserts "Quit and Keep Windows" as an alternate item
// automatically by using the -terminate: action.
Item(IDS_EXIT_MAC)
.string_format_1(product_name)
.tag(IDC_EXIT)
.target(nsapp)
.action(@selector(terminate:)),
})
.Build();
NSMenuItem* services_item = [[item submenu] itemWithTag:-1];
[services_item setTag:0];
[nsapp setServicesMenu:[services_item submenu]];
return item;
}
base::scoped_nsobject<NSMenuItem> BuildFileMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_FILE_MENU_MAC)
.tag(IDC_FILE_MENU)
.submenu({
Item(IDS_NEW_TAB_MAC).command_id(IDC_NEW_TAB).remove_if(is_pwa),
Item(IDS_NEW_WINDOW_MAC).command_id(IDC_NEW_WINDOW),
Item(IDS_NEW_INCOGNITO_WINDOW_MAC)
.command_id(IDC_NEW_INCOGNITO_WINDOW)
.remove_if(is_pwa),
Item(IDS_REOPEN_CLOSED_TABS_MAC)
.command_id(IDC_RESTORE_TAB)
.remove_if(is_pwa),
Item(IDS_OPEN_FILE_MAC)
.command_id(IDC_OPEN_FILE)
.remove_if(is_pwa),
Item(IDS_OPEN_LOCATION_MAC)
.command_id(IDC_FOCUS_LOCATION)
.remove_if(is_pwa),
Item().is_separator(),
// AppKit inserts "Close All" as an alternate item automatically
// by using the -performClose: action.
Item(IDS_CLOSE_WINDOW_MAC)
.tag(IDC_CLOSE_WINDOW)
.action(@selector(performClose:)),
Item(IDS_CLOSE_TAB_MAC)
.command_id(IDC_CLOSE_TAB)
.remove_if(is_pwa),
Item(IDS_SAVE_PAGE_MAC)
.command_id(IDC_SAVE_PAGE)
.remove_if(is_pwa),
Item().is_separator().remove_if(is_pwa),
Item(IDS_SHARE_MAC).remove_if(is_pwa), Item().is_separator(),
Item(IDS_PRINT).command_id(IDC_PRINT),
Item(IDS_PRINT_USING_SYSTEM_DIALOG_MAC)
.command_id(IDC_BASIC_PRINT)
.is_alternate()
.remove_if(is_pwa),
})
.Build();
return item;
}
base::scoped_nsobject<NSMenuItem> BuildEditMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_EDIT_MENU_MAC)
.tag(IDC_EDIT_MENU)
.submenu({
Item(IDS_EDIT_UNDO_MAC)
.tag(IDC_CONTENT_CONTEXT_UNDO)
.action(@selector(undo:)),
Item(IDS_EDIT_REDO_MAC)
.tag(IDC_CONTENT_CONTEXT_REDO)
.action(@selector(redo:)),
Item().is_separator(),
Item(IDS_CUT_MAC)
.tag(IDC_CONTENT_CONTEXT_CUT)
.action(@selector(cut:)),
Item(IDS_COPY_MAC)
.tag(IDC_CONTENT_CONTEXT_COPY)
.action(@selector(copy:)),
Item(IDS_PASTE_MAC)
.tag(IDC_CONTENT_CONTEXT_PASTE)
.action(@selector(paste:)),
Item(IDS_PASTE_MATCH_STYLE_MAC)
.tag(IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE)
.action(@selector(pasteAndMatchStyle:)),
Item(IDS_PASTE_MATCH_STYLE_MAC)
.action(@selector(pasteAndMatchStyle:))
.is_alternate()
.key_equivalent(@"V", NSEventModifierFlagCommand |
NSEventModifierFlagOption),
Item(IDS_EDIT_DELETE_MAC)
.tag(IDC_CONTENT_CONTEXT_DELETE)
.action(@selector(delete:)),
Item(IDS_EDIT_SELECT_ALL_MAC)
.tag(IDC_CONTENT_CONTEXT_SELECTALL)
.action(@selector(selectAll:)),
Item().is_separator(),
Item(IDS_EDIT_FIND_SUBMENU_MAC).tag(IDC_FIND_MENU).submenu({
Item(IDS_EDIT_SEARCH_WEB_MAC).command_id(IDC_FOCUS_SEARCH),
Item().is_separator(),
Item(IDS_EDIT_FIND_MAC).command_id(IDC_FIND),
Item(IDS_EDIT_FIND_NEXT_MAC).command_id(IDC_FIND_NEXT),
Item(IDS_EDIT_FIND_PREVIOUS_MAC)
.command_id(IDC_FIND_PREVIOUS),
Item(IDS_EDIT_USE_SELECTION_MAC)
.action(@selector(copyToFindPboard:))
.key_equivalent(@"e", NSEventModifierFlagCommand),
Item(IDS_EDIT_JUMP_TO_SELECTION_MAC)
.action(@selector(centerSelectionInVisibleArea:))
.key_equivalent(@"j", NSEventModifierFlagCommand),
}),
Item(IDS_EDIT_SPELLING_GRAMMAR_MAC)
.tag(IDC_SPELLCHECK_MENU)
.submenu({
Item(IDS_EDIT_SHOW_SPELLING_GRAMMAR_MAC)
.action(@selector(showGuessPanel:))
.key_equivalent(@":", NSEventModifierFlagCommand),
Item(IDS_EDIT_CHECK_DOCUMENT_MAC)
.action(@selector(checkSpelling:))
.key_equivalent(@";", NSEventModifierFlagCommand),
Item(IDS_EDIT_CHECK_SPELLING_TYPING_MAC)
.action(@selector
(toggleContinuousSpellChecking:)),
Item(IDS_EDIT_CHECK_GRAMMAR_MAC)
.action(@selector(toggleGrammarChecking:)),
}),
Item(IDS_SPEECH_MAC).tag(50158).submenu({
Item(IDS_SPEECH_START_SPEAKING_MAC)
.action(@selector(startSpeaking:)),
Item(IDS_SPEECH_STOP_SPEAKING_MAC)
.action(@selector(stopSpeaking:)),
}),
// The "Start Dictation..." and "Emoji & Symbols" items are
// inserted by AppKit.
})
.Build();
return item;
}
base::scoped_nsobject<NSMenuItem> BuildViewMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_VIEW_MENU_MAC)
.tag(IDC_VIEW_MENU)
.submenu({
Item(IDS_BOOKMARK_BAR_ALWAYS_SHOW_MAC)
.command_id(IDC_SHOW_BOOKMARK_BAR)
.remove_if(is_pwa),
Item(IDS_TOGGLE_FULLSCREEN_TOOLBAR_MAC)
.command_id(IDC_TOGGLE_FULLSCREEN_TOOLBAR),
Item(IDS_CUSTOMIZE_TOUCH_BAR)
.tag(IDC_CUSTOMIZE_TOUCH_BAR)
.action(@selector(toggleTouchBarCustomizationPalette:))
.remove_if(is_pwa),
Item().is_separator(),
Item(IDS_STOP_MENU_MAC).command_id(IDC_STOP),
Item(IDS_RELOAD_MENU_MAC).command_id(IDC_RELOAD),
Item(IDS_RELOAD_BYPASSING_CACHE_MENU_MAC)
.command_id(IDC_RELOAD_BYPASSING_CACHE)
.is_alternate(),
Item().is_separator(),
Item(IDS_ENTER_FULLSCREEN_MAC)
.tag(IDC_FULLSCREEN)
.action(@selector(toggleFullScreen:)),
Item(IDS_TEXT_DEFAULT_MAC).command_id(IDC_ZOOM_NORMAL),
Item(IDS_TEXT_BIGGER_MAC).command_id(IDC_ZOOM_PLUS),
Item(IDS_TEXT_SMALLER_MAC).command_id(IDC_ZOOM_MINUS),
Item().is_separator(),
Item(IDS_MEDIA_ROUTER_MENU_ITEM_TITLE)
.command_id(IDC_ROUTE_MEDIA),
Item().is_separator(),
Item(IDS_DEVELOPER_MENU_MAC)
.tag(IDC_DEVELOPER_MENU)
.submenu({
Item(IDS_VIEW_SOURCE_MAC).command_id(IDC_VIEW_SOURCE),
Item(IDS_DEV_TOOLS_MAC).command_id(IDC_DEV_TOOLS),
Item(IDS_DEV_TOOLS_ELEMENTS_MAC)
.command_id(IDC_DEV_TOOLS_INSPECT),
Item(IDS_DEV_TOOLS_CONSOLE_MAC)
.command_id(IDC_DEV_TOOLS_CONSOLE),
Item(IDS_ALLOW_JAVASCRIPT_APPLE_EVENTS_MAC)
.command_id(IDC_TOGGLE_JAVASCRIPT_APPLE_EVENTS),
}),
})
.Build();
return item;
}
base::scoped_nsobject<NSMenuItem> BuildHistoryMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_HISTORY_MENU_MAC)
.tag(IDC_HISTORY_MENU)
.submenu({
Item(IDS_HISTORY_HOME_MAC).command_id(IDC_HOME).remove_if(is_pwa),
Item(IDS_HISTORY_BACK_MAC).command_id(IDC_BACK),
Item(IDS_HISTORY_FORWARD_MAC).command_id(IDC_FORWARD),
Item()
.tag(HistoryMenuBridge::kRecentlyClosedSeparator)
.is_separator()
.remove_if(is_pwa),
Item(IDS_HISTORY_CLOSED_MAC)
.tag(HistoryMenuBridge::kRecentlyClosedTitle)
.remove_if(is_pwa),
Item()
.tag(HistoryMenuBridge::kVisitedSeparator)
.is_separator()
.remove_if(is_pwa),
Item(IDS_HISTORY_VISITED_MAC)
.tag(HistoryMenuBridge::kVisitedTitle)
.remove_if(is_pwa),
Item()
.tag(HistoryMenuBridge::kShowFullSeparator)
.is_separator()
.remove_if(is_pwa),
Item(IDS_HISTORY_SHOWFULLHISTORY_LINK)
.command_id(IDC_SHOW_HISTORY)
.remove_if(is_pwa),
})
.Build();
return item;
}
base::scoped_nsobject<NSMenuItem> BuildBookmarksMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
if (is_pwa)
return base::scoped_nsobject<NSMenuItem>();
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_BOOKMARKS_MENU)
.tag(IDC_BOOKMARKS_MENU)
.submenu({
Item(IDS_BOOKMARK_MANAGER).command_id(IDC_SHOW_BOOKMARK_MANAGER),
Item().tag(IDC_BOOKMARK_PAGE).is_separator(),
Item(IDS_BOOKMARK_THIS_PAGE).command_id(IDC_BOOKMARK_PAGE),
Item(IDS_BOOKMARK_ALL_TABS_MAC).command_id(IDC_BOOKMARK_ALL_TABS),
Item().tag(IDC_BOOKMARK_PAGE).is_separator(),
})
.Build();
return item;
}
base::scoped_nsobject<NSMenuItem> BuildPeopleMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
if (is_pwa)
return base::scoped_nsobject<NSMenuItem>();
base::scoped_nsobject<NSMenuItem> item = Item(IDS_PROFILES_OPTIONS_GROUP_NAME)
.tag(IDC_PROFILE_MAIN_MENU)
.submenu({})
.Build();
return item;
}
base::scoped_nsobject<NSMenuItem> BuildWindowMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_WINDOW_MENU_MAC)
.tag(IDC_WINDOW_MENU)
.submenu({
Item(IDS_MINIMIZE_WINDOW_MAC)
.tag(IDC_MINIMIZE_WINDOW)
.action(@selector(performMiniaturize:)),
Item(IDS_ZOOM_WINDOW_MAC)
.tag(IDC_MAXIMIZE_WINDOW)
.action(@selector(performZoom:)),
Item().is_separator(),
Item(IDS_NEXT_TAB_MAC)
.command_id(IDC_SELECT_NEXT_TAB)
.remove_if(is_pwa),
Item(IDS_PREV_TAB_MAC)
.command_id(IDC_SELECT_PREVIOUS_TAB)
.remove_if(is_pwa),
Item(IDS_SHOW_AS_TAB)
.command_id(IDC_SHOW_AS_TAB)
.remove_if(is_pwa),
Item(IDS_DUPLICATE_TAB_MAC)
.command_id(IDC_DUPLICATE_TAB)
.remove_if(is_pwa),
Item(IDS_MUTE_SITE_MAC)
.command_id(IDC_WINDOW_MUTE_SITE)
.remove_if(is_pwa),
Item(IDS_PIN_TAB_MAC)
.command_id(IDC_WINDOW_PIN_TAB)
.remove_if(is_pwa),
Item().is_separator().remove_if(is_pwa),
Item(IDS_SHOW_DOWNLOADS_MAC)
.command_id(IDC_SHOW_DOWNLOADS)
.remove_if(is_pwa),
Item(IDS_SHOW_EXTENSIONS_MAC)
.command_id(IDC_MANAGE_EXTENSIONS)
.remove_if(is_pwa),
Item(IDS_TASK_MANAGER_MAC)
.command_id(IDC_TASK_MANAGER)
.remove_if(is_pwa),
Item().is_separator().remove_if(is_pwa),
Item(IDS_ALL_WINDOWS_FRONT_MAC)
.tag(IDC_ALL_WINDOWS_FRONT)
.action(@selector(arrangeInFront:)),
Item().is_separator(),
})
.Build();
[nsapp setWindowsMenu:[item submenu]];
return item;
}
base::scoped_nsobject<NSMenuItem> BuildHelpMenu(
NSApplication* nsapp,
id app_delegate,
const base::string16& product_name,
bool is_pwa) {
if (is_pwa)
return base::scoped_nsobject<NSMenuItem>();
base::scoped_nsobject<NSMenuItem> item =
Item(IDS_HELP_MENU_MAC)
.submenu({
#if defined(GOOGLE_CHROME_BUILD)
Item(IDS_FEEDBACK_MAC).command_id(IDC_FEEDBACK),
#endif
Item(IDS_HELP_MAC)
.string_format_1(product_name)
.command_id(IDC_HELP_PAGE_VIA_MENU),
})
.Build();
[nsapp setHelpMenu:[item submenu]];
return item;
}
} // namespace
void BuildMainMenu(NSApplication* nsapp,
id<NSApplicationDelegate> app_delegate,
const base::string16& product_name,
bool is_pwa) {
base::scoped_nsobject<NSMenu> main_menu([[NSMenu alloc] initWithTitle:@""]);
using Builder = base::scoped_nsobject<NSMenuItem> (*)(
NSApplication*, id, const base::string16&, bool);
static const Builder kBuilderFuncs[] = {
&BuildAppMenu, &BuildFileMenu, &BuildEditMenu,
&BuildViewMenu, &BuildHistoryMenu, &BuildBookmarksMenu,
&BuildPeopleMenu, &BuildWindowMenu, &BuildHelpMenu,
};
for (auto* builder : kBuilderFuncs) {
auto item = builder(nsapp, app_delegate, product_name, is_pwa);
if (item)
[main_menu addItem:item];
}
[nsapp setMainMenu:main_menu];
}
namespace internal {
MenuItemBuilder::MenuItemBuilder(int string_id) : string_id_(string_id) {}
MenuItemBuilder::MenuItemBuilder(const MenuItemBuilder&) = default;
MenuItemBuilder& MenuItemBuilder::operator=(const MenuItemBuilder&) = default;
MenuItemBuilder::~MenuItemBuilder() = default;
base::scoped_nsobject<NSMenuItem> MenuItemBuilder::Build() const {
if (is_removed_)
return base::scoped_nsobject<NSMenuItem>();
if (is_separator_) {
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem separatorItem] retain]);
if (tag_) {
[item setTag:tag_];
}
return item;
}
// If the item is command-dispatched, look up the relevant key equivalent
// from the accelerator table. Otherwise, use the builder-specified key
// equivalent.
NSString* key_equivalent = key_equivalent_;
NSEventModifierFlags key_equivalent_flags = key_equivalent_flags_;
if (tag_ != 0) {
if (const ui::Accelerator* accelerator =
AcceleratorsCocoa::GetInstance()->GetAcceleratorForCommand(tag_)) {
GetKeyEquivalentAndModifierMaskFromAccelerator(
*accelerator, &key_equivalent, &key_equivalent_flags);
}
}
NSString* title;
if (!string_arg1_.empty())
title = l10n_util::GetNSStringFWithFixup(string_id_, string_arg1_);
else
title = l10n_util::GetNSStringWithFixup(string_id_);
SEL action = !submenu_.has_value() ? action_ : nil;
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
initWithTitle:title
action:action
keyEquivalent:key_equivalent]);
[item setTarget:target_];
[item setTag:tag_];
[item setKeyEquivalentModifierMask:key_equivalent_flags];
[item setAlternate:is_alternate_];
if (submenu_.has_value()) {
base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:title]);
for (const auto& subitem : submenu_.value()) {
base::scoped_nsobject<NSMenuItem> ns_subitem = subitem.Build();
if (ns_subitem)
[menu addItem:ns_subitem];
}
[item setSubmenu:menu];
}
return item;
}
} // namespace internal
} // namespace chrome