1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/browser_window_utils.h" 6 7 #include <Carbon/Carbon.h> 8 9 #include "base/logging.h" 10 #include "chrome/app/chrome_command_ids.h" 11 #include "chrome/browser/global_keyboard_shortcuts_mac.h" 12 #include "chrome/browser/ui/browser.h" 13 #import "chrome/browser/ui/cocoa/chrome_event_processing_window.h" 14 #import "chrome/browser/ui/cocoa/nsmenuitem_additions.h" 15 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 16 #include "content/public/browser/native_web_keyboard_event.h" 17 18 using content::NativeWebKeyboardEvent; 19 20 @interface MenuWalker : NSObject 21 + (NSMenuItem*)itemForKeyEquivalent:(NSEvent*)key 22 menu:(NSMenu*)menu; 23 @end 24 25 @implementation MenuWalker 26 + (NSMenuItem*)itemForKeyEquivalent:(NSEvent*)key 27 menu:(NSMenu*)menu { 28 NSMenuItem* result = nil; 29 30 for (NSMenuItem* item in [menu itemArray]) { 31 NSMenu* submenu = [item submenu]; 32 if (submenu) { 33 if (submenu != [NSApp servicesMenu]) 34 result = [self itemForKeyEquivalent:key 35 menu:submenu]; 36 } else if ([item cr_firesForKeyEventIfEnabled:key]) { 37 result = item; 38 } 39 40 if (result) 41 break; 42 } 43 44 return result; 45 } 46 @end 47 48 @implementation BrowserWindowUtils 49 + (BOOL)shouldHandleKeyboardEvent:(const NativeWebKeyboardEvent&)event { 50 if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char) 51 return NO; 52 DCHECK(event.os_event != NULL); 53 return YES; 54 } 55 56 + (int)getCommandId:(const NativeWebKeyboardEvent&)event { 57 if ([event.os_event type] != NSKeyDown) 58 return -1; 59 60 // Look in menu. 61 NSMenuItem* item = [MenuWalker itemForKeyEquivalent:event.os_event 62 menu:[NSApp mainMenu]]; 63 64 if (item && [item action] == @selector(commandDispatch:) && [item tag] > 0) 65 return [item tag]; 66 67 // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items 68 // that do not correspond to IDC_ constants need no special treatment however, 69 // as they can't be blacklisted in 70 // |BrowserCommandController::IsReservedCommandOrKey()| anyhow. 71 if (item && [item action] == @selector(performClose:)) 72 return IDC_CLOSE_WINDOW; 73 74 // "Exit" doesn't use the |commandDispatch:| mechanism either. 75 if (item && [item action] == @selector(terminate:)) 76 return IDC_EXIT; 77 78 // Look in secondary keyboard shortcuts. 79 NSUInteger modifiers = [event.os_event modifierFlags]; 80 const bool cmdKey = (modifiers & NSCommandKeyMask) != 0; 81 const bool shiftKey = (modifiers & NSShiftKeyMask) != 0; 82 const bool cntrlKey = (modifiers & NSControlKeyMask) != 0; 83 const bool optKey = (modifiers & NSAlternateKeyMask) != 0; 84 const int keyCode = [event.os_event keyCode]; 85 const unichar keyChar = KeyCharacterForEvent(event.os_event); 86 87 int cmdNum = CommandForWindowKeyboardShortcut( 88 cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar); 89 if (cmdNum != -1) 90 return cmdNum; 91 92 cmdNum = CommandForBrowserKeyboardShortcut( 93 cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar); 94 if (cmdNum != -1) 95 return cmdNum; 96 97 return -1; 98 } 99 100 + (BOOL)handleKeyboardEvent:(NSEvent*)event 101 inWindow:(NSWindow*)window { 102 ChromeEventProcessingWindow* event_window = 103 static_cast<ChromeEventProcessingWindow*>(window); 104 DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]); 105 106 // Do not fire shortcuts on key up. 107 if ([event type] == NSKeyDown) { 108 // Send the event to the menu before sending it to the browser/window 109 // shortcut handling, so that if a user configures cmd-left to mean 110 // "previous tab", it takes precedence over the built-in "history back" 111 // binding. Other than that, the |-redispatchKeyEvent:| call would take care 112 // of invoking the original menu item shortcut as well. 113 114 if ([[NSApp mainMenu] performKeyEquivalent:event]) 115 return true; 116 117 if ([event_window handleExtraBrowserKeyboardShortcut:event]) 118 return true; 119 120 if ([event_window handleExtraWindowKeyboardShortcut:event]) 121 return true; 122 123 if ([event_window handleDelayedWindowKeyboardShortcut:event]) 124 return true; 125 } 126 127 return [event_window redispatchKeyEvent:event]; 128 } 129 130 + (NSString*)scheduleReplaceOldTitle:(NSString*)oldTitle 131 withNewTitle:(NSString*)newTitle 132 forWindow:(NSWindow*)window { 133 if (oldTitle) 134 [[NSRunLoop currentRunLoop] 135 cancelPerformSelector:@selector(setTitle:) 136 target:window 137 argument:oldTitle]; 138 139 [[NSRunLoop currentRunLoop] 140 performSelector:@selector(setTitle:) 141 target:window 142 argument:newTitle 143 order:0 144 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; 145 return [newTitle copy]; 146 } 147 148 // Our patterns want to be drawn from the upper left hand corner of the view. 149 // Cocoa wants to do it from the lower left of the window. 150 // 151 // Rephase our pattern to fit this view. Some other views (Tabs, Toolbar etc.) 152 // will phase their patterns relative to this so all the views look right. 153 // 154 // To line up the background pattern with the pattern in the browser window 155 // the background pattern for the tabs needs to be moved left by 5 pixels. 156 const CGFloat kPatternHorizontalOffset = -5; 157 // To match Windows and CrOS, have to offset vertically by 2 pixels. 158 // Without tab strip, offset an extra pixel (determined by experimentation). 159 const CGFloat kPatternVerticalOffset = 2; 160 const CGFloat kPatternVerticalOffsetNoTabStrip = 3; 161 162 163 + (NSPoint)themePatternPhaseFor:(NSView*)windowView 164 withTabStrip:(NSView*)tabStripView { 165 // When we have a tab strip, line up with the top of the tab, otherwise, 166 // line up with the top of the window. 167 if (!tabStripView) 168 return NSMakePoint(kPatternHorizontalOffset, 169 NSHeight([windowView bounds]) 170 + kPatternVerticalOffsetNoTabStrip); 171 172 NSRect tabStripViewWindowBounds = [tabStripView bounds]; 173 tabStripViewWindowBounds = 174 [tabStripView convertRect:tabStripViewWindowBounds 175 toView:windowView]; 176 return NSMakePoint(NSMinX(tabStripViewWindowBounds) 177 + kPatternHorizontalOffset, 178 NSMinY(tabStripViewWindowBounds) 179 + [TabStripController defaultTabHeight] 180 + kPatternVerticalOffset); 181 } 182 183 + (void)activateWindowForController:(NSWindowController*)controller { 184 // Per http://crbug.com/73779 and http://crbug.com/75223, we need this to 185 // properly activate windows if Chrome is not the active application. 186 [[controller window] makeKeyAndOrderFront:controller]; 187 ProcessSerialNumber psn; 188 GetCurrentProcess(&psn); 189 SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); 190 } 191 192 @end 193