| // Copyright 2013 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/apps/app_shim_menu_controller_mac.h" |
| |
| #include "base/command_line.h" |
| #include "base/mac/scoped_nsautorelease_pool.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h" |
| #include "chrome/browser/apps/app_window_registry_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #import "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/common/extension.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/l10n_util_mac.h" |
| |
| using extensions::Extension; |
| |
| namespace { |
| |
| // When an app window loses main status, AppKit may make another app window main |
| // instead. Rather than trying to predict what AppKit will do (which is hard), |
| // just protect against changes in the event queue that will clobber each other. |
| int g_window_cycle_sequence_number = 0; |
| |
| // Whether Custom Cmd+` window cycling is enabled for apps. |
| bool IsAppWindowCyclingEnabled() { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kDisableAppWindowCycling)) |
| return false; |
| if (command_line->HasSwitch(switches::kEnableAppWindowCycling)) |
| return true; |
| |
| return false; // Current default. |
| } |
| |
| // Gets an item from the main menu given the tag of the top level item |
| // |menu_tag| and the tag of the item |item_tag|. |
| NSMenuItem* GetItemByTag(NSInteger menu_tag, NSInteger item_tag) { |
| return [[[[NSApp mainMenu] itemWithTag:menu_tag] submenu] |
| itemWithTag:item_tag]; |
| } |
| |
| // Finds a top level menu item using |menu_tag| and creates a new NSMenuItem |
| // with the same title. |
| NSMenuItem* NewTopLevelItemFrom(NSInteger menu_tag) { |
| NSMenuItem* original = [[NSApp mainMenu] itemWithTag:menu_tag]; |
| base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc] |
| initWithTitle:[original title] |
| action:nil |
| keyEquivalent:@""]); |
| DCHECK([original hasSubmenu]); |
| base::scoped_nsobject<NSMenu> sub_menu([[NSMenu alloc] |
| initWithTitle:[[original submenu] title]]); |
| [item setSubmenu:sub_menu]; |
| return item.autorelease(); |
| } |
| |
| // Finds an item using |menu_tag| and |item_tag| and adds a duplicate of it to |
| // the submenu of |top_level_item|. |
| void AddDuplicateItem(NSMenuItem* top_level_item, |
| NSInteger menu_tag, |
| NSInteger item_tag) { |
| base::scoped_nsobject<NSMenuItem> item( |
| [GetItemByTag(menu_tag, item_tag) copy]); |
| DCHECK(item); |
| [[top_level_item submenu] addItem:item]; |
| } |
| |
| // Finds an item with |item_tag| and removes it from the submenu of |
| // |top_level_item|. |
| void RemoveMenuItemWithTag(NSMenuItem* top_level_item, |
| NSInteger item_tag, |
| bool remove_following_separator) { |
| NSMenu* submenu = [top_level_item submenu]; |
| NSInteger index = [submenu indexOfItemWithTag:item_tag]; |
| if (index < 0) |
| return; |
| |
| [submenu removeItemAtIndex:index]; |
| |
| if (!remove_following_separator || index == [submenu numberOfItems]) |
| return; |
| |
| NSMenuItem* nextItem = [submenu itemAtIndex:index]; |
| if ([nextItem isSeparatorItem]) |
| [submenu removeItem:nextItem]; |
| } |
| |
| // Sets the menu item with |item_tag| in |top_level_item| visible. |
| // If |has_alternate| is true, the item immediately following |item_tag| is |
| // assumed to be its (only) alternate. Since AppKit is unable to hide items |
| // with alternates, |has_alternate| will cause -[NSMenuItem alternate] to be |
| // removed when hiding and restored when showing. |
| void SetItemWithTagVisible(NSMenuItem* top_level_item, |
| NSInteger item_tag, |
| bool visible, |
| bool has_alternate) { |
| NSMenu* submenu = [top_level_item submenu]; |
| NSMenuItem* menu_item = [submenu itemWithTag:item_tag]; |
| DCHECK(menu_item); |
| |
| if (visible != [menu_item isHidden]) |
| return; |
| |
| if (!has_alternate) { |
| [menu_item setHidden:!visible]; |
| return; |
| } |
| |
| NSInteger next_index = [submenu indexOfItem:menu_item] + 1; |
| DCHECK_LT(next_index, [submenu numberOfItems]); |
| |
| NSMenuItem* alternate_item = [submenu itemAtIndex:next_index]; |
| if (!visible) { |
| // When hiding (only), we can verify the assumption that the item following |
| // |item_tag| is actually an alternate. |
| DCHECK([alternate_item isAlternate]); |
| } |
| |
| // The alternate item visibility should always be in sync. |
| DCHECK_EQ([alternate_item isHidden], [menu_item isHidden]); |
| [alternate_item setAlternate:visible]; |
| [alternate_item setHidden:!visible]; |
| [menu_item setHidden:!visible]; |
| } |
| |
| // Return the Extension (if any) associated with the given window. If it is not |
| // a platform app nor hosted app, but it is a browser, |is_browser| will be set |
| // to true (otherwise false). |
| const Extension* GetExtensionForNSWindow(NSWindow* window, bool* is_browser) { |
| const Extension* extension = nullptr; |
| Browser* browser = nullptr; |
| |
| extensions::AppWindow* app_window = |
| AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(window); |
| if (app_window) { |
| extension = app_window->GetExtension(); |
| } else { |
| // If there is no corresponding AppWindow, this could be a hosted app, so |
| // check for a browser. |
| browser = chrome::FindBrowserWithWindow(window); |
| extension = apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser); |
| } |
| |
| *is_browser = extension == nullptr && browser != nullptr; |
| return extension; |
| } |
| |
| // Sets or clears NSWindowCollectionBehaviorIgnoresCycle for |window|. Does not |
| // change NSWindowCollectionBehaviorParticipatesInCycle. That exists, e.g, for |
| // an NSPanel to override its default behavior, but this should only ever be |
| // called for Browser windows and App windows (which are not panels). |
| bool SetWindowParticipatesInCycle(NSWindow* window, bool participates) { |
| const NSWindowCollectionBehavior past_behavior = [window collectionBehavior]; |
| NSWindowCollectionBehavior behavior = past_behavior; |
| if (participates) |
| behavior &= ~NSWindowCollectionBehaviorIgnoresCycle; |
| else |
| behavior |= NSWindowCollectionBehaviorIgnoresCycle; |
| |
| // Often, there is no change. AppKit has no early exit since the value is |
| // derived partially from styleMask and other things, so do our own. |
| if (behavior == past_behavior) |
| return false; |
| |
| [window setCollectionBehavior:behavior]; |
| return true; |
| } |
| |
| // Sets the window cycle list to |app_id|'s windows only. |
| void SetAppCyclesWindows(const std::string& app_id, int sequence_number) { |
| if (g_window_cycle_sequence_number != sequence_number) |
| return; |
| |
| bool any_change = false; |
| for (NSWindow* window : [NSApp windows]) { |
| bool is_browser; |
| const Extension* extension = GetExtensionForNSWindow(window, &is_browser); |
| if (extension && extension->id() == app_id) |
| any_change |= SetWindowParticipatesInCycle(window, true); |
| else if (extension || is_browser) |
| any_change |= SetWindowParticipatesInCycle(window, false); |
| } |
| |
| // Without the following, -[NSApplication _getLockedWindowListForCycle] will |
| // happily return windows that were just set to ignore window cycling. Doing |
| // this seems to trick AppKit into updating the window cycle list. But it is a |
| // bit scary, so avoid it when there is no change. These attempts were based |
| // on the observation that clicking a window twice to switch focus would |
| // always work. Also tried (without luck): |
| // - [NSApp setWindowsNeedUpdate:YES], |
| // - Creating a deferred NSWindow and immediately releasing it, |
| // - Calling private methods like [NSApp _unlockWindowListForCycle], |
| // - [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined... |
| // (an attempt to tickle AppKit into an update of some kind), |
| // - Calling synchronously (i.e. not via PostTask) <- this was actually the |
| // initial attempt. Then, switching to PostTask didn't help with this |
| // quirk, but was useful for the sequence number stuff, and |
| // - Re-ordering collection behavior changes to ensure one window was always |
| // participating (i.e. all 'adds' before any 'removes'). |
| if (any_change) |
| [[NSApp keyWindow] makeKeyAndOrderFront:nil]; |
| } |
| |
| // Sets the window cycle list to Chrome browser windows only. |
| void SetChromeCyclesWindows(int sequence_number) { |
| if (g_window_cycle_sequence_number != sequence_number) |
| return; |
| |
| bool any_change = false; |
| for (NSWindow* window : [NSApp windows]) { |
| bool is_browser; |
| const Extension* extension = GetExtensionForNSWindow(window, &is_browser); |
| if (extension || is_browser) |
| any_change |= SetWindowParticipatesInCycle(window, is_browser); |
| } |
| if (any_change) |
| [[NSApp keyWindow] makeKeyAndOrderFront:nil]; |
| } |
| |
| } // namespace |
| |
| // Used by AppShimMenuController to manage menu items that are a copy of a |
| // Chrome menu item but with a different action. This manages unsetting and |
| // restoring the original item's key equivalent, so that we can use the same |
| // key equivalent in the copied item with a different action. If |resourceId_| |
| // is non-zero, this will also update the title to include the app name. |
| // If the copy (menuItem) has no key equivalent, and the title does not have the |
| // app name, then enableForApp and disable do not need to be called. I.e. the |
| // doppelganger just copies the item and sets a new action. |
| @interface DoppelgangerMenuItem : NSObject { |
| @private |
| base::scoped_nsobject<NSMenuItem> menuItem_; |
| base::scoped_nsobject<NSMenuItem> sourceItem_; |
| base::scoped_nsobject<NSString> sourceKeyEquivalent_; |
| int resourceId_; |
| } |
| |
| @property(readonly, nonatomic) NSMenuItem* menuItem; |
| |
| // Get the source item using the tags and create the menu item. |
| - (id)initWithController:(AppShimMenuController*)controller |
| menuTag:(NSInteger)menuTag |
| itemTag:(NSInteger)itemTag |
| resourceId:(int)resourceId |
| action:(SEL)action |
| keyEquivalent:(NSString*)keyEquivalent; |
| // Retain the source item given |menuTag| and |sourceItemTag|. Copy |
| // the menu item given |menuTag| and |targetItemTag|. |
| // This is useful when we want a doppelganger with a different source item. |
| // For example, if there are conflicting key equivalents. |
| - (id)initWithMenuTag:(NSInteger)menuTag |
| sourceItemTag:(NSInteger)sourceItemTag |
| targetItemTag:(NSInteger)targetItemTag |
| keyEquivalent:(NSString*)keyEquivalent; |
| // Set the title using |resourceId_| and unset the source item's key equivalent. |
| - (void)enableForApp:(const Extension*)app; |
| // Restore the source item's key equivalent. |
| - (void)disable; |
| @end |
| |
| @implementation DoppelgangerMenuItem |
| |
| - (NSMenuItem*)menuItem { |
| return menuItem_; |
| } |
| |
| - (id)initWithController:(AppShimMenuController*)controller |
| menuTag:(NSInteger)menuTag |
| itemTag:(NSInteger)itemTag |
| resourceId:(int)resourceId |
| action:(SEL)action |
| keyEquivalent:(NSString*)keyEquivalent { |
| if ((self = [super init])) { |
| sourceItem_.reset([GetItemByTag(menuTag, itemTag) retain]); |
| DCHECK(sourceItem_); |
| sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]); |
| menuItem_.reset([[NSMenuItem alloc] |
| initWithTitle:[sourceItem_ title] |
| action:action |
| keyEquivalent:keyEquivalent]); |
| [menuItem_ setTarget:controller]; |
| [menuItem_ setTag:itemTag]; |
| resourceId_ = resourceId; |
| } |
| return self; |
| } |
| |
| - (id)initWithMenuTag:(NSInteger)menuTag |
| sourceItemTag:(NSInteger)sourceItemTag |
| targetItemTag:(NSInteger)targetItemTag |
| keyEquivalent:(NSString*)keyEquivalent { |
| if ((self = [super init])) { |
| menuItem_.reset([GetItemByTag(menuTag, targetItemTag) copy]); |
| sourceItem_.reset([GetItemByTag(menuTag, sourceItemTag) retain]); |
| DCHECK(menuItem_); |
| DCHECK(sourceItem_); |
| sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]); |
| } |
| return self; |
| } |
| |
| - (void)enableForApp:(const Extension*)app { |
| // It seems that two menu items that have the same key equivalent must also |
| // have the same action for the keyboard shortcut to work. (This refers to the |
| // original keyboard shortcut, regardless of any overrides set in OSX). |
| // In order to let the app menu items have a different action, we remove the |
| // key equivalent of the original items and restore them later. |
| [sourceItem_ setKeyEquivalent:@""]; |
| if (!resourceId_) |
| return; |
| |
| [menuItem_ setTitle:l10n_util::GetNSStringF(resourceId_, |
| base::UTF8ToUTF16(app->name()))]; |
| } |
| |
| - (void)disable { |
| // Restore the keyboard shortcut to Chrome. This just needs to be set back to |
| // the original keyboard shortcut, regardless of any overrides in OSX. The |
| // overrides still work as they are based on the title of the menu item. |
| [sourceItem_ setKeyEquivalent:sourceKeyEquivalent_]; |
| } |
| |
| @end |
| |
| @interface AppShimMenuController () |
| // Construct the NSMenuItems for apps. |
| - (void)buildAppMenuItems; |
| // Register for NSWindow notifications. |
| - (void)registerEventHandlers; |
| // If the window is an app window, add or remove menu items. |
| - (void)windowMainStatusChanged:(NSNotification*)notification; |
| // Called when |app| becomes the main window in the Chrome process. |
| - (void)appBecameMain:(const Extension*)app; |
| // Called when there is no main window, or if the main window is not an app. |
| - (void)chromeBecameMain; |
| // Add menu items for an app and hide Chrome menu items. |
| - (void)addMenuItems:(const Extension*)app; |
| // If the window belongs to the currently focused app, remove the menu items and |
| // unhide Chrome menu items. |
| - (void)removeMenuItems; |
| // If the currently focused window belongs to a platform app, quit the app. |
| - (void)quitCurrentPlatformApp; |
| // If the currently focused window belongs to a platform app, hide the app. |
| - (void)hideCurrentPlatformApp; |
| // If the currently focused window belongs to a platform app, focus the app. |
| - (void)focusCurrentPlatformApp; |
| @end |
| |
| @implementation AppShimMenuController |
| |
| - (id)init { |
| if ((self = [super init])) { |
| [self buildAppMenuItems]; |
| [self registerEventHandlers]; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| [super dealloc]; |
| } |
| |
| - (void)buildAppMenuItems { |
| aboutDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithController:self |
| menuTag:IDC_CHROME_MENU |
| itemTag:IDC_ABOUT |
| resourceId:IDS_ABOUT_MAC |
| action:nil |
| keyEquivalent:@""]); |
| hideDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithController:self |
| menuTag:IDC_CHROME_MENU |
| itemTag:IDC_HIDE_APP |
| resourceId:IDS_HIDE_APP_MAC |
| action:@selector(hideCurrentPlatformApp) |
| keyEquivalent:@"h"]); |
| quitDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithController:self |
| menuTag:IDC_CHROME_MENU |
| itemTag:IDC_EXIT |
| resourceId:IDS_EXIT_MAC |
| action:@selector(quitCurrentPlatformApp) |
| keyEquivalent:@"q"]); |
| newDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithController:self |
| menuTag:IDC_FILE_MENU |
| itemTag:IDC_NEW_WINDOW |
| resourceId:0 |
| action:nil |
| keyEquivalent:@"n"]); |
| // Since the "Close Window" menu item will have the same shortcut as "Close |
| // Tab" on the Chrome menu, we need to create a doppelganger. |
| closeWindowDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithMenuTag:IDC_FILE_MENU |
| sourceItemTag:IDC_CLOSE_TAB |
| targetItemTag:IDC_CLOSE_WINDOW |
| keyEquivalent:@"w"]); |
| // For apps, the "Window" part of "New Window" is dropped to match the default |
| // menu set given to Cocoa Apps. |
| [[newDoppelganger_ menuItem] setTitle:l10n_util::GetNSString(IDS_NEW_MAC)]; |
| openDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithController:self |
| menuTag:IDC_FILE_MENU |
| itemTag:IDC_OPEN_FILE |
| resourceId:0 |
| action:nil |
| keyEquivalent:@"o"]); |
| allToFrontDoppelganger_.reset([[DoppelgangerMenuItem alloc] |
| initWithController:self |
| menuTag:IDC_WINDOW_MENU |
| itemTag:IDC_ALL_WINDOWS_FRONT |
| resourceId:0 |
| action:@selector(focusCurrentPlatformApp) |
| keyEquivalent:@""]); |
| |
| // The app's menu. |
| appMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@"" |
| action:nil |
| keyEquivalent:@""]); |
| base::scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:@""]); |
| [appMenuItem_ setSubmenu:appMenu]; |
| [appMenu setAutoenablesItems:NO]; |
| |
| [appMenu addItem:[aboutDoppelganger_ menuItem]]; |
| [[aboutDoppelganger_ menuItem] setEnabled:NO]; // Not implemented yet. |
| [appMenu addItem:[NSMenuItem separatorItem]]; |
| [appMenu addItem:[hideDoppelganger_ menuItem]]; |
| [appMenu addItem:[NSMenuItem separatorItem]]; |
| [appMenu addItem:[quitDoppelganger_ menuItem]]; |
| |
| // File menu. |
| fileMenuItem_.reset([NewTopLevelItemFrom(IDC_FILE_MENU) retain]); |
| [[fileMenuItem_ submenu] addItem:[newDoppelganger_ menuItem]]; |
| [[fileMenuItem_ submenu] addItem:[openDoppelganger_ menuItem]]; |
| [[fileMenuItem_ submenu] addItem:[NSMenuItem separatorItem]]; |
| [[fileMenuItem_ submenu] addItem:[closeWindowDoppelganger_ menuItem]]; |
| |
| // Edit menu. We copy the menu because the last two items, "Start Dictation" |
| // and "Special Characters" are added by OSX, so we can't copy them |
| // explicitly. |
| editMenuItem_.reset([[[NSApp mainMenu] itemWithTag:IDC_EDIT_MENU] copy]); |
| |
| // View menu. Remove "Always Show Bookmark Bar" and separator. |
| viewMenuItem_.reset([[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] copy]); |
| RemoveMenuItemWithTag(viewMenuItem_, IDC_SHOW_BOOKMARK_BAR, YES); |
| |
| // History menu. |
| historyMenuItem_.reset([NewTopLevelItemFrom(IDC_HISTORY_MENU) retain]); |
| AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_BACK); |
| AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_FORWARD); |
| |
| // Window menu. |
| windowMenuItem_.reset([NewTopLevelItemFrom(IDC_WINDOW_MENU) retain]); |
| AddDuplicateItem(windowMenuItem_, IDC_WINDOW_MENU, IDC_MINIMIZE_WINDOW); |
| AddDuplicateItem(windowMenuItem_, IDC_WINDOW_MENU, IDC_MAXIMIZE_WINDOW); |
| [[windowMenuItem_ submenu] addItem:[NSMenuItem separatorItem]]; |
| [[windowMenuItem_ submenu] addItem:[allToFrontDoppelganger_ menuItem]]; |
| } |
| |
| - (void)registerEventHandlers { |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(windowMainStatusChanged:) |
| name:NSWindowDidBecomeMainNotification |
| object:nil]; |
| |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(windowMainStatusChanged:) |
| name:NSWindowDidResignMainNotification |
| object:nil]; |
| } |
| |
| - (void)windowMainStatusChanged:(NSNotification*)notification { |
| // A Yosemite AppKit bug causes this notification to be sent during the |
| // -dealloc for a specific NSWindow. Any autoreleases sent to that window |
| // must be drained before the window finishes -dealloc. In this method, an |
| // autorelease is sent by the invocation of [NSApp windows]. |
| // http://crbug.com/406944. |
| base::mac::ScopedNSAutoreleasePool pool; |
| |
| NSString* name = [notification name]; |
| if ([name isEqualToString:NSWindowDidBecomeMainNotification]) { |
| id window = [notification object]; |
| bool is_browser; |
| const Extension* extension = GetExtensionForNSWindow(window, &is_browser); |
| // Ignore is_browser: if a window becomes main that does not belong to an |
| // extension or browser, treat it the same as switching to a browser. |
| if (extension) |
| [self appBecameMain:extension]; |
| else |
| [self chromeBecameMain]; |
| } else if ([name isEqualToString:NSWindowDidResignMainNotification]) { |
| // When a window resigns main status, reset back to the Chrome menu. |
| // In the past we've tried: |
| // - Only doing this when a window closes, but this would not be triggered |
| // when an app becomes hidden (Cmd+h), and there are no Chrome windows to |
| // become main. |
| // - Scanning [NSApp windows] to predict whether we could |
| // expect another Chrome window to become main, and skip the reset. However, |
| // panels need to do strange things during window close to ensure panels |
| // never get chosen for key status over a browser window (which is likely |
| // because they are given an elevated [NSWindow level]). Trying to handle |
| // this case is not robust. |
| // |
| // Unfortunately, resetting the menu to Chrome |
| // unconditionally means that if another packaged app window becomes key, |
| // the menu will flicker. TODO(tapted): Investigate restoring the logic when |
| // the panel code is removed. |
| [self chromeBecameMain]; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| - (void)appBecameMain:(const Extension*)app { |
| if (appId_ == app->id()) |
| return; |
| |
| if (!appId_.empty()) |
| [self removeMenuItems]; |
| |
| appId_ = app->id(); |
| [self addMenuItems:app]; |
| if (IsAppWindowCyclingEnabled()) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(&SetAppCyclesWindows, appId_, |
| ++g_window_cycle_sequence_number)); |
| } |
| } |
| |
| - (void)chromeBecameMain { |
| if (appId_.empty()) |
| return; |
| |
| appId_.clear(); |
| [self removeMenuItems]; |
| if (IsAppWindowCyclingEnabled()) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&SetChromeCyclesWindows, ++g_window_cycle_sequence_number)); |
| } |
| } |
| |
| - (void)addMenuItems:(const Extension*)app { |
| DCHECK_EQ(appId_, app->id()); |
| NSString* title = base::SysUTF8ToNSString(app->name()); |
| |
| // Hide Chrome menu items. |
| NSMenu* mainMenu = [NSApp mainMenu]; |
| for (NSMenuItem* item in [mainMenu itemArray]) |
| [item setHidden:YES]; |
| |
| [aboutDoppelganger_ enableForApp:app]; |
| [hideDoppelganger_ enableForApp:app]; |
| [quitDoppelganger_ enableForApp:app]; |
| [newDoppelganger_ enableForApp:app]; |
| [openDoppelganger_ enableForApp:app]; |
| [closeWindowDoppelganger_ enableForApp:app]; |
| |
| [appMenuItem_ setTitle:base::SysUTF8ToNSString(appId_)]; |
| [[appMenuItem_ submenu] setTitle:title]; |
| |
| [mainMenu addItem:appMenuItem_]; |
| [mainMenu addItem:fileMenuItem_]; |
| |
| SetItemWithTagVisible(editMenuItem_, |
| IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE, |
| app->is_hosted_app(), true); |
| SetItemWithTagVisible(editMenuItem_, IDC_FIND_MENU, app->is_hosted_app(), |
| false); |
| [mainMenu addItem:editMenuItem_]; |
| |
| if (app->is_hosted_app()) { |
| [mainMenu addItem:viewMenuItem_]; |
| [mainMenu addItem:historyMenuItem_]; |
| } |
| [mainMenu addItem:windowMenuItem_]; |
| } |
| |
| - (void)removeMenuItems { |
| NSMenu* mainMenu = [NSApp mainMenu]; |
| [mainMenu removeItem:appMenuItem_]; |
| [mainMenu removeItem:fileMenuItem_]; |
| if ([mainMenu indexOfItem:viewMenuItem_] >= 0) |
| [mainMenu removeItem:viewMenuItem_]; |
| if ([mainMenu indexOfItem:historyMenuItem_] >= 0) |
| [mainMenu removeItem:historyMenuItem_]; |
| [mainMenu removeItem:editMenuItem_]; |
| [mainMenu removeItem:windowMenuItem_]; |
| |
| // Restore the Chrome main menu bar. |
| for (NSMenuItem* item in [mainMenu itemArray]) |
| [item setHidden:NO]; |
| |
| [aboutDoppelganger_ disable]; |
| [hideDoppelganger_ disable]; |
| [quitDoppelganger_ disable]; |
| [newDoppelganger_ disable]; |
| [openDoppelganger_ disable]; |
| [closeWindowDoppelganger_ disable]; |
| } |
| |
| - (void)quitCurrentPlatformApp { |
| extensions::AppWindow* appWindow = |
| AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile( |
| [NSApp keyWindow]); |
| if (appWindow) { |
| apps::ExtensionAppShimHandler::QuitAppForWindow(appWindow); |
| } else { |
| Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]); |
| const Extension* extension = |
| apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser); |
| if (extension) |
| apps::ExtensionAppShimHandler::QuitHostedAppForWindow(browser->profile(), |
| extension->id()); |
| } |
| } |
| |
| - (void)hideCurrentPlatformApp { |
| extensions::AppWindow* appWindow = |
| AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile( |
| [NSApp keyWindow]); |
| if (appWindow) { |
| apps::ExtensionAppShimHandler::HideAppForWindow(appWindow); |
| } else { |
| Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]); |
| const Extension* extension = |
| apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser); |
| if (extension) |
| apps::ExtensionAppShimHandler::HideHostedApp(browser->profile(), |
| extension->id()); |
| } |
| } |
| |
| - (void)focusCurrentPlatformApp { |
| extensions::AppWindow* appWindow = |
| AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile( |
| [NSApp keyWindow]); |
| if (appWindow) |
| apps::ExtensionAppShimHandler::FocusAppForWindow(appWindow); |
| } |
| |
| @end |