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