Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / apps / chrome_native_app_window_views_aura.cc
blob2adfb24ed68646ce6501c379c1d03ecd05eb41cb
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.
5 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.h"
7 #include "apps/ui/views/app_window_frame_view.h"
8 #include "ash/ash_constants.h"
9 #include "ash/frame/custom_frame_view_ash.h"
10 #include "ash/screen_util.h"
11 #include "ash/shell.h"
12 #include "ash/wm/immersive_fullscreen_controller.h"
13 #include "ash/wm/panels/panel_frame_view.h"
14 #include "ash/wm/window_properties.h"
15 #include "ash/wm/window_state.h"
16 #include "ash/wm/window_state_delegate.h"
17 #include "ash/wm/window_state_observer.h"
18 #include "chrome/browser/ui/ash/ash_util.h"
19 #include "chrome/browser/ui/ash/multi_user/multi_user_context_menu.h"
20 #include "chrome/browser/ui/host_desktop.h"
21 #include "chrome/browser/ui/views/apps/app_window_easy_resize_window_targeter.h"
22 #include "chrome/browser/ui/views/apps/shaped_app_window_targeter.h"
23 #include "chrome/browser/web_applications/web_app.h"
24 #include "ui/aura/client/aura_constants.h"
25 #include "ui/aura/window.h"
26 #include "ui/aura/window_observer.h"
27 #include "ui/base/hit_test.h"
28 #include "ui/base/models/simple_menu_model.h"
29 #include "ui/gfx/image/image_skia.h"
30 #include "ui/views/controls/menu/menu_runner.h"
31 #include "ui/views/widget/widget.h"
33 #if defined(OS_CHROMEOS)
34 #include "ash/shell_window_ids.h"
35 #endif
37 #if defined(OS_LINUX)
38 #include "chrome/browser/shell_integration_linux.h"
39 #endif
41 using extensions::AppWindow;
43 namespace {
45 // This class handles a user's fullscreen request (Shift+F4/F4).
46 class NativeAppWindowStateDelegate : public ash::wm::WindowStateDelegate,
47 public ash::wm::WindowStateObserver,
48 public aura::WindowObserver {
49 public:
50 NativeAppWindowStateDelegate(AppWindow* app_window,
51 extensions::NativeAppWindow* native_app_window)
52 : app_window_(app_window),
53 window_state_(
54 ash::wm::GetWindowState(native_app_window->GetNativeWindow())) {
55 // Add a window state observer to exit fullscreen properly in case
56 // fullscreen is exited without going through AppWindow::Restore(). This
57 // is the case when exiting immersive fullscreen via the "Restore" window
58 // control.
59 // TODO(pkotwicz): This is a hack. Remove ASAP. http://crbug.com/319048
60 window_state_->AddObserver(this);
61 window_state_->window()->AddObserver(this);
63 ~NativeAppWindowStateDelegate() override {
64 if (window_state_) {
65 window_state_->RemoveObserver(this);
66 window_state_->window()->RemoveObserver(this);
70 private:
71 // Overridden from ash::wm::WindowStateDelegate.
72 bool ToggleFullscreen(ash::wm::WindowState* window_state) override {
73 // Windows which cannot be maximized should not be fullscreened.
74 DCHECK(window_state->IsFullscreen() || window_state->CanMaximize());
75 if (window_state->IsFullscreen())
76 app_window_->Restore();
77 else if (window_state->CanMaximize())
78 app_window_->OSFullscreen();
79 return true;
82 // Overridden from ash::wm::WindowStateDelegate.
83 bool RestoreAlwaysOnTop(ash::wm::WindowState* window_state) override {
84 app_window_->RestoreAlwaysOnTop();
85 return true;
88 // Overridden from ash::wm::WindowStateObserver:
89 void OnPostWindowStateTypeChange(ash::wm::WindowState* window_state,
90 ash::wm::WindowStateType old_type) override {
91 // Since the window state might get set by a window manager, it is possible
92 // to come here before the application set its |BaseWindow|.
93 if (!window_state->IsFullscreen() && !window_state->IsMinimized() &&
94 app_window_->GetBaseWindow() &&
95 app_window_->GetBaseWindow()->IsFullscreenOrPending()) {
96 app_window_->Restore();
97 // Usually OnNativeWindowChanged() is called when the window bounds are
98 // changed as a result of a state type change. Because the change in state
99 // type has already occurred, we need to call OnNativeWindowChanged()
100 // explicitly.
101 app_window_->OnNativeWindowChanged();
105 // Overridden from aura::WindowObserver:
106 void OnWindowDestroying(aura::Window* window) override {
107 window_state_->RemoveObserver(this);
108 window_state_->window()->RemoveObserver(this);
109 window_state_ = NULL;
112 // Not owned.
113 AppWindow* app_window_;
114 ash::wm::WindowState* window_state_;
116 DISALLOW_COPY_AND_ASSIGN(NativeAppWindowStateDelegate);
119 } // namespace
121 ChromeNativeAppWindowViewsAura::ChromeNativeAppWindowViewsAura() {
124 ChromeNativeAppWindowViewsAura::~ChromeNativeAppWindowViewsAura() {
127 void ChromeNativeAppWindowViewsAura::InitializeWindow(
128 AppWindow* app_window,
129 const AppWindow::CreateParams& create_params) {
130 ChromeNativeAppWindowViews::InitializeWindow(app_window, create_params);
131 // Restore docked state on ash desktop and ignore it elsewhere.
132 if (create_params.state == ui::SHOW_STATE_DOCKED &&
133 chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) ==
134 chrome::HOST_DESKTOP_TYPE_ASH) {
135 widget()->GetNativeWindow()->SetProperty(aura::client::kShowStateKey,
136 create_params.state);
140 void ChromeNativeAppWindowViewsAura::OnBeforeWidgetInit(
141 const AppWindow::CreateParams& create_params,
142 views::Widget::InitParams* init_params,
143 views::Widget* widget) {
144 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
145 std::string app_name = web_app::GenerateApplicationNameFromExtensionId(
146 app_window()->extension_id());
147 // Set up a custom WM_CLASS for app windows. This allows task switchers in
148 // X11 environments to distinguish them from main browser windows.
149 init_params->wm_class_name = web_app::GetWMClassFromAppName(app_name);
150 init_params->wm_class_class = shell_integration_linux::GetProgramClassName();
151 const char kX11WindowRoleApp[] = "app";
152 init_params->wm_role_name = std::string(kX11WindowRoleApp);
153 #endif
155 ChromeNativeAppWindowViews::OnBeforeWidgetInit(create_params, init_params,
156 widget);
158 #if defined(OS_CHROMEOS)
159 if (create_params.is_ime_window) {
160 // Puts ime windows into ime window container.
161 init_params->parent =
162 ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
163 ash::kShellWindowId_ImeWindowParentContainer);
165 #endif
168 void ChromeNativeAppWindowViewsAura::OnBeforePanelWidgetInit(
169 bool use_default_bounds,
170 views::Widget::InitParams* init_params,
171 views::Widget* widget) {
172 ChromeNativeAppWindowViews::OnBeforePanelWidgetInit(use_default_bounds,
173 init_params,
174 widget);
176 if (ash::Shell::HasInstance() && use_default_bounds) {
177 // Open a new panel on the target root.
178 init_params->bounds = ash::ScreenUtil::ConvertRectToScreen(
179 ash::Shell::GetTargetRootWindow(), gfx::Rect(GetPreferredSize()));
183 views::NonClientFrameView*
184 ChromeNativeAppWindowViewsAura::CreateNonStandardAppFrame() {
185 apps::AppWindowFrameView* frame =
186 new apps::AppWindowFrameView(widget(), this, HasFrameColor(),
187 ActiveFrameColor(), InactiveFrameColor());
188 frame->Init();
190 // For Aura windows on the Ash desktop the sizes are different and the user
191 // can resize the window from slightly outside the bounds as well.
192 if (chrome::IsNativeWindowInAsh(widget()->GetNativeWindow())) {
193 frame->SetResizeSizes(ash::kResizeInsideBoundsSize,
194 ash::kResizeOutsideBoundsSize,
195 ash::kResizeAreaCornerSize);
198 #if !defined(OS_CHROMEOS)
199 // For non-Ash windows, install an easy resize window targeter, which ensures
200 // that the root window (not the app) receives mouse events on the edges.
201 if (chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) !=
202 chrome::HOST_DESKTOP_TYPE_ASH) {
203 aura::Window* window = widget()->GetNativeWindow();
204 int resize_inside = frame->resize_inside_bounds_size();
205 gfx::Insets inset(resize_inside, resize_inside, resize_inside,
206 resize_inside);
207 // Add the AppWindowEasyResizeWindowTargeter on the window, not its root
208 // window. The root window does not have a delegate, which is needed to
209 // handle the event in Linux.
210 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
211 new AppWindowEasyResizeWindowTargeter(window, inset, this)));
213 #endif
215 return frame;
218 gfx::Rect ChromeNativeAppWindowViewsAura::GetRestoredBounds() const {
219 gfx::Rect* bounds =
220 widget()->GetNativeWindow()->GetProperty(ash::kRestoreBoundsOverrideKey);
221 if (bounds && !bounds->IsEmpty())
222 return *bounds;
224 return ChromeNativeAppWindowViews::GetRestoredBounds();
227 ui::WindowShowState ChromeNativeAppWindowViewsAura::GetRestoredState() const {
228 // Use kRestoreShowStateKey in case a window is minimized/hidden.
229 ui::WindowShowState restore_state = widget()->GetNativeWindow()->GetProperty(
230 aura::client::kRestoreShowStateKey);
231 if (widget()->GetNativeWindow()->GetProperty(
232 ash::kRestoreBoundsOverrideKey)) {
233 // If an override is given, we use that restore state (after filtering).
234 restore_state = widget()->GetNativeWindow()->GetProperty(
235 ash::kRestoreShowStateOverrideKey);
236 } else {
237 // Otherwise first normal states are checked.
238 if (IsMaximized())
239 return ui::SHOW_STATE_MAXIMIZED;
240 if (IsFullscreen()) {
241 if (immersive_fullscreen_controller_.get() &&
242 immersive_fullscreen_controller_->IsEnabled()) {
243 // Restore windows which were previously in immersive fullscreen to
244 // maximized. Restoring the window to a different fullscreen type
245 // makes for a bad experience.
246 return ui::SHOW_STATE_MAXIMIZED;
248 return ui::SHOW_STATE_FULLSCREEN;
250 if (widget()->GetNativeWindow()->GetProperty(
251 aura::client::kShowStateKey) == ui::SHOW_STATE_DOCKED ||
252 widget()->GetNativeWindow()->GetProperty(
253 aura::client::kRestoreShowStateKey) == ui::SHOW_STATE_DOCKED) {
254 return ui::SHOW_STATE_DOCKED;
257 // Whitelist states to return so that invalid and transient states
258 // are not saved and used to restore windows when they are recreated.
259 switch (restore_state) {
260 case ui::SHOW_STATE_NORMAL:
261 case ui::SHOW_STATE_MAXIMIZED:
262 case ui::SHOW_STATE_FULLSCREEN:
263 return restore_state;
265 case ui::SHOW_STATE_DEFAULT:
266 case ui::SHOW_STATE_MINIMIZED:
267 case ui::SHOW_STATE_INACTIVE:
268 case ui::SHOW_STATE_DOCKED:
269 case ui::SHOW_STATE_END:
270 return ui::SHOW_STATE_NORMAL;
273 return ui::SHOW_STATE_NORMAL;
276 bool ChromeNativeAppWindowViewsAura::IsAlwaysOnTop() const {
277 return app_window()->window_type_is_panel()
278 ? ash::wm::GetWindowState(widget()->GetNativeWindow())
279 ->panel_attached()
280 : widget()->IsAlwaysOnTop();
283 void ChromeNativeAppWindowViewsAura::ShowContextMenuForView(
284 views::View* source,
285 const gfx::Point& p,
286 ui::MenuSourceType source_type) {
287 #if defined(OS_CHROMEOS)
288 scoped_ptr<ui::MenuModel> model =
289 CreateMultiUserContextMenu(app_window()->GetNativeWindow());
290 if (!model.get())
291 return;
293 // Only show context menu if point is in caption.
294 gfx::Point point_in_view_coords(p);
295 views::View::ConvertPointFromScreen(widget()->non_client_view(),
296 &point_in_view_coords);
297 int hit_test =
298 widget()->non_client_view()->NonClientHitTest(point_in_view_coords);
299 if (hit_test == HTCAPTION) {
300 menu_runner_.reset(new views::MenuRunner(
301 model.get(),
302 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
303 if (menu_runner_->RunMenuAt(source->GetWidget(), NULL,
304 gfx::Rect(p, gfx::Size(0, 0)),
305 views::MENU_ANCHOR_TOPLEFT, source_type) ==
306 views::MenuRunner::MENU_DELETED) {
307 return;
310 #endif
313 views::NonClientFrameView*
314 ChromeNativeAppWindowViewsAura::CreateNonClientFrameView(
315 views::Widget* widget) {
316 if (chrome::IsNativeViewInAsh(widget->GetNativeView())) {
317 // Set the delegate now because CustomFrameViewAsh sets the
318 // WindowStateDelegate if one is not already set.
319 ash::wm::GetWindowState(GetNativeWindow())
320 ->SetDelegate(
321 scoped_ptr<ash::wm::WindowStateDelegate>(
322 new NativeAppWindowStateDelegate(app_window(), this)).Pass());
324 if (IsFrameless())
325 return CreateNonStandardAppFrame();
327 if (app_window()->window_type_is_panel()) {
328 views::NonClientFrameView* frame_view =
329 new ash::PanelFrameView(widget, ash::PanelFrameView::FRAME_ASH);
330 frame_view->set_context_menu_controller(this);
331 return frame_view;
334 ash::CustomFrameViewAsh* custom_frame_view =
335 new ash::CustomFrameViewAsh(widget);
336 // Non-frameless app windows can be put into immersive fullscreen.
337 immersive_fullscreen_controller_.reset(
338 new ash::ImmersiveFullscreenController());
339 custom_frame_view->InitImmersiveFullscreenControllerForView(
340 immersive_fullscreen_controller_.get());
341 custom_frame_view->GetHeaderView()->set_context_menu_controller(this);
343 if (HasFrameColor()) {
344 custom_frame_view->SetFrameColors(ActiveFrameColor(),
345 InactiveFrameColor());
348 return custom_frame_view;
351 return ChromeNativeAppWindowViews::CreateNonClientFrameView(widget);
354 void ChromeNativeAppWindowViewsAura::SetFullscreen(int fullscreen_types) {
355 ChromeNativeAppWindowViews::SetFullscreen(fullscreen_types);
357 if (immersive_fullscreen_controller_.get()) {
358 // |immersive_fullscreen_controller_| should only be set if immersive
359 // fullscreen is the fullscreen type used by the OS.
360 immersive_fullscreen_controller_->SetEnabled(
361 ash::ImmersiveFullscreenController::WINDOW_TYPE_PACKAGED_APP,
362 (fullscreen_types & AppWindow::FULLSCREEN_TYPE_OS) != 0);
363 // Autohide the shelf instead of hiding the shelf completely when only in
364 // OS fullscreen.
365 ash::wm::WindowState* window_state =
366 ash::wm::GetWindowState(widget()->GetNativeWindow());
367 window_state->set_hide_shelf_when_fullscreen(fullscreen_types !=
368 AppWindow::FULLSCREEN_TYPE_OS);
369 DCHECK(ash::Shell::HasInstance());
370 ash::Shell::GetInstance()->UpdateShelfVisibility();
374 void ChromeNativeAppWindowViewsAura::UpdateShape(scoped_ptr<SkRegion> region) {
375 bool had_shape = !!shape();
377 ChromeNativeAppWindowViews::UpdateShape(region.Pass());
379 aura::Window* native_window = widget()->GetNativeWindow();
380 if (shape() && !had_shape) {
381 native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
382 new ShapedAppWindowTargeter(native_window, this)));
383 } else if (!shape() && had_shape) {
384 native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>());