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/favicon/favicon_tab_helper.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/views/apps/desktop_keyboard_capture.h"
13 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
14 #include "chrome/browser/ui/views/frame/taskbar_decorator.h"
15 #include "components/ui/zoom/page_zoom.h"
16 #include "components/ui/zoom/zoom_controller.h"
17 #include "ui/views/controls/webview/webview.h"
18 #include "ui/views/widget/widget.h"
20 using extensions::AppWindow
;
24 const int kMinPanelWidth
= 100;
25 const int kMinPanelHeight
= 100;
26 const int kDefaultPanelWidth
= 200;
27 const int kDefaultPanelHeight
= 300;
29 struct AcceleratorMapping
{
30 ui::KeyboardCode keycode
;
35 const AcceleratorMapping kAppWindowAcceleratorMap
[] = {
36 { ui::VKEY_W
, ui::EF_CONTROL_DOWN
, IDC_CLOSE_WINDOW
},
37 { ui::VKEY_W
, ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
, IDC_CLOSE_WINDOW
},
38 { ui::VKEY_F4
, ui::EF_ALT_DOWN
, IDC_CLOSE_WINDOW
},
41 // These accelerators will only be available in kiosk mode. These allow the
42 // user to manually zoom app windows. This is only necessary in kiosk mode
43 // (in normal mode, the user can zoom via the screen magnifier).
44 // TODO(xiyuan): Write a test for kiosk accelerators.
45 const AcceleratorMapping kAppWindowKioskAppModeAcceleratorMap
[] = {
46 { ui::VKEY_OEM_MINUS
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_MINUS
},
47 { ui::VKEY_OEM_MINUS
, ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
,
49 { ui::VKEY_SUBTRACT
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_MINUS
},
50 { ui::VKEY_OEM_PLUS
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_PLUS
},
51 { ui::VKEY_OEM_PLUS
, ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
, IDC_ZOOM_PLUS
},
52 { ui::VKEY_ADD
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_PLUS
},
53 { ui::VKEY_0
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_NORMAL
},
54 { ui::VKEY_NUMPAD0
, ui::EF_CONTROL_DOWN
, IDC_ZOOM_NORMAL
},
57 void AddAcceleratorsFromMapping(const AcceleratorMapping mapping
[],
58 size_t mapping_length
,
59 std::map
<ui::Accelerator
, int>* accelerators
) {
60 for (size_t i
= 0; i
< mapping_length
; ++i
) {
61 ui::Accelerator
accelerator(mapping
[i
].keycode
, mapping
[i
].modifiers
);
62 (*accelerators
)[accelerator
] = mapping
[i
].command_id
;
66 const std::map
<ui::Accelerator
, int>& GetAcceleratorTable() {
67 typedef std::map
<ui::Accelerator
, int> AcceleratorMap
;
68 CR_DEFINE_STATIC_LOCAL(AcceleratorMap
, accelerators
, ());
69 if (!chrome::IsRunningInForcedAppMode()) {
70 if (accelerators
.empty()) {
71 AddAcceleratorsFromMapping(
72 kAppWindowAcceleratorMap
,
73 arraysize(kAppWindowAcceleratorMap
),
79 CR_DEFINE_STATIC_LOCAL(AcceleratorMap
, app_mode_accelerators
, ());
80 if (app_mode_accelerators
.empty()) {
81 AddAcceleratorsFromMapping(
82 kAppWindowAcceleratorMap
,
83 arraysize(kAppWindowAcceleratorMap
),
84 &app_mode_accelerators
);
85 AddAcceleratorsFromMapping(
86 kAppWindowKioskAppModeAcceleratorMap
,
87 arraysize(kAppWindowKioskAppModeAcceleratorMap
),
88 &app_mode_accelerators
);
90 return app_mode_accelerators
;
95 ChromeNativeAppWindowViews::ChromeNativeAppWindowViews()
96 : is_fullscreen_(false),
97 has_frame_color_(false),
98 active_frame_color_(SK_ColorBLACK
),
99 inactive_frame_color_(SK_ColorBLACK
) {
102 ChromeNativeAppWindowViews::~ChromeNativeAppWindowViews() {}
104 void ChromeNativeAppWindowViews::OnBeforeWidgetInit(
105 const AppWindow::CreateParams
& create_params
,
106 views::Widget::InitParams
* init_params
,
107 views::Widget
* widget
) {
110 void ChromeNativeAppWindowViews::OnBeforePanelWidgetInit(
111 views::Widget::InitParams
* init_params
,
112 views::Widget
* widget
) {
115 void ChromeNativeAppWindowViews::InitializeDefaultWindow(
116 const AppWindow::CreateParams
& create_params
) {
117 views::Widget::InitParams
init_params(views::Widget::InitParams::TYPE_WINDOW
);
118 init_params
.delegate
= this;
119 init_params
.remove_standard_frame
= IsFrameless() || has_frame_color_
;
120 init_params
.use_system_default_icon
= true;
121 if (create_params
.alpha_enabled
) {
122 init_params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
124 // The given window is most likely not rectangular since it uses
125 // transparency and has no standard frame, don't show a shadow for it.
126 // TODO(skuhne): If we run into an application which should have a shadow
127 // but does not have, a new attribute has to be added.
129 init_params
.shadow_type
= views::Widget::InitParams::SHADOW_TYPE_NONE
;
131 init_params
.keep_on_top
= create_params
.always_on_top
;
132 init_params
.visible_on_all_workspaces
=
133 create_params
.visible_on_all_workspaces
;
135 OnBeforeWidgetInit(create_params
, &init_params
, widget());
136 widget()->Init(init_params
);
138 // The frame insets are required to resolve the bounds specifications
139 // correctly. So we set the window bounds and constraints now.
140 gfx::Insets frame_insets
= GetFrameInsets();
141 gfx::Rect window_bounds
= create_params
.GetInitialWindowBounds(frame_insets
);
142 SetContentSizeConstraints(create_params
.GetContentMinimumSize(frame_insets
),
143 create_params
.GetContentMaximumSize(frame_insets
));
144 if (!window_bounds
.IsEmpty()) {
145 typedef AppWindow::BoundsSpecification BoundsSpecification
;
146 bool position_specified
=
147 window_bounds
.x() != BoundsSpecification::kUnspecifiedPosition
&&
148 window_bounds
.y() != BoundsSpecification::kUnspecifiedPosition
;
149 if (!position_specified
)
150 widget()->CenterWindow(window_bounds
.size());
152 widget()->SetBounds(window_bounds
);
155 #if defined(OS_CHROMEOS)
156 if (create_params
.is_ime_window
)
160 // Register accelarators supported by app windows.
161 // TODO(jeremya/stevenjb): should these be registered for panels too?
162 views::FocusManager
* focus_manager
= GetFocusManager();
163 const std::map
<ui::Accelerator
, int>& accelerator_table
=
164 GetAcceleratorTable();
165 const bool is_kiosk_app_mode
= chrome::IsRunningInForcedAppMode();
167 // Ensures that kiosk mode accelerators are enabled when in kiosk mode (to be
168 // future proof). This is needed because GetAcceleratorTable() uses a static
169 // to store data and only checks kiosk mode once. If a platform app is
170 // launched before kiosk mode starts, the kiosk accelerators will not be
171 // registered. This CHECK catches the case.
172 CHECK(!is_kiosk_app_mode
||
173 accelerator_table
.size() ==
174 arraysize(kAppWindowAcceleratorMap
) +
175 arraysize(kAppWindowKioskAppModeAcceleratorMap
));
177 // Ensure there is a ZoomController in kiosk mode, otherwise the processing
178 // of the accelerators will cause a crash. Note CHECK here because DCHECK
179 // will not be noticed, as this could only be relevant on real hardware.
180 CHECK(!is_kiosk_app_mode
||
181 ui_zoom::ZoomController::FromWebContents(web_view()->GetWebContents()));
183 for (std::map
<ui::Accelerator
, int>::const_iterator iter
=
184 accelerator_table
.begin();
185 iter
!= accelerator_table
.end(); ++iter
) {
186 if (is_kiosk_app_mode
&& !chrome::IsCommandAllowedInAppMode(iter
->second
))
189 focus_manager
->RegisterAccelerator(
190 iter
->first
, ui::AcceleratorManager::kNormalPriority
, this);
194 void ChromeNativeAppWindowViews::InitializePanelWindow(
195 const AppWindow::CreateParams
& create_params
) {
196 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_PANEL
);
197 params
.delegate
= this;
199 gfx::Rect initial_window_bounds
=
200 create_params
.GetInitialWindowBounds(gfx::Insets());
201 preferred_size_
= gfx::Size(initial_window_bounds
.width(),
202 initial_window_bounds
.height());
203 if (preferred_size_
.width() == 0)
204 preferred_size_
.set_width(kDefaultPanelWidth
);
205 else if (preferred_size_
.width() < kMinPanelWidth
)
206 preferred_size_
.set_width(kMinPanelWidth
);
208 if (preferred_size_
.height() == 0)
209 preferred_size_
.set_height(kDefaultPanelHeight
);
210 else if (preferred_size_
.height() < kMinPanelHeight
)
211 preferred_size_
.set_height(kMinPanelHeight
);
213 params
.bounds
= gfx::Rect(preferred_size_
);
214 OnBeforePanelWidgetInit(¶ms
, widget());
215 widget()->Init(params
);
216 widget()->set_focus_on_creation(create_params
.focused
);
219 views::NonClientFrameView
*
220 ChromeNativeAppWindowViews::CreateStandardDesktopAppFrame() {
221 return views::WidgetDelegateView::CreateNonClientFrameView(widget());
224 apps::AppWindowFrameView
*
225 ChromeNativeAppWindowViews::CreateNonStandardAppFrame() {
226 apps::AppWindowFrameView
* frame
=
227 new apps::AppWindowFrameView(widget(), this, has_frame_color_
,
228 active_frame_color_
, inactive_frame_color_
);
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 FaviconTabHelper
* favicon_tab_helper
=
267 FaviconTabHelper::FromWebContents(web_contents
);
268 gfx::Image app_icon
= favicon_tab_helper
->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())
333 is_fullscreen_
= (fullscreen_types
!= AppWindow::FULLSCREEN_TYPE_NONE
);
334 widget()->SetFullscreen(is_fullscreen_
);
336 // TODO(jeremya) we need to call RenderViewHost::ExitFullscreen() if we
337 // ever drop the window out of fullscreen in response to something that
338 // wasn't the app calling webkitCancelFullScreen().
341 bool ChromeNativeAppWindowViews::IsFullscreenOrPending() const {
342 return is_fullscreen_
;
345 void ChromeNativeAppWindowViews::UpdateBadgeIcon() {
346 const gfx::Image
* icon
= NULL
;
347 if (!app_window()->badge_icon().IsEmpty()) {
348 icon
= &app_window()->badge_icon();
349 // chrome::DrawTaskbarDecoration can do interesting things with non-square
351 // TODO(benwells): Refactor chrome::DrawTaskbarDecoration to not be avatar
352 // specific, and lift this restriction.
353 if (icon
->Width() != icon
->Height()) {
354 LOG(ERROR
) << "Attempt to set a non-square badge; request ignored.";
358 chrome::DrawTaskbarDecoration(GetNativeWindow(), icon
);
361 void ChromeNativeAppWindowViews::UpdateShape(scoped_ptr
<SkRegion
> region
) {
362 shape_
= region
.Pass();
363 widget()->SetShape(shape() ? new SkRegion(*shape()) : nullptr);
364 widget()->OnSizeConstraintsChanged();
367 bool ChromeNativeAppWindowViews::HasFrameColor() const {
368 return has_frame_color_
;
371 SkColor
ChromeNativeAppWindowViews::ActiveFrameColor() const {
372 return active_frame_color_
;
375 SkColor
ChromeNativeAppWindowViews::InactiveFrameColor() const {
376 return inactive_frame_color_
;
379 void ChromeNativeAppWindowViews::SetInterceptAllKeys(bool want_all_keys
) {
380 if (want_all_keys
&& (desktop_keyboard_capture_
.get() == NULL
)) {
381 desktop_keyboard_capture_
.reset(new DesktopKeyboardCapture(widget()));
382 } else if (!want_all_keys
) {
383 desktop_keyboard_capture_
.reset(NULL
);
387 // NativeAppWindowViews implementation.
389 void ChromeNativeAppWindowViews::InitializeWindow(
390 AppWindow
* app_window
,
391 const AppWindow::CreateParams
& create_params
) {
393 has_frame_color_
= create_params
.has_frame_color
;
394 active_frame_color_
= create_params
.active_frame_color
;
395 inactive_frame_color_
= create_params
.inactive_frame_color
;
396 if (create_params
.window_type
== AppWindow::WINDOW_TYPE_PANEL
||
397 create_params
.window_type
== AppWindow::WINDOW_TYPE_V1_PANEL
) {
398 InitializePanelWindow(create_params
);
400 InitializeDefaultWindow(create_params
);
402 extension_keybinding_registry_
.reset(new ExtensionKeybindingRegistryViews(
403 Profile::FromBrowserContext(app_window
->browser_context()),
404 widget()->GetFocusManager(),
405 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY
,