| // 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 |