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 "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h"
7 #include "apps/app_shim/extension_app_shim_handler_mac.h"
8 #include "base/command_line.h"
9 #include "base/mac/mac_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/cocoa/browser_window_utils.h"
13 #import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
14 #include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
15 #include "chrome/browser/ui/cocoa/extensions/extension_view_mac.h"
16 #import "chrome/browser/ui/cocoa/nsview_additions.h"
17 #include "chrome/common/chrome_switches.h"
18 #include "content/public/browser/native_web_keyboard_event.h"
19 #include "content/public/browser/render_widget_host_view.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_view.h"
22 #include "extensions/common/extension.h"
23 #include "third_party/skia/include/core/SkRegion.h"
24 #include "ui/gfx/skia_util.h"
26 // NOTE: State Before Update.
28 // Internal state, such as |is_maximized_|, must be set before the window
29 // state is changed so that it is accurate when e.g. a resize results in a call
30 // to |OnNativeWindowChanged|.
32 // NOTE: Maximize and Zoom.
34 // Zooming is implemented manually in order to implement maximize functionality
35 // and to support non resizable windows. The window will be resized explicitly
36 // in the |WindowWillZoom| call.
38 // Attempting maximize and restore functionality with non resizable windows
39 // using the native zoom method did not work, even with
40 // windowWillUseStandardFrame, as the window would not restore back to the
43 using apps::AppWindow;
45 @interface NSWindow (NSPrivateApis)
46 - (void)setBottomCornerRounded:(BOOL)rounded;
49 // Replicate specific 10.7 SDK declarations for building with prior SDKs.
50 #if !defined(MAC_OS_X_VERSION_10_7) || \
51 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
53 @interface NSWindow (LionSDKDeclarations)
54 - (void)toggleFullScreen:(id)sender;
58 NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
59 NSFullScreenWindowMask = 1 << 14
62 #endif // MAC_OS_X_VERSION_10_7
66 void SetFullScreenCollectionBehavior(NSWindow* window, bool allow_fullscreen) {
67 NSWindowCollectionBehavior behavior = [window collectionBehavior];
69 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
71 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
72 [window setCollectionBehavior:behavior];
75 void InitCollectionBehavior(NSWindow* window) {
76 // Since always-on-top windows have a higher window level
77 // than NSNormalWindowLevel, they will default to
78 // NSWindowCollectionBehaviorTransient. Set the value
79 // explicitly here to match normal windows.
80 NSWindowCollectionBehavior behavior = [window collectionBehavior];
81 behavior |= NSWindowCollectionBehaviorManaged;
82 [window setCollectionBehavior:behavior];
85 // Returns the level for windows that are configured to be always on top.
86 // This is not a constant because NSFloatingWindowLevel is a macro defined
87 // as a function call.
88 NSInteger AlwaysOnTopWindowLevel() {
89 return NSFloatingWindowLevel;
92 NSRect GfxToCocoaBounds(gfx::Rect bounds) {
93 typedef apps::AppWindow::BoundsSpecification BoundsSpecification;
95 NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame];
97 // If coordinates are unspecified, center window on primary screen.
98 if (bounds.x() == BoundsSpecification::kUnspecifiedPosition)
99 bounds.set_x(floor((NSWidth(main_screen_rect) - bounds.width()) / 2));
100 if (bounds.y() == BoundsSpecification::kUnspecifiedPosition)
101 bounds.set_y(floor((NSHeight(main_screen_rect) - bounds.height()) / 2));
103 // Convert to Mac coordinates.
104 NSRect cocoa_bounds = NSRectFromCGRect(bounds.ToCGRect());
105 cocoa_bounds.origin.y = NSHeight(main_screen_rect) - NSMaxY(cocoa_bounds);
109 // Return a vector of non-draggable regions that fill a window of size
110 // |width| by |height|, but leave gaps where the window should be draggable.
111 std::vector<gfx::Rect> CalculateNonDraggableRegions(
112 const std::vector<extensions::DraggableRegion>& regions,
115 scoped_ptr<SkRegion> draggable(
116 AppWindow::RawDraggableRegionsToSkRegion(regions));
117 scoped_ptr<SkRegion> non_draggable(new SkRegion);
118 non_draggable->op(0, 0, width, height, SkRegion::kUnion_Op);
119 non_draggable->op(*draggable, SkRegion::kDifference_Op);
121 std::vector<gfx::Rect> result;
122 for (SkRegion::Iterator it(*non_draggable); !it.done(); it.next()) {
123 result.push_back(gfx::SkIRectToRect(it.rect()));
130 @implementation NativeAppWindowController
132 @synthesize appWindow = appWindow_;
134 - (void)windowWillClose:(NSNotification*)notification {
136 appWindow_->WindowWillClose();
139 - (void)windowDidBecomeKey:(NSNotification*)notification {
141 appWindow_->WindowDidBecomeKey();
144 - (void)windowDidResignKey:(NSNotification*)notification {
146 appWindow_->WindowDidResignKey();
149 - (void)windowDidResize:(NSNotification*)notification {
151 appWindow_->WindowDidResize();
154 - (void)windowDidEndLiveResize:(NSNotification*)notification {
156 appWindow_->WindowDidFinishResize();
159 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
161 appWindow_->WindowDidFinishResize();
164 - (void)windowDidExitFullScreen:(NSNotification*)notification {
166 appWindow_->WindowDidFinishResize();
169 - (void)windowDidMove:(NSNotification*)notification {
171 appWindow_->WindowDidMove();
174 - (void)windowDidMiniaturize:(NSNotification*)notification {
176 appWindow_->WindowDidMiniaturize();
179 - (void)windowDidDeminiaturize:(NSNotification*)notification {
181 appWindow_->WindowDidDeminiaturize();
184 - (BOOL)windowShouldZoom:(NSWindow*)window
185 toFrame:(NSRect)newFrame {
187 appWindow_->WindowWillZoom();
188 return NO; // See top of file NOTE: Maximize and Zoom.
191 // Allow non resizable windows (without NSResizableWindowMask) to enter
192 // fullscreen by passing through the full size in willUseFullScreenContentSize.
193 - (NSSize)window:(NSWindow *)window
194 willUseFullScreenContentSize:(NSSize)proposedSize {
198 - (void)executeCommand:(int)command {
199 // No-op, swallow the event.
202 - (BOOL)handledByExtensionCommand:(NSEvent*)event {
204 return appWindow_->HandledByExtensionCommand(event);
210 // This is really a method on NSGrayFrame, so it should only be called on the
211 // view passed into -[NSWindow drawCustomFrameRect:forView:].
212 @interface NSView (PrivateMethods)
213 - (CGFloat)roundedCornerRadius;
216 // TODO(jamescook): Should these be AppNSWindow to match apps::AppWindow?
217 // http://crbug.com/344082
218 @interface ShellNSWindow : ChromeEventProcessingWindow
220 @implementation ShellNSWindow
223 @interface ShellCustomFrameNSWindow : ShellNSWindow
225 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view;
229 @implementation ShellCustomFrameNSWindow
231 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {
232 [[NSBezierPath bezierPathWithRect:rect] addClip];
233 [[NSColor clearColor] set];
237 CGFloat cornerRadius = 4.0;
238 if ([view respondsToSelector:@selector(roundedCornerRadius)])
239 cornerRadius = [view roundedCornerRadius];
240 [[NSBezierPath bezierPathWithRoundedRect:[view bounds]
242 yRadius:cornerRadius] addClip];
243 [[NSColor whiteColor] set];
249 @interface ShellFramelessNSWindow : ShellCustomFrameNSWindow
253 @implementation ShellFramelessNSWindow
255 + (NSRect)frameRectForContentRect:(NSRect)contentRect
256 styleMask:(NSUInteger)mask {
260 + (NSRect)contentRectForFrameRect:(NSRect)frameRect
261 styleMask:(NSUInteger)mask {
265 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
269 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
275 @interface ControlRegionView : NSView
278 @implementation ControlRegionView
280 - (BOOL)mouseDownCanMoveWindow {
284 - (NSView*)hitTest:(NSPoint)aPoint {
290 @interface NSView (WebContentsView)
291 - (void)setMouseDownCanMoveWindow:(BOOL)can_move;
294 NativeAppWindowCocoa::NativeAppWindowCocoa(
295 AppWindow* app_window,
296 const AppWindow::CreateParams& params)
297 : app_window_(app_window),
298 has_frame_(params.frame == AppWindow::FRAME_CHROME),
300 is_hidden_with_app_(false),
301 is_maximized_(false),
302 is_fullscreen_(false),
303 is_resizable_(params.resizable),
304 shows_resize_controls_(true),
305 shows_fullscreen_controls_(true),
306 attention_request_id_(0) {
307 Observe(web_contents());
309 base::scoped_nsobject<NSWindow> window;
312 bool should_use_native_frame =
313 CommandLine::ForCurrentProcess()->HasSwitch(
314 switches::kAppsUseNativeFrame);
315 window_class = should_use_native_frame ?
316 [ShellNSWindow class] : [ShellCustomFrameNSWindow class];
318 window_class = [ShellFramelessNSWindow class];
321 // Estimate the initial bounds of the window. Once the frame insets are known,
322 // the window bounds and constraints can be set precisely.
323 NSRect cocoa_bounds = GfxToCocoaBounds(
324 params.GetInitialWindowBounds(gfx::Insets()));
325 window.reset([[window_class alloc]
326 initWithContentRect:cocoa_bounds
327 styleMask:GetWindowStyleMask()
328 backing:NSBackingStoreBuffered
330 [window setTitle:base::SysUTF8ToNSString(extension()->name())];
331 [[window contentView] cr_setWantsLayer:YES];
333 if (base::mac::IsOSSnowLeopard() &&
334 [window respondsToSelector:@selector(setBottomCornerRounded:)])
335 [window setBottomCornerRounded:NO];
337 if (params.always_on_top)
338 [window setLevel:AlwaysOnTopWindowLevel()];
339 InitCollectionBehavior(window);
341 window_controller_.reset(
342 [[NativeAppWindowController alloc] initWithWindow:window.release()]);
344 NSView* view = web_contents()->GetView()->GetNativeView();
345 [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
347 // By default, the whole frameless window is not draggable.
349 gfx::Rect window_bounds(
350 0, 0, NSWidth(cocoa_bounds), NSHeight(cocoa_bounds));
351 system_drag_exclude_areas_.push_back(window_bounds);
356 [[window_controller_ window] setDelegate:window_controller_];
357 [window_controller_ setAppWindow:this];
359 // We can now compute the precise window bounds and constraints.
360 gfx::Insets insets = GetFrameInsets();
361 SetBounds(params.GetInitialWindowBounds(insets));
362 SetContentSizeConstraints(params.GetContentMinimumSize(insets),
363 params.GetContentMaximumSize(insets));
365 // Initialize |restored_bounds_|.
366 restored_bounds_ = [this->window() frame];
368 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa(
369 Profile::FromBrowserContext(app_window_->browser_context()),
371 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
375 NSUInteger NativeAppWindowCocoa::GetWindowStyleMask() const {
376 NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
377 NSMiniaturizableWindowMask;
378 if (shows_resize_controls_)
379 style_mask |= NSResizableWindowMask;
381 !CommandLine::ForCurrentProcess()->HasSwitch(
382 switches::kAppsUseNativeFrame)) {
383 style_mask |= NSTexturedBackgroundWindowMask;
388 void NativeAppWindowCocoa::InstallView() {
389 NSView* view = web_contents()->GetView()->GetNativeView();
391 [view setFrame:[[window() contentView] bounds]];
392 [[window() contentView] addSubview:view];
393 if (!shows_fullscreen_controls_)
394 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
395 if (!shows_resize_controls_)
396 [window() setShowsResizeIndicator:NO];
398 // TODO(jeremya): find a cleaner way to send this information to the
399 // WebContentsViewCocoa view.
401 respondsToSelector:@selector(setMouseDownCanMoveWindow:)]);
402 [view setMouseDownCanMoveWindow:YES];
404 NSView* frameView = [[window() contentView] superview];
405 [view setFrame:[frameView bounds]];
406 [frameView addSubview:view];
408 [[window() standardWindowButton:NSWindowZoomButton] setHidden:YES];
409 [[window() standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
410 [[window() standardWindowButton:NSWindowCloseButton] setHidden:YES];
412 // Some third-party OS X utilities check the zoom button's enabled state to
413 // determine whether to show custom UI on hover, so we disable it here to
414 // prevent them from doing so in a frameless app window.
415 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
417 InstallDraggableRegionViews();
421 void NativeAppWindowCocoa::UninstallView() {
422 NSView* view = web_contents()->GetView()->GetNativeView();
423 [view removeFromSuperview];
426 bool NativeAppWindowCocoa::IsActive() const {
427 return [window() isKeyWindow];
430 bool NativeAppWindowCocoa::IsMaximized() const {
431 return is_maximized_;
434 bool NativeAppWindowCocoa::IsMinimized() const {
435 return [window() isMiniaturized];
438 bool NativeAppWindowCocoa::IsFullscreen() const {
439 return is_fullscreen_;
442 void NativeAppWindowCocoa::SetFullscreen(int fullscreen_types) {
443 bool fullscreen = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
444 if (fullscreen == is_fullscreen_)
446 is_fullscreen_ = fullscreen;
448 if (base::mac::IsOSLionOrLater()) {
449 // If going fullscreen, but the window is constrained (fullscreen UI control
450 // is disabled), temporarily enable it. It will be disabled again on leaving
452 if (fullscreen && !shows_fullscreen_controls_)
453 SetFullScreenCollectionBehavior(window(), true);
454 [window() toggleFullScreen:nil];
458 DCHECK(base::mac::IsOSSnowLeopard());
461 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
462 bool did_fade_out = false;
463 CGDisplayFadeReservationToken token;
464 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) ==
467 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
468 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
471 // Since frameless windows insert the WebContentsView into the NSThemeFrame
472 // ([[window contentView] superview]), and since that NSThemeFrame is
473 // destroyed and recreated when we change the styleMask of the window, we
474 // need to remove the view from the window when we change the style, and
475 // add it back afterwards.
478 UpdateRestoredBounds();
479 [window() setStyleMask:NSBorderlessWindowMask];
480 [window() setFrame:[window()
481 frameRectForContentRect:[[window() screen] frame]]
483 base::mac::RequestFullScreen(base::mac::kFullScreenModeAutoHideAll);
485 base::mac::ReleaseFullScreen(base::mac::kFullScreenModeAutoHideAll);
486 [window() setStyleMask:GetWindowStyleMask()];
487 [window() setFrame:restored_bounds_ display:YES];
493 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
494 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
495 CGReleaseDisplayFadeReservation(token);
499 bool NativeAppWindowCocoa::IsFullscreenOrPending() const {
500 return is_fullscreen_;
503 bool NativeAppWindowCocoa::IsDetached() const {
507 gfx::NativeWindow NativeAppWindowCocoa::GetNativeWindow() {
511 gfx::Rect NativeAppWindowCocoa::GetRestoredBounds() const {
512 // Flip coordinates based on the primary screen.
513 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
514 NSRect frame = restored_bounds_;
515 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
516 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
520 ui::WindowShowState NativeAppWindowCocoa::GetRestoredState() const {
522 return ui::SHOW_STATE_MAXIMIZED;
524 return ui::SHOW_STATE_FULLSCREEN;
525 return ui::SHOW_STATE_NORMAL;
528 gfx::Rect NativeAppWindowCocoa::GetBounds() const {
529 // Flip coordinates based on the primary screen.
530 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
531 NSRect frame = [window() frame];
532 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
533 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
537 void NativeAppWindowCocoa::Show() {
540 if (is_hidden_with_app_) {
541 // If there is a shim to gently request attention, return here. Otherwise
542 // show the window as usual.
543 if (apps::ExtensionAppShimHandler::RequestUserAttentionForWindow(
549 [window_controller_ showWindow:nil];
553 void NativeAppWindowCocoa::ShowInactive() {
555 [window() orderFront:window_controller_];
558 void NativeAppWindowCocoa::Hide() {
560 HideWithoutMarkingHidden();
563 void NativeAppWindowCocoa::Close() {
564 [window() performClose:nil];
567 void NativeAppWindowCocoa::Activate() {
568 [BrowserWindowUtils activateWindowForController:window_controller_];
571 void NativeAppWindowCocoa::Deactivate() {
572 // TODO(jcivelli): http://crbug.com/51364 Implement me.
576 void NativeAppWindowCocoa::Maximize() {
577 UpdateRestoredBounds();
578 is_maximized_ = true; // See top of file NOTE: State Before Update.
579 [window() setFrame:[[window() screen] visibleFrame] display:YES animate:YES];
582 void NativeAppWindowCocoa::Minimize() {
583 [window() miniaturize:window_controller_];
586 void NativeAppWindowCocoa::Restore() {
587 DCHECK(!IsFullscreenOrPending()); // SetFullscreen, not Restore, expected.
590 is_maximized_ = false; // See top of file NOTE: State Before Update.
591 [window() setFrame:restored_bounds() display:YES animate:YES];
592 } else if (IsMinimized()) {
593 is_maximized_ = false; // See top of file NOTE: State Before Update.
594 [window() deminiaturize:window_controller_];
598 void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) {
599 // Enforce minimum/maximum bounds.
600 gfx::Rect checked_bounds = bounds;
602 NSSize min_size = [window() minSize];
603 if (bounds.width() < min_size.width)
604 checked_bounds.set_width(min_size.width);
605 if (bounds.height() < min_size.height)
606 checked_bounds.set_height(min_size.height);
607 NSSize max_size = [window() maxSize];
608 if (checked_bounds.width() > max_size.width)
609 checked_bounds.set_width(max_size.width);
610 if (checked_bounds.height() > max_size.height)
611 checked_bounds.set_height(max_size.height);
613 NSRect cocoa_bounds = GfxToCocoaBounds(checked_bounds);
614 [window() setFrame:cocoa_bounds display:YES];
615 // setFrame: without animate: does not trigger a windowDidEndLiveResize: so
617 WindowDidFinishResize();
620 void NativeAppWindowCocoa::UpdateWindowIcon() {
621 // TODO(junmin): implement.
624 void NativeAppWindowCocoa::UpdateWindowTitle() {
625 base::string16 title = app_window_->GetTitle();
626 [window() setTitle:base::SysUTF16ToNSString(title)];
629 void NativeAppWindowCocoa::UpdateBadgeIcon() {
630 // TODO(benwells): implement.
634 void NativeAppWindowCocoa::UpdateShape(scoped_ptr<SkRegion> region) {
638 void NativeAppWindowCocoa::UpdateDraggableRegions(
639 const std::vector<extensions::DraggableRegion>& regions) {
640 // Draggable region is not supported for non-frameless window.
644 // Draggable regions is implemented by having the whole web view draggable
645 // (mouseDownCanMoveWindow) and overlaying regions that are not draggable.
646 NSView* web_view = web_contents()->GetView()->GetNativeView();
647 system_drag_exclude_areas_ = CalculateNonDraggableRegions(
648 regions, NSWidth([web_view bounds]), NSHeight([web_view bounds]));
650 InstallDraggableRegionViews();
653 SkRegion* NativeAppWindowCocoa::GetDraggableRegion() {
657 void NativeAppWindowCocoa::HandleKeyboardEvent(
658 const content::NativeWebKeyboardEvent& event) {
659 if (event.skip_in_browser ||
660 event.type == content::NativeWebKeyboardEvent::Char) {
663 [window() redispatchKeyEvent:event.os_event];
666 void NativeAppWindowCocoa::InstallDraggableRegionViews() {
669 // All ControlRegionViews should be added as children of the WebContentsView,
670 // because WebContentsView will be removed and re-added when entering and
671 // leaving fullscreen mode.
672 NSView* webView = web_contents()->GetView()->GetNativeView();
673 NSInteger webViewHeight = NSHeight([webView bounds]);
675 // Remove all ControlRegionViews that are added last time.
676 // Note that [webView subviews] returns the view's mutable internal array and
677 // it should be copied to avoid mutating the original array while enumerating
679 base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
680 for (NSView* subview in subviews.get())
681 if ([subview isKindOfClass:[ControlRegionView class]])
682 [subview removeFromSuperview];
684 // Create and add ControlRegionView for each region that needs to be excluded
685 // from the dragging.
686 for (std::vector<gfx::Rect>::const_iterator iter =
687 system_drag_exclude_areas_.begin();
688 iter != system_drag_exclude_areas_.end();
690 base::scoped_nsobject<NSView> controlRegion(
691 [[ControlRegionView alloc] initWithFrame:NSZeroRect]);
692 [controlRegion setFrame:NSMakeRect(iter->x(),
693 webViewHeight - iter->bottom(),
696 [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
697 [webView addSubview:controlRegion];
701 void NativeAppWindowCocoa::FlashFrame(bool flash) {
703 attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
705 [NSApp cancelUserAttentionRequest:attention_request_id_];
706 attention_request_id_ = 0;
710 bool NativeAppWindowCocoa::IsAlwaysOnTop() const {
711 return [window() level] == AlwaysOnTopWindowLevel();
714 void NativeAppWindowCocoa::RenderViewCreated(content::RenderViewHost* rvh) {
716 web_contents()->GetView()->RestoreFocus();
719 bool NativeAppWindowCocoa::IsFrameless() const {
723 bool NativeAppWindowCocoa::HasFrameColor() const {
724 // TODO(benwells): Implement this.
728 SkColor NativeAppWindowCocoa::FrameColor() const {
729 // TODO(benwells): Implement this.
733 gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const {
735 return gfx::Insets();
737 // Flip the coordinates based on the main screen.
738 NSInteger screen_height =
739 NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
741 NSRect frame_nsrect = [window() frame];
742 gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect));
743 frame_rect.set_y(screen_height - NSMaxY(frame_nsrect));
745 NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect];
746 gfx::Rect content_rect(NSRectToCGRect(content_nsrect));
747 content_rect.set_y(screen_height - NSMaxY(content_nsrect));
749 return frame_rect.InsetsFrom(content_rect);
752 gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
757 gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) {
762 gfx::Size NativeAppWindowCocoa::GetMaximumDialogSize() {
767 void NativeAppWindowCocoa::AddObserver(
768 web_modal::ModalDialogHostObserver* observer) {
772 void NativeAppWindowCocoa::RemoveObserver(
773 web_modal::ModalDialogHostObserver* observer) {
777 void NativeAppWindowCocoa::WindowWillClose() {
778 [window_controller_ setAppWindow:NULL];
779 app_window_->OnNativeWindowChanged();
780 app_window_->OnNativeClose();
783 void NativeAppWindowCocoa::WindowDidBecomeKey() {
784 content::RenderWidgetHostView* rwhv =
785 web_contents()->GetRenderWidgetHostView();
787 rwhv->SetActive(true);
788 app_window_->OnNativeWindowActivated();
790 web_contents()->GetView()->RestoreFocus();
793 void NativeAppWindowCocoa::WindowDidResignKey() {
794 // If our app is still active and we're still the key window, ignore this
795 // message, since it just means that a menu extra (on the "system status bar")
796 // was activated; we'll get another |-windowDidResignKey| if we ever really
797 // lose key window status.
798 if ([NSApp isActive] && ([NSApp keyWindow] == window()))
801 web_contents()->GetView()->StoreFocus();
803 content::RenderWidgetHostView* rwhv =
804 web_contents()->GetRenderWidgetHostView();
806 rwhv->SetActive(false);
809 void NativeAppWindowCocoa::WindowDidFinishResize() {
810 // Update |is_maximized_| if needed:
811 // - Exit maximized state if resized.
812 // - Consider us maximized if resize places us back to maximized location.
813 // This happens when returning from fullscreen.
814 NSRect frame = [window() frame];
815 NSRect screen = [[window() screen] visibleFrame];
816 if (!NSEqualSizes(frame.size, screen.size))
817 is_maximized_ = false;
818 else if (NSEqualPoints(frame.origin, screen.origin))
819 is_maximized_ = true;
821 // Update |is_fullscreen_| if needed.
822 is_fullscreen_ = ([window() styleMask] & NSFullScreenWindowMask) != 0;
823 // If not fullscreen but the window is constrained, disable the fullscreen UI
825 if (!is_fullscreen_ && !shows_fullscreen_controls_)
826 SetFullScreenCollectionBehavior(window(), false);
828 UpdateRestoredBounds();
831 void NativeAppWindowCocoa::WindowDidResize() {
832 app_window_->OnNativeWindowChanged();
835 void NativeAppWindowCocoa::WindowDidMove() {
836 UpdateRestoredBounds();
837 app_window_->OnNativeWindowChanged();
840 void NativeAppWindowCocoa::WindowDidMiniaturize() {
841 app_window_->OnNativeWindowChanged();
844 void NativeAppWindowCocoa::WindowDidDeminiaturize() {
845 app_window_->OnNativeWindowChanged();
848 void NativeAppWindowCocoa::WindowWillZoom() {
849 // See top of file NOTE: Maximize and Zoom.
856 bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) {
857 return extension_keybinding_registry_->ProcessKeyEvent(
858 content::NativeWebKeyboardEvent(event));
861 void NativeAppWindowCocoa::ShowWithApp() {
862 is_hidden_with_app_ = false;
867 void NativeAppWindowCocoa::HideWithApp() {
868 is_hidden_with_app_ = true;
869 HideWithoutMarkingHidden();
872 void NativeAppWindowCocoa::UpdateShelfMenu() {
873 // TODO(tmdiep): To be implemented for Mac.
877 gfx::Size NativeAppWindowCocoa::GetContentMinimumSize() const {
878 return size_constraints_.GetMinimumSize();
881 gfx::Size NativeAppWindowCocoa::GetContentMaximumSize() const {
882 return size_constraints_.GetMaximumSize();
885 void NativeAppWindowCocoa::SetContentSizeConstraints(
886 const gfx::Size& min_size, const gfx::Size& max_size) {
887 // Update the size constraints.
888 size_constraints_.set_minimum_size(min_size);
889 size_constraints_.set_maximum_size(max_size);
891 gfx::Size minimum_size = size_constraints_.GetMinimumSize();
892 [window() setContentMinSize:NSMakeSize(minimum_size.width(),
893 minimum_size.height())];
895 gfx::Size maximum_size = size_constraints_.GetMaximumSize();
896 const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize;
897 CGFloat max_width = maximum_size.width() == kUnboundedSize ?
898 CGFLOAT_MAX : maximum_size.width();
899 CGFloat max_height = maximum_size.height() == kUnboundedSize ?
900 CGFLOAT_MAX : maximum_size.height();
901 [window() setContentMaxSize:NSMakeSize(max_width, max_height)];
903 // Update the window controls.
904 shows_resize_controls_ =
905 is_resizable_ && !size_constraints_.HasFixedSize();
906 shows_fullscreen_controls_ =
907 is_resizable_ && !size_constraints_.HasMaximumSize();
909 if (!is_fullscreen_) {
910 [window() setStyleMask:GetWindowStyleMask()];
912 // Set the window to participate in Lion Fullscreen mode. Setting this flag
913 // has no effect on Snow Leopard or earlier. UI controls for fullscreen are
914 // only shown for apps that have unbounded size.
915 SetFullScreenCollectionBehavior(window(), shows_fullscreen_controls_);
919 [window() setShowsResizeIndicator:shows_resize_controls_];
920 [[window() standardWindowButton:NSWindowZoomButton]
921 setEnabled:shows_fullscreen_controls_];
925 void NativeAppWindowCocoa::SetAlwaysOnTop(bool always_on_top) {
926 [window() setLevel:(always_on_top ? AlwaysOnTopWindowLevel() :
927 NSNormalWindowLevel)];
930 NativeAppWindowCocoa::~NativeAppWindowCocoa() {
933 ShellNSWindow* NativeAppWindowCocoa::window() const {
934 NSWindow* window = [window_controller_ window];
935 CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]);
936 return static_cast<ShellNSWindow*>(window);
939 void NativeAppWindowCocoa::UpdateRestoredBounds() {
940 if (IsRestored(*this))
941 restored_bounds_ = [window() frame];
944 void NativeAppWindowCocoa::HideWithoutMarkingHidden() {
945 [window() orderOut:window_controller_];