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 18using 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. 162const CGFloat kPatternHorizontalOffset = -5; 163// Without tab strip, offset an extra pixel (determined by experimentation). 164const CGFloat kPatternVerticalOffset = 2; 165const 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