blob: 4cebcd9907b3457999dc57df0dc53c5839a2f2ec [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h"
#include "apps/app_shim/extension_app_shim_handler_mac.h"
#include "base/command_line.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/cocoa/browser_window_utils.h"
#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
#include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
#include "chrome/browser/ui/cocoa/extensions/extension_view_mac.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "third_party/skia/include/core/SkRegion.h"
// NOTE: State Before Update.
//
// Internal state, such as |is_maximized_|, must be set before the window
// state is changed so that it is accurate when e.g. a resize results in a call
// to |OnNativeWindowChanged|.
// NOTE: Maximize and Zoom.
//
// Zooming is implemented manually in order to implement maximize functionality
// and to support non resizable windows. The window will be resized explicitly
// in the |WindowWillZoom| call.
//
// Attempting maximize and restore functionality with non resizable windows
// using the native zoom method did not work, even with
// windowWillUseStandardFrame, as the window would not restore back to the
// desired size.
using apps::ShellWindow;
@interface NSWindow (NSPrivateApis)
- (void)setBottomCornerRounded:(BOOL)rounded;
@end
// Replicate specific 10.7 SDK declarations for building with prior SDKs.
#if !defined(MAC_OS_X_VERSION_10_7) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
@interface NSWindow (LionSDKDeclarations)
- (void)toggleFullScreen:(id)sender;
@end
enum {
NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
NSFullScreenWindowMask = 1 << 14
};
#endif // MAC_OS_X_VERSION_10_7
namespace {
// When gfx::Size is used as a min/max size, a zero represents an unbounded
// component. This method checks whether either component is specified.
// Note we can't use gfx::Size::IsEmpty as it returns true if either width or
// height is zero.
bool IsBoundedSize(const gfx::Size& size) {
return size.width() != 0 || size.height() != 0;
}
void SetFullScreenCollectionBehavior(NSWindow* window, bool allow_fullscreen) {
NSWindowCollectionBehavior behavior = [window collectionBehavior];
if (allow_fullscreen)
behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
else
behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
[window setCollectionBehavior:behavior];
}
// Returns the level for windows that are configured to be always on top.
// This is not a constant because NSFloatingWindowLevel is a macro defined
// as a function call.
NSInteger AlwaysOnTopWindowLevel() {
return NSFloatingWindowLevel;
}
} // namespace
@implementation NativeAppWindowController
@synthesize appWindow = appWindow_;
- (void)windowWillClose:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowWillClose();
}
- (void)windowDidBecomeKey:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidBecomeKey();
}
- (void)windowDidResignKey:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidResignKey();
}
- (void)windowDidResize:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidResize();
}
- (void)windowDidEndLiveResize:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidFinishResize();
}
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidFinishResize();
}
- (void)windowDidExitFullScreen:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidFinishResize();
}
- (void)windowDidMove:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidMove();
}
- (void)windowDidMiniaturize:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidMiniaturize();
}
- (void)windowDidDeminiaturize:(NSNotification*)notification {
if (appWindow_)
appWindow_->WindowDidDeminiaturize();
}
- (BOOL)windowShouldZoom:(NSWindow*)window
toFrame:(NSRect)newFrame {
if (appWindow_)
appWindow_->WindowWillZoom();
return NO; // See top of file NOTE: Maximize and Zoom.
}
// Allow non resizable windows (without NSResizableWindowMask) to enter
// fullscreen by passing through the full size in willUseFullScreenContentSize.
- (NSSize)window:(NSWindow *)window
willUseFullScreenContentSize:(NSSize)proposedSize {
return proposedSize;
}
- (void)executeCommand:(int)command {
// No-op, swallow the event.
}
- (BOOL)handledByExtensionCommand:(NSEvent*)event {
if (appWindow_)
return appWindow_->HandledByExtensionCommand(event);
return NO;
}
@end
// This is really a method on NSGrayFrame, so it should only be called on the
// view passed into -[NSWindow drawCustomFrameRect:forView:].
@interface NSView (PrivateMethods)
- (CGFloat)roundedCornerRadius;
@end
@interface ShellNSWindow : ChromeEventProcessingWindow
@end
@implementation ShellNSWindow
@end
@interface ShellCustomFrameNSWindow : ShellNSWindow
- (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view;
@end
@implementation ShellCustomFrameNSWindow
- (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {
[[NSBezierPath bezierPathWithRect:rect] addClip];
[[NSColor clearColor] set];
NSRectFill(rect);
// Set up our clip.
CGFloat cornerRadius = 4.0;
if ([view respondsToSelector:@selector(roundedCornerRadius)])
cornerRadius = [view roundedCornerRadius];
[[NSBezierPath bezierPathWithRoundedRect:[view bounds]
xRadius:cornerRadius
yRadius:cornerRadius] addClip];
[[NSColor whiteColor] set];
NSRectFill(rect);
}
@end
@interface ShellFramelessNSWindow : ShellCustomFrameNSWindow
@end
@implementation ShellFramelessNSWindow
+ (NSRect)frameRectForContentRect:(NSRect)contentRect
styleMask:(NSUInteger)mask {
return contentRect;
}
+ (NSRect)contentRectForFrameRect:(NSRect)frameRect
styleMask:(NSUInteger)mask {
return frameRect;
}
- (NSRect)frameRectForContentRect:(NSRect)contentRect {
return contentRect;
}
- (NSRect)contentRectForFrameRect:(NSRect)frameRect {
return frameRect;
}
@end
@interface ControlRegionView : NSView {
@private
NativeAppWindowCocoa* appWindow_; // Weak; owns self.
}
@end
@implementation ControlRegionView
- (id)initWithAppWindow:(NativeAppWindowCocoa*)appWindow {
if ((self = [super init]))
appWindow_ = appWindow;
return self;
}
- (BOOL)mouseDownCanMoveWindow {
return NO;
}
- (NSView*)hitTest:(NSPoint)aPoint {
if (appWindow_->use_system_drag() ||
!appWindow_->IsWithinDraggableRegion(aPoint)) {
return nil;
}
return self;
}
- (void)mouseDown:(NSEvent*)event {
appWindow_->HandleMouseEvent(event);
}
- (void)mouseDragged:(NSEvent*)event {
appWindow_->HandleMouseEvent(event);
}
@end
@interface NSView (WebContentsView)
- (void)setMouseDownCanMoveWindow:(BOOL)can_move;
@end
NativeAppWindowCocoa::NativeAppWindowCocoa(
ShellWindow* shell_window,
const ShellWindow::CreateParams& params)
: shell_window_(shell_window),
has_frame_(params.frame == ShellWindow::FRAME_CHROME),
is_hidden_(false),
is_hidden_with_app_(false),
is_maximized_(false),
is_fullscreen_(false),
attention_request_id_(0),
use_system_drag_(true) {
Observe(web_contents());
// Flip coordinates based on the primary screen.
NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame];
NSRect cocoa_bounds = NSMakeRect(params.bounds.x(),
NSHeight(main_screen_rect) - params.bounds.y() - params.bounds.height(),
params.bounds.width(), params.bounds.height());
// If coordinates are < 0, center window on primary screen.
if (params.bounds.x() == INT_MIN) {
cocoa_bounds.origin.x =
floor((NSWidth(main_screen_rect) - NSWidth(cocoa_bounds)) / 2);
}
if (params.bounds.y() == INT_MIN) {
cocoa_bounds.origin.y =
floor((NSHeight(main_screen_rect) - NSHeight(cocoa_bounds)) / 2);
}
// Initialize |restored_bounds_| after |cocoa_bounds| have been sanitized.
restored_bounds_ = cocoa_bounds;
base::scoped_nsobject<NSWindow> window;
Class window_class;
if (has_frame_) {
bool should_use_native_frame =
CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAppsUseNativeFrame);
window_class = should_use_native_frame ?
[ShellNSWindow class] : [ShellCustomFrameNSWindow class];
} else {
window_class = [ShellFramelessNSWindow class];
}
min_size_ = params.minimum_size;
max_size_ = params.maximum_size;
shows_resize_controls_ = params.resizable &&
(min_size_.IsEmpty() || max_size_.IsEmpty() || min_size_ != max_size_);
shows_fullscreen_controls_ = params.resizable && !IsBoundedSize(max_size_);
window.reset([[window_class alloc]
initWithContentRect:cocoa_bounds
styleMask:GetWindowStyleMask()
backing:NSBackingStoreBuffered
defer:NO]);
[window setTitle:base::SysUTF8ToNSString(extension()->name())];
if (IsBoundedSize(min_size_)) {
[window setContentMinSize:
NSMakeSize(min_size_.width(), min_size_.height())];
}
if (IsBoundedSize(max_size_)) {
CGFloat max_width = max_size_.width() ? max_size_.width() : CGFLOAT_MAX;
CGFloat max_height = max_size_.height() ? max_size_.height() : CGFLOAT_MAX;
[window setContentMaxSize:NSMakeSize(max_width, max_height)];
}
if (base::mac::IsOSSnowLeopard() &&
[window respondsToSelector:@selector(setBottomCornerRounded:)])
[window setBottomCornerRounded:NO];
if (params.always_on_top)
[window setLevel:AlwaysOnTopWindowLevel()];
// Set the window to participate in Lion Fullscreen mode. Setting this flag
// has no effect on Snow Leopard or earlier. UI controls for fullscreen are
// only shown for apps that have unbounded size.
if (shows_fullscreen_controls_)
SetFullScreenCollectionBehavior(window, true);
window_controller_.reset(
[[NativeAppWindowController alloc] initWithWindow:window.release()]);
NSView* view = web_contents()->GetView()->GetNativeView();
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
// By default, the whole frameless window is not draggable.
if (!has_frame_) {
gfx::Rect window_bounds(
0, 0, NSWidth(cocoa_bounds), NSHeight(cocoa_bounds));
system_drag_exclude_areas_.push_back(window_bounds);
}
InstallView();
[[window_controller_ window] setDelegate:window_controller_];
[window_controller_ setAppWindow:this];
extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa(
shell_window_->profile(),
window,
extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
shell_window));
}
NSUInteger NativeAppWindowCocoa::GetWindowStyleMask() const {
NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask;
if (shows_resize_controls_)
style_mask |= NSResizableWindowMask;
if (!has_frame_ ||
!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAppsUseNativeFrame)) {
style_mask |= NSTexturedBackgroundWindowMask;
}
return style_mask;
}
void NativeAppWindowCocoa::InstallView() {
NSView* view = web_contents()->GetView()->GetNativeView();
if (has_frame_) {
[view setFrame:[[window() contentView] bounds]];
[[window() contentView] addSubview:view];
if (!shows_fullscreen_controls_)
[[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
if (!shows_resize_controls_)
[window() setShowsResizeIndicator:NO];
} else {
// TODO(jeremya): find a cleaner way to send this information to the
// WebContentsViewCocoa view.
DCHECK([view
respondsToSelector:@selector(setMouseDownCanMoveWindow:)]);
[view setMouseDownCanMoveWindow:YES];
NSView* frameView = [[window() contentView] superview];
[view setFrame:[frameView bounds]];
[frameView addSubview:view];
[[window() standardWindowButton:NSWindowZoomButton] setHidden:YES];
[[window() standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
[[window() standardWindowButton:NSWindowCloseButton] setHidden:YES];
// Some third-party OS X utilities check the zoom button's enabled state to
// determine whether to show custom UI on hover, so we disable it here to
// prevent them from doing so in a frameless app window.
[[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
InstallDraggableRegionViews();
}
}
void NativeAppWindowCocoa::UninstallView() {
NSView* view = web_contents()->GetView()->GetNativeView();
[view removeFromSuperview];
}
bool NativeAppWindowCocoa::IsActive() const {
return [window() isKeyWindow];
}
bool NativeAppWindowCocoa::IsMaximized() const {
return is_maximized_;
}
bool NativeAppWindowCocoa::IsMinimized() const {
return [window() isMiniaturized];
}
bool NativeAppWindowCocoa::IsFullscreen() const {
return is_fullscreen_;
}
void NativeAppWindowCocoa::SetFullscreen(bool fullscreen) {
if (fullscreen == is_fullscreen_)
return;
is_fullscreen_ = fullscreen;
if (base::mac::IsOSLionOrLater()) {
// If going fullscreen, but the window is constrained (fullscreen UI control
// is disabled), temporarily enable it. It will be disabled again on leaving
// fullscreen.
if (fullscreen && !shows_fullscreen_controls_)
SetFullScreenCollectionBehavior(window(), true);
[window() toggleFullScreen:nil];
return;
}
DCHECK(base::mac::IsOSSnowLeopard());
// Fade to black.
const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
bool did_fade_out = false;
CGDisplayFadeReservationToken token;
if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) ==
kCGErrorSuccess) {
did_fade_out = true;
CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
}
// Since frameless windows insert the WebContentsView into the NSThemeFrame
// ([[window contentView] superview]), and since that NSThemeFrame is
// destroyed and recreated when we change the styleMask of the window, we
// need to remove the view from the window when we change the style, and
// add it back afterwards.
UninstallView();
if (fullscreen) {
UpdateRestoredBounds();
[window() setStyleMask:NSBorderlessWindowMask];
[window() setFrame:[window()
frameRectForContentRect:[[window() screen] frame]]
display:YES];
base::mac::RequestFullScreen(base::mac::kFullScreenModeAutoHideAll);
} else {
base::mac::ReleaseFullScreen(base::mac::kFullScreenModeAutoHideAll);
[window() setStyleMask:GetWindowStyleMask()];
[window() setFrame:restored_bounds_ display:YES];
}
InstallView();
// Fade back in.
if (did_fade_out) {
CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
CGReleaseDisplayFadeReservation(token);
}
}
bool NativeAppWindowCocoa::IsFullscreenOrPending() const {
return is_fullscreen_;
}
bool NativeAppWindowCocoa::IsDetached() const {
return false;
}
gfx::NativeWindow NativeAppWindowCocoa::GetNativeWindow() {
return window();
}
gfx::Rect NativeAppWindowCocoa::GetRestoredBounds() const {
// Flip coordinates based on the primary screen.
NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
NSRect frame = restored_bounds_;
gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
return bounds;
}
ui::WindowShowState NativeAppWindowCocoa::GetRestoredState() const {
if (IsMaximized())
return ui::SHOW_STATE_MAXIMIZED;
if (IsFullscreen())
return ui::SHOW_STATE_FULLSCREEN;
return ui::SHOW_STATE_NORMAL;
}
gfx::Rect NativeAppWindowCocoa::GetBounds() const {
// Flip coordinates based on the primary screen.
NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
NSRect frame = [window() frame];
gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
return bounds;
}
void NativeAppWindowCocoa::Show() {
is_hidden_ = false;
if (is_hidden_with_app_) {
// If there is a shim to gently request attention, return here. Otherwise
// show the window as usual.
if (apps::ExtensionAppShimHandler::RequestUserAttentionForWindow(
shell_window_)) {
return;
}
}
[window_controller_ showWindow:nil];
[window() makeKeyAndOrderFront:window_controller_];
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::ShowInactive() {
is_hidden_ = false;
[window() orderFront:window_controller_];
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::Hide() {
is_hidden_ = true;
HideWithoutMarkingHidden();
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::Close() {
[window() performClose:nil];
}
void NativeAppWindowCocoa::Activate() {
[BrowserWindowUtils activateWindowForController:window_controller_];
}
void NativeAppWindowCocoa::Deactivate() {
// TODO(jcivelli): http://crbug.com/51364 Implement me.
NOTIMPLEMENTED();
}
void NativeAppWindowCocoa::Maximize() {
UpdateRestoredBounds();
is_maximized_ = true; // See top of file NOTE: State Before Update.
[window() setFrame:[[window() screen] visibleFrame] display:YES animate:YES];
}
void NativeAppWindowCocoa::Minimize() {
[window() miniaturize:window_controller_];
}
void NativeAppWindowCocoa::Restore() {
DCHECK(!IsFullscreenOrPending()); // SetFullscreen, not Restore, expected.
if (IsMaximized()) {
is_maximized_ = false; // See top of file NOTE: State Before Update.
[window() setFrame:restored_bounds() display:YES animate:YES];
} else if (IsMinimized()) {
is_maximized_ = false; // See top of file NOTE: State Before Update.
[window() deminiaturize:window_controller_];
}
}
void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) {
// Enforce minimum/maximum bounds.
gfx::Rect checked_bounds = bounds;
NSSize min_size = [window() minSize];
if (bounds.width() < min_size.width)
checked_bounds.set_width(min_size.width);
if (bounds.height() < min_size.height)
checked_bounds.set_height(min_size.height);
NSSize max_size = [window() maxSize];
if (checked_bounds.width() > max_size.width)
checked_bounds.set_width(max_size.width);
if (checked_bounds.height() > max_size.height)
checked_bounds.set_height(max_size.height);
NSRect cocoa_bounds = NSMakeRect(checked_bounds.x(), 0,
checked_bounds.width(),
checked_bounds.height());
// Flip coordinates based on the primary screen.
NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
cocoa_bounds.origin.y = NSHeight([screen frame]) - checked_bounds.bottom();
[window() setFrame:cocoa_bounds display:YES];
// setFrame: without animate: does not trigger a windowDidEndLiveResize: so
// call it here.
WindowDidFinishResize();
}
void NativeAppWindowCocoa::UpdateWindowIcon() {
// TODO(junmin): implement.
}
void NativeAppWindowCocoa::UpdateWindowTitle() {
string16 title = shell_window_->GetTitle();
[window() setTitle:base::SysUTF16ToNSString(title)];
}
void NativeAppWindowCocoa::UpdateInputRegion(scoped_ptr<SkRegion> region) {
NOTIMPLEMENTED();
}
void NativeAppWindowCocoa::UpdateDraggableRegions(
const std::vector<extensions::DraggableRegion>& regions) {
// Draggable region is not supported for non-frameless window.
if (has_frame_)
return;
// To use system drag, the window has to be marked as draggable with
// non-draggable areas being excluded via overlapping views.
// 1) If no draggable area is provided, the window is not draggable at all.
// 2) If only one draggable area is given, as this is the most common
// case, use the system drag. The non-draggable areas that are opposite of
// the draggable area are computed.
// 3) Otherwise, use the custom drag. As such, we lose the capability to
// support some features like snapping into other space.
// Determine how to perform the drag by counting the number of draggable
// areas.
const extensions::DraggableRegion* draggable_area = NULL;
use_system_drag_ = true;
for (std::vector<extensions::DraggableRegion>::const_iterator iter =
regions.begin();
iter != regions.end();
++iter) {
if (iter->draggable) {
// If more than one draggable area is found, use custom drag.
if (draggable_area) {
use_system_drag_ = false;
break;
}
draggable_area = &(*iter);
}
}
if (use_system_drag_)
UpdateDraggableRegionsForSystemDrag(regions, draggable_area);
else
UpdateDraggableRegionsForCustomDrag(regions);
InstallDraggableRegionViews();
}
SkRegion* NativeAppWindowCocoa::GetDraggableRegion() {
return draggable_region_.get();
}
void NativeAppWindowCocoa::UpdateDraggableRegionsForSystemDrag(
const std::vector<extensions::DraggableRegion>& regions,
const extensions::DraggableRegion* draggable_area) {
NSView* web_view = web_contents()->GetView()->GetNativeView();
NSInteger web_view_width = NSWidth([web_view bounds]);
NSInteger web_view_height = NSHeight([web_view bounds]);
system_drag_exclude_areas_.clear();
// The whole window is not draggable if no draggable area is given.
if (!draggable_area) {
gfx::Rect window_bounds(0, 0, web_view_width, web_view_height);
system_drag_exclude_areas_.push_back(window_bounds);
return;
}
// Otherwise, there is only one draggable area. Compute non-draggable areas
// that are the opposite of the given draggable area, combined with the
// remaining provided non-draggable areas.
// Copy all given non-draggable areas.
for (std::vector<extensions::DraggableRegion>::const_iterator iter =
regions.begin();
iter != regions.end();
++iter) {
if (!iter->draggable)
system_drag_exclude_areas_.push_back(iter->bounds);
}
gfx::Rect draggable_bounds = draggable_area->bounds;
gfx::Rect non_draggable_bounds;
// Add the non-draggable area above the given draggable area.
if (draggable_bounds.y() > 0) {
non_draggable_bounds.SetRect(0,
0,
web_view_width,
draggable_bounds.y() - 1);
system_drag_exclude_areas_.push_back(non_draggable_bounds);
}
// Add the non-draggable area below the given draggable area.
if (draggable_bounds.bottom() < web_view_height) {
non_draggable_bounds.SetRect(0,
draggable_bounds.bottom() + 1,
web_view_width,
web_view_height - draggable_bounds.bottom());
system_drag_exclude_areas_.push_back(non_draggable_bounds);
}
// Add the non-draggable area to the left of the given draggable area.
if (draggable_bounds.x() > 0) {
non_draggable_bounds.SetRect(0,
draggable_bounds.y(),
draggable_bounds.x() - 1,
draggable_bounds.height());
system_drag_exclude_areas_.push_back(non_draggable_bounds);
}
// Add the non-draggable area to the right of the given draggable area.
if (draggable_bounds.right() < web_view_width) {
non_draggable_bounds.SetRect(draggable_bounds.right() + 1,
draggable_bounds.y(),
web_view_width - draggable_bounds.right(),
draggable_bounds.height());
system_drag_exclude_areas_.push_back(non_draggable_bounds);
}
}
void NativeAppWindowCocoa::UpdateDraggableRegionsForCustomDrag(
const std::vector<extensions::DraggableRegion>& regions) {
// We still need one ControlRegionView to cover the whole window such that
// mouse events could be captured.
NSView* web_view = web_contents()->GetView()->GetNativeView();
gfx::Rect window_bounds(
0, 0, NSWidth([web_view bounds]), NSHeight([web_view bounds]));
system_drag_exclude_areas_.clear();
system_drag_exclude_areas_.push_back(window_bounds);
// Aggregate the draggable areas and non-draggable areas such that hit test
// could be performed easily.
draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions));
}
void NativeAppWindowCocoa::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
if (event.skip_in_browser ||
event.type == content::NativeWebKeyboardEvent::Char) {
return;
}
[window() redispatchKeyEvent:event.os_event];
}
void NativeAppWindowCocoa::InstallDraggableRegionViews() {
DCHECK(!has_frame_);
// All ControlRegionViews should be added as children of the WebContentsView,
// because WebContentsView will be removed and re-added when entering and
// leaving fullscreen mode.
NSView* webView = web_contents()->GetView()->GetNativeView();
NSInteger webViewHeight = NSHeight([webView bounds]);
// Remove all ControlRegionViews that are added last time.
// Note that [webView subviews] returns the view's mutable internal array and
// it should be copied to avoid mutating the original array while enumerating
// it.
base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
for (NSView* subview in subviews.get())
if ([subview isKindOfClass:[ControlRegionView class]])
[subview removeFromSuperview];
// Create and add ControlRegionView for each region that needs to be excluded
// from the dragging.
for (std::vector<gfx::Rect>::const_iterator iter =
system_drag_exclude_areas_.begin();
iter != system_drag_exclude_areas_.end();
++iter) {
base::scoped_nsobject<NSView> controlRegion(
[[ControlRegionView alloc] initWithAppWindow:this]);
[controlRegion setFrame:NSMakeRect(iter->x(),
webViewHeight - iter->bottom(),
iter->width(),
iter->height())];
[controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[webView addSubview:controlRegion];
}
}
void NativeAppWindowCocoa::FlashFrame(bool flash) {
if (flash) {
attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
} else {
[NSApp cancelUserAttentionRequest:attention_request_id_];
attention_request_id_ = 0;
}
}
bool NativeAppWindowCocoa::IsAlwaysOnTop() const {
return [window() level] == AlwaysOnTopWindowLevel();
}
void NativeAppWindowCocoa::RenderViewHostChanged(
content::RenderViewHost* old_host,
content::RenderViewHost* new_host) {
web_contents()->GetView()->Focus();
}
bool NativeAppWindowCocoa::IsFrameless() const {
return !has_frame_;
}
gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const {
if (!has_frame_)
return gfx::Insets();
// Flip the coordinates based on the main screen.
NSInteger screen_height =
NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
NSRect frame_nsrect = [window() frame];
gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect));
frame_rect.set_y(screen_height - NSMaxY(frame_nsrect));
NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect];
gfx::Rect content_rect(NSRectToCGRect(content_nsrect));
content_rect.set_y(screen_height - NSMaxY(content_nsrect));
return frame_rect.InsetsFrom(content_rect);
}
bool NativeAppWindowCocoa::IsVisible() const {
return [window() isVisible];
}
gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
NOTIMPLEMENTED();
return NULL;
}
gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) {
NOTIMPLEMENTED();
return gfx::Point();
}
gfx::Size NativeAppWindowCocoa::GetMaximumDialogSize() {
NOTIMPLEMENTED();
return gfx::Size();
}
void NativeAppWindowCocoa::AddObserver(
web_modal::ModalDialogHostObserver* observer) {
NOTIMPLEMENTED();
}
void NativeAppWindowCocoa::RemoveObserver(
web_modal::ModalDialogHostObserver* observer) {
NOTIMPLEMENTED();
}
void NativeAppWindowCocoa::WindowWillClose() {
[window_controller_ setAppWindow:NULL];
shell_window_->OnNativeWindowChanged();
shell_window_->OnNativeClose();
}
void NativeAppWindowCocoa::WindowDidBecomeKey() {
content::RenderWidgetHostView* rwhv =
web_contents()->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetActive(true);
shell_window_->OnNativeWindowActivated();
}
void NativeAppWindowCocoa::WindowDidResignKey() {
// If our app is still active and we're still the key window, ignore this
// message, since it just means that a menu extra (on the "system status bar")
// was activated; we'll get another |-windowDidResignKey| if we ever really
// lose key window status.
if ([NSApp isActive] && ([NSApp keyWindow] == window()))
return;
content::RenderWidgetHostView* rwhv =
web_contents()->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetActive(false);
}
void NativeAppWindowCocoa::WindowDidFinishResize() {
// Update |is_maximized_| if needed:
// - Exit maximized state if resized.
// - Consider us maximized if resize places us back to maximized location.
// This happens when returning from fullscreen.
NSRect frame = [window() frame];
NSRect screen = [[window() screen] visibleFrame];
if (!NSEqualSizes(frame.size, screen.size))
is_maximized_ = false;
else if (NSEqualPoints(frame.origin, screen.origin))
is_maximized_ = true;
// Update |is_fullscreen_| if needed.
is_fullscreen_ = ([window() styleMask] & NSFullScreenWindowMask) != 0;
// If not fullscreen but the window is constrained, disable the fullscreen UI
// control.
if (!is_fullscreen_ && !shows_fullscreen_controls_)
SetFullScreenCollectionBehavior(window(), false);
UpdateRestoredBounds();
}
void NativeAppWindowCocoa::WindowDidResize() {
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::WindowDidMove() {
UpdateRestoredBounds();
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::WindowDidMiniaturize() {
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::WindowDidDeminiaturize() {
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::WindowWillZoom() {
// See top of file NOTE: Maximize and Zoom.
if (IsMaximized())
Restore();
else
Maximize();
}
bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) {
return extension_keybinding_registry_->ProcessKeyEvent(
content::NativeWebKeyboardEvent(event));
}
void NativeAppWindowCocoa::HandleMouseEvent(NSEvent* event) {
if ([event type] == NSLeftMouseDown) {
last_mouse_location_ =
[window() convertBaseToScreen:[event locationInWindow]];
} else if ([event type] == NSLeftMouseDragged) {
NSPoint current_mouse_location =
[window() convertBaseToScreen:[event locationInWindow]];
NSPoint frame_origin = [window() frame].origin;
frame_origin.x += current_mouse_location.x - last_mouse_location_.x;
frame_origin.y += current_mouse_location.y - last_mouse_location_.y;
[window() setFrameOrigin:frame_origin];
last_mouse_location_ = current_mouse_location;
}
}
bool NativeAppWindowCocoa::IsWithinDraggableRegion(NSPoint point) const {
if (!draggable_region_)
return false;
NSView* webView = web_contents()->GetView()->GetNativeView();
NSInteger webViewHeight = NSHeight([webView bounds]);
// |draggable_region_| is stored in local platform-indepdent coordiate system
// while |point| is in local Cocoa coordinate system. Do the conversion
// to match these two.
return draggable_region_->contains(point.x, webViewHeight - point.y);
}
void NativeAppWindowCocoa::HideWithApp() {
is_hidden_with_app_ = true;
HideWithoutMarkingHidden();
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::ShowWithApp() {
is_hidden_with_app_ = false;
if (!is_hidden_)
ShowInactive();
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::SetAlwaysOnTop(bool always_on_top) {
[window() setLevel:(always_on_top ? AlwaysOnTopWindowLevel() :
NSNormalWindowLevel)];
shell_window_->OnNativeWindowChanged();
}
void NativeAppWindowCocoa::HideWithoutMarkingHidden() {
[window() orderOut:window_controller_];
}
NativeAppWindowCocoa::~NativeAppWindowCocoa() {
}
ShellNSWindow* NativeAppWindowCocoa::window() const {
NSWindow* window = [window_controller_ window];
CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]);
return static_cast<ShellNSWindow*>(window);
}
void NativeAppWindowCocoa::UpdateRestoredBounds() {
if (IsRestored(*this))
restored_bounds_ = [window() frame];
}