1// Copyright 2015 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#import "chrome/browser/ui/views/apps/chrome_native_app_window_views_mac.h"
6
7#import <Cocoa/Cocoa.h>
8
9#import "base/mac/scoped_nsobject.h"
10#include "chrome/browser/apps/app_shim/app_shim_manager_mac.h"
11#include "chrome/browser/profiles/profile.h"
12#import "chrome/browser/ui/views/apps/app_window_native_widget_mac.h"
13#import "chrome/browser/ui/views/apps/native_app_window_frame_view_mac.h"
14#import "ui/gfx/mac/coordinate_conversion.h"
15
16// This observer is used to get NSWindow notifications. We need to monitor
17// zoom and full screen events to store the correct bounds to Restore() to.
18@interface ResizeNotificationObserver : NSObject {
19 @private
20  // Weak. Owns us.
21  ChromeNativeAppWindowViewsMac* _nativeAppWindow;
22}
23- (id)initForNativeAppWindow:(ChromeNativeAppWindowViewsMac*)nativeAppWindow;
24- (void)onWindowWillStartLiveResize:(NSNotification*)notification;
25- (void)onWindowWillExitFullScreen:(NSNotification*)notification;
26- (void)onWindowDidExitFullScreen:(NSNotification*)notification;
27- (void)stopObserving;
28@end
29
30@implementation ResizeNotificationObserver
31
32- (id)initForNativeAppWindow:(ChromeNativeAppWindowViewsMac*)nativeAppWindow {
33  if ((self = [super init])) {
34    _nativeAppWindow = nativeAppWindow;
35    [[NSNotificationCenter defaultCenter]
36        addObserver:self
37           selector:@selector(onWindowWillStartLiveResize:)
38               name:NSWindowWillStartLiveResizeNotification
39             object:static_cast<ui::BaseWindow*>(nativeAppWindow)
40                        ->GetNativeWindow()
41                        .GetNativeNSWindow()];
42    [[NSNotificationCenter defaultCenter]
43        addObserver:self
44           selector:@selector(onWindowWillExitFullScreen:)
45               name:NSWindowWillExitFullScreenNotification
46             object:static_cast<ui::BaseWindow*>(nativeAppWindow)
47                        ->GetNativeWindow()
48                        .GetNativeNSWindow()];
49    [[NSNotificationCenter defaultCenter]
50        addObserver:self
51           selector:@selector(onWindowDidExitFullScreen:)
52               name:NSWindowDidExitFullScreenNotification
53             object:static_cast<ui::BaseWindow*>(nativeAppWindow)
54                        ->GetNativeWindow()
55                        .GetNativeNSWindow()];
56  }
57  return self;
58}
59
60- (void)onWindowWillStartLiveResize:(NSNotification*)notification {
61  _nativeAppWindow->OnWindowWillStartLiveResize();
62}
63
64- (void)onWindowWillExitFullScreen:(NSNotification*)notification {
65  _nativeAppWindow->OnWindowWillExitFullScreen();
66}
67
68- (void)onWindowDidExitFullScreen:(NSNotification*)notification {
69  _nativeAppWindow->OnWindowDidExitFullScreen();
70}
71
72- (void)stopObserving {
73  [[NSNotificationCenter defaultCenter] removeObserver:self];
74  _nativeAppWindow = nullptr;
75}
76
77@end
78
79namespace {
80
81bool NSWindowIsMaximized(NSWindow* window) {
82  // -[NSWindow isZoomed] only works if the zoom button is enabled.
83  if ([[window standardWindowButton:NSWindowZoomButton] isEnabled])
84    return [window isZoomed];
85
86  // We don't attempt to distinguish between a window that has been explicitly
87  // maximized versus one that has just been dragged by the user to fill the
88  // screen. This is the same behavior as -[NSWindow isZoomed] above.
89  return NSEqualRects([window frame], [[window screen] visibleFrame]);
90}
91
92}  // namespace
93
94ChromeNativeAppWindowViewsMac::ChromeNativeAppWindowViewsMac() {}
95
96ChromeNativeAppWindowViewsMac::~ChromeNativeAppWindowViewsMac() {
97  [nswindow_observer_ stopObserving];
98}
99
100void ChromeNativeAppWindowViewsMac::OnWindowWillStartLiveResize() {
101  if (!NSWindowIsMaximized(GetNativeWindow().GetNativeNSWindow()) &&
102      !in_fullscreen_transition_) {
103    bounds_before_maximize_ = [GetNativeWindow().GetNativeNSWindow() frame];
104  }
105}
106
107void ChromeNativeAppWindowViewsMac::OnWindowWillExitFullScreen() {
108  in_fullscreen_transition_ = true;
109}
110
111void ChromeNativeAppWindowViewsMac::OnWindowDidExitFullScreen() {
112  in_fullscreen_transition_ = false;
113}
114
115void ChromeNativeAppWindowViewsMac::OnBeforeWidgetInit(
116    const extensions::AppWindow::CreateParams& create_params,
117    views::Widget::InitParams* init_params,
118    views::Widget* widget) {
119  DCHECK(!init_params->native_widget);
120  init_params->remove_standard_frame = IsFrameless();
121  init_params->native_widget = new AppWindowNativeWidgetMac(widget, this);
122  ChromeNativeAppWindowViews::OnBeforeWidgetInit(create_params, init_params,
123                                                 widget);
124}
125
126std::unique_ptr<views::NonClientFrameView>
127ChromeNativeAppWindowViewsMac::CreateStandardDesktopAppFrame() {
128  return std::make_unique<NativeAppWindowFrameViewMac>(widget(), this);
129}
130
131std::unique_ptr<views::NonClientFrameView>
132ChromeNativeAppWindowViewsMac::CreateNonStandardAppFrame() {
133  return std::make_unique<NativeAppWindowFrameViewMac>(widget(), this);
134}
135
136bool ChromeNativeAppWindowViewsMac::IsMaximized() const {
137  return !IsMinimized() && !IsFullscreen() &&
138         NSWindowIsMaximized(GetNativeWindow().GetNativeNSWindow());
139}
140
141gfx::Rect ChromeNativeAppWindowViewsMac::GetRestoredBounds() const {
142  if (NSWindowIsMaximized(GetNativeWindow().GetNativeNSWindow()))
143    return gfx::ScreenRectFromNSRect(bounds_before_maximize_);
144
145  return ChromeNativeAppWindowViews::GetRestoredBounds();
146}
147
148void ChromeNativeAppWindowViewsMac::Maximize() {
149  if (IsFullscreen())
150    return;
151
152  NSWindow* window = GetNativeWindow().GetNativeNSWindow();
153  if (!NSWindowIsMaximized(window))
154    [window setFrame:[[window screen] visibleFrame] display:YES animate:YES];
155
156  if (IsMinimized())
157    [window deminiaturize:nil];
158}
159
160void ChromeNativeAppWindowViewsMac::Restore() {
161  NSWindow* window = GetNativeWindow().GetNativeNSWindow();
162  if (NSWindowIsMaximized(window))
163    [window setFrame:bounds_before_maximize_ display:YES animate:YES];
164
165  ChromeNativeAppWindowViews::Restore();
166}
167
168void ChromeNativeAppWindowViewsMac::FlashFrame(bool flash) {
169  Profile* profile =
170      Profile::FromBrowserContext(app_window()->browser_context());
171  AppShimHost* shim_host = apps::AppShimManager::Get()->FindHost(
172      profile, app_window()->extension_id());
173  if (!shim_host)
174    return;
175  shim_host->GetAppShim()->SetUserAttention(
176      flash ? chrome::mojom::AppShimAttentionType::kCritical
177            : chrome::mojom::AppShimAttentionType::kCancel);
178}
179
180void ChromeNativeAppWindowViewsMac::OnWidgetCreated(views::Widget* widget) {
181  nswindow_observer_.reset(
182      [[ResizeNotificationObserver alloc] initForNativeAppWindow:this]);
183}
184