Upstreaming browser/ui/uikit_ui_util from iOS.
[chromium-blink-merge.git] / content / shell / browser / shell_mac.mm
blob0c741d99ce9b09304e72fc36f3cb89666f101c2e
1 // Copyright 2013 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.
5 #include "content/shell/browser/shell.h"
7 #include <algorithm>
9 #include "base/logging.h"
10 #import "base/mac/scoped_nsobject.h"
11 #include "base/mac/sdk_forward_declarations.h"
12 #include "base/strings/string_piece.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "content/public/browser/native_web_keyboard_event.h"
15 #include "content/public/browser/web_contents.h"
16 #include "content/shell/app/resource.h"
17 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
18 #include "url/gurl.h"
20 // Receives notification that the window is closing so that it can start the
21 // tear-down process. Is responsible for deleting itself when done.
22 @interface ContentShellWindowDelegate : NSObject<NSWindowDelegate> {
23  @private
24   content::Shell* shell_;
26 - (id)initWithShell:(content::Shell*)shell;
27 @end
29 @implementation ContentShellWindowDelegate
31 - (id)initWithShell:(content::Shell*)shell {
32   if ((self = [super init])) {
33     shell_ = shell;
34   }
35   return self;
38 // Called when the window is about to close. Perform the self-destruction
39 // sequence by getting rid of the shell and removing it and the window from
40 // the various global lists. By returning YES, we allow the window to be
41 // removed from the screen.
42 - (BOOL)windowShouldClose:(id)window {
43   [window autorelease];
44   delete shell_;
45   [self release];
47   return YES;
50 - (void)performAction:(id)sender {
51   shell_->ActionPerformed([sender tag]);
54 - (void)takeURLStringValueFrom:(id)sender {
55   shell_->URLEntered(base::SysNSStringToUTF8([sender stringValue]));
58 @end
60 @interface CrShellWindow : UnderlayOpenGLHostingWindow {
61  @private
62   content::Shell* shell_;
64 - (void)setShell:(content::Shell*)shell;
65 - (void)showDevTools:(id)sender;
66 @end
68 @implementation CrShellWindow
70 - (void)setShell:(content::Shell*)shell {
71   shell_ = shell;
74 - (void)showDevTools:(id)sender {
75   shell_->ShowDevTools();
78 @end
80 namespace {
82 NSString* kWindowTitle = @"Content Shell";
84 // Layout constants (in view coordinates)
85 const CGFloat kButtonWidth = 72;
86 const CGFloat kURLBarHeight = 24;
88 // The minimum size of the window's content (in view coordinates)
89 const CGFloat kMinimumWindowWidth = 400;
90 const CGFloat kMinimumWindowHeight = 300;
92 void MakeShellButton(NSRect* rect,
93                      NSString* title,
94                      NSView* parent,
95                      int control,
96                      NSView* target,
97                      NSString* key,
98                      NSUInteger modifier) {
99   base::scoped_nsobject<NSButton> button(
100       [[NSButton alloc] initWithFrame:*rect]);
101   [button setTitle:title];
102   [button setBezelStyle:NSSmallSquareBezelStyle];
103   [button setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)];
104   [button setTarget:target];
105   [button setAction:@selector(performAction:)];
106   [button setTag:control];
107   [button setKeyEquivalent:key];
108   [button setKeyEquivalentModifierMask:modifier];
109   [parent addSubview:button];
110   rect->origin.x += kButtonWidth;
113 }  // namespace
115 namespace content {
117 void Shell::PlatformInitialize(const gfx::Size& default_window_size) {
120 void Shell::PlatformExit() {
123 void Shell::PlatformCleanUp() {
126 void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) {
127   if (headless_)
128     return;
130   int id;
131   switch (control) {
132     case BACK_BUTTON:
133       id = IDC_NAV_BACK;
134       break;
135     case FORWARD_BUTTON:
136       id = IDC_NAV_FORWARD;
137       break;
138     case STOP_BUTTON:
139       id = IDC_NAV_STOP;
140       break;
141     default:
142       NOTREACHED() << "Unknown UI control";
143       return;
144   }
145   [[[window_ contentView] viewWithTag:id] setEnabled:is_enabled];
148 void Shell::PlatformSetAddressBarURL(const GURL& url) {
149   if (headless_)
150     return;
152   NSString* url_string = base::SysUTF8ToNSString(url.spec());
153   [url_edit_view_ setStringValue:url_string];
156 void Shell::PlatformSetIsLoading(bool loading) {
159 void Shell::PlatformCreateWindow(int width, int height) {
160   if (headless_) {
161     content_size_ = gfx::Size(width, height);
162     return;
163   }
165   NSRect initial_window_bounds =
166       NSMakeRect(0, 0, width, height + kURLBarHeight);
167   NSRect content_rect = initial_window_bounds;
168   NSUInteger style_mask = NSTitledWindowMask |
169                           NSClosableWindowMask |
170                           NSMiniaturizableWindowMask |
171                           NSResizableWindowMask;
172   CrShellWindow* window =
173       [[CrShellWindow alloc] initWithContentRect:content_rect
174                                        styleMask:style_mask
175                                          backing:NSBackingStoreBuffered
176                                            defer:NO];
177   window_ = window;
178   [window setShell:this];
179   [window_ setTitle:kWindowTitle];
180   NSView* content = [window_ contentView];
182   // If the window is allowed to get too small, it will wreck the view bindings.
183   NSSize min_size = NSMakeSize(kMinimumWindowWidth, kMinimumWindowHeight);
184   min_size = [content convertSize:min_size toView:nil];
185   // Note that this takes window coordinates.
186   [window_ setContentMinSize:min_size];
188   // Set the shell window to participate in Lion Fullscreen mode. Set
189   // Setting this flag has no effect on Snow Leopard or earlier.
190   NSUInteger collectionBehavior = [window_ collectionBehavior];
191   collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
192   [window_ setCollectionBehavior:collectionBehavior];
194   // Rely on the window delegate to clean us up rather than immediately
195   // releasing when the window gets closed. We use the delegate to do
196   // everything from the autorelease pool so the shell isn't on the stack
197   // during cleanup (ie, a window close from javascript).
198   [window_ setReleasedWhenClosed:NO];
200   // Create a window delegate to watch for when it's asked to go away. It will
201   // clean itself up so we don't need to hold a reference.
202   ContentShellWindowDelegate* delegate =
203       [[ContentShellWindowDelegate alloc] initWithShell:this];
204   [window_ setDelegate:delegate];
206   NSRect button_frame =
207       NSMakeRect(0, NSMaxY(initial_window_bounds) - kURLBarHeight,
208                  kButtonWidth, kURLBarHeight);
210   MakeShellButton(&button_frame, @"Back", content, IDC_NAV_BACK,
211                   (NSView*)delegate, @"[", NSCommandKeyMask);
212   MakeShellButton(&button_frame, @"Forward", content, IDC_NAV_FORWARD,
213                   (NSView*)delegate, @"]", NSCommandKeyMask);
214   MakeShellButton(&button_frame, @"Reload", content, IDC_NAV_RELOAD,
215                   (NSView*)delegate, @"r", NSCommandKeyMask);
216   MakeShellButton(&button_frame, @"Stop", content, IDC_NAV_STOP,
217                   (NSView*)delegate, @".", NSCommandKeyMask);
219   button_frame.size.width =
220       NSWidth(initial_window_bounds) - NSMinX(button_frame);
221   base::scoped_nsobject<NSTextField> url_edit_view(
222       [[NSTextField alloc] initWithFrame:button_frame]);
223   [content addSubview:url_edit_view];
224   [url_edit_view setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
225   [url_edit_view setTarget:delegate];
226   [url_edit_view setAction:@selector(takeURLStringValueFrom:)];
227   [[url_edit_view cell] setWraps:NO];
228   [[url_edit_view cell] setScrollable:YES];
229   url_edit_view_ = url_edit_view.get();
231   // show the window
232   [window_ makeKeyAndOrderFront:nil];
235 void Shell::PlatformSetContents() {
236   NSView* web_view = web_contents_->GetNativeView();
237   [web_view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
239   if (headless_) {
240     SizeTo(content_size_);
241     return;
242   }
244   NSView* content = [window_ contentView];
245   [content addSubview:web_view];
247   NSRect frame = [content bounds];
248   frame.size.height -= kURLBarHeight;
249   [web_view setFrame:frame];
250   [web_view setNeedsDisplay:YES];
253 void Shell::SizeTo(const gfx::Size& content_size) {
254   if (!headless_) {
255     NSRect frame = NSMakeRect(
256         0, 0, content_size.width(), content_size.height() + kURLBarHeight);
257     [window().contentView setFrame:frame];
258     return;
259   }
260   NSView* web_view = web_contents_->GetNativeView();
261   NSRect frame = NSMakeRect(0, 0, content_size.width(), content_size.height());
262   [web_view setFrame:frame];
265 void Shell::PlatformResizeSubViews() {
266   // Not needed; subviews are bound.
269 void Shell::PlatformSetTitle(const base::string16& title) {
270   if (headless_)
271     return;
273   NSString* title_string = base::SysUTF16ToNSString(title);
274   [window_ setTitle:title_string];
277 bool Shell::PlatformHandleContextMenu(
278     const content::ContextMenuParams& params) {
279   return false;
282 void Shell::Close() {
283   if (headless_)
284     delete this;
285   else
286     [window_ performClose:nil];
289 void Shell::ActionPerformed(int control) {
290   switch (control) {
291     case IDC_NAV_BACK:
292       GoBackOrForward(-1);
293       break;
294     case IDC_NAV_FORWARD:
295       GoBackOrForward(1);
296       break;
297     case IDC_NAV_RELOAD:
298       Reload();
299       break;
300     case IDC_NAV_STOP:
301       Stop();
302       break;
303   }
306 void Shell::URLEntered(std::string url_string) {
307   if (!url_string.empty()) {
308     GURL url(url_string);
309     if (!url.has_scheme())
310       url = GURL("http://" + url_string);
311     LoadURL(url);
312   }
315 void Shell::HandleKeyboardEvent(WebContents* source,
316                                 const NativeWebKeyboardEvent& event) {
317   if (event.skip_in_browser)
318     return;
320   // The event handling to get this strictly right is a tangle; cheat here a bit
321   // by just letting the menus have a chance at it.
322   if ([event.os_event type] == NSKeyDown) {
323     if (([event.os_event modifierFlags] & NSCommandKeyMask) &&
324         [[event.os_event characters] isEqual:@"l"]) {
325       [window_ makeFirstResponder:url_edit_view_];
326       return;
327     }
329     [[NSApp mainMenu] performKeyEquivalent:event.os_event];
330   }
333 }  // namespace content