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 // The titlebar/tabstrip header on the mac is slightly smaller than on Windows. 149 // There is also no window frame to the left and right of the web contents on 150 // mac. 151 // To keep: 152 // - the window background pattern (IDR_THEME_FRAME.*) lined up vertically with 153 // the tab and toolbar patterns 154 // - the toolbar pattern lined up horizontally with the NTP background. 155 // we have to shift the pattern slightly, rather than drawing from the top left 156 // corner of the frame / tabstrip. The offsets below were empirically determined 157 // in order to line these patterns up. 158 // 159 // This will make the themes look slightly different than in Windows/Linux 160 // because of the differing heights between window top and tab top, but this has 161 // been approved by UI. 162 const CGFloat kPatternHorizontalOffset = -5; 163 // Without tab strip, offset an extra pixel (determined by experimentation). 164 const CGFloat kPatternVerticalOffset = 2; 165 const CGFloat kPatternVerticalOffsetNoTabStrip = 3; 166 167 + (NSPoint)themeImagePositionFor:(NSView*)windowView 168 withTabStrip:(NSView*)tabStripView 169 alignment:(ThemeImageAlignment)alignment { 170 if (!tabStripView) { 171 return NSMakePoint(kPatternHorizontalOffset, 172 NSHeight([windowView bounds]) + 173 kPatternVerticalOffsetNoTabStrip); 174 } 175 176 NSPoint position = 177 [BrowserWindowUtils themeImagePositionInTabStripCoords:tabStripView 178 alignment:alignment]; 179 return [tabStripView convertPoint:position toView:windowView]; 180 } 181 182 + (NSPoint)themeImagePositionInTabStripCoords:(NSView*)tabStripView 183 alignment:(ThemeImageAlignment)alignment { 184 DCHECK(tabStripView); 185 186 if (alignment == THEME_IMAGE_ALIGN_WITH_TAB_STRIP) { 187 // The theme image is lined up with the top of the tab which is below the 188 // top of the tab strip. 189 return NSMakePoint(kPatternHorizontalOffset, 190 [TabStripController defaultTabHeight] + 191 kPatternVerticalOffset); 192 } 193 // The theme image is lined up with the top of the tab strip (as opposed to 194 // the top of the tab above). This is the same as lining up with the top of 195 // the window's root view when not in presentation mode. 196 return NSMakePoint(kPatternHorizontalOffset, 197 NSHeight([tabStripView bounds]) + 198 kPatternVerticalOffsetNoTabStrip); 199 } 200 201 + (void)activateWindowForController:(NSWindowController*)controller { 202 // Per http://crbug.com/73779 and http://crbug.com/75223, we need this to 203 // properly activate windows if Chrome is not the active application. 204 [[controller window] makeKeyAndOrderFront:controller]; 205 ProcessSerialNumber psn; 206 GetCurrentProcess(&psn); 207 SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); 208 } 209 210 @end 211