Home | History | Annotate | Download | only in cocoa
      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