Popular sites on the NTP: Favicon improvements
[chromium-blink-merge.git] / ui / views / widget / native_widget_mac.mm
blobc5546ea187898ce84721791b8be94accbd550ab0
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> {
28  @private
29   base::scoped_nsobject<NSWindow> window_;
30   base::scoped_nsobject<NSAnimation> animation_;
33 + (void)closeWindowWithAnimation:(NSWindow*)window;
35 @end
37 namespace views {
38 namespace {
40 NSInteger 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;
45   if (params.type == Widget::InitParams::TYPE_WINDOW) {
46     return NSTitledWindowMask | NSClosableWindowMask |
47            NSMiniaturizableWindowMask | NSResizableWindowMask |
48            NSTexturedBackgroundWindowMask;
49   }
50   return NSBorderlessWindowMask;
53 }  // namespace
55 ////////////////////////////////////////////////////////////////////////////////
56 // NativeWidgetMac, public:
58 NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
59     : delegate_(delegate),
60       bridge_(new BridgedNativeWidget(this)),
61       ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
64 NativeWidgetMac::~NativeWidgetMac() {
65   if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
66     delete delegate_;
67   else
68     CloseNow();
71 // static
72 BridgedNativeWidget* NativeWidgetMac::GetBridgeForNativeWindow(
73     gfx::NativeWindow window) {
74   id<NSWindowDelegate> window_delegate = [window delegate];
75   if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
76     ViewsNSWindowDelegate* delegate =
77         base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
78     return [delegate nativeWidgetMac]->bridge_.get();
79   }
80   return nullptr;  // Not created by NativeWidgetMac.
83 bool NativeWidgetMac::IsWindowModalSheet() const {
84   return GetWidget()->widget_delegate()->GetModalType() ==
85          ui::MODAL_TYPE_WINDOW;
88 void NativeWidgetMac::OnWindowWillClose() {
89   // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed
90   // by the user, or via Close() and a RunLoop, notify observers while |bridge_|
91   // is still a valid pointer, then reset it.
92   if (bridge_) {
93     delegate_->OnNativeWidgetDestroying();
94     bridge_.reset();
95   }
96   delegate_->OnNativeWidgetDestroyed();
97   if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
98     delete this;
101 ////////////////////////////////////////////////////////////////////////////////
102 // NativeWidgetMac, internal::NativeWidgetPrivate implementation:
104 void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) {
105   ownership_ = params.ownership;
106   base::scoped_nsobject<NSWindow> window([CreateNSWindow(params) retain]);
107   [window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
108   bridge_->Init(window, params);
110   // Only set always-on-top here if it is true since setting it may affect how
111   // the window is treated by Expose.
112   if (params.keep_on_top)
113     SetAlwaysOnTop(true);
115   delegate_->OnNativeWidgetCreated(true);
117   bridge_->SetFocusManager(GetWidget()->GetFocusManager());
119   DCHECK(GetWidget()->GetRootView());
120   bridge_->SetRootView(GetWidget()->GetRootView());
122   // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit().
123   DCHECK_NE(Widget::InitParams::INFER_OPACITY, params.opacity);
124   bool translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW;
125   bridge_->CreateLayer(params.layer_type, translucent);
128 NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() {
129   return new NativeFrameView(GetWidget());
132 bool NativeWidgetMac::ShouldUseNativeFrame() const {
133   return true;
136 bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
137   // On Windows, this returns true when Aero is enabled which draws the titlebar
138   // with translucency.
139   return false;
142 void NativeWidgetMac::FrameTypeChanged() {
143   NOTIMPLEMENTED();
146 Widget* NativeWidgetMac::GetWidget() {
147   return delegate_->AsWidget();
150 const Widget* NativeWidgetMac::GetWidget() const {
151   return delegate_->AsWidget();
154 gfx::NativeView NativeWidgetMac::GetNativeView() const {
155   // Returns a BridgedContentView, unless there is no views::RootView set.
156   return [GetNativeWindow() contentView];
159 gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
160   return bridge_ ? bridge_->ns_window() : nil;
163 Widget* NativeWidgetMac::GetTopLevelWidget() {
164   NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView());
165   return native_widget ? native_widget->GetWidget() : NULL;
168 const ui::Compositor* NativeWidgetMac::GetCompositor() const {
169   return bridge_ && bridge_->layer() ? bridge_->layer()->GetCompositor()
170                                      : nullptr;
173 const ui::Layer* NativeWidgetMac::GetLayer() const {
174   return bridge_ ? bridge_->layer() : nullptr;
177 void NativeWidgetMac::ReorderNativeViews() {
178   if (bridge_)
179     bridge_->SetRootView(GetWidget()->GetRootView());
182 void NativeWidgetMac::ViewRemoved(View* view) {
183   NOTIMPLEMENTED();
186 void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
187   if (bridge_)
188     bridge_->SetNativeWindowProperty(name, value);
191 void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
192   if (bridge_)
193     return bridge_->GetNativeWindowProperty(name);
195   return nullptr;
198 TooltipManager* NativeWidgetMac::GetTooltipManager() const {
199   if (bridge_)
200     return bridge_->tooltip_manager();
202   return nullptr;
205 void NativeWidgetMac::SetCapture() {
206   if (bridge_ && !bridge_->HasCapture())
207     bridge_->AcquireCapture();
210 void NativeWidgetMac::ReleaseCapture() {
211   if (bridge_)
212     bridge_->ReleaseCapture();
215 bool NativeWidgetMac::HasCapture() const {
216   return bridge_ && bridge_->HasCapture();
219 ui::InputMethod* NativeWidgetMac::GetInputMethod() {
220   return bridge_ ? bridge_->GetInputMethod() : NULL;
223 void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
224   SetSize(
225       BridgedNativeWidget::GetWindowSizeForClientSize(GetNativeWindow(), size));
226   // Note that this is not the precise center of screen, but it is the standard
227   // location for windows like dialogs to appear on screen for Mac.
228   // TODO(tapted): If there is a parent window, center in that instead.
229   [GetNativeWindow() center];
232 void NativeWidgetMac::GetWindowPlacement(
233     gfx::Rect* bounds,
234     ui::WindowShowState* show_state) const {
235   *bounds = GetRestoredBounds();
236   if (IsFullscreen())
237     *show_state = ui::SHOW_STATE_FULLSCREEN;
238   else if (IsMinimized())
239     *show_state = ui::SHOW_STATE_MINIMIZED;
240   else
241     *show_state = ui::SHOW_STATE_NORMAL;
244 bool NativeWidgetMac::SetWindowTitle(const base::string16& title) {
245   NSWindow* window = GetNativeWindow();
246   NSString* current_title = [window title];
247   NSString* new_title = SysUTF16ToNSString(title);
248   if ([current_title isEqualToString:new_title])
249     return false;
251   [window setTitle:new_title];
252   return true;
255 void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
256                                      const gfx::ImageSkia& app_icon) {
257   NOTIMPLEMENTED();
260 void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
261   if (modal_type == ui::MODAL_TYPE_NONE)
262     return;
264   // System modal windows not implemented (or used) on Mac.
265   DCHECK_NE(ui::MODAL_TYPE_SYSTEM, modal_type);
266   DCHECK(bridge_->parent());
267   // Everyhing happens upon show.
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   if (bridge_)
286     bridge_->SetBounds(bounds);
289 void NativeWidgetMac::SetSize(const gfx::Size& size) {
290   // Ensure the top-left corner stays in-place (rather than the bottom-left,
291   // which -[NSWindow setContentSize:] would do).
292   SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size));
295 void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
296   NOTIMPLEMENTED();
299 void NativeWidgetMac::StackAtTop() {
300   NOTIMPLEMENTED();
303 void NativeWidgetMac::StackBelow(gfx::NativeView native_view) {
304   NOTIMPLEMENTED();
307 void NativeWidgetMac::SetShape(SkRegion* shape) {
308   NOTIMPLEMENTED();
311 void NativeWidgetMac::Close() {
312   if (!bridge_)
313     return;
315   NSWindow* window = GetNativeWindow();
316   if (IsWindowModalSheet()) {
317     // Sheets can't be closed normally. This starts the sheet closing. Once the
318     // sheet has finished animating, it will call sheetDidEnd: on the parent
319     // window's delegate. Note it still needs to be asynchronous, since code
320     // calling Widget::Close() doesn't expect things to be deleted upon return.
321     [NSApp performSelector:@selector(endSheet:) withObject:window afterDelay:0];
322     return;
323   }
325   // For other modal types, animate the close.
326   if (delegate_->IsModal()) {
327     [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window];
328     return;
329   }
331   // Clear the view early to suppress repaints.
332   bridge_->SetRootView(NULL);
334   // Calling performClose: will momentarily highlight the close button, but
335   // AppKit will reject it if there is no close button.
336   SEL close_selector = ([window styleMask] & NSClosableWindowMask)
337                            ? @selector(performClose:)
338                            : @selector(close);
339   [window performSelector:close_selector withObject:nil afterDelay:0];
342 void NativeWidgetMac::CloseNow() {
343   if (!bridge_)
344     return;
346   // Notify observers while |bridged_| is still valid.
347   delegate_->OnNativeWidgetDestroying();
348   // Reset |bridge_| to NULL before destroying it.
349   scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass());
352 void NativeWidgetMac::Show() {
353   ShowWithWindowState(ui::SHOW_STATE_NORMAL);
356 void NativeWidgetMac::Hide() {
357   if (!bridge_)
358     return;
360   bridge_->SetVisibilityState(BridgedNativeWidget::HIDE_WINDOW);
363 void NativeWidgetMac::ShowMaximizedWithBounds(
364     const gfx::Rect& restored_bounds) {
365   NOTIMPLEMENTED();
368 void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) {
369   if (!bridge_)
370     return;
372   switch (state) {
373     case ui::SHOW_STATE_DEFAULT:
374     case ui::SHOW_STATE_NORMAL:
375     case ui::SHOW_STATE_INACTIVE:
376       break;
377     case ui::SHOW_STATE_MINIMIZED:
378     case ui::SHOW_STATE_MAXIMIZED:
379     case ui::SHOW_STATE_FULLSCREEN:
380     case ui::SHOW_STATE_DOCKED:
381       NOTIMPLEMENTED();
382       break;
383     case ui::SHOW_STATE_END:
384       NOTREACHED();
385       break;
386   }
387   bridge_->SetVisibilityState(state == ui::SHOW_STATE_INACTIVE
388       ? BridgedNativeWidget::SHOW_INACTIVE
389       : BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
392 bool NativeWidgetMac::IsVisible() const {
393   return bridge_ && bridge_->window_visible();
396 void NativeWidgetMac::Activate() {
397   if (!bridge_)
398     return;
400   bridge_->SetVisibilityState(BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW);
403 void NativeWidgetMac::Deactivate() {
404   NOTIMPLEMENTED();
407 bool NativeWidgetMac::IsActive() const {
408   return [GetNativeWindow() isKeyWindow];
411 void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) {
412   gfx::SetNSWindowAlwaysOnTop(GetNativeWindow(), always_on_top);
415 bool NativeWidgetMac::IsAlwaysOnTop() const {
416   return gfx::IsNSWindowAlwaysOnTop(GetNativeWindow());
419 void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
420   gfx::SetNSWindowVisibleOnAllWorkspaces(GetNativeWindow(), always_visible);
423 void NativeWidgetMac::Maximize() {
424   NOTIMPLEMENTED();  // See IsMaximized().
427 void NativeWidgetMac::Minimize() {
428   NSWindow* window = GetNativeWindow();
429   // Calling performMiniaturize: will momentarily highlight the button, but
430   // AppKit will reject it if there is no miniaturize button.
431   if ([window styleMask] & NSMiniaturizableWindowMask)
432     [window performMiniaturize:nil];
433   else
434     [window miniaturize:nil];
437 bool NativeWidgetMac::IsMaximized() const {
438   // The window frame isn't altered on Mac unless going fullscreen. The green
439   // "+" button just makes the window bigger. So, always false.
440   return false;
443 bool NativeWidgetMac::IsMinimized() const {
444   return [GetNativeWindow() isMiniaturized];
447 void NativeWidgetMac::Restore() {
448   SetFullscreen(false);
449   [GetNativeWindow() deminiaturize:nil];
452 void NativeWidgetMac::SetFullscreen(bool fullscreen) {
453   if (!bridge_ || fullscreen == IsFullscreen())
454     return;
456   bridge_->ToggleDesiredFullscreenState();
459 bool NativeWidgetMac::IsFullscreen() const {
460   return bridge_ && bridge_->target_fullscreen_state();
463 void NativeWidgetMac::SetOpacity(unsigned char opacity) {
464   NOTIMPLEMENTED();
467 void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) {
468   NOTIMPLEMENTED();
471 void NativeWidgetMac::FlashFrame(bool flash_frame) {
472   NOTIMPLEMENTED();
475 void NativeWidgetMac::RunShellDrag(View* view,
476                                    const ui::OSExchangeData& data,
477                                    const gfx::Point& location,
478                                    int operation,
479                                    ui::DragDropTypes::DragEventSource source) {
480   NOTIMPLEMENTED();
483 void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
484   // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate
485   // system of |rect| has been converted.
486   [GetNativeView() setNeedsDisplay:YES];
487   if (bridge_ && bridge_->layer())
488     bridge_->layer()->SchedulePaint(rect);
491 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
492   if (bridge_)
493     bridge_->SetCursor(cursor);
496 bool NativeWidgetMac::IsMouseEventsEnabled() const {
497   // On platforms with touch, mouse events get disabled and calls to this method
498   // can affect hover states. Since there is no touch on desktop Mac, this is
499   // always true. Touch on Mac is tracked in http://crbug.com/445520.
500   return true;
503 void NativeWidgetMac::ClearNativeFocus() {
504   // To quote DesktopWindowTreeHostX11, "This method is weird and misnamed."
505   // The goal is to set focus to the content window, thereby removing focus from
506   // any NSView in the window that doesn't belong to toolkit-views.
507   [GetNativeWindow() makeFirstResponder:GetNativeView()];
510 gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
511   NOTIMPLEMENTED();
512   return gfx::Rect();
515 Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
516     const gfx::Vector2d& drag_offset,
517     Widget::MoveLoopSource source,
518     Widget::MoveLoopEscapeBehavior escape_behavior) {
519   NOTIMPLEMENTED();
520   return Widget::MOVE_LOOP_CANCELED;
523 void NativeWidgetMac::EndMoveLoop() {
524   NOTIMPLEMENTED();
527 void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
528   NOTIMPLEMENTED();
531 void NativeWidgetMac::SetVisibilityAnimationDuration(
532     const base::TimeDelta& duration) {
533   NOTIMPLEMENTED();
536 void NativeWidgetMac::SetVisibilityAnimationTransition(
537     Widget::VisibilityTransition transition) {
538   NOTIMPLEMENTED();
541 ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const {
542   return ui::NativeTheme::instance();
545 void NativeWidgetMac::OnRootViewLayout() {
546   // Ensure possible changes to the non-client view (e.g. Minimum/Maximum size)
547   // propagate through to the NSWindow properties.
548   OnSizeConstraintsChanged();
551 bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
552   return false;
555 void NativeWidgetMac::OnSizeConstraintsChanged() {
556   bridge_->OnSizeConstraintsChanged();
559 void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) {
560   NOTIMPLEMENTED();
563 ////////////////////////////////////////////////////////////////////////////////
564 // NativeWidgetMac, protected:
566 NSWindow* NativeWidgetMac::CreateNSWindow(const Widget::InitParams& params) {
567   return [[[NativeWidgetMacNSWindow alloc]
568       initWithContentRect:ui::kWindowSizeDeterminedLater
569                 styleMask:StyleMaskForParams(params)
570                   backing:NSBackingStoreBuffered
571                     defer:NO] autorelease];
574 ////////////////////////////////////////////////////////////////////////////////
575 // Widget, public:
577 bool Widget::ConvertRect(const Widget* source,
578                          const Widget* target,
579                          gfx::Rect* rect) {
580   return false;
583 namespace internal {
585 ////////////////////////////////////////////////////////////////////////////////
586 // internal::NativeWidgetPrivate, public:
588 // static
589 NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
590     internal::NativeWidgetDelegate* delegate) {
591   return new NativeWidgetMac(delegate);
594 // static
595 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
596     gfx::NativeView native_view) {
597   return GetNativeWidgetForNativeWindow([native_view window]);
600 // static
601 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
602     gfx::NativeWindow native_window) {
603   id<NSWindowDelegate> window_delegate = [native_window delegate];
604   if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
605     ViewsNSWindowDelegate* delegate =
606         base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
607     return [delegate nativeWidgetMac];
608   }
609   return NULL;  // Not created by NativeWidgetMac.
612 // static
613 NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
614     gfx::NativeView native_view) {
615   BridgedNativeWidget* bridge =
616       NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
617   if (!bridge)
618     return nullptr;
620   NativeWidgetPrivate* ancestor =
621       bridge->parent() ? GetTopLevelNativeWidget(
622                              [bridge->parent()->GetNSWindow() contentView])
623                        : nullptr;
624   return ancestor ? ancestor : bridge->native_widget_mac();
627 // static
628 void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
629                                              Widget::Widgets* children) {
630   BridgedNativeWidget* bridge =
631       NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
632   if (!bridge)
633     return;
635   // If |native_view| is a subview of the contentView, it will share an
636   // NSWindow, but will itself be a native child of the Widget. That is, adding
637   // bridge->..->GetWidget() to |children| would be adding the _parent_ of
638   // |native_view|, not the Widget for |native_view|. |native_view| doesn't have
639   // a corresponding Widget of its own in this case (and so can't have Widget
640   // children of its own on Mac).
641   if (bridge->ns_view() != native_view)
642     return;
644   // Code expects widget for |native_view| to be added to |children|.
645   if (bridge->native_widget_mac()->GetWidget())
646     children->insert(bridge->native_widget_mac()->GetWidget());
648   for (BridgedNativeWidget* child : bridge->child_windows())
649     GetAllChildWidgets(child->ns_view(), children);
652 // static
653 void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view,
654                                              Widget::Widgets* owned) {
655   NOTIMPLEMENTED();
658 // static
659 void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view,
660                                              gfx::NativeView new_parent) {
661   BridgedNativeWidget* bridge =
662       NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
663   if (bridge && bridge->parent() &&
664       bridge->parent()->GetNSWindow() == [new_parent window])
665     return;  // Nothing to do.
667   // Not supported. See http://crbug.com/514920.
668   NOTREACHED();
671 // static
672 bool NativeWidgetPrivate::IsMouseButtonDown() {
673   return [NSEvent pressedMouseButtons] != 0;
676 // static
677 gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() {
678   NOTIMPLEMENTED();
679   return gfx::FontList();
682 }  // namespace internal
683 }  // namespace views
685 @implementation ViewsNSWindowCloseAnimator
687 - (id)initWithWindow:(NSWindow*)window {
688   if ((self = [super init])) {
689     window_.reset([window retain]);
690     animation_.reset(
691         [[ConstrainedWindowAnimationHide alloc] initWithWindow:window]);
692     [animation_ setDelegate:self];
693     [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
694     [animation_ startAnimation];
695   }
696   return self;
699 + (void)closeWindowWithAnimation:(NSWindow*)window {
700   [[ViewsNSWindowCloseAnimator alloc] initWithWindow:window];
703 - (void)animationDidEnd:(NSAnimation*)animation {
704   [window_ close];
705   [animation_ setDelegate:nil];
706   [self release];
709 @end