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 scoped_ptr<ui::EventTargeter> targeter; |
| 349 aura::Window* native_window = widget()->GetNativeWindow(); |
| 350 if (shape() && !had_shape) |
| 351 targeter.reset(new ShapedAppWindowTargeter(native_window, this)); |
| 352 native_window->SetEventTargeter(targeter.Pass()); |
| 353 } |
OLD | NEW |