Chromium Code Reviews| 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 |