Commit 732c3b5f authored by jackhou@chromium.org's avatar jackhou@chromium.org

Add Hide to app menu.

This adds "Hide <app>" to the app menu that is shown when an app window
has focus. This also enables the Cmd+H shortcut. 

This also adds an IPC message to instruct the shim to hide.

This also adds a command ID for Hide and a corresponding tag to
MainMenu.xib.

BUG=168080, 276052
TEST=Start an app.
Press Cmd+H or select Hide from the main menu. The app should hide.
Clicking the app's dock icon, or Cmd+Tab to the app should show it again.

Review URL: https://chromiumcodereview.appspot.com/23514021

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221676 0039d316-1c4b-4281-b951-d872f2087c98
parent 3b5e88dd
...@@ -24,6 +24,8 @@ class AppShimHandler { ...@@ -24,6 +24,8 @@ class AppShimHandler {
virtual void OnAppLaunchComplete(AppShimLaunchResult result) = 0; virtual void OnAppLaunchComplete(AppShimLaunchResult result) = 0;
// Invoked when the app is closed in the browser process. // Invoked when the app is closed in the browser process.
virtual void OnAppClosed() = 0; virtual void OnAppClosed() = 0;
// Invoked when the app should be hidden.
virtual void OnAppHide() = 0;
// Invoked when the app is requesting user attention. // Invoked when the app is requesting user attention.
virtual void OnAppRequestUserAttention() = 0; virtual void OnAppRequestUserAttention() = 0;
......
...@@ -114,6 +114,10 @@ void AppShimHost::OnAppClosed() { ...@@ -114,6 +114,10 @@ void AppShimHost::OnAppClosed() {
Close(); Close();
} }
void AppShimHost::OnAppHide() {
Send(new AppShimMsg_Hide);
}
void AppShimHost::OnAppRequestUserAttention() { void AppShimHost::OnAppRequestUserAttention() {
Send(new AppShimMsg_RequestUserAttention); Send(new AppShimMsg_RequestUserAttention);
} }
......
...@@ -65,6 +65,7 @@ class AppShimHost : public IPC::Listener, ...@@ -65,6 +65,7 @@ class AppShimHost : public IPC::Listener,
// apps::AppShimHandler::Host overrides: // apps::AppShimHandler::Host overrides:
virtual void OnAppLaunchComplete(apps::AppShimLaunchResult result) OVERRIDE; virtual void OnAppLaunchComplete(apps::AppShimLaunchResult result) OVERRIDE;
virtual void OnAppClosed() OVERRIDE; virtual void OnAppClosed() OVERRIDE;
virtual void OnAppHide() OVERRIDE;
virtual void OnAppRequestUserAttention() OVERRIDE; virtual void OnAppRequestUserAttention() OVERRIDE;
virtual base::FilePath GetProfilePath() const OVERRIDE; virtual base::FilePath GetProfilePath() const OVERRIDE;
virtual std::string GetAppId() const OVERRIDE; virtual std::string GetAppId() const OVERRIDE;
......
...@@ -25,6 +25,9 @@ IPC_ENUM_TRAITS_MAX_VALUE(apps::AppShimFocusType, ...@@ -25,6 +25,9 @@ IPC_ENUM_TRAITS_MAX_VALUE(apps::AppShimFocusType,
IPC_MESSAGE_CONTROL1(AppShimMsg_LaunchApp_Done, IPC_MESSAGE_CONTROL1(AppShimMsg_LaunchApp_Done,
apps::AppShimLaunchResult /* launch result */) apps::AppShimLaunchResult /* launch result */)
// Instructs the shim to hide the app.
IPC_MESSAGE_CONTROL0(AppShimMsg_Hide)
// Instructs the shim to request user attention. // Instructs the shim to request user attention.
IPC_MESSAGE_CONTROL0(AppShimMsg_RequestUserAttention) IPC_MESSAGE_CONTROL0(AppShimMsg_RequestUserAttention)
......
...@@ -39,6 +39,7 @@ class FakeHost : public apps::AppShimHandler::Host { ...@@ -39,6 +39,7 @@ class FakeHost : public apps::AppShimHandler::Host {
virtual void OnAppClosed() OVERRIDE { virtual void OnAppClosed() OVERRIDE {
handler_->OnShimClose(this); handler_->OnShimClose(this);
} }
virtual void OnAppHide() OVERRIDE {}
virtual void OnAppRequestUserAttention() OVERRIDE {} virtual void OnAppRequestUserAttention() OVERRIDE {}
virtual base::FilePath GetProfilePath() const OVERRIDE { virtual base::FilePath GetProfilePath() const OVERRIDE {
return profile_path_; return profile_path_;
......
...@@ -99,6 +99,9 @@ class AppShimController : public IPC::Listener { ...@@ -99,6 +99,9 @@ class AppShimController : public IPC::Listener {
// shim process should die. // shim process should die.
void OnLaunchAppDone(apps::AppShimLaunchResult result); void OnLaunchAppDone(apps::AppShimLaunchResult result);
// Hide this app.
void OnHide();
// Requests user attention. // Requests user attention.
void OnRequestUserAttention(); void OnRequestUserAttention();
...@@ -191,6 +194,7 @@ bool AppShimController::OnMessageReceived(const IPC::Message& message) { ...@@ -191,6 +194,7 @@ bool AppShimController::OnMessageReceived(const IPC::Message& message) {
bool handled = true; bool handled = true;
IPC_BEGIN_MESSAGE_MAP(AppShimController, message) IPC_BEGIN_MESSAGE_MAP(AppShimController, message)
IPC_MESSAGE_HANDLER(AppShimMsg_LaunchApp_Done, OnLaunchAppDone) IPC_MESSAGE_HANDLER(AppShimMsg_LaunchApp_Done, OnLaunchAppDone)
IPC_MESSAGE_HANDLER(AppShimMsg_Hide, OnHide)
IPC_MESSAGE_HANDLER(AppShimMsg_RequestUserAttention, OnRequestUserAttention) IPC_MESSAGE_HANDLER(AppShimMsg_RequestUserAttention, OnRequestUserAttention)
IPC_MESSAGE_UNHANDLED(handled = false) IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP() IPC_END_MESSAGE_MAP()
...@@ -211,6 +215,10 @@ void AppShimController::OnLaunchAppDone(apps::AppShimLaunchResult result) { ...@@ -211,6 +215,10 @@ void AppShimController::OnLaunchAppDone(apps::AppShimLaunchResult result) {
launch_app_done_ = true; launch_app_done_ = true;
} }
void AppShimController::OnHide() {
[NSApp hide:nil];
}
void AppShimController::OnRequestUserAttention() { void AppShimController::OnRequestUserAttention() {
[NSApp requestUserAttention:NSInformationalRequest]; [NSApp requestUserAttention:NSInformationalRequest];
} }
......
...@@ -183,6 +183,18 @@ void ExtensionAppShimHandler::QuitAppForWindow(ShellWindow* shell_window) { ...@@ -183,6 +183,18 @@ void ExtensionAppShimHandler::QuitAppForWindow(ShellWindow* shell_window) {
} }
} }
void ExtensionAppShimHandler::HideAppForWindow(ShellWindow* shell_window) {
ExtensionAppShimHandler* handler =
g_browser_process->platform_part()->app_shim_host_manager()->
extension_app_shim_handler();
Profile* profile = shell_window->profile();
Host* host = handler->FindHost(profile, shell_window->extension_id());
if (host)
host->OnAppHide();
else
SetAppHidden(profile, shell_window->extension_id(), true);
}
// static // static
bool ExtensionAppShimHandler::RequestUserAttentionForWindow( bool ExtensionAppShimHandler::RequestUserAttentionForWindow(
ShellWindow* shell_window) { ShellWindow* shell_window) {
......
...@@ -69,6 +69,8 @@ class ExtensionAppShimHandler : public AppShimHandler, ...@@ -69,6 +69,8 @@ class ExtensionAppShimHandler : public AppShimHandler,
static void QuitAppForWindow(ShellWindow* shell_window); static void QuitAppForWindow(ShellWindow* shell_window);
static void HideAppForWindow(ShellWindow* shell_window);
// Brings the window to the front without showing it and instructs the shim to // Brings the window to the front without showing it and instructs the shim to
// request user attention. Returns false if there is no shim for this window. // request user attention. Returns false if there is no shim for this window.
static bool RequestUserAttentionForWindow(ShellWindow* shell_window); static bool RequestUserAttentionForWindow(ShellWindow* shell_window);
......
...@@ -102,6 +102,7 @@ class FakeHost : public apps::AppShimHandler::Host { ...@@ -102,6 +102,7 @@ class FakeHost : public apps::AppShimHandler::Host {
handler_->OnShimClose(this); handler_->OnShimClose(this);
++close_count_; ++close_count_;
} }
virtual void OnAppHide() OVERRIDE {}
virtual void OnAppRequestUserAttention() OVERRIDE {} virtual void OnAppRequestUserAttention() OVERRIDE {}
virtual base::FilePath GetProfilePath() const OVERRIDE { virtual base::FilePath GetProfilePath() const OVERRIDE {
return profile_path_; return profile_path_;
......
...@@ -244,6 +244,7 @@ ...@@ -244,6 +244,7 @@
#define IDC_VIEW_MENU 44000 // OSX only #define IDC_VIEW_MENU 44000 // OSX only
#define IDC_FILE_MENU 44001 // OSX only #define IDC_FILE_MENU 44001 // OSX only
#define IDC_CHROME_MENU 44002 // OSX only #define IDC_CHROME_MENU 44002 // OSX only
#define IDC_HIDE_APP 44003 // OSX only
#define IDC_HISTORY_MENU 46000 // OSX only #define IDC_HISTORY_MENU 46000 // OSX only
#define IDC_PROFILE_MAIN_MENU 46100 // OSX only #define IDC_PROFILE_MAIN_MENU 46100 // OSX only
#define IDC_INPUT_METHODS_MENU 46300 // Linux only #define IDC_INPUT_METHODS_MENU 46300 // Linux only
......
...@@ -172,6 +172,7 @@ ...@@ -172,6 +172,7 @@
<int key="NSMnemonicLoc">2147483647</int> <int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="353210768"/> <reference key="NSOnImage" ref="353210768"/>
<reference key="NSMixedImage" ref="549394948"/> <reference key="NSMixedImage" ref="549394948"/>
<int key="NSTag">44003</int>
</object> </object>
<object class="NSMenuItem" id="342932134"> <object class="NSMenuItem" id="342932134">
<reference key="NSMenu" ref="110575045"/> <reference key="NSMenu" ref="110575045"/>
......
...@@ -37,6 +37,7 @@ class AppListServiceMacBrowserTest : public InProcessBrowserTest, ...@@ -37,6 +37,7 @@ class AppListServiceMacBrowserTest : public InProcessBrowserTest,
virtual void OnAppClosed() OVERRIDE { virtual void OnAppClosed() OVERRIDE {
NOTREACHED(); NOTREACHED();
} }
virtual void OnAppHide() OVERRIDE {}
virtual void OnAppRequestUserAttention() OVERRIDE {} virtual void OnAppRequestUserAttention() OVERRIDE {}
virtual base::FilePath GetProfilePath() const OVERRIDE { virtual base::FilePath GetProfilePath() const OVERRIDE {
NOTREACHED(); // Currently unused in this test. NOTREACHED(); // Currently unused in this test.
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "base/mac/scoped_nsobject.h" #include "base/mac/scoped_nsobject.h"
@class DoppelgangerMenuItem;
// This controller listens to NSWindowDidBecomeMainNotification and // This controller listens to NSWindowDidBecomeMainNotification and
// NSWindowDidResignMainNotification and modifies the main menu bar to mimic a // NSWindowDidResignMainNotification and modifies the main menu bar to mimic a
// main menu for the app. When an app window becomes main, all Chrome menu items // main menu for the app. When an app window becomes main, all Chrome menu items
...@@ -19,8 +21,9 @@ ...@@ -19,8 +21,9 @@
@private @private
// The extension id of the currently focused packaged app. // The extension id of the currently focused packaged app.
base::scoped_nsobject<NSString> appId_; base::scoped_nsobject<NSString> appId_;
// A reference to the "Quit Chrome" menu item. // Items that need a doppelganger.
base::scoped_nsobject<NSMenuItem> chromeMenuQuitItem_; base::scoped_nsobject<DoppelgangerMenuItem> hideDoppelganger_;
base::scoped_nsobject<DoppelgangerMenuItem> quitDoppelganger_;
// Menu items for the currently focused packaged app. // Menu items for the currently focused packaged app.
base::scoped_nsobject<NSMenuItem> appMenuItem_; base::scoped_nsobject<NSMenuItem> appMenuItem_;
base::scoped_nsobject<NSMenuItem> fileMenuItem_; base::scoped_nsobject<NSMenuItem> fileMenuItem_;
......
...@@ -53,6 +53,83 @@ void AddDuplicateItem(NSMenuItem* top_level_item, ...@@ -53,6 +53,83 @@ void AddDuplicateItem(NSMenuItem* top_level_item,
} // namespace } // 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.
@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;
// Set the title using |resourceId_| and unset the source item's key equivalent.
- (void)enableForApp:(const extensions::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:@""
action:action
keyEquivalent:keyEquivalent]);
[menuItem_ setTarget:controller];
[menuItem_ setTag:itemTag];
resourceId_ = resourceId;
}
return self;
}
- (void)enableForApp:(const extensions::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 () @interface AppShimMenuController ()
// Construct the NSMenuItems for apps. // Construct the NSMenuItems for apps.
- (void)buildAppMenuItems; - (void)buildAppMenuItems;
...@@ -67,6 +144,8 @@ void AddDuplicateItem(NSMenuItem* top_level_item, ...@@ -67,6 +144,8 @@ void AddDuplicateItem(NSMenuItem* top_level_item,
- (void)removeMenuItems:(NSString*)appId; - (void)removeMenuItems:(NSString*)appId;
// If the currently focused window belongs to a platform app, quit the app. // If the currently focused window belongs to a platform app, quit the app.
- (void)quitCurrentPlatformApp; - (void)quitCurrentPlatformApp;
// If the currently focused window belongs to a platform app, hide the app.
- (void)hideCurrentPlatformApp;
@end @end
@implementation AppShimMenuController @implementation AppShimMenuController
...@@ -85,24 +164,32 @@ void AddDuplicateItem(NSMenuItem* top_level_item, ...@@ -85,24 +164,32 @@ void AddDuplicateItem(NSMenuItem* top_level_item,
} }
- (void)buildAppMenuItems { - (void)buildAppMenuItems {
// Find the "Quit Chrome" menu item. hideDoppelganger_.reset([[DoppelgangerMenuItem alloc]
chromeMenuQuitItem_.reset([GetItemByTag(IDC_CHROME_MENU, IDC_EXIT) retain]); initWithController:self
DCHECK(chromeMenuQuitItem_); 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"]);
// The app's menu. // The app's menu.
appMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@"" appMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@""
action:nil action:nil
keyEquivalent:@""]); keyEquivalent:@""]);
base::scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:@""]); base::scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:@""]);
[appMenu setAutoenablesItems:NO];
NSMenuItem* appMenuQuitItem =
[appMenu addItemWithTitle:@""
action:@selector(quitCurrentPlatformApp)
keyEquivalent:@"q"];
[appMenuQuitItem setKeyEquivalentModifierMask:
[chromeMenuQuitItem_ keyEquivalentModifierMask]];
[appMenuQuitItem setTarget:self];
[appMenuItem_ setSubmenu:appMenu]; [appMenuItem_ setSubmenu:appMenu];
[appMenu setAutoenablesItems:NO];
[appMenu addItem:[hideDoppelganger_ menuItem]];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItem:[quitDoppelganger_ menuItem]];
// File menu. // File menu.
fileMenuItem_.reset([NewTopLevelItemFrom(IDC_FILE_MENU) retain]); fileMenuItem_.reset([NewTopLevelItemFrom(IDC_FILE_MENU) retain]);
...@@ -177,17 +264,8 @@ void AddDuplicateItem(NSMenuItem* top_level_item, ...@@ -177,17 +264,8 @@ void AddDuplicateItem(NSMenuItem* top_level_item,
for (NSMenuItem* item in [mainMenu itemArray]) for (NSMenuItem* item in [mainMenu itemArray])
[item setHidden:YES]; [item setHidden:YES];
NSString* localizedQuitApp = [hideDoppelganger_ enableForApp:app];
l10n_util::GetNSStringF(IDS_EXIT_MAC, base::UTF8ToUTF16(app->name())); [quitDoppelganger_ enableForApp:app];
NSMenuItem* appMenuQuitItem = [[[appMenuItem_ submenu] itemArray] lastObject];
[appMenuQuitItem setTitle:localizedQuitApp];
// 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 appMenuQuitItem have a different action, we remove the
// key equivalent from the chromeMenuQuitItem and restore it later.
[chromeMenuQuitItem_ setKeyEquivalent:@""];
[appMenuItem_ setTitle:appId]; [appMenuItem_ setTitle:appId];
[[appMenuItem_ submenu] setTitle:title]; [[appMenuItem_ submenu] setTitle:title];
...@@ -214,10 +292,8 @@ void AddDuplicateItem(NSMenuItem* top_level_item, ...@@ -214,10 +292,8 @@ void AddDuplicateItem(NSMenuItem* top_level_item,
for (NSMenuItem* item in [mainMenu itemArray]) for (NSMenuItem* item in [mainMenu itemArray])
[item setHidden:NO]; [item setHidden:NO];
// Restore the keyboard shortcut to Chrome. This just needs to be set back to [hideDoppelganger_ disable];
// the original keyboard shortcut, regardless of any overrides in OSX. The [quitDoppelganger_ disable];
// overrides still work as they are based on the title of the menu item.
[chromeMenuQuitItem_ setKeyEquivalent:@"q"];
} }
- (void)quitCurrentPlatformApp { - (void)quitCurrentPlatformApp {
...@@ -228,4 +304,12 @@ void AddDuplicateItem(NSMenuItem* top_level_item, ...@@ -228,4 +304,12 @@ void AddDuplicateItem(NSMenuItem* top_level_item,
apps::ExtensionAppShimHandler::QuitAppForWindow(shellWindow); apps::ExtensionAppShimHandler::QuitAppForWindow(shellWindow);
} }
- (void)hideCurrentPlatformApp {
apps::ShellWindow* shellWindow =
apps::ShellWindowRegistry::GetShellWindowForNativeWindowAnyProfile(
[NSApp keyWindow]);
if (shellWindow)
apps::ExtensionAppShimHandler::HideAppForWindow(shellWindow);
}
@end @end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment