OLD | NEW |
---|---|
(Empty) | |
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 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.h" | |
6 | |
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/shaped_app_window_targeter.h" | |
22 #include "chrome/browser/web_applications/web_app.h" | |
23 #include "ui/aura/client/aura_constants.h" | |
24 #include "ui/aura/window.h" | |
25 #include "ui/aura/window_observer.h" | |
26 #include "ui/base/hit_test.h" | |
27 #include "ui/base/models/simple_menu_model.h" | |
28 #include "ui/gfx/image/image_skia.h" | |
29 #include "ui/views/controls/menu/menu_runner.h" | |
30 #include "ui/views/widget/widget.h" | |
31 #include "ui/wm/core/easy_resize_window_targeter.h" | |
32 | |
33 #if defined(OS_CHROMEOS) | |
34 #include "ash/shell_window_ids.h" | |
35 #endif | |
36 | |
37 #if defined(OS_LINUX) | |
38 #include "chrome/browser/shell_integration_linux.h" | |
39 #endif | |
40 | |
41 using extensions::AppWindow; | |
42 | |
43 namespace { | |
44 | |
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); | |
62 } | |
63 ~NativeAppWindowStateDelegate() override { | |
64 if (window_state_) { | |
65 window_state_->RemoveObserver(this); | |
66 window_state_->window()->RemoveObserver(this); | |
67 } | |
68 } | |
69 | |
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; | |
80 } | |
81 | |
82 // Overridden from ash::wm::WindowStateObserver: | |
83 void OnPostWindowStateTypeChange(ash::wm::WindowState* window_state, | |
84 ash::wm::WindowStateType old_type) override { | |
85 // Since the window state might get set by a window manager, it is possible | |
86 // to come here before the application set its |BaseWindow|. | |
87 if (!window_state->IsFullscreen() && !window_state->IsMinimized() && | |
88 app_window_->GetBaseWindow() && | |
89 app_window_->GetBaseWindow()->IsFullscreenOrPending()) { | |
90 app_window_->Restore(); | |
91 // Usually OnNativeWindowChanged() is called when the window bounds are | |
92 // changed as a result of a state type change. Because the change in state | |
93 // type has already occurred, we need to call OnNativeWindowChanged() | |
94 // explicitly. | |
95 app_window_->OnNativeWindowChanged(); | |
96 } | |
97 } | |
98 | |
99 // Overridden from aura::WindowObserver: | |
100 void OnWindowDestroying(aura::Window* window) override { | |
101 window_state_->RemoveObserver(this); | |
102 window_state_->window()->RemoveObserver(this); | |
103 window_state_ = NULL; | |
104 } | |
105 | |
106 // Not owned. | |
107 AppWindow* app_window_; | |
108 ash::wm::WindowState* window_state_; | |
109 | |
110 DISALLOW_COPY_AND_ASSIGN(NativeAppWindowStateDelegate); | |
111 }; | |
112 | |
113 } // namespace | |
114 | |
115 ChromeNativeAppWindowViewsAura::ChromeNativeAppWindowViewsAura() { | |
116 } | |
117 | |
118 ChromeNativeAppWindowViewsAura::~ChromeNativeAppWindowViewsAura() { | |
119 } | |
120 | |
121 void ChromeNativeAppWindowViewsAura::OnBeforeWidgetInit( | |
122 const AppWindow::CreateParams& create_params, | |
123 views::Widget::InitParams* init_params, | |
124 views::Widget* widget) { | |
125 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
126 std::string app_name = web_app::GenerateApplicationNameFromExtensionId( | |
127 app_window()->extension_id()); | |
128 // Set up a custom WM_CLASS for app windows. This allows task switchers in | |
129 // X11 environments to distinguish them from main browser windows. | |
130 init_params->wm_class_name = web_app::GetWMClassFromAppName(app_name); | |
131 init_params->wm_class_class = shell_integration_linux::GetProgramClassName(); | |
132 const char kX11WindowRoleApp[] = "app"; | |
133 init_params->wm_role_name = std::string(kX11WindowRoleApp); | |
134 #endif | |
135 | |
136 ChromeNativeAppWindowViews::OnBeforeWidgetInit(create_params, init_params, | |
137 widget); | |
138 | |
139 #if defined(OS_CHROMEOS) | |
140 if (create_params.is_ime_window) { | |
141 // Puts ime windows into ime window container. | |
142 init_params->parent = | |
143 ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(), | |
144 ash::kShellWindowId_ImeWindowParentContainer); | |
145 } | |
146 #endif | |
147 } | |
148 | |
149 void ChromeNativeAppWindowViewsAura::OnBeforePanelWidgetInit( | |
150 views::Widget::InitParams* init_params, | |
151 views::Widget* widget) { | |
152 ChromeNativeAppWindowViews::OnBeforePanelWidgetInit(init_params, widget); | |
153 | |
154 if (ash::Shell::HasInstance()) { | |
155 // Open a new panel on the target root. | |
156 init_params->bounds = ash::ScreenUtil::ConvertRectToScreen( | |
157 ash::Shell::GetTargetRootWindow(), gfx::Rect(GetPreferredSize())); | |
158 } | |
159 } | |
160 | |
161 apps::AppWindowFrameView* | |
162 ChromeNativeAppWindowViewsAura::CreateNonStandardAppFrame() { | |
163 apps::AppWindowFrameView* frame = | |
164 ChromeNativeAppWindowViews::CreateNonStandardAppFrame(); | |
165 | |
166 // For Aura windows on the Ash desktop the sizes are different and the user | |
167 // can resize the window from slightly outside the bounds as well. | |
168 if (chrome::IsNativeWindowInAsh(widget()->GetNativeWindow())) { | |
169 frame->SetResizeSizes(ash::kResizeInsideBoundsSize, | |
170 ash::kResizeOutsideBoundsSize, | |
171 ash::kResizeAreaCornerSize); | |
172 } | |
173 | |
174 #if !defined(OS_CHROMEOS) | |
175 // For non-Ash windows, install an easy resize window targeter, which ensures | |
176 // that the root window (not the app) receives mouse events on the edges. | |
177 if (chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) != | |
178 chrome::HOST_DESKTOP_TYPE_ASH) { | |
179 aura::Window* window = widget()->GetNativeWindow(); | |
180 int resize_inside = frame->resize_inside_bounds_size(); | |
181 gfx::Insets inset(resize_inside, resize_inside, resize_inside, | |
182 resize_inside); | |
183 // Add the EasyResizeWindowTargeter on the window, not its root window. The | |
184 // root window does not have a delegate, which is needed to handle the event | |
185 // in Linux. | |
186 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>( | |
187 new wm::EasyResizeWindowTargeter(window, inset, inset))); | |
188 } | |
189 #endif | |
190 | |
191 return frame; | |
192 } | |
193 | |
194 gfx::Rect ChromeNativeAppWindowViewsAura::GetRestoredBounds() const { | |
195 gfx::Rect* bounds = | |
196 widget()->GetNativeWindow()->GetProperty(ash::kRestoreBoundsOverrideKey); | |
197 if (bounds && !bounds->IsEmpty()) | |
198 return *bounds; | |
199 | |
200 return ChromeNativeAppWindowViews::GetRestoredBounds(); | |
201 } | |
202 | |
203 ui::WindowShowState ChromeNativeAppWindowViewsAura::GetRestoredState() const { | |
204 // Use kRestoreShowStateKey in case a window is minimized/hidden. | |
205 ui::WindowShowState restore_state = widget()->GetNativeWindow()->GetProperty( | |
206 aura::client::kRestoreShowStateKey); | |
207 if (widget()->GetNativeWindow()->GetProperty( | |
208 ash::kRestoreBoundsOverrideKey)) { | |
209 // If an override is given, we use that restore state (after filtering). | |
210 restore_state = widget()->GetNativeWindow()->GetProperty( | |
211 ash::kRestoreShowStateOverrideKey); | |
212 } else { | |
213 // Otherwise first normal states are checked. | |
214 if (IsMaximized()) | |
215 return ui::SHOW_STATE_MAXIMIZED; | |
216 if (IsFullscreen()) { | |
217 if (immersive_fullscreen_controller_.get() && | |
218 immersive_fullscreen_controller_->IsEnabled()) { | |
219 // Restore windows which were previously in immersive fullscreen to | |
220 // maximized. Restoring the window to a different fullscreen type | |
221 // makes for a bad experience. | |
222 return ui::SHOW_STATE_MAXIMIZED; | |
223 } | |
224 return ui::SHOW_STATE_FULLSCREEN; | |
225 } | |
226 } | |
227 // Whitelist states to return so that invalid and transient states | |
228 // are not saved and used to restore windows when they are recreated. | |
229 switch (restore_state) { | |
230 case ui::SHOW_STATE_NORMAL: | |
231 case ui::SHOW_STATE_MAXIMIZED: | |
232 case ui::SHOW_STATE_FULLSCREEN: | |
233 return restore_state; | |
234 | |
235 case ui::SHOW_STATE_DEFAULT: | |
236 case ui::SHOW_STATE_MINIMIZED: | |
237 case ui::SHOW_STATE_INACTIVE: | |
238 case ui::SHOW_STATE_END: | |
239 return ui::SHOW_STATE_NORMAL; | |
240 } | |
241 | |
242 return ui::SHOW_STATE_NORMAL; | |
243 } | |
244 | |
245 bool ChromeNativeAppWindowViewsAura::IsAlwaysOnTop() const { | |
246 return app_window()->window_type_is_panel() | |
247 ? ash::wm::GetWindowState(widget()->GetNativeWindow()) | |
248 ->panel_attached() | |
249 : widget()->IsAlwaysOnTop(); | |
250 } | |
251 | |
252 void ChromeNativeAppWindowViewsAura::ShowContextMenuForView( | |
253 views::View* source, | |
254 const gfx::Point& p, | |
255 ui::MenuSourceType source_type) { | |
256 #if defined(OS_CHROMEOS) | |
257 scoped_ptr<ui::MenuModel> model = | |
258 CreateMultiUserContextMenu(app_window()->GetNativeWindow()); | |
259 if (!model.get()) | |
260 return; | |
261 | |
262 // Only show context menu if point is in caption. | |
263 gfx::Point point_in_view_coords(p); | |
264 views::View::ConvertPointFromScreen(widget()->non_client_view(), | |
265 &point_in_view_coords); | |
266 int hit_test = | |
267 widget()->non_client_view()->NonClientHitTest(point_in_view_coords); | |
268 if (hit_test == HTCAPTION) { | |
269 menu_runner_.reset(new views::MenuRunner( | |
270 model.get(), | |
271 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU)); | |
272 if (menu_runner_->RunMenuAt(source->GetWidget(), NULL, | |
273 gfx::Rect(p, gfx::Size(0, 0)), | |
274 views::MENU_ANCHOR_TOPLEFT, source_type) == | |
275 views::MenuRunner::MENU_DELETED) { | |
276 return; | |
277 } | |
278 } | |
279 #endif | |
280 } | |
281 | |
282 views::NonClientFrameView* | |
283 ChromeNativeAppWindowViewsAura::CreateNonClientFrameView( | |
284 views::Widget* widget) { | |
285 if (chrome::IsNativeViewInAsh(widget->GetNativeView())) { | |
286 // Set the delegate now because CustomFrameViewAsh sets the | |
287 // WindowStateDelegate if one is not already set. | |
288 ash::wm::GetWindowState(GetNativeWindow()) | |
289 ->SetDelegate( | |
290 scoped_ptr<ash::wm::WindowStateDelegate>( | |
291 new NativeAppWindowStateDelegate(app_window(), this)).Pass()); | |
292 | |
293 if (IsFrameless()) | |
294 return CreateNonStandardAppFrame(); | |
295 | |
296 if (app_window()->window_type_is_panel()) { | |
297 views::NonClientFrameView* frame_view = | |
298 new ash::PanelFrameView(widget, ash::PanelFrameView::FRAME_ASH); | |
299 frame_view->set_context_menu_controller(this); | |
300 return frame_view; | |
301 } | |
302 | |
303 ash::CustomFrameViewAsh* custom_frame_view = | |
304 new ash::CustomFrameViewAsh(widget); | |
305 // Non-frameless app windows can be put into immersive fullscreen. | |
306 immersive_fullscreen_controller_.reset( | |
307 new ash::ImmersiveFullscreenController()); | |
308 custom_frame_view->InitImmersiveFullscreenControllerForView( | |
309 immersive_fullscreen_controller_.get()); | |
310 custom_frame_view->GetHeaderView()->set_context_menu_controller(this); | |
311 | |
312 if (HasFrameColor()) { | |
313 custom_frame_view->SetFrameColors(ActiveFrameColor(), | |
314 InactiveFrameColor()); | |
315 } | |
316 | |
317 return custom_frame_view; | |
318 } | |
319 | |
320 return ChromeNativeAppWindowViews::CreateNonClientFrameView(widget); | |
321 } | |
322 | |
323 void ChromeNativeAppWindowViewsAura::SetFullscreen(int fullscreen_types) { | |
324 ChromeNativeAppWindowViews::SetFullscreen(fullscreen_types); | |
325 | |
326 if (immersive_fullscreen_controller_.get()) { | |
327 // |immersive_fullscreen_controller_| should only be set if immersive | |
328 // fullscreen is the fullscreen type used by the OS. | |
329 immersive_fullscreen_controller_->SetEnabled( | |
330 ash::ImmersiveFullscreenController::WINDOW_TYPE_PACKAGED_APP, | |
331 (fullscreen_types & AppWindow::FULLSCREEN_TYPE_OS) != 0); | |
332 // Autohide the shelf instead of hiding the shelf completely when only in | |
333 // OS fullscreen. | |
334 ash::wm::WindowState* window_state = | |
335 ash::wm::GetWindowState(widget()->GetNativeWindow()); | |
336 window_state->set_hide_shelf_when_fullscreen(fullscreen_types != | |
337 AppWindow::FULLSCREEN_TYPE_OS); | |
338 DCHECK(ash::Shell::HasInstance()); | |
339 ash::Shell::GetInstance()->UpdateShelfVisibility(); | |
340 } | |
341 } | |
342 | |
343 void ChromeNativeAppWindowViewsAura::UpdateShape(scoped_ptr<SkRegion> region) { | |
344 bool had_shape = !!shape(); | |
345 | |
346 ChromeNativeAppWindowViews::UpdateShape(region.Pass()); | |
347 | |
348 aura::Window* native_window = widget()->GetNativeWindow(); | |
349 if (shape() && !had_shape) { | |
jackhou1
2015/02/25 23:47:30
Changed this back to two separate blocks because i
| |
350 native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>( | |
351 new ShapedAppWindowTargeter(native_window, this))); | |
352 } else if (!shape() && had_shape) { | |
353 native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>()); | |
354 } | |
355 } | |
OLD | NEW |