1 // Copyright 2014 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.h"
7 #include "apps/ui/views/app_window_frame_view.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/app_mode/app_mode_utils.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
12 #include "chrome/browser/ui/views/frame/taskbar_decorator.h"
13 #include "components/favicon/content/content_favicon_driver.h"
14 #include "components/ui/zoom/page_zoom.h"
15 #include "components/ui/zoom/zoom_controller.h"
16 #include "ui/views/controls/webview/webview.h"
17 #include "ui/views/widget/widget.h"
19 using extensions::AppWindow
;
23 const int kMinPanelWidth
= 100;
24 const int kMinPanelHeight
= 100;
25 const int kDefaultPanelWidth
= 200;
26 const int kDefaultPanelHeight
= 300;
28 struct AcceleratorMapping
{
29 ui::KeyboardCode keycode
;
34 const AcceleratorMapping kAppWindowAcceleratorMap
[] = {
35 { ui::VKEY_W
, ui::EF_CONTROL_DOWN
, IDC_CLOSE_WINDOW
},
36 { ui::VKEY_W
, ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
, IDC_CLOSE_WINDOW
},
37 { ui::VKEY_F4
, ui::EF_ALT_DOWN
, IDC_CLOSE_WINDOW
},
40 // These accelerators will only be available in kiosk mode. These allow the
41 // user to manually zoom app windows. This is only necessary in kiosk mode
42 // (in normal mode, the user can zoom via the screen magnifier).
43 // TODO(xiyuan): Write a test for kiosk accelerators.
44 const AcceleratorMapping kAppWindowKioskAppModeAcceleratorMap
[] = {
45 { ui::VKEY_OEM_MINUS
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_MINUS
},
46 { ui::VKEY_OEM_MINUS
, ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
,
48 { ui::VKEY_SUBTRACT
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_MINUS
},
49 { ui::VKEY_OEM_PLUS
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_PLUS
},
50 { ui::VKEY_OEM_PLUS
, ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
, IDC_ZOOM_PLUS
},
51 { ui::VKEY_ADD
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_PLUS
},
52 { ui::VKEY_0
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_NORMAL
},
53 { ui::VKEY_NUMPAD0
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_NORMAL
},
56 void AddAcceleratorsFromMapping(const AcceleratorMapping mapping
[],
57 size_t mapping_length
,
58 std::map
<ui::Accelerator
, int>* accelerators
) {
59 for (size_t i
= 0; i
< mapping_length
; ++i
) {
60 ui::Accelerator
accelerator(mapping
[i
].keycode
, mapping
[i
].modifiers
);
61 (*accelerators
)[accelerator
] = mapping
[i
].command_id
;
65 const std::map
<ui::Accelerator
, int>& GetAcceleratorTable() {
66 typedef std::map
<ui::Accelerator
, int> AcceleratorMap
;
67 CR_DEFINE_STATIC_LOCAL(AcceleratorMap
, accelerators
, ());
68 if (!chrome::IsRunningInForcedAppMode()) {
69 if (accelerators
.empty()) {
70 AddAcceleratorsFromMapping(
71 kAppWindowAcceleratorMap
,
72 arraysize(kAppWindowAcceleratorMap
),
78 CR_DEFINE_STATIC_LOCAL(AcceleratorMap
, app_mode_accelerators
, ());
79 if (app_mode_accelerators
.empty()) {
80 AddAcceleratorsFromMapping(
81 kAppWindowAcceleratorMap
,
82 arraysize(kAppWindowAcceleratorMap
),
83 &app_mode_accelerators
);
84 AddAcceleratorsFromMapping(
85 kAppWindowKioskAppModeAcceleratorMap
,
86 arraysize(kAppWindowKioskAppModeAcceleratorMap
),
87 &app_mode_accelerators
);
89 return app_mode_accelerators
;
94 ChromeNativeAppWindowViews::ChromeNativeAppWindowViews()
95 : has_frame_color_(false),
96 active_frame_color_(SK_ColorBLACK
),
97 inactive_frame_color_(SK_ColorBLACK
) {
100 ChromeNativeAppWindowViews::~ChromeNativeAppWindowViews() {}
102 void ChromeNativeAppWindowViews::OnBeforeWidgetInit(
103 const AppWindow::CreateParams
& create_params
,
104 views::Widget::InitParams
* init_params
,
105 views::Widget
* widget
) {
108 void ChromeNativeAppWindowViews::OnBeforePanelWidgetInit(
109 bool use_default_bounds
,
110 views::Widget::InitParams
* init_params
,
111 views::Widget
* widget
) {
114 void ChromeNativeAppWindowViews::InitializeDefaultWindow(
115 const AppWindow::CreateParams
& create_params
) {
116 views::Widget::InitParams
init_params(views::Widget::InitParams::TYPE_WINDOW
);
117 init_params
.delegate
= this;
118 init_params
.remove_standard_frame
= IsFrameless() || has_frame_color_
;
119 init_params
.use_system_default_icon
= true;
120 if (create_params
.alpha_enabled
) {
121 init_params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
123 // The given window is most likely not rectangular since it uses
124 // transparency and has no standard frame, don't show a shadow for it.
125 // TODO(skuhne): If we run into an application which should have a shadow
126 // but does not have, a new attribute has to be added.
128 init_params
.shadow_type
= views::Widget::InitParams::SHADOW_TYPE_NONE
;
130 init_params
.keep_on_top
= create_params
.always_on_top
;
131 init_params
.visible_on_all_workspaces
=
132 create_params
.visible_on_all_workspaces
;
134 OnBeforeWidgetInit(create_params
, &init_params
, widget());
135 widget()->Init(init_params
);
137 // The frame insets are required to resolve the bounds specifications
138 // correctly. So we set the window bounds and constraints now.
139 gfx::Insets frame_insets
= GetFrameInsets();
140 gfx::Rect window_bounds
= create_params
.GetInitialWindowBounds(frame_insets
);
141 SetContentSizeConstraints(create_params
.GetContentMinimumSize(frame_insets
),
142 create_params
.GetContentMaximumSize(frame_insets
));
143 if (!window_bounds
.IsEmpty()) {
144 using BoundsSpecification
= AppWindow::BoundsSpecification
;
145 bool position_specified
=
146 window_bounds
.x() != BoundsSpecification::kUnspecifiedPosition
&&
147 window_bounds
.y() != BoundsSpecification::kUnspecifiedPosition
;
148 if (!position_specified
)
149 widget()->CenterWindow(window_bounds
.size());
151 widget()->SetBounds(window_bounds
);
154 #if defined(OS_CHROMEOS)
155 if (create_params
.is_ime_window
)
159 // Register accelarators supported by app windows.
160 // TODO(jeremya/stevenjb): should these be registered for panels too?
161 views::FocusManager
* focus_manager
= GetFocusManager();
162 const std::map
<ui::Accelerator
, int>& accelerator_table
=
163 GetAcceleratorTable();
164 const bool is_kiosk_app_mode
= chrome::IsRunningInForcedAppMode();
166 // Ensures that kiosk mode accelerators are enabled when in kiosk mode (to be
167 // future proof). This is needed because GetAcceleratorTable() uses a static
168 // to store data and only checks kiosk mode once. If a platform app is
169 // launched before kiosk mode starts, the kiosk accelerators will not be
170 // registered. This CHECK catches the case.
171 CHECK(!is_kiosk_app_mode
||
172 accelerator_table
.size() ==
173 arraysize(kAppWindowAcceleratorMap
) +
174 arraysize(kAppWindowKioskAppModeAcceleratorMap
));
176 // Ensure there is a ZoomController in kiosk mode, otherwise the processing
177 // of the accelerators will cause a crash. Note CHECK here because DCHECK
178 // will not be noticed, as this could only be relevant on real hardware.
179 CHECK(!is_kiosk_app_mode
||
180 ui_zoom::ZoomController::FromWebContents(web_view()->GetWebContents()));
182 for (std::map
<ui::Accelerator
, int>::const_iterator iter
=
183 accelerator_table
.begin();
184 iter
!= accelerator_table
.end(); ++iter
) {
185 if (is_kiosk_app_mode
&& !chrome::IsCommandAllowedInAppMode(iter
->second
))
188 focus_manager
->RegisterAccelerator(
189 iter
->first
, ui::AcceleratorManager::kNormalPriority
, this);
193 void ChromeNativeAppWindowViews::InitializePanelWindow(
194 const AppWindow::CreateParams
& create_params
) {
195 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_PANEL
);
196 params
.delegate
= this;
198 gfx::Rect initial_window_bounds
=
199 create_params
.GetInitialWindowBounds(gfx::Insets());
200 preferred_size_
= gfx::Size(initial_window_bounds
.width(),
201 initial_window_bounds
.height());
202 if (preferred_size_
.width() == 0)
203 preferred_size_
.set_width(kDefaultPanelWidth
);
204 else if (preferred_size_
.width() < kMinPanelWidth
)
205 preferred_size_
.set_width(kMinPanelWidth
);
207 if (preferred_size_
.height() == 0)
208 preferred_size_
.set_height(kDefaultPanelHeight
);
209 else if (preferred_size_
.height() < kMinPanelHeight
)
210 preferred_size_
.set_height(kMinPanelHeight
);
212 // When a panel is not docked it will be placed at a default origin in the
213 // currently active target root window.
214 bool use_default_bounds
= create_params
.state
!= ui::SHOW_STATE_DOCKED
;
215 // Sanitize initial origin reseting it in case it was not specified.
216 using BoundsSpecification
= AppWindow::BoundsSpecification
;
217 bool position_specified
=
218 initial_window_bounds
.x() != BoundsSpecification::kUnspecifiedPosition
&&
219 initial_window_bounds
.y() != BoundsSpecification::kUnspecifiedPosition
;
220 params
.bounds
= (use_default_bounds
|| !position_specified
) ?
221 gfx::Rect(preferred_size_
) :
222 gfx::Rect(initial_window_bounds
.origin(), preferred_size_
);
223 OnBeforePanelWidgetInit(use_default_bounds
, ¶ms
, widget());
224 widget()->Init(params
);
225 widget()->set_focus_on_creation(create_params
.focused
);
228 views::NonClientFrameView
*
229 ChromeNativeAppWindowViews::CreateStandardDesktopAppFrame() {
230 return views::WidgetDelegateView::CreateNonClientFrameView(widget());
233 // ui::BaseWindow implementation.
235 gfx::Rect
ChromeNativeAppWindowViews::GetRestoredBounds() const {
236 return widget()->GetRestoredBounds();
239 ui::WindowShowState
ChromeNativeAppWindowViews::GetRestoredState() const {
241 return ui::SHOW_STATE_MAXIMIZED
;
243 return ui::SHOW_STATE_FULLSCREEN
;
245 return ui::SHOW_STATE_NORMAL
;
248 bool ChromeNativeAppWindowViews::IsAlwaysOnTop() const {
249 // TODO(jackhou): On Mac, only docked panels are always-on-top.
250 return app_window()->window_type_is_panel() || widget()->IsAlwaysOnTop();
253 // views::WidgetDelegate implementation.
255 gfx::ImageSkia
ChromeNativeAppWindowViews::GetWindowAppIcon() {
256 gfx::Image app_icon
= app_window()->app_icon();
257 if (app_icon
.IsEmpty())
258 return GetWindowIcon();
260 return *app_icon
.ToImageSkia();
263 gfx::ImageSkia
ChromeNativeAppWindowViews::GetWindowIcon() {
264 content::WebContents
* web_contents
= app_window()->web_contents();
266 favicon::FaviconDriver
* favicon_driver
=
267 favicon::ContentFaviconDriver::FromWebContents(web_contents
);
268 gfx::Image app_icon
= favicon_driver
->GetFavicon();
269 if (!app_icon
.IsEmpty())
270 return *app_icon
.ToImageSkia();
272 return gfx::ImageSkia();
275 views::NonClientFrameView
* ChromeNativeAppWindowViews::CreateNonClientFrameView(
276 views::Widget
* widget
) {
277 return (IsFrameless() || has_frame_color_
) ?
278 CreateNonStandardAppFrame() : CreateStandardDesktopAppFrame();
281 bool ChromeNativeAppWindowViews::WidgetHasHitTestMask() const {
282 return shape_
!= NULL
;
285 void ChromeNativeAppWindowViews::GetWidgetHitTestMask(gfx::Path
* mask
) const {
286 shape_
->getBoundaryPath(mask
);
289 // views::View implementation.
291 gfx::Size
ChromeNativeAppWindowViews::GetPreferredSize() const {
292 if (!preferred_size_
.IsEmpty())
293 return preferred_size_
;
294 return NativeAppWindowViews::GetPreferredSize();
297 bool ChromeNativeAppWindowViews::AcceleratorPressed(
298 const ui::Accelerator
& accelerator
) {
299 const std::map
<ui::Accelerator
, int>& accelerator_table
=
300 GetAcceleratorTable();
301 std::map
<ui::Accelerator
, int>::const_iterator iter
=
302 accelerator_table
.find(accelerator
);
303 DCHECK(iter
!= accelerator_table
.end());
304 int command_id
= iter
->second
;
305 switch (command_id
) {
306 case IDC_CLOSE_WINDOW
:
310 ui_zoom::PageZoom::Zoom(web_view()->GetWebContents(),
311 content::PAGE_ZOOM_OUT
);
313 case IDC_ZOOM_NORMAL
:
314 ui_zoom::PageZoom::Zoom(web_view()->GetWebContents(),
315 content::PAGE_ZOOM_RESET
);
318 ui_zoom::PageZoom::Zoom(web_view()->GetWebContents(),
319 content::PAGE_ZOOM_IN
);
322 NOTREACHED() << "Unknown accelerator sent to app window.";
324 return NativeAppWindowViews::AcceleratorPressed(accelerator
);
327 // NativeAppWindow implementation.
329 void ChromeNativeAppWindowViews::SetFullscreen(int fullscreen_types
) {
330 // Fullscreen not supported by panels.
331 if (app_window()->window_type_is_panel())
334 widget()->SetFullscreen(fullscreen_types
!= AppWindow::FULLSCREEN_TYPE_NONE
);
337 bool ChromeNativeAppWindowViews::IsFullscreenOrPending() const {
338 return widget()->IsFullscreen();
341 void ChromeNativeAppWindowViews::UpdateShape(scoped_ptr
<SkRegion
> region
) {
342 shape_
= region
.Pass();
343 widget()->SetShape(shape() ? new SkRegion(*shape()) : nullptr);
344 widget()->OnSizeConstraintsChanged();
347 bool ChromeNativeAppWindowViews::HasFrameColor() const {
348 return has_frame_color_
;
351 SkColor
ChromeNativeAppWindowViews::ActiveFrameColor() const {
352 return active_frame_color_
;
355 SkColor
ChromeNativeAppWindowViews::InactiveFrameColor() const {
356 return inactive_frame_color_
;
359 // NativeAppWindowViews implementation.
361 void ChromeNativeAppWindowViews::InitializeWindow(
362 AppWindow
* app_window
,
363 const AppWindow::CreateParams
& create_params
) {
365 has_frame_color_
= create_params
.has_frame_color
;
366 active_frame_color_
= create_params
.active_frame_color
;
367 inactive_frame_color_
= create_params
.inactive_frame_color
;
368 if (create_params
.window_type
== AppWindow::WINDOW_TYPE_PANEL
||
369 create_params
.window_type
== AppWindow::WINDOW_TYPE_V1_PANEL
) {
370 InitializePanelWindow(create_params
);
372 InitializeDefaultWindow(create_params
);
374 extension_keybinding_registry_
.reset(new ExtensionKeybindingRegistryViews(
375 Profile::FromBrowserContext(app_window
->browser_context()),
376 widget()->GetFocusManager(),
377 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY
,