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.
4
5#include "ui/views/widget/native_widget_mac.h"
6
7#import <Cocoa/Cocoa.h>
8
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"
19
20@interface NativeWidgetMacNSWindow : NSWindow
21@end
22
23@implementation NativeWidgetMacNSWindow
24
25// Override canBecome{Key,Main}Window to always return YES, otherwise Windows
26// with a styleMask of NSBorderlessWindowMask default to NO.
27- (BOOL)canBecomeKeyWindow {
28  return YES;
29}
30
31- (BOOL)canBecomeMainWindow {
32  return YES;
33}
34
35@end
36
37namespace views {
38namespace {
39
40NSInteger StyleMaskForParams(const Widget::InitParams& params) {
41  // TODO(tapted): Determine better masks when there are use cases for it.
42  if (params.remove_standard_frame)
43    return NSBorderlessWindowMask;
44
45  if (params.type == Widget::InitParams::TYPE_WINDOW) {
46    return NSTitledWindowMask | NSClosableWindowMask |
47           NSMiniaturizableWindowMask | NSResizableWindowMask;
48  }
49  return NSBorderlessWindowMask;
50}
51
52NSRect ValidateContentRect(NSRect content_rect) {
53  // A contentRect with zero width or height is a banned practice in Chrome, due
54  // to unpredictable OSX treatment. For now, silently give a minimum dimension.
55  // TODO(tapted): Add a DCHECK, or add emulation logic (e.g. to auto-hide).
56  if (NSWidth(content_rect) == 0)
57    content_rect.size.width = 1;
58
59  if (NSHeight(content_rect) == 0)
60    content_rect.size.height = 1;
61
62  return content_rect;
63}
64
65gfx::Size WindowSizeForClientAreaSize(NSWindow* window, const gfx::Size& size) {
66  NSRect content_rect = NSMakeRect(0, 0, size.width(), size.height());
67  NSRect frame_rect = [window frameRectForContentRect:content_rect];
68  return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
69}
70
71}  // namespace
72
73////////////////////////////////////////////////////////////////////////////////
74// NativeWidgetMac, public:
75
76NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
77    : delegate_(delegate),
78      bridge_(new BridgedNativeWidget(this)),
79      ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
80}
81
82NativeWidgetMac::~NativeWidgetMac() {
83  if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
84    delete delegate_;
85  else
86    CloseNow();
87}
88
89void NativeWidgetMac::OnWindowWillClose() {
90  delegate_->OnNativeWidgetDestroying();
91  // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed
92  // by the user, or via Close() and a RunLoop, this will reset it.
93  bridge_.reset();
94  delegate_->OnNativeWidgetDestroyed();
95  if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
96    delete this;
97}
98
99////////////////////////////////////////////////////////////////////////////////
100// NativeWidgetMac, internal::NativeWidgetPrivate implementation:
101
102void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) {
103  ownership_ = params.ownership;
104
105  NSInteger style_mask = StyleMaskForParams(params);
106  NSRect content_rect = ValidateContentRect(
107      [NSWindow contentRectForFrameRect:gfx::ScreenRectToNSRect(params.bounds)
108                              styleMask:style_mask]);
109
110  base::scoped_nsobject<NSWindow> window([[NativeWidgetMacNSWindow alloc]
111      initWithContentRect:content_rect
112                styleMask:style_mask
113                  backing:NSBackingStoreBuffered
114                    defer:YES]);
115  [window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
116  bridge_->Init(window, params);
117
118  delegate_->OnNativeWidgetCreated(true);
119
120  bridge_->SetFocusManager(GetWidget()->GetFocusManager());
121
122  DCHECK(GetWidget()->GetRootView());
123  bridge_->SetRootView(GetWidget()->GetRootView());
124}
125
126NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() {
127  return new NativeFrameView(GetWidget());
128}
129
130bool NativeWidgetMac::ShouldUseNativeFrame() const {
131  return true;
132}
133
134bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
135  NOTIMPLEMENTED();
136  return false;
137}
138
139void NativeWidgetMac::FrameTypeChanged() {
140  NOTIMPLEMENTED();
141}
142
143Widget* NativeWidgetMac::GetWidget() {
144  return delegate_->AsWidget();
145}
146
147const Widget* NativeWidgetMac::GetWidget() const {
148  return delegate_->AsWidget();
149}
150
151gfx::NativeView NativeWidgetMac::GetNativeView() const {
152  // Returns a BridgedContentView, unless there is no views::RootView set.
153  return [GetNativeWindow() contentView];
154}
155
156gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
157  return bridge_ ? bridge_->ns_window() : nil;
158}
159
160Widget* NativeWidgetMac::GetTopLevelWidget() {
161  NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView());
162  return native_widget ? native_widget->GetWidget() : NULL;
163}
164
165const ui::Compositor* NativeWidgetMac::GetCompositor() const {
166  NOTIMPLEMENTED();
167  return NULL;
168}
169
170ui::Compositor* NativeWidgetMac::GetCompositor() {
171  NOTIMPLEMENTED();
172  return NULL;
173}
174
175ui::Layer* NativeWidgetMac::GetLayer() {
176  NOTIMPLEMENTED();
177  return NULL;
178}
179
180void NativeWidgetMac::ReorderNativeViews() {
181  if (bridge_)
182    bridge_->SetRootView(GetWidget()->GetRootView());
183}
184
185void NativeWidgetMac::ViewRemoved(View* view) {
186  NOTIMPLEMENTED();
187}
188
189void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
190  NOTIMPLEMENTED();
191}
192
193void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
194  NOTIMPLEMENTED();
195  return NULL;
196}
197
198TooltipManager* NativeWidgetMac::GetTooltipManager() const {
199  NOTIMPLEMENTED();
200  return NULL;
201}
202
203void NativeWidgetMac::SetCapture() {
204  NOTIMPLEMENTED();
205}
206
207void NativeWidgetMac::ReleaseCapture() {
208  NOTIMPLEMENTED();
209}
210
211bool NativeWidgetMac::HasCapture() const {
212  NOTIMPLEMENTED();
213  return false;
214}
215
216InputMethod* NativeWidgetMac::CreateInputMethod() {
217  return bridge_ ? bridge_->CreateInputMethod() : NULL;
218}
219
220internal::InputMethodDelegate* NativeWidgetMac::GetInputMethodDelegate() {
221  return bridge_.get();
222}
223
224ui::InputMethod* NativeWidgetMac::GetHostInputMethod() {
225  return bridge_ ? bridge_->GetHostInputMethod() : NULL;
226}
227
228void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
229  SetSize(WindowSizeForClientAreaSize(GetNativeWindow(), size));
230  // Note that this is not the precise center of screen, but it is the standard
231  // location for windows like dialogs to appear on screen for Mac.
232  // TODO(tapted): If there is a parent window, center in that instead.
233  [GetNativeWindow() center];
234}
235
236void NativeWidgetMac::GetWindowPlacement(gfx::Rect* bounds,
237                                         ui::WindowShowState* maximized) const {
238  NOTIMPLEMENTED();
239}
240
241bool NativeWidgetMac::SetWindowTitle(const base::string16& title) {
242  NSWindow* window = GetNativeWindow();
243  NSString* current_title = [window title];
244  NSString* new_title = SysUTF16ToNSString(title);
245  if ([current_title isEqualToString:new_title])
246    return false;
247
248  [window setTitle:new_title];
249  return true;
250}
251
252void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
253                                     const gfx::ImageSkia& app_icon) {
254  NOTIMPLEMENTED();
255}
256
257void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
258  NOTIMPLEMENTED();
259}
260
261gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
262  return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]);
263}
264
265gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const {
266  NSWindow* window = GetNativeWindow();
267  return gfx::ScreenRectFromNSRect(
268      [window contentRectForFrameRect:[window frame]]);
269}
270
271gfx::Rect NativeWidgetMac::GetRestoredBounds() const {
272  NOTIMPLEMENTED();
273  return gfx::Rect();
274}
275
276void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) {
277  [GetNativeWindow() setFrame:gfx::ScreenRectToNSRect(bounds)
278                      display:YES
279                      animate:NO];
280}
281
282void NativeWidgetMac::SetSize(const gfx::Size& size) {
283  // Ensure the top-left corner stays in-place (rather than the bottom-left,
284  // which -[NSWindow setContentSize:] would do).
285  SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size));
286}
287
288void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
289  NOTIMPLEMENTED();
290}
291
292void NativeWidgetMac::StackAtTop() {
293  NOTIMPLEMENTED();
294}
295
296void NativeWidgetMac::StackBelow(gfx::NativeView native_view) {
297  NOTIMPLEMENTED();
298}
299
300void NativeWidgetMac::SetShape(gfx::NativeRegion shape) {
301  NOTIMPLEMENTED();
302}
303
304void NativeWidgetMac::Close() {
305  NSWindow* window = GetNativeWindow();
306  // Calling performClose: will momentarily highlight the close button, but
307  // AppKit will reject it if there is no close button.
308  SEL close_selector = ([window styleMask] & NSClosableWindowMask)
309                           ? @selector(performClose:)
310                           : @selector(close);
311  [window performSelector:close_selector withObject:nil afterDelay:0];
312}
313
314void NativeWidgetMac::CloseNow() {
315  // Reset |bridge_| to NULL before destroying it.
316  scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass());
317}
318
319void NativeWidgetMac::Show() {
320  ShowWithWindowState(ui::SHOW_STATE_NORMAL);
321}
322
323void NativeWidgetMac::Hide() {
324  NOTIMPLEMENTED();
325}
326
327void NativeWidgetMac::ShowMaximizedWithBounds(
328    const gfx::Rect& restored_bounds) {
329  NOTIMPLEMENTED();
330}
331
332void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) {
333  switch (state) {
334    case ui::SHOW_STATE_DEFAULT:
335    case ui::SHOW_STATE_NORMAL:
336    case ui::SHOW_STATE_INACTIVE:
337      break;
338    case ui::SHOW_STATE_MINIMIZED:
339    case ui::SHOW_STATE_MAXIMIZED:
340    case ui::SHOW_STATE_FULLSCREEN:
341      NOTIMPLEMENTED();
342      break;
343    case ui::SHOW_STATE_END:
344      NOTREACHED();
345      break;
346  }
347  if (state == ui::SHOW_STATE_INACTIVE) {
348    if (!IsVisible())
349      [GetNativeWindow() orderBack:nil];
350  } else {
351    Activate();
352  }
353}
354
355bool NativeWidgetMac::IsVisible() const {
356  return [GetNativeWindow() isVisible];
357}
358
359void NativeWidgetMac::Activate() {
360  [GetNativeWindow() makeKeyAndOrderFront:nil];
361  [NSApp activateIgnoringOtherApps:YES];
362}
363
364void NativeWidgetMac::Deactivate() {
365  NOTIMPLEMENTED();
366}
367
368bool NativeWidgetMac::IsActive() const {
369  // To behave like ::GetActiveWindow on Windows, IsActive() must return the
370  // "active" window attached to the calling application. NSWindow provides
371  // -isKeyWindow and -isMainWindow, but these are system-wide and update
372  // asynchronously. A window can not be main or key on Mac without the
373  // application being active.
374  // Here, define the active window as the frontmost visible window in the
375  // application.
376  // Note that this might not be the keyWindow, even when Chrome is active.
377  // Also note that -[NSApplication orderedWindows] excludes panels and other
378  // "unscriptable" windows, but includes invisible windows.
379  if (!IsVisible())
380    return false;
381
382  NSWindow* window = GetNativeWindow();
383  for (NSWindow* other_window in [NSApp orderedWindows]) {
384    if ([window isEqual:other_window])
385      return true;
386
387    if ([other_window isVisible])
388      return false;
389  }
390
391  return false;
392}
393
394void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) {
395  NOTIMPLEMENTED();
396}
397
398bool NativeWidgetMac::IsAlwaysOnTop() const {
399  NOTIMPLEMENTED();
400  return false;
401}
402
403void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
404  NOTIMPLEMENTED();
405}
406
407void NativeWidgetMac::Maximize() {
408  NOTIMPLEMENTED();
409}
410
411void NativeWidgetMac::Minimize() {
412  NOTIMPLEMENTED();
413}
414
415bool NativeWidgetMac::IsMaximized() const {
416  NOTIMPLEMENTED();
417  return false;
418}
419
420bool NativeWidgetMac::IsMinimized() const {
421  NOTIMPLEMENTED();
422  return false;
423}
424
425void NativeWidgetMac::Restore() {
426  NOTIMPLEMENTED();
427}
428
429void NativeWidgetMac::SetFullscreen(bool fullscreen) {
430  NOTIMPLEMENTED();
431}
432
433bool NativeWidgetMac::IsFullscreen() const {
434  NOTIMPLEMENTED();
435  return false;
436}
437
438void NativeWidgetMac::SetOpacity(unsigned char opacity) {
439  NOTIMPLEMENTED();
440}
441
442void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) {
443  NOTIMPLEMENTED();
444}
445
446void NativeWidgetMac::FlashFrame(bool flash_frame) {
447  NOTIMPLEMENTED();
448}
449
450void NativeWidgetMac::RunShellDrag(View* view,
451                                   const ui::OSExchangeData& data,
452                                   const gfx::Point& location,
453                                   int operation,
454                                   ui::DragDropTypes::DragEventSource source) {
455  NOTIMPLEMENTED();
456}
457
458void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
459  // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate
460  // system of |rect| has been converted.
461  [GetNativeView() setNeedsDisplay:YES];
462}
463
464void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
465  NOTIMPLEMENTED();
466}
467
468bool NativeWidgetMac::IsMouseEventsEnabled() const {
469  NOTIMPLEMENTED();
470  return true;
471}
472
473void NativeWidgetMac::ClearNativeFocus() {
474  NOTIMPLEMENTED();
475}
476
477gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
478  NOTIMPLEMENTED();
479  return gfx::Rect();
480}
481
482Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
483    const gfx::Vector2d& drag_offset,
484    Widget::MoveLoopSource source,
485    Widget::MoveLoopEscapeBehavior escape_behavior) {
486  NOTIMPLEMENTED();
487  return Widget::MOVE_LOOP_CANCELED;
488}
489
490void NativeWidgetMac::EndMoveLoop() {
491  NOTIMPLEMENTED();
492}
493
494void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
495  NOTIMPLEMENTED();
496}
497
498ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const {
499  return ui::NativeTheme::instance();
500}
501
502void NativeWidgetMac::OnRootViewLayout() {
503  NOTIMPLEMENTED();
504}
505
506bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
507  return false;
508}
509
510void NativeWidgetMac::OnSizeConstraintsChanged() {
511  NOTIMPLEMENTED();
512}
513
514void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) {
515  NOTIMPLEMENTED();
516}
517
518////////////////////////////////////////////////////////////////////////////////
519// Widget, public:
520
521bool Widget::ConvertRect(const Widget* source,
522                         const Widget* target,
523                         gfx::Rect* rect) {
524  return false;
525}
526
527namespace internal {
528
529////////////////////////////////////////////////////////////////////////////////
530// internal::NativeWidgetPrivate, public:
531
532// static
533NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
534    internal::NativeWidgetDelegate* delegate) {
535  return new NativeWidgetMac(delegate);
536}
537
538// static
539NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
540    gfx::NativeView native_view) {
541  return GetNativeWidgetForNativeWindow([native_view window]);
542}
543
544// static
545NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
546    gfx::NativeWindow native_window) {
547  id<NSWindowDelegate> window_delegate = [native_window delegate];
548  if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
549    ViewsNSWindowDelegate* delegate =
550        base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
551    return [delegate nativeWidgetMac];
552  }
553  return NULL;  // Not created by NativeWidgetMac.
554}
555
556// static
557NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
558    gfx::NativeView native_view) {
559  NativeWidgetPrivate* native_widget =
560      GetNativeWidgetForNativeView(native_view);
561  if (!native_widget)
562    return NULL;
563
564  for (NativeWidgetPrivate* parent;
565       (parent = GetNativeWidgetForNativeWindow(
566            [native_widget->GetNativeWindow() parentWindow]));
567       native_widget = parent) {
568  }
569  return native_widget;
570}
571
572// static
573void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
574                                             Widget::Widgets* children) {
575  NativeWidgetPrivate* native_widget =
576      GetNativeWidgetForNativeView(native_view);
577  if (!native_widget)
578    return;
579
580  // Code expects widget for |native_view| to be added to |children|.
581  if (native_widget->GetWidget())
582    children->insert(native_widget->GetWidget());
583
584  for (NSWindow* child_window : [native_widget->GetNativeWindow() childWindows])
585    GetAllChildWidgets([child_window contentView], children);
586}
587
588// static
589void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view,
590                                             Widget::Widgets* owned) {
591  NOTIMPLEMENTED();
592}
593
594// static
595void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view,
596                                             gfx::NativeView new_parent) {
597  NOTIMPLEMENTED();
598}
599
600// static
601bool NativeWidgetPrivate::IsMouseButtonDown() {
602  return [NSEvent pressedMouseButtons] != 0;
603}
604
605// static
606gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() {
607  NOTIMPLEMENTED();
608  return gfx::FontList();
609}
610
611}  // namespace internal
612}  // namespace views
613