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/views_nswindow_delegate.h"
18 #include "ui/views/window/native_frame_view.h"
20 @interface NativeWidgetMacNSWindow : NSWindow
21 - (ViewsNSWindowDelegate*)viewsNSWindowDelegate;
24 @implementation NativeWidgetMacNSWindow
26 - (ViewsNSWindowDelegate*)viewsNSWindowDelegate {
27 return base::mac::ObjCCastStrict<ViewsNSWindowDelegate>([self delegate]);
30 // Override canBecome{Key,Main}Window to always return YES, otherwise Windows
31 // with a styleMask of NSBorderlessWindowMask default to NO.
32 - (BOOL)canBecomeKeyWindow {
36 - (BOOL)canBecomeMainWindow {
40 // Override orderWindow to intercept visibility changes, since there is no way
41 // to observe these changes via NSWindowDelegate.
42 - (void)orderWindow:(NSWindowOrderingMode)orderingMode
43 relativeTo:(NSInteger)otherWindowNumber {
44 [[self viewsNSWindowDelegate] onWindowOrderWillChange:orderingMode];
45 [super orderWindow:orderingMode relativeTo:otherWindowNumber];
46 [[self viewsNSWindowDelegate] onWindowOrderChanged];
54 NSInteger StyleMaskForParams(const Widget::InitParams& params) {
55 // TODO(tapted): Determine better masks when there are use cases for it.
56 if (params.remove_standard_frame)
57 return NSBorderlessWindowMask;
59 if (params.type == Widget::InitParams::TYPE_WINDOW) {
60 return NSTitledWindowMask | NSClosableWindowMask |
61 NSMiniaturizableWindowMask | NSResizableWindowMask;
63 return NSBorderlessWindowMask;
66 NSRect ValidateContentRect(NSRect content_rect) {
67 // A contentRect with zero width or height is a banned practice in Chrome, due
68 // to unpredictable OSX treatment. For now, silently give a minimum dimension.
69 // TODO(tapted): Add a DCHECK, or add emulation logic (e.g. to auto-hide).
70 if (NSWidth(content_rect) == 0)
71 content_rect.size.width = 1;
73 if (NSHeight(content_rect) == 0)
74 content_rect.size.height = 1;
79 gfx::Size WindowSizeForClientAreaSize(NSWindow* window, const gfx::Size& size) {
80 NSRect content_rect = NSMakeRect(0, 0, size.width(), size.height());
81 NSRect frame_rect = [window frameRectForContentRect:content_rect];
82 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
87 ////////////////////////////////////////////////////////////////////////////////
88 // NativeWidgetMac, public:
90 NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
91 : delegate_(delegate),
92 bridge_(new BridgedNativeWidget(this)),
93 ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
96 NativeWidgetMac::~NativeWidgetMac() {
97 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
103 void NativeWidgetMac::OnWindowWillClose() {
104 delegate_->OnNativeWidgetDestroying();
105 // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed
106 // by the user, or via Close() and a RunLoop, this will reset it.
108 delegate_->OnNativeWidgetDestroyed();
109 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
113 ////////////////////////////////////////////////////////////////////////////////
114 // NativeWidgetMac, internal::NativeWidgetPrivate implementation:
116 void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) {
117 ownership_ = params.ownership;
119 NSInteger style_mask = StyleMaskForParams(params);
120 NSRect content_rect = ValidateContentRect(
121 [NSWindow contentRectForFrameRect:gfx::ScreenRectToNSRect(params.bounds)
122 styleMask:style_mask]);
124 base::scoped_nsobject<NSWindow> window([[NativeWidgetMacNSWindow alloc]
125 initWithContentRect:content_rect
127 backing:NSBackingStoreBuffered
129 [window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject.
130 bridge_->Init(window, params);
132 delegate_->OnNativeWidgetCreated(true);
134 bridge_->SetFocusManager(GetWidget()->GetFocusManager());
136 DCHECK(GetWidget()->GetRootView());
137 bridge_->SetRootView(GetWidget()->GetRootView());
140 NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() {
141 return new NativeFrameView(GetWidget());
144 bool NativeWidgetMac::ShouldUseNativeFrame() const {
148 bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
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 {
184 const ui::Layer* NativeWidgetMac::GetLayer() const {
189 void NativeWidgetMac::ReorderNativeViews() {
191 bridge_->SetRootView(GetWidget()->GetRootView());
194 void NativeWidgetMac::ViewRemoved(View* view) {
198 void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
202 void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
207 TooltipManager* NativeWidgetMac::GetTooltipManager() const {
212 void NativeWidgetMac::SetCapture() {
216 void NativeWidgetMac::ReleaseCapture() {
220 bool NativeWidgetMac::HasCapture() const {
225 InputMethod* NativeWidgetMac::CreateInputMethod() {
226 return bridge_ ? bridge_->CreateInputMethod() : NULL;
229 internal::InputMethodDelegate* NativeWidgetMac::GetInputMethodDelegate() {
230 return bridge_.get();
233 ui::InputMethod* NativeWidgetMac::GetHostInputMethod() {
234 return bridge_ ? bridge_->GetHostInputMethod() : NULL;
237 void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
238 SetSize(WindowSizeForClientAreaSize(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(gfx::Rect* bounds,
246 ui::WindowShowState* maximized) const {
250 bool NativeWidgetMac::SetWindowTitle(const base::string16& title) {
251 NSWindow* window = GetNativeWindow();
252 NSString* current_title = [window title];
253 NSString* new_title = SysUTF16ToNSString(title);
254 if ([current_title isEqualToString:new_title])
257 [window setTitle:new_title];
261 void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
262 const gfx::ImageSkia& app_icon) {
266 void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
270 gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
271 return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]);
274 gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const {
275 NSWindow* window = GetNativeWindow();
276 return gfx::ScreenRectFromNSRect(
277 [window contentRectForFrameRect:[window frame]]);
280 gfx::Rect NativeWidgetMac::GetRestoredBounds() const {
281 return bridge_ ? bridge_->GetRestoredBounds() : gfx::Rect();
284 void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) {
285 [GetNativeWindow() setFrame:gfx::ScreenRectToNSRect(bounds)
290 void NativeWidgetMac::SetSize(const gfx::Size& size) {
291 // Ensure the top-left corner stays in-place (rather than the bottom-left,
292 // which -[NSWindow setContentSize:] would do).
293 SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size));
296 void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
300 void NativeWidgetMac::StackAtTop() {
304 void NativeWidgetMac::StackBelow(gfx::NativeView native_view) {
308 void NativeWidgetMac::SetShape(gfx::NativeRegion shape) {
312 void NativeWidgetMac::Close() {
316 // Clear the view early to suppress repaints.
317 bridge_->SetRootView(NULL);
319 NSWindow* window = GetNativeWindow();
320 // Calling performClose: will momentarily highlight the close button, but
321 // AppKit will reject it if there is no close button.
322 SEL close_selector = ([window styleMask] & NSClosableWindowMask)
323 ? @selector(performClose:)
325 [window performSelector:close_selector withObject:nil afterDelay:0];
328 void NativeWidgetMac::CloseNow() {
329 // Reset |bridge_| to NULL before destroying it.
330 scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass());
333 void NativeWidgetMac::Show() {
334 ShowWithWindowState(ui::SHOW_STATE_NORMAL);
337 void NativeWidgetMac::Hide() {
341 void NativeWidgetMac::ShowMaximizedWithBounds(
342 const gfx::Rect& restored_bounds) {
346 void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) {
348 case ui::SHOW_STATE_DEFAULT:
349 case ui::SHOW_STATE_NORMAL:
350 case ui::SHOW_STATE_INACTIVE:
352 case ui::SHOW_STATE_MINIMIZED:
353 case ui::SHOW_STATE_MAXIMIZED:
354 case ui::SHOW_STATE_FULLSCREEN:
357 case ui::SHOW_STATE_END:
361 if (state == ui::SHOW_STATE_INACTIVE) {
363 [GetNativeWindow() orderBack:nil];
369 bool NativeWidgetMac::IsVisible() const {
370 return [GetNativeWindow() isVisible];
373 void NativeWidgetMac::Activate() {
374 [GetNativeWindow() makeKeyAndOrderFront:nil];
375 [NSApp activateIgnoringOtherApps:YES];
378 void NativeWidgetMac::Deactivate() {
382 bool NativeWidgetMac::IsActive() const {
383 // To behave like ::GetActiveWindow on Windows, IsActive() must return the
384 // "active" window attached to the calling application. NSWindow provides
385 // -isKeyWindow and -isMainWindow, but these are system-wide and update
386 // asynchronously. A window can not be main or key on Mac without the
387 // application being active.
388 // Here, define the active window as the frontmost visible window in the
390 // Note that this might not be the keyWindow, even when Chrome is active.
391 // Also note that -[NSApplication orderedWindows] excludes panels and other
392 // "unscriptable" windows, but includes invisible windows.
396 NSWindow* window = GetNativeWindow();
397 for (NSWindow* other_window in [NSApp orderedWindows]) {
398 if ([window isEqual:other_window])
401 if ([other_window isVisible])
408 void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) {
412 bool NativeWidgetMac::IsAlwaysOnTop() const {
417 void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
421 void NativeWidgetMac::Maximize() {
422 NOTIMPLEMENTED(); // See IsMaximized().
425 void NativeWidgetMac::Minimize() {
429 bool NativeWidgetMac::IsMaximized() const {
430 // The window frame isn't altered on Mac unless going fullscreen. The green
431 // "+" button just makes the window bigger. So, always false.
435 bool NativeWidgetMac::IsMinimized() const {
440 void NativeWidgetMac::Restore() {
444 void NativeWidgetMac::SetFullscreen(bool fullscreen) {
445 if (!bridge_ || fullscreen == IsFullscreen())
448 bridge_->ToggleDesiredFullscreenState();
451 bool NativeWidgetMac::IsFullscreen() const {
452 return bridge_ && bridge_->target_fullscreen_state();
455 void NativeWidgetMac::SetOpacity(unsigned char opacity) {
459 void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) {
463 void NativeWidgetMac::FlashFrame(bool flash_frame) {
467 void NativeWidgetMac::RunShellDrag(View* view,
468 const ui::OSExchangeData& data,
469 const gfx::Point& location,
471 ui::DragDropTypes::DragEventSource source) {
475 void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
476 // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate
477 // system of |rect| has been converted.
478 [GetNativeView() setNeedsDisplay:YES];
481 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
485 bool NativeWidgetMac::IsMouseEventsEnabled() const {
490 void NativeWidgetMac::ClearNativeFocus() {
494 gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
499 Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
500 const gfx::Vector2d& drag_offset,
501 Widget::MoveLoopSource source,
502 Widget::MoveLoopEscapeBehavior escape_behavior) {
504 return Widget::MOVE_LOOP_CANCELED;
507 void NativeWidgetMac::EndMoveLoop() {
511 void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
515 void NativeWidgetMac::SetVisibilityAnimationDuration(
516 const base::TimeDelta& duration) {
520 void NativeWidgetMac::SetVisibilityAnimationTransition(
521 Widget::VisibilityTransition transition) {
525 ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const {
526 return ui::NativeTheme::instance();
529 void NativeWidgetMac::OnRootViewLayout() {
533 bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
537 void NativeWidgetMac::OnSizeConstraintsChanged() {
541 void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) {
545 ////////////////////////////////////////////////////////////////////////////////
548 bool Widget::ConvertRect(const Widget* source,
549 const Widget* target,
556 ////////////////////////////////////////////////////////////////////////////////
557 // internal::NativeWidgetPrivate, public:
560 NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
561 internal::NativeWidgetDelegate* delegate) {
562 return new NativeWidgetMac(delegate);
566 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
567 gfx::NativeView native_view) {
568 return GetNativeWidgetForNativeWindow([native_view window]);
572 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
573 gfx::NativeWindow native_window) {
574 id<NSWindowDelegate> window_delegate = [native_window delegate];
575 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
576 ViewsNSWindowDelegate* delegate =
577 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
578 return [delegate nativeWidgetMac];
580 return NULL; // Not created by NativeWidgetMac.
584 NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
585 gfx::NativeView native_view) {
586 NativeWidgetPrivate* native_widget =
587 GetNativeWidgetForNativeView(native_view);
591 for (NativeWidgetPrivate* parent;
592 (parent = GetNativeWidgetForNativeWindow(
593 [native_widget->GetNativeWindow() parentWindow]));
594 native_widget = parent) {
596 return native_widget;
600 void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
601 Widget::Widgets* children) {
602 NativeWidgetPrivate* native_widget =
603 GetNativeWidgetForNativeView(native_view);
607 // Code expects widget for |native_view| to be added to |children|.
608 if (native_widget->GetWidget())
609 children->insert(native_widget->GetWidget());
611 for (NSWindow* child_window : [native_widget->GetNativeWindow() childWindows])
612 GetAllChildWidgets([child_window contentView], 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