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 #include "ui/gfx/font_list.h"
13 #import "ui/gfx/mac/coordinate_conversion.h"
14 #include "ui/native_theme/native_theme.h"
15 #import "ui/views/cocoa/bridged_content_view.h"
16 #import "ui/views/cocoa/bridged_native_widget.h"
17 #import "ui/views/cocoa/native_widget_mac_nswindow.h"
18 #import "ui/views/cocoa/views_nswindow_delegate.h"
19 #include "ui/views/window/native_frame_view.h"
24 NSInteger StyleMaskForParams(const Widget::InitParams& params) {
25 // TODO(tapted): Determine better masks when there are use cases for it.
26 if (params.remove_standard_frame)
27 return NSBorderlessWindowMask;
29 if (params.type == Widget::InitParams::TYPE_WINDOW) {
30 return NSTitledWindowMask | NSClosableWindowMask |
31 NSMiniaturizableWindowMask | NSResizableWindowMask;
33 return NSBorderlessWindowMask;
36 NSRect ValidateContentRect(NSRect content_rect) {
37 // A contentRect with zero width or height is a banned practice in Chrome, due
38 // to unpredictable OSX treatment. For now, silently give a minimum dimension.
39 // TODO(tapted): Add a DCHECK, or add emulation logic (e.g. to auto-hide).
40 if (NSWidth(content_rect) == 0)
41 content_rect.size.width = 1;
43 if (NSHeight(content_rect) == 0)
44 content_rect.size.height = 1;
49 gfx::Size WindowSizeForClientAreaSize(NSWindow* window, const gfx::Size& size) {
50 NSRect content_rect = NSMakeRect(0, 0, size.width(), size.height());
51 NSRect frame_rect = [window frameRectForContentRect:content_rect];
52 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
57 ////////////////////////////////////////////////////////////////////////////////
58 // NativeWidgetMac, public:
60 NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
61 : delegate_(delegate),
62 bridge_(new BridgedNativeWidget(this)),
63 ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
66 NativeWidgetMac::~NativeWidgetMac() {
67 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
74 BridgedNativeWidget* NativeWidgetMac::GetBridgeForNativeWindow(
75 gfx::NativeWindow window) {
76 id<NSWindowDelegate> window_delegate = [window delegate];
77 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
78 ViewsNSWindowDelegate* delegate =
79 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
80 return [delegate nativeWidgetMac]->bridge_.get();
82 return nullptr; // Not created by NativeWidgetMac.
85 void NativeWidgetMac::OnWindowWillClose() {
86 delegate_->OnNativeWidgetDestroying();
87 // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed
88 // by the user, or via Close() and a RunLoop, this will reset it.
90 delegate_->OnNativeWidgetDestroyed();
91 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
95 ////////////////////////////////////////////////////////////////////////////////
96 // NativeWidgetMac, internal::NativeWidgetPrivate implementation:
98 void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) {
99 ownership_ = params.ownership;
101 NSInteger style_mask = StyleMaskForParams(params);
102 NSRect content_rect = ValidateContentRect(
103 [NSWindow contentRectForFrameRect:gfx::ScreenRectToNSRect(params.bounds)
104 styleMask:style_mask]);
106 base::scoped_nsobject<NSWindow> window([[NativeWidgetMacNSWindow alloc]
107 initWithContentRect:content_rect
109 backing:NSBackingStoreBuffered
111 [window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject.
112 bridge_->Init(window, params);
114 delegate_->OnNativeWidgetCreated(true);
116 bridge_->SetFocusManager(GetWidget()->GetFocusManager());
118 DCHECK(GetWidget()->GetRootView());
119 bridge_->SetRootView(GetWidget()->GetRootView());
121 // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit().
122 DCHECK_NE(Widget::InitParams::INFER_OPACITY, params.opacity);
123 bool translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW;
124 switch (params.layer_type) {
125 case aura::WINDOW_LAYER_NONE:
127 case aura::WINDOW_LAYER_TEXTURED:
128 bridge_->CreateLayer(ui::LAYER_TEXTURED, translucent);
130 case aura::WINDOW_LAYER_NOT_DRAWN:
131 bridge_->CreateLayer(ui::LAYER_NOT_DRAWN, translucent);
133 case aura::WINDOW_LAYER_SOLID_COLOR:
134 bridge_->CreateLayer(ui::LAYER_SOLID_COLOR, translucent);
139 NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() {
140 return new NativeFrameView(GetWidget());
143 bool NativeWidgetMac::ShouldUseNativeFrame() const {
147 bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
148 // On Windows, this returns false when DWM is unavailable (e.g. XP, RDP or
149 // classic mode). OSX always has a compositing window manager.
153 void NativeWidgetMac::FrameTypeChanged() {
157 Widget* NativeWidgetMac::GetWidget() {
158 return delegate_->AsWidget();
161 const Widget* NativeWidgetMac::GetWidget() const {
162 return delegate_->AsWidget();
165 gfx::NativeView NativeWidgetMac::GetNativeView() const {
166 // Returns a BridgedContentView, unless there is no views::RootView set.
167 return [GetNativeWindow() contentView];
170 gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
171 return bridge_ ? bridge_->ns_window() : nil;
174 Widget* NativeWidgetMac::GetTopLevelWidget() {
175 NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView());
176 return native_widget ? native_widget->GetWidget() : NULL;
179 const ui::Compositor* NativeWidgetMac::GetCompositor() const {
180 return bridge_ && bridge_->layer() ? bridge_->layer()->GetCompositor()
184 const ui::Layer* NativeWidgetMac::GetLayer() const {
185 return bridge_ ? bridge_->layer() : nullptr;
188 void NativeWidgetMac::ReorderNativeViews() {
190 bridge_->SetRootView(GetWidget()->GetRootView());
193 void NativeWidgetMac::ViewRemoved(View* view) {
197 void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
199 bridge_->SetNativeWindowProperty(name, value);
202 void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
204 return bridge_->GetNativeWindowProperty(name);
209 TooltipManager* NativeWidgetMac::GetTooltipManager() const {
214 void NativeWidgetMac::SetCapture() {
215 if (bridge_ && !bridge_->HasCapture())
216 bridge_->AcquireCapture();
219 void NativeWidgetMac::ReleaseCapture() {
221 bridge_->ReleaseCapture();
224 bool NativeWidgetMac::HasCapture() const {
225 return bridge_ && bridge_->HasCapture();
228 InputMethod* NativeWidgetMac::CreateInputMethod() {
229 return bridge_ ? bridge_->CreateInputMethod() : NULL;
232 internal::InputMethodDelegate* NativeWidgetMac::GetInputMethodDelegate() {
233 return bridge_.get();
236 ui::InputMethod* NativeWidgetMac::GetHostInputMethod() {
237 return bridge_ ? bridge_->GetHostInputMethod() : NULL;
240 void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
241 SetSize(WindowSizeForClientAreaSize(GetNativeWindow(), size));
242 // Note that this is not the precise center of screen, but it is the standard
243 // location for windows like dialogs to appear on screen for Mac.
244 // TODO(tapted): If there is a parent window, center in that instead.
245 [GetNativeWindow() center];
248 void NativeWidgetMac::GetWindowPlacement(gfx::Rect* bounds,
249 ui::WindowShowState* maximized) const {
253 bool NativeWidgetMac::SetWindowTitle(const base::string16& title) {
254 NSWindow* window = GetNativeWindow();
255 NSString* current_title = [window title];
256 NSString* new_title = SysUTF16ToNSString(title);
257 if ([current_title isEqualToString:new_title])
260 [window setTitle:new_title];
264 void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
265 const gfx::ImageSkia& app_icon) {
269 void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
273 gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
274 return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]);
277 gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const {
278 NSWindow* window = GetNativeWindow();
279 return gfx::ScreenRectFromNSRect(
280 [window contentRectForFrameRect:[window frame]]);
283 gfx::Rect NativeWidgetMac::GetRestoredBounds() const {
284 return bridge_ ? bridge_->GetRestoredBounds() : gfx::Rect();
287 void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) {
289 bridge_->SetBounds(bounds);
292 void NativeWidgetMac::SetSize(const gfx::Size& size) {
293 // Ensure the top-left corner stays in-place (rather than the bottom-left,
294 // which -[NSWindow setContentSize:] would do).
295 SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size));
298 void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
302 void NativeWidgetMac::StackAtTop() {
306 void NativeWidgetMac::StackBelow(gfx::NativeView native_view) {
310 void NativeWidgetMac::SetShape(gfx::NativeRegion shape) {
314 void NativeWidgetMac::Close() {
318 // Clear the view early to suppress repaints.
319 bridge_->SetRootView(NULL);
321 NSWindow* window = GetNativeWindow();
322 // Calling performClose: will momentarily highlight the close button, but
323 // AppKit will reject it if there is no close button.
324 SEL close_selector = ([window styleMask] & NSClosableWindowMask)
325 ? @selector(performClose:)
327 [window performSelector:close_selector withObject:nil afterDelay:0];
330 void NativeWidgetMac::CloseNow() {
331 // Reset |bridge_| to NULL before destroying it.
332 scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass());
335 void NativeWidgetMac::Show() {
336 ShowWithWindowState(ui::SHOW_STATE_NORMAL);
339 void NativeWidgetMac::Hide() {
343 bridge_->SetVisibilityState(BridgedNativeWidget::HIDE_WINDOW);
346 void NativeWidgetMac::ShowMaximizedWithBounds(
347 const gfx::Rect& restored_bounds) {
351 void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) {
356 case ui::SHOW_STATE_DEFAULT:
357 case ui::SHOW_STATE_NORMAL:
358 case ui::SHOW_STATE_INACTIVE:
360 case ui::SHOW_STATE_MINIMIZED:
361 case ui::SHOW_STATE_MAXIMIZED:
362 case ui::SHOW_STATE_FULLSCREEN:
365 case ui::SHOW_STATE_END:
369 bridge_->SetVisibilityState(state == ui::SHOW_STATE_INACTIVE
370 ? BridgedNativeWidget::SHOW_INACTIVE
371 : BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
374 bool NativeWidgetMac::IsVisible() const {
375 return bridge_ && bridge_->window_visible();
378 void NativeWidgetMac::Activate() {
382 bridge_->SetVisibilityState(BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
385 void NativeWidgetMac::Deactivate() {
389 bool NativeWidgetMac::IsActive() const {
390 return [GetNativeWindow() isKeyWindow];
393 void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) {
397 bool NativeWidgetMac::IsAlwaysOnTop() const {
402 void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
406 void NativeWidgetMac::Maximize() {
407 NOTIMPLEMENTED(); // See IsMaximized().
410 void NativeWidgetMac::Minimize() {
411 NSWindow* window = GetNativeWindow();
412 // Calling performMiniaturize: will momentarily highlight the button, but
413 // AppKit will reject it if there is no miniaturize button.
414 if ([window styleMask] & NSMiniaturizableWindowMask)
415 [window performMiniaturize:nil];
417 [window miniaturize:nil];
420 bool NativeWidgetMac::IsMaximized() const {
421 // The window frame isn't altered on Mac unless going fullscreen. The green
422 // "+" button just makes the window bigger. So, always false.
426 bool NativeWidgetMac::IsMinimized() const {
427 return [GetNativeWindow() isMiniaturized];
430 void NativeWidgetMac::Restore() {
431 [GetNativeWindow() deminiaturize:nil];
434 void NativeWidgetMac::SetFullscreen(bool fullscreen) {
435 if (!bridge_ || fullscreen == IsFullscreen())
438 bridge_->ToggleDesiredFullscreenState();
441 bool NativeWidgetMac::IsFullscreen() const {
442 return bridge_ && bridge_->target_fullscreen_state();
445 void NativeWidgetMac::SetOpacity(unsigned char opacity) {
449 void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) {
453 void NativeWidgetMac::FlashFrame(bool flash_frame) {
457 void NativeWidgetMac::RunShellDrag(View* view,
458 const ui::OSExchangeData& data,
459 const gfx::Point& location,
461 ui::DragDropTypes::DragEventSource source) {
465 void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
466 // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate
467 // system of |rect| has been converted.
468 [GetNativeView() setNeedsDisplay:YES];
469 if (bridge_ && bridge_->layer())
470 bridge_->layer()->SchedulePaint(rect);
473 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
475 bridge_->SetCursor(cursor);
478 bool NativeWidgetMac::IsMouseEventsEnabled() const {
483 void NativeWidgetMac::ClearNativeFocus() {
484 // To quote DesktopWindowTreeHostX11, "This method is weird and misnamed."
485 // The goal is to set focus to the content window, thereby removing focus from
486 // any NSView in the window that doesn't belong to toolkit-views.
487 [GetNativeWindow() makeFirstResponder:GetNativeView()];
490 gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
495 Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
496 const gfx::Vector2d& drag_offset,
497 Widget::MoveLoopSource source,
498 Widget::MoveLoopEscapeBehavior escape_behavior) {
500 return Widget::MOVE_LOOP_CANCELED;
503 void NativeWidgetMac::EndMoveLoop() {
507 void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
511 void NativeWidgetMac::SetVisibilityAnimationDuration(
512 const base::TimeDelta& duration) {
516 void NativeWidgetMac::SetVisibilityAnimationTransition(
517 Widget::VisibilityTransition transition) {
521 ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const {
522 return ui::NativeTheme::instance();
525 void NativeWidgetMac::OnRootViewLayout() {
529 bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
533 void NativeWidgetMac::OnSizeConstraintsChanged() {
537 void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) {
541 ////////////////////////////////////////////////////////////////////////////////
544 bool Widget::ConvertRect(const Widget* source,
545 const Widget* target,
552 ////////////////////////////////////////////////////////////////////////////////
553 // internal::NativeWidgetPrivate, public:
556 NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
557 internal::NativeWidgetDelegate* delegate) {
558 return new NativeWidgetMac(delegate);
562 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
563 gfx::NativeView native_view) {
564 return GetNativeWidgetForNativeWindow([native_view window]);
568 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
569 gfx::NativeWindow native_window) {
570 id<NSWindowDelegate> window_delegate = [native_window delegate];
571 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
572 ViewsNSWindowDelegate* delegate =
573 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
574 return [delegate nativeWidgetMac];
576 return NULL; // Not created by NativeWidgetMac.
580 NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
581 gfx::NativeView native_view) {
582 BridgedNativeWidget* bridge =
583 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
587 for (BridgedNativeWidget* parent;
588 (parent = bridge->parent());
591 return bridge->native_widget_mac();
595 void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
596 Widget::Widgets* children) {
597 BridgedNativeWidget* bridge =
598 NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
602 // If |native_view| is a subview of the contentView, it will share an
603 // NSWindow, but will itself be a native child of the Widget. That is, adding
604 // bridge->..->GetWidget() to |children| would be adding the _parent_ of
605 // |native_view|, not the Widget for |native_view|. |native_view| doesn't have
606 // a corresponding Widget of its own in this case (and so can't have Widget
607 // children of its own on Mac).
608 if (bridge->ns_view() != native_view)
611 // Code expects widget for |native_view| to be added to |children|.
612 if (bridge->native_widget_mac()->GetWidget())
613 children->insert(bridge->native_widget_mac()->GetWidget());
615 for (BridgedNativeWidget* child : bridge->child_windows())
616 GetAllChildWidgets(child->ns_view(), children);
620 void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view,
621 Widget::Widgets* owned) {
626 void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view,
627 gfx::NativeView new_parent) {
632 bool NativeWidgetPrivate::IsMouseButtonDown() {
633 return [NSEvent pressedMouseButtons] != 0;
637 gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() {
639 return gfx::FontList();
642 } // namespace internal