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/window_size_constants.h"
13 #include "ui/gfx/font_list.h"
14 #import "ui/gfx/mac/coordinate_conversion.h"
15 #include "ui/native_theme/native_theme.h"
16 #import "ui/views/cocoa/bridged_content_view.h"
17 #import "ui/views/cocoa/bridged_native_widget.h"
18 #import "ui/views/cocoa/native_widget_mac_nswindow.h"
19 #import "ui/views/cocoa/views_nswindow_delegate.h"
20 #include "ui/views/window/native_frame_view.h"
25 NSInteger StyleMaskForParams(const Widget::InitParams& params) {
26 // TODO(tapted): Determine better masks when there are use cases for it.
27 if (params.remove_standard_frame)
28 return NSBorderlessWindowMask;
30 if (params.type == Widget::InitParams::TYPE_WINDOW) {
31 return NSTitledWindowMask | NSClosableWindowMask |
32 NSMiniaturizableWindowMask | NSResizableWindowMask;
34 return NSBorderlessWindowMask;
37 gfx::Size WindowSizeForClientAreaSize(NSWindow* window, const gfx::Size& size) {
38 NSRect content_rect = NSMakeRect(0, 0, size.width(), size.height());
39 NSRect frame_rect = [window frameRectForContentRect:content_rect];
40 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
45 ////////////////////////////////////////////////////////////////////////////////
46 // NativeWidgetMac, public:
48 NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
49 : delegate_(delegate),
50 bridge_(new BridgedNativeWidget(this)),
51 ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
54 NativeWidgetMac::~NativeWidgetMac() {
55 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
62 BridgedNativeWidget* NativeWidgetMac::GetBridgeForNativeWindow(
63 gfx::NativeWindow window) {
64 id<NSWindowDelegate> window_delegate = [window delegate];
65 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
66 ViewsNSWindowDelegate* delegate =
67 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
68 return [delegate nativeWidgetMac]->bridge_.get();
70 return nullptr; // Not created by NativeWidgetMac.
73 void NativeWidgetMac::OnWindowWillClose() {
74 delegate_->OnNativeWidgetDestroying();
75 // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed
76 // by the user, or via Close() and a RunLoop, this will reset it.
78 delegate_->OnNativeWidgetDestroyed();
79 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
83 ////////////////////////////////////////////////////////////////////////////////
84 // NativeWidgetMac, internal::NativeWidgetPrivate implementation:
86 void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) {
87 ownership_ = params.ownership;
88 base::scoped_nsobject<NSWindow> window([CreateNSWindow(params) retain]);
89 [window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject.
90 bridge_->Init(window, params);
92 delegate_->OnNativeWidgetCreated(true);
94 OnSizeConstraintsChanged();
96 bridge_->SetFocusManager(GetWidget()->GetFocusManager());
98 DCHECK(GetWidget()->GetRootView());
99 bridge_->SetRootView(GetWidget()->GetRootView());
101 // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit().
102 DCHECK_NE(Widget::InitParams::INFER_OPACITY, params.opacity);
103 bool translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW;
104 bridge_->CreateLayer(params.layer_type, translucent);
107 NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() {
108 return new NativeFrameView(GetWidget());
111 bool NativeWidgetMac::ShouldUseNativeFrame() const {
115 bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
116 // On Windows, this returns true when Aero is enabled which draws the titlebar
117 // with translucency.
121 void NativeWidgetMac::FrameTypeChanged() {
125 Widget* NativeWidgetMac::GetWidget() {
126 return delegate_->AsWidget();
129 const Widget* NativeWidgetMac::GetWidget() const {
130 return delegate_->AsWidget();
133 gfx::NativeView NativeWidgetMac::GetNativeView() const {
134 // Returns a BridgedContentView, unless there is no views::RootView set.
135 return [GetNativeWindow() contentView];
138 gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
139 return bridge_ ? bridge_->ns_window() : nil;
142 Widget* NativeWidgetMac::GetTopLevelWidget() {
143 NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView());
144 return native_widget ? native_widget->GetWidget() : NULL;
147 const ui::Compositor* NativeWidgetMac::GetCompositor() const {
148 return bridge_ && bridge_->layer() ? bridge_->layer()->GetCompositor()
152 const ui::Layer* NativeWidgetMac::GetLayer() const {
153 return bridge_ ? bridge_->layer() : nullptr;
156 void NativeWidgetMac::ReorderNativeViews() {
158 bridge_->SetRootView(GetWidget()->GetRootView());
161 void NativeWidgetMac::ViewRemoved(View* view) {
165 void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
167 bridge_->SetNativeWindowProperty(name, value);
170 void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
172 return bridge_->GetNativeWindowProperty(name);
177 TooltipManager* NativeWidgetMac::GetTooltipManager() const {
182 void NativeWidgetMac::SetCapture() {
183 if (bridge_ && !bridge_->HasCapture())
184 bridge_->AcquireCapture();
187 void NativeWidgetMac::ReleaseCapture() {
189 bridge_->ReleaseCapture();
192 bool NativeWidgetMac::HasCapture() const {
193 return bridge_ && bridge_->HasCapture();
196 InputMethod* NativeWidgetMac::CreateInputMethod() {
197 return bridge_ ? bridge_->CreateInputMethod() : NULL;
200 internal::InputMethodDelegate* NativeWidgetMac::GetInputMethodDelegate() {
201 return bridge_.get();
204 ui::InputMethod* NativeWidgetMac::GetHostInputMethod() {
205 return bridge_ ? bridge_->GetHostInputMethod() : NULL;
208 void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
209 SetSize(WindowSizeForClientAreaSize(GetNativeWindow(), size));
210 // Note that this is not the precise center of screen, but it is the standard
211 // location for windows like dialogs to appear on screen for Mac.
212 // TODO(tapted): If there is a parent window, center in that instead.
213 [GetNativeWindow() center];
216 void NativeWidgetMac::GetWindowPlacement(gfx::Rect* bounds,
217 ui::WindowShowState* maximized) const {
221 bool NativeWidgetMac::SetWindowTitle(const base::string16& title) {
222 NSWindow* window = GetNativeWindow();
223 NSString* current_title = [window title];
224 NSString* new_title = SysUTF16ToNSString(title);
225 if ([current_title isEqualToString:new_title])
228 [window setTitle:new_title];
232 void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
233 const gfx::ImageSkia& app_icon) {
237 void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
238 if (modal_type == ui::MODAL_TYPE_NONE)
241 // System modal windows not implemented (or used) on Mac.
242 DCHECK_NE(ui::MODAL_TYPE_SYSTEM, modal_type);
243 DCHECK(bridge_->parent());
244 // Everyhing happens upon show.
247 gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
248 return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]);
251 gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const {
252 NSWindow* window = GetNativeWindow();
253 return gfx::ScreenRectFromNSRect(
254 [window contentRectForFrameRect:[window frame]]);
257 gfx::Rect NativeWidgetMac::GetRestoredBounds() const {
258 return bridge_ ? bridge_->GetRestoredBounds() : gfx::Rect();
261 void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) {
263 bridge_->SetBounds(bounds);
266 void NativeWidgetMac::SetSize(const gfx::Size& size) {
267 // Ensure the top-left corner stays in-place (rather than the bottom-left,
268 // which -[NSWindow setContentSize:] would do).
269 SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size));
272 void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
276 void NativeWidgetMac::StackAtTop() {
280 void NativeWidgetMac::StackBelow(gfx::NativeView native_view) {
284 void NativeWidgetMac::SetShape(gfx::NativeRegion shape) {
288 void NativeWidgetMac::Close() {
292 if (delegate_->IsModal()) {
293 // Sheets can't be closed normally. This starts the sheet closing. Once the
294 // sheet has finished animating, it will call sheetDidEnd: on the parent
295 // window's delegate. Note it still needs to be asynchronous, since code
296 // calling Widget::Close() doesn't expect things to be deleted upon return.
297 [NSApp performSelector:@selector(endSheet:)
298 withObject:GetNativeWindow()
303 // Clear the view early to suppress repaints.
304 bridge_->SetRootView(NULL);
306 NSWindow* window = GetNativeWindow();
307 // Calling performClose: will momentarily highlight the close button, but
308 // AppKit will reject it if there is no close button.
309 SEL close_selector = ([window styleMask] & NSClosableWindowMask)
310 ? @selector(performClose:)
312 [window performSelector:close_selector withObject:nil afterDelay:0];
315 void NativeWidgetMac::CloseNow() {
316 // Reset |bridge_| to NULL before destroying it.
317 scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass());
320 void NativeWidgetMac::Show() {
321 ShowWithWindowState(ui::SHOW_STATE_NORMAL);
324 void NativeWidgetMac::Hide() {
328 bridge_->SetVisibilityState(BridgedNativeWidget::HIDE_WINDOW);
331 void NativeWidgetMac::ShowMaximizedWithBounds(
332 const gfx::Rect& restored_bounds) {
336 void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) {
341 case ui::SHOW_STATE_DEFAULT:
342 case ui::SHOW_STATE_NORMAL:
343 case ui::SHOW_STATE_INACTIVE:
345 case ui::SHOW_STATE_MINIMIZED:
346 case ui::SHOW_STATE_MAXIMIZED:
347 case ui::SHOW_STATE_FULLSCREEN:
350 case ui::SHOW_STATE_END:
354 bridge_->SetVisibilityState(state == ui::SHOW_STATE_INACTIVE
355 ? BridgedNativeWidget::SHOW_INACTIVE
356 : BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
359 bool NativeWidgetMac::IsVisible() const {
360 return bridge_ && bridge_->window_visible();
363 void NativeWidgetMac::Activate() {
367 bridge_->SetVisibilityState(BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
370 void NativeWidgetMac::Deactivate() {
374 bool NativeWidgetMac::IsActive() const {
375 return [GetNativeWindow() isKeyWindow];
378 void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) {
382 bool NativeWidgetMac::IsAlwaysOnTop() const {
387 void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
391 void NativeWidgetMac::Maximize() {
392 NOTIMPLEMENTED(); // See IsMaximized().
395 void NativeWidgetMac::Minimize() {
396 NSWindow* window = GetNativeWindow();
397 // Calling performMiniaturize: will momentarily highlight the button, but
398 // AppKit will reject it if there is no miniaturize button.
399 if ([window styleMask] & NSMiniaturizableWindowMask)
400 [window performMiniaturize:nil];
402 [window miniaturize:nil];
405 bool NativeWidgetMac::IsMaximized() const {
406 // The window frame isn't altered on Mac unless going fullscreen. The green
407 // "+" button just makes the window bigger. So, always false.
411 bool NativeWidgetMac::IsMinimized() const {
412 return [GetNativeWindow() isMiniaturized];
415 void NativeWidgetMac::Restore() {
416 [GetNativeWindow() deminiaturize:nil];
419 void NativeWidgetMac::SetFullscreen(bool fullscreen) {
420 if (!bridge_ || fullscreen == IsFullscreen())
423 bridge_->ToggleDesiredFullscreenState();
426 bool NativeWidgetMac::IsFullscreen() const {
427 return bridge_ && bridge_->target_fullscreen_state();
430 void NativeWidgetMac::SetOpacity(unsigned char opacity) {
434 void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) {
438 void NativeWidgetMac::FlashFrame(bool flash_frame) {
442 void NativeWidgetMac::RunShellDrag(View* view,
443 const ui::OSExchangeData& data,
444 const gfx::Point& location,
446 ui::DragDropTypes::DragEventSource source) {
450 void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
451 // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate
452 // system of |rect| has been converted.
453 [GetNativeView() setNeedsDisplay:YES];
454 if (bridge_ && bridge_->layer())
455 bridge_->layer()->SchedulePaint(rect);
458 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
460 bridge_->SetCursor(cursor);
463 bool NativeWidgetMac::IsMouseEventsEnabled() const {
468 void NativeWidgetMac::ClearNativeFocus() {
469 // To quote DesktopWindowTreeHostX11, "This method is weird and misnamed."
470 // The goal is to set focus to the content window, thereby removing focus from
471 // any NSView in the window that doesn't belong to toolkit-views.
472 [GetNativeWindow() makeFirstResponder:GetNativeView()];
475 gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
480 Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
481 const gfx::Vector2d& drag_offset,
482 Widget::MoveLoopSource source,
483 Widget::MoveLoopEscapeBehavior escape_behavior) {
485 return Widget::MOVE_LOOP_CANCELED;
488 void NativeWidgetMac::EndMoveLoop() {
492 void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
496 void NativeWidgetMac::SetVisibilityAnimationDuration(
497 const base::TimeDelta& duration) {
501 void NativeWidgetMac::SetVisibilityAnimationTransition(
502 Widget::VisibilityTransition transition) {
506 ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const {
507 return ui::NativeTheme::instance();
510 void NativeWidgetMac::OnRootViewLayout() {
514 bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
518 void NativeWidgetMac::OnSizeConstraintsChanged() {
519 bridge_->OnSizeConstraintsChanged();
522 void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) {
526 ////////////////////////////////////////////////////////////////////////////////
527 // NativeWidgetMac, protected:
529 NSWindow* NativeWidgetMac::CreateNSWindow(const Widget::InitParams& params) {
530 return [[[NativeWidgetMacNSWindow alloc]
531 initWithContentRect:ui::kWindowSizeDeterminedLater
532 styleMask:StyleMaskForParams(params)
533 backing:NSBackingStoreBuffered
534 defer:YES] autorelease];
537 ////////////////////////////////////////////////////////////////////////////////
540 bool Widget::ConvertRect(const Widget* source,
541 const Widget* target,
548 ////////////////////////////////////////////////////////////////////////////////
549 // internal::NativeWidgetPrivate, public:
552 NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
553 internal::NativeWidgetDelegate* delegate) {
554 return new NativeWidgetMac(delegate);
558 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
559 gfx::NativeView native_view) {
560 return GetNativeWidgetForNativeWindow([native_view window]);
564 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
565 gfx::NativeWindow native_window) {
566 id<NSWindowDelegate> window_delegate = [native_window delegate];
567 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
568 ViewsNSWindowDelegate* delegate =
569 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
570 return [delegate nativeWidgetMac];
572 return NULL; // Not created by NativeWidgetMac.
576 NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
577 gfx::NativeView native_view) {
578 BridgedNativeWidget* bridge =
579 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
583 for (BridgedNativeWidget* parent;
584 (parent = bridge->parent());
587 return bridge->native_widget_mac();
591 void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
592 Widget::Widgets* children) {
593 BridgedNativeWidget* bridge =
594 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
598 // If |native_view| is a subview of the contentView, it will share an
599 // NSWindow, but will itself be a native child of the Widget. That is, adding
600 // bridge->..->GetWidget() to |children| would be adding the _parent_ of
601 // |native_view|, not the Widget for |native_view|. |native_view| doesn't have
602 // a corresponding Widget of its own in this case (and so can't have Widget
603 // children of its own on Mac).
604 if (bridge->ns_view() != native_view)
607 // Code expects widget for |native_view| to be added to |children|.
608 if (bridge->native_widget_mac()->GetWidget())
609 children->insert(bridge->native_widget_mac()->GetWidget());
611 for (BridgedNativeWidget* child : bridge->child_windows())
612 GetAllChildWidgets(child->ns_view(), children);
616 void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view,
617 Widget::Widgets* owned) {
622 void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view,
623 gfx::NativeView new_parent) {
628 bool NativeWidgetPrivate::IsMouseButtonDown() {
629 return [NSEvent pressedMouseButtons] != 0;
633 gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() {
635 return gfx::FontList();
638 } // namespace internal