[MacViews] Implement colored window frames.
[chromium-blink-merge.git] / chrome / browser / ui / views / apps / chrome_native_app_window_views.cc
blob830d0491cae1b3884788c1439f11a3e0f1693163
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/apps/desktop_keyboard_capture.h"
12 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
13 #include "chrome/browser/ui/views/frame/taskbar_decorator.h"
14 #include "components/favicon/content/content_favicon_driver.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;
22 namespace {
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;
31 int modifiers;
32 int command_id;
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,
48 IDC_ZOOM_MINUS },
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),
74 &accelerators);
76 return accelerators;
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;
93 } // namespace
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 bool use_default_bounds,
112 views::Widget::InitParams* init_params,
113 views::Widget* widget) {
116 void ChromeNativeAppWindowViews::InitializeDefaultWindow(
117 const AppWindow::CreateParams& create_params) {
118 views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_WINDOW);
119 init_params.delegate = this;
120 init_params.remove_standard_frame = IsFrameless() || has_frame_color_;
121 init_params.use_system_default_icon = true;
122 if (create_params.alpha_enabled) {
123 init_params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
125 // The given window is most likely not rectangular since it uses
126 // transparency and has no standard frame, don't show a shadow for it.
127 // TODO(skuhne): If we run into an application which should have a shadow
128 // but does not have, a new attribute has to be added.
129 if (IsFrameless())
130 init_params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
132 init_params.keep_on_top = create_params.always_on_top;
133 init_params.visible_on_all_workspaces =
134 create_params.visible_on_all_workspaces;
136 OnBeforeWidgetInit(create_params, &init_params, widget());
137 widget()->Init(init_params);
139 // The frame insets are required to resolve the bounds specifications
140 // correctly. So we set the window bounds and constraints now.
141 gfx::Insets frame_insets = GetFrameInsets();
142 gfx::Rect window_bounds = create_params.GetInitialWindowBounds(frame_insets);
143 SetContentSizeConstraints(create_params.GetContentMinimumSize(frame_insets),
144 create_params.GetContentMaximumSize(frame_insets));
145 if (!window_bounds.IsEmpty()) {
146 using BoundsSpecification = AppWindow::BoundsSpecification;
147 bool position_specified =
148 window_bounds.x() != BoundsSpecification::kUnspecifiedPosition &&
149 window_bounds.y() != BoundsSpecification::kUnspecifiedPosition;
150 if (!position_specified)
151 widget()->CenterWindow(window_bounds.size());
152 else
153 widget()->SetBounds(window_bounds);
156 #if defined(OS_CHROMEOS)
157 if (create_params.is_ime_window)
158 return;
159 #endif
161 // Register accelarators supported by app windows.
162 // TODO(jeremya/stevenjb): should these be registered for panels too?
163 views::FocusManager* focus_manager = GetFocusManager();
164 const std::map<ui::Accelerator, int>& accelerator_table =
165 GetAcceleratorTable();
166 const bool is_kiosk_app_mode = chrome::IsRunningInForcedAppMode();
168 // Ensures that kiosk mode accelerators are enabled when in kiosk mode (to be
169 // future proof). This is needed because GetAcceleratorTable() uses a static
170 // to store data and only checks kiosk mode once. If a platform app is
171 // launched before kiosk mode starts, the kiosk accelerators will not be
172 // registered. This CHECK catches the case.
173 CHECK(!is_kiosk_app_mode ||
174 accelerator_table.size() ==
175 arraysize(kAppWindowAcceleratorMap) +
176 arraysize(kAppWindowKioskAppModeAcceleratorMap));
178 // Ensure there is a ZoomController in kiosk mode, otherwise the processing
179 // of the accelerators will cause a crash. Note CHECK here because DCHECK
180 // will not be noticed, as this could only be relevant on real hardware.
181 CHECK(!is_kiosk_app_mode ||
182 ui_zoom::ZoomController::FromWebContents(web_view()->GetWebContents()));
184 for (std::map<ui::Accelerator, int>::const_iterator iter =
185 accelerator_table.begin();
186 iter != accelerator_table.end(); ++iter) {
187 if (is_kiosk_app_mode && !chrome::IsCommandAllowedInAppMode(iter->second))
188 continue;
190 focus_manager->RegisterAccelerator(
191 iter->first, ui::AcceleratorManager::kNormalPriority, this);
195 void ChromeNativeAppWindowViews::InitializePanelWindow(
196 const AppWindow::CreateParams& create_params) {
197 views::Widget::InitParams params(views::Widget::InitParams::TYPE_PANEL);
198 params.delegate = this;
200 gfx::Rect initial_window_bounds =
201 create_params.GetInitialWindowBounds(gfx::Insets());
202 preferred_size_ = gfx::Size(initial_window_bounds.width(),
203 initial_window_bounds.height());
204 if (preferred_size_.width() == 0)
205 preferred_size_.set_width(kDefaultPanelWidth);
206 else if (preferred_size_.width() < kMinPanelWidth)
207 preferred_size_.set_width(kMinPanelWidth);
209 if (preferred_size_.height() == 0)
210 preferred_size_.set_height(kDefaultPanelHeight);
211 else if (preferred_size_.height() < kMinPanelHeight)
212 preferred_size_.set_height(kMinPanelHeight);
214 // When a panel is not docked it will be placed at a default origin in the
215 // currently active target root window.
216 bool use_default_bounds = create_params.state != ui::SHOW_STATE_DOCKED;
217 // Sanitize initial origin reseting it in case it was not specified.
218 using BoundsSpecification = AppWindow::BoundsSpecification;
219 bool position_specified =
220 initial_window_bounds.x() != BoundsSpecification::kUnspecifiedPosition &&
221 initial_window_bounds.y() != BoundsSpecification::kUnspecifiedPosition;
222 params.bounds = (use_default_bounds || !position_specified) ?
223 gfx::Rect(preferred_size_) :
224 gfx::Rect(initial_window_bounds.origin(), preferred_size_);
225 OnBeforePanelWidgetInit(use_default_bounds, &params, widget());
226 widget()->Init(params);
227 widget()->set_focus_on_creation(create_params.focused);
230 views::NonClientFrameView*
231 ChromeNativeAppWindowViews::CreateStandardDesktopAppFrame() {
232 return views::WidgetDelegateView::CreateNonClientFrameView(widget());
235 // ui::BaseWindow implementation.
237 gfx::Rect ChromeNativeAppWindowViews::GetRestoredBounds() const {
238 return widget()->GetRestoredBounds();
241 ui::WindowShowState ChromeNativeAppWindowViews::GetRestoredState() const {
242 if (IsMaximized())
243 return ui::SHOW_STATE_MAXIMIZED;
244 if (IsFullscreen())
245 return ui::SHOW_STATE_FULLSCREEN;
247 return ui::SHOW_STATE_NORMAL;
250 bool ChromeNativeAppWindowViews::IsAlwaysOnTop() const {
251 // TODO(jackhou): On Mac, only docked panels are always-on-top.
252 return app_window()->window_type_is_panel() || widget()->IsAlwaysOnTop();
255 // views::WidgetDelegate implementation.
257 gfx::ImageSkia ChromeNativeAppWindowViews::GetWindowAppIcon() {
258 gfx::Image app_icon = app_window()->app_icon();
259 if (app_icon.IsEmpty())
260 return GetWindowIcon();
261 else
262 return *app_icon.ToImageSkia();
265 gfx::ImageSkia ChromeNativeAppWindowViews::GetWindowIcon() {
266 content::WebContents* web_contents = app_window()->web_contents();
267 if (web_contents) {
268 favicon::FaviconDriver* favicon_driver =
269 favicon::ContentFaviconDriver::FromWebContents(web_contents);
270 gfx::Image app_icon = favicon_driver->GetFavicon();
271 if (!app_icon.IsEmpty())
272 return *app_icon.ToImageSkia();
274 return gfx::ImageSkia();
277 views::NonClientFrameView* ChromeNativeAppWindowViews::CreateNonClientFrameView(
278 views::Widget* widget) {
279 return (IsFrameless() || has_frame_color_) ?
280 CreateNonStandardAppFrame() : CreateStandardDesktopAppFrame();
283 bool ChromeNativeAppWindowViews::WidgetHasHitTestMask() const {
284 return shape_ != NULL;
287 void ChromeNativeAppWindowViews::GetWidgetHitTestMask(gfx::Path* mask) const {
288 shape_->getBoundaryPath(mask);
291 // views::View implementation.
293 gfx::Size ChromeNativeAppWindowViews::GetPreferredSize() const {
294 if (!preferred_size_.IsEmpty())
295 return preferred_size_;
296 return NativeAppWindowViews::GetPreferredSize();
299 bool ChromeNativeAppWindowViews::AcceleratorPressed(
300 const ui::Accelerator& accelerator) {
301 const std::map<ui::Accelerator, int>& accelerator_table =
302 GetAcceleratorTable();
303 std::map<ui::Accelerator, int>::const_iterator iter =
304 accelerator_table.find(accelerator);
305 DCHECK(iter != accelerator_table.end());
306 int command_id = iter->second;
307 switch (command_id) {
308 case IDC_CLOSE_WINDOW:
309 Close();
310 return true;
311 case IDC_ZOOM_MINUS:
312 ui_zoom::PageZoom::Zoom(web_view()->GetWebContents(),
313 content::PAGE_ZOOM_OUT);
314 return true;
315 case IDC_ZOOM_NORMAL:
316 ui_zoom::PageZoom::Zoom(web_view()->GetWebContents(),
317 content::PAGE_ZOOM_RESET);
318 return true;
319 case IDC_ZOOM_PLUS:
320 ui_zoom::PageZoom::Zoom(web_view()->GetWebContents(),
321 content::PAGE_ZOOM_IN);
322 return true;
323 default:
324 NOTREACHED() << "Unknown accelerator sent to app window.";
326 return NativeAppWindowViews::AcceleratorPressed(accelerator);
329 // NativeAppWindow implementation.
331 void ChromeNativeAppWindowViews::SetFullscreen(int fullscreen_types) {
332 // Fullscreen not supported by panels.
333 if (app_window()->window_type_is_panel())
334 return;
335 is_fullscreen_ = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
336 widget()->SetFullscreen(is_fullscreen_);
338 // TODO(jeremya) we need to call RenderViewHost::ExitFullscreen() if we
339 // ever drop the window out of fullscreen in response to something that
340 // wasn't the app calling webkitCancelFullScreen().
343 bool ChromeNativeAppWindowViews::IsFullscreenOrPending() const {
344 return is_fullscreen_;
347 void ChromeNativeAppWindowViews::UpdateShape(scoped_ptr<SkRegion> region) {
348 shape_ = region.Pass();
349 widget()->SetShape(shape() ? new SkRegion(*shape()) : nullptr);
350 widget()->OnSizeConstraintsChanged();
353 bool ChromeNativeAppWindowViews::HasFrameColor() const {
354 return has_frame_color_;
357 SkColor ChromeNativeAppWindowViews::ActiveFrameColor() const {
358 return active_frame_color_;
361 SkColor ChromeNativeAppWindowViews::InactiveFrameColor() const {
362 return inactive_frame_color_;
365 void ChromeNativeAppWindowViews::SetInterceptAllKeys(bool want_all_keys) {
366 if (want_all_keys && (desktop_keyboard_capture_.get() == NULL)) {
367 desktop_keyboard_capture_.reset(new DesktopKeyboardCapture(widget()));
368 } else if (!want_all_keys) {
369 desktop_keyboard_capture_.reset(NULL);
373 // NativeAppWindowViews implementation.
375 void ChromeNativeAppWindowViews::InitializeWindow(
376 AppWindow* app_window,
377 const AppWindow::CreateParams& create_params) {
378 DCHECK(widget());
379 has_frame_color_ = create_params.has_frame_color;
380 active_frame_color_ = create_params.active_frame_color;
381 inactive_frame_color_ = create_params.inactive_frame_color;
382 if (create_params.window_type == AppWindow::WINDOW_TYPE_PANEL ||
383 create_params.window_type == AppWindow::WINDOW_TYPE_V1_PANEL) {
384 InitializePanelWindow(create_params);
385 } else {
386 InitializeDefaultWindow(create_params);
388 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
389 Profile::FromBrowserContext(app_window->browser_context()),
390 widget()->GetFocusManager(),
391 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
392 NULL));