1 // Copyright 2014 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 "ui/views/widget/native_widget_mac.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/mac/foundation_util.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/strings/sys_string_conversions.h"
12 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
13 #import "ui/base/cocoa/window_size_constants.h"
14 #include "ui/gfx/font_list.h"
15 #import "ui/gfx/mac/coordinate_conversion.h"
16 #import "ui/gfx/mac/nswindow_frame_controls.h"
17 #include "ui/native_theme/native_theme.h"
18 #import "ui/views/cocoa/bridged_content_view.h"
19 #import "ui/views/cocoa/bridged_native_widget.h"
20 #import "ui/views/cocoa/native_widget_mac_nswindow.h"
21 #import "ui/views/cocoa/views_nswindow_delegate.h"
22 #include "ui/views/widget/widget_delegate.h"
23 #include "ui/views/window/native_frame_view.h"
25 // Self-owning animation delegate that starts a hide animation, then calls
26 // -[NSWindow close] when the animation ends, releasing itself.
27 @interface ViewsNSWindowCloseAnimator : NSObject<NSAnimationDelegate> {
29 base::scoped_nsobject<NSWindow> window_;
30 base::scoped_nsobject<NSAnimation> animation_;
33 + (void)closeWindowWithAnimation:(NSWindow*)window;
40 NSInteger StyleMaskForParams(const Widget::InitParams& params) {
41 // If the Widget is modal, it will be displayed as a sheet. This works best if
42 // it has NSTitledWindowMask. For example, with NSBorderlessWindowMask, the
43 // parent window still accepts input.
44 if (params.delegate &&
45 params.delegate->GetModalType() == ui::MODAL_TYPE_WINDOW)
46 return NSTitledWindowMask;
48 // TODO(tapted): Determine better masks when there are use cases for it.
49 if (params.remove_standard_frame)
50 return NSBorderlessWindowMask;
52 if (params.type == Widget::InitParams::TYPE_WINDOW) {
53 return NSTitledWindowMask | NSClosableWindowMask |
54 NSMiniaturizableWindowMask | NSResizableWindowMask |
55 NSTexturedBackgroundWindowMask;
57 return NSBorderlessWindowMask;
62 ////////////////////////////////////////////////////////////////////////////////
63 // NativeWidgetMac, public:
65 NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
66 : delegate_(delegate),
67 bridge_(new BridgedNativeWidget(this)),
68 ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
71 NativeWidgetMac::~NativeWidgetMac() {
72 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
79 BridgedNativeWidget* NativeWidgetMac::GetBridgeForNativeWindow(
80 gfx::NativeWindow window) {
81 id<NSWindowDelegate> window_delegate = [window delegate];
82 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
83 ViewsNSWindowDelegate* delegate =
84 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
85 return [delegate nativeWidgetMac]->bridge_.get();
87 return nullptr; // Not created by NativeWidgetMac.
90 bool NativeWidgetMac::IsWindowModalSheet() const {
91 return GetWidget()->widget_delegate()->GetModalType() ==
92 ui::MODAL_TYPE_WINDOW;
95 void NativeWidgetMac::OnWindowWillClose() {
96 // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed
97 // by the user, or via Close() and a RunLoop, notify observers while |bridge_|
98 // is still a valid pointer, then reset it.
100 delegate_->OnNativeWidgetDestroying();
103 delegate_->OnNativeWidgetDestroyed();
104 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
108 int NativeWidgetMac::SheetPositionY() {
109 NSView* view = GetNativeView();
111 [view convertPoint:NSMakePoint(0, NSHeight([view frame])) toView:nil].y;
114 ////////////////////////////////////////////////////////////////////////////////
115 // NativeWidgetMac, internal::NativeWidgetPrivate implementation:
117 void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) {
118 ownership_ = params.ownership;
119 base::scoped_nsobject<NSWindow> window([CreateNSWindow(params) retain]);
120 [window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject.
121 bridge_->Init(window, params);
123 // Only set always-on-top here if it is true since setting it may affect how
124 // the window is treated by Expose.
125 if (params.keep_on_top)
126 SetAlwaysOnTop(true);
128 delegate_->OnNativeWidgetCreated(true);
130 bridge_->SetFocusManager(GetWidget()->GetFocusManager());
132 DCHECK(GetWidget()->GetRootView());
133 bridge_->SetRootView(GetWidget()->GetRootView());
135 // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit().
136 DCHECK_NE(Widget::InitParams::INFER_OPACITY, params.opacity);
137 bool translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW;
138 bridge_->CreateLayer(params.layer_type, translucent);
141 NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() {
142 return new NativeFrameView(GetWidget());
145 bool NativeWidgetMac::ShouldUseNativeFrame() const {
149 bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
150 // On Windows, this returns true when Aero is enabled which draws the titlebar
151 // with translucency.
155 void NativeWidgetMac::FrameTypeChanged() {
159 Widget* NativeWidgetMac::GetWidget() {
160 return delegate_->AsWidget();
163 const Widget* NativeWidgetMac::GetWidget() const {
164 return delegate_->AsWidget();
167 gfx::NativeView NativeWidgetMac::GetNativeView() const {
168 // Returns a BridgedContentView, unless there is no views::RootView set.
169 return [GetNativeWindow() contentView];
172 gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
173 return bridge_ ? bridge_->ns_window() : nil;
176 Widget* NativeWidgetMac::GetTopLevelWidget() {
177 NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView());
178 return native_widget ? native_widget->GetWidget() : NULL;
181 const ui::Compositor* NativeWidgetMac::GetCompositor() const {
182 return bridge_ && bridge_->layer() ? bridge_->layer()->GetCompositor()
186 const ui::Layer* NativeWidgetMac::GetLayer() const {
187 return bridge_ ? bridge_->layer() : nullptr;
190 void NativeWidgetMac::ReorderNativeViews() {
192 bridge_->SetRootView(GetWidget()->GetRootView());
195 void NativeWidgetMac::ViewRemoved(View* view) {
199 void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
201 bridge_->SetNativeWindowProperty(name, value);
204 void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
206 return bridge_->GetNativeWindowProperty(name);
211 TooltipManager* NativeWidgetMac::GetTooltipManager() const {
213 return bridge_->tooltip_manager();
218 void NativeWidgetMac::SetCapture() {
219 if (bridge_ && !bridge_->HasCapture())
220 bridge_->AcquireCapture();
223 void NativeWidgetMac::ReleaseCapture() {
225 bridge_->ReleaseCapture();
228 bool NativeWidgetMac::HasCapture() const {
229 return bridge_ && bridge_->HasCapture();
232 ui::InputMethod* NativeWidgetMac::GetInputMethod() {
233 return bridge_ ? bridge_->GetInputMethod() : NULL;
236 void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
238 BridgedNativeWidget::GetWindowSizeForClientSize(GetNativeWindow(), size));
239 // Note that this is not the precise center of screen, but it is the standard
240 // location for windows like dialogs to appear on screen for Mac.
241 // TODO(tapted): If there is a parent window, center in that instead.
242 [GetNativeWindow() center];
245 void NativeWidgetMac::GetWindowPlacement(
247 ui::WindowShowState* show_state) const {
248 *bounds = GetRestoredBounds();
250 *show_state = ui::SHOW_STATE_FULLSCREEN;
251 else if (IsMinimized())
252 *show_state = ui::SHOW_STATE_MINIMIZED;
254 *show_state = ui::SHOW_STATE_NORMAL;
257 bool NativeWidgetMac::SetWindowTitle(const base::string16& title) {
258 NSWindow* window = GetNativeWindow();
259 NSString* current_title = [window title];
260 NSString* new_title = SysUTF16ToNSString(title);
261 if ([current_title isEqualToString:new_title])
264 [window setTitle:new_title];
268 void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
269 const gfx::ImageSkia& app_icon) {
273 void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
274 if (modal_type == ui::MODAL_TYPE_NONE)
277 // System modal windows not implemented (or used) on Mac.
278 DCHECK_NE(ui::MODAL_TYPE_SYSTEM, modal_type);
279 DCHECK(bridge_->parent());
280 // Everyhing happens upon show.
283 gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
284 return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]);
287 gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const {
288 NSWindow* window = GetNativeWindow();
289 return gfx::ScreenRectFromNSRect(
290 [window contentRectForFrameRect:[window frame]]);
293 gfx::Rect NativeWidgetMac::GetRestoredBounds() const {
294 return bridge_ ? bridge_->GetRestoredBounds() : gfx::Rect();
297 void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) {
299 bridge_->SetBounds(bounds);
302 void NativeWidgetMac::SetSize(const gfx::Size& size) {
303 // Ensure the top-left corner stays in-place (rather than the bottom-left,
304 // which -[NSWindow setContentSize:] would do).
305 SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size));
308 void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
312 void NativeWidgetMac::StackAtTop() {
316 void NativeWidgetMac::StackBelow(gfx::NativeView native_view) {
320 void NativeWidgetMac::SetShape(SkRegion* shape) {
324 void NativeWidgetMac::Close() {
328 NSWindow* window = GetNativeWindow();
329 if (IsWindowModalSheet()) {
330 // Sheets can't be closed normally. This starts the sheet closing. Once the
331 // sheet has finished animating, it will call sheetDidEnd: on the parent
332 // window's delegate. Note it still needs to be asynchronous, since code
333 // calling Widget::Close() doesn't expect things to be deleted upon return.
334 [NSApp performSelector:@selector(endSheet:) withObject:window afterDelay:0];
338 // For other modal types, animate the close.
339 if (delegate_->IsModal()) {
340 [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window];
344 // Clear the view early to suppress repaints.
345 bridge_->SetRootView(NULL);
347 // Calling performClose: will momentarily highlight the close button, but
348 // AppKit will reject it if there is no close button.
349 SEL close_selector = ([window styleMask] & NSClosableWindowMask)
350 ? @selector(performClose:)
352 [window performSelector:close_selector withObject:nil afterDelay:0];
355 void NativeWidgetMac::CloseNow() {
359 // Notify observers while |bridged_| is still valid.
360 delegate_->OnNativeWidgetDestroying();
361 // Reset |bridge_| to NULL before destroying it.
362 scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass());
365 void NativeWidgetMac::Show() {
366 ShowWithWindowState(ui::SHOW_STATE_NORMAL);
369 void NativeWidgetMac::Hide() {
373 bridge_->SetVisibilityState(BridgedNativeWidget::HIDE_WINDOW);
376 void NativeWidgetMac::ShowMaximizedWithBounds(
377 const gfx::Rect& restored_bounds) {
381 void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) {
386 case ui::SHOW_STATE_DEFAULT:
387 case ui::SHOW_STATE_NORMAL:
388 case ui::SHOW_STATE_INACTIVE:
390 case ui::SHOW_STATE_MINIMIZED:
391 case ui::SHOW_STATE_MAXIMIZED:
392 case ui::SHOW_STATE_FULLSCREEN:
393 case ui::SHOW_STATE_DOCKED:
396 case ui::SHOW_STATE_END:
400 bridge_->SetVisibilityState(state == ui::SHOW_STATE_INACTIVE
401 ? BridgedNativeWidget::SHOW_INACTIVE
402 : BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
405 bool NativeWidgetMac::IsVisible() const {
406 return bridge_ && bridge_->window_visible();
409 void NativeWidgetMac::Activate() {
413 bridge_->SetVisibilityState(BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
416 void NativeWidgetMac::Deactivate() {
420 bool NativeWidgetMac::IsActive() const {
421 return [GetNativeWindow() isKeyWindow];
424 void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) {
425 gfx::SetNSWindowAlwaysOnTop(GetNativeWindow(), always_on_top);
428 bool NativeWidgetMac::IsAlwaysOnTop() const {
429 return gfx::IsNSWindowAlwaysOnTop(GetNativeWindow());
432 void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
433 gfx::SetNSWindowVisibleOnAllWorkspaces(GetNativeWindow(), always_visible);
436 void NativeWidgetMac::Maximize() {
437 NOTIMPLEMENTED(); // See IsMaximized().
440 void NativeWidgetMac::Minimize() {
441 NSWindow* window = GetNativeWindow();
442 // Calling performMiniaturize: will momentarily highlight the button, but
443 // AppKit will reject it if there is no miniaturize button.
444 if ([window styleMask] & NSMiniaturizableWindowMask)
445 [window performMiniaturize:nil];
447 [window miniaturize:nil];
450 bool NativeWidgetMac::IsMaximized() const {
451 // The window frame isn't altered on Mac unless going fullscreen. The green
452 // "+" button just makes the window bigger. So, always false.
456 bool NativeWidgetMac::IsMinimized() const {
457 return [GetNativeWindow() isMiniaturized];
460 void NativeWidgetMac::Restore() {
461 SetFullscreen(false);
462 [GetNativeWindow() deminiaturize:nil];
465 void NativeWidgetMac::SetFullscreen(bool fullscreen) {
466 if (!bridge_ || fullscreen == IsFullscreen())
469 bridge_->ToggleDesiredFullscreenState();
472 bool NativeWidgetMac::IsFullscreen() const {
473 return bridge_ && bridge_->target_fullscreen_state();
476 void NativeWidgetMac::SetOpacity(unsigned char opacity) {
480 void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) {
484 void NativeWidgetMac::FlashFrame(bool flash_frame) {
488 void NativeWidgetMac::RunShellDrag(View* view,
489 const ui::OSExchangeData& data,
490 const gfx::Point& location,
492 ui::DragDropTypes::DragEventSource source) {
496 void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
497 // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate
498 // system of |rect| has been converted.
499 [GetNativeView() setNeedsDisplay:YES];
500 if (bridge_ && bridge_->layer())
501 bridge_->layer()->SchedulePaint(rect);
504 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
506 bridge_->SetCursor(cursor);
509 bool NativeWidgetMac::IsMouseEventsEnabled() const {
510 // On platforms with touch, mouse events get disabled and calls to this method
511 // can affect hover states. Since there is no touch on desktop Mac, this is
512 // always true. Touch on Mac is tracked in http://crbug.com/445520.
516 void NativeWidgetMac::ClearNativeFocus() {
517 // To quote DesktopWindowTreeHostX11, "This method is weird and misnamed."
518 // The goal is to set focus to the content window, thereby removing focus from
519 // any NSView in the window that doesn't belong to toolkit-views.
520 [GetNativeWindow() makeFirstResponder:GetNativeView()];
523 gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
524 return gfx::ScreenRectFromNSRect([[GetNativeWindow() screen] visibleFrame]);
527 Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
528 const gfx::Vector2d& drag_offset,
529 Widget::MoveLoopSource source,
530 Widget::MoveLoopEscapeBehavior escape_behavior) {
532 return Widget::MOVE_LOOP_CANCELED;
535 void NativeWidgetMac::EndMoveLoop() {
539 void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
543 void NativeWidgetMac::SetVisibilityAnimationDuration(
544 const base::TimeDelta& duration) {
548 void NativeWidgetMac::SetVisibilityAnimationTransition(
549 Widget::VisibilityTransition transition) {
553 ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const {
554 return ui::NativeTheme::instance();
557 void NativeWidgetMac::OnRootViewLayout() {
558 // Ensure possible changes to the non-client view (e.g. Minimum/Maximum size)
559 // propagate through to the NSWindow properties.
560 OnSizeConstraintsChanged();
563 bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
567 void NativeWidgetMac::OnSizeConstraintsChanged() {
568 bridge_->OnSizeConstraintsChanged();
571 void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) {
575 ////////////////////////////////////////////////////////////////////////////////
576 // NativeWidgetMac, protected:
578 NativeWidgetMacNSWindow* NativeWidgetMac::CreateNSWindow(
579 const Widget::InitParams& params) {
580 return [[[NativeWidgetMacNSWindow alloc]
581 initWithContentRect:ui::kWindowSizeDeterminedLater
582 styleMask:StyleMaskForParams(params)
583 backing:NSBackingStoreBuffered
584 defer:NO] autorelease];
587 ////////////////////////////////////////////////////////////////////////////////
590 bool Widget::ConvertRect(const Widget* source,
591 const Widget* target,
598 ////////////////////////////////////////////////////////////////////////////////
599 // internal::NativeWidgetPrivate, public:
602 NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
603 internal::NativeWidgetDelegate* delegate) {
604 return new NativeWidgetMac(delegate);
608 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
609 gfx::NativeView native_view) {
610 return GetNativeWidgetForNativeWindow([native_view window]);
614 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
615 gfx::NativeWindow native_window) {
616 id<NSWindowDelegate> window_delegate = [native_window delegate];
617 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
618 ViewsNSWindowDelegate* delegate =
619 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
620 return [delegate nativeWidgetMac];
622 return NULL; // Not created by NativeWidgetMac.
626 NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
627 gfx::NativeView native_view) {
628 BridgedNativeWidget* bridge =
629 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
633 NativeWidgetPrivate* ancestor =
634 bridge->parent() ? GetTopLevelNativeWidget(
635 [bridge->parent()->GetNSWindow() contentView])
637 return ancestor ? ancestor : bridge->native_widget_mac();
641 void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
642 Widget::Widgets* children) {
643 BridgedNativeWidget* bridge =
644 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
648 // If |native_view| is a subview of the contentView, it will share an
649 // NSWindow, but will itself be a native child of the Widget. That is, adding
650 // bridge->..->GetWidget() to |children| would be adding the _parent_ of
651 // |native_view|, not the Widget for |native_view|. |native_view| doesn't have
652 // a corresponding Widget of its own in this case (and so can't have Widget
653 // children of its own on Mac).
654 if (bridge->ns_view() != native_view)
657 // Code expects widget for |native_view| to be added to |children|.
658 if (bridge->native_widget_mac()->GetWidget())
659 children->insert(bridge->native_widget_mac()->GetWidget());
661 for (BridgedNativeWidget* child : bridge->child_windows())
662 GetAllChildWidgets(child->ns_view(), children);
666 void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view,
667 Widget::Widgets* owned) {
672 void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view,
673 gfx::NativeView new_parent) {
674 BridgedNativeWidget* bridge =
675 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
676 if (bridge && bridge->parent() &&
677 bridge->parent()->GetNSWindow() == [new_parent window])
678 return; // Nothing to do.
680 // Not supported. See http://crbug.com/514920.
685 bool NativeWidgetPrivate::IsMouseButtonDown() {
686 return [NSEvent pressedMouseButtons] != 0;
690 gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() {
692 return gfx::FontList();
695 } // namespace internal
698 @implementation ViewsNSWindowCloseAnimator
700 - (id)initWithWindow:(NSWindow*)window {
701 if ((self = [super init])) {
702 window_.reset([window retain]);
704 [[ConstrainedWindowAnimationHide alloc] initWithWindow:window]);
705 [animation_ setDelegate:self];
706 [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
707 [animation_ startAnimation];
712 + (void)closeWindowWithAnimation:(NSWindow*)window {
713 [[ViewsNSWindowCloseAnimator alloc] initWithWindow:window];
716 - (void)animationDidEnd:(NSAnimation*)animation {
718 [animation_ setDelegate:nil];