chrome_native_app_window_views.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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.
4
5#include "chrome/browser/ui/views/apps/chrome_native_app_window_views.h"
6
7#include "apps/ui/views/app_window_frame_view.h"
8#include "base/command_line.h"
9#include "chrome/app/chrome_command_ids.h"
10#include "chrome/browser/app_mode/app_mode_utils.h"
11#include "chrome/browser/chrome_page_zoom.h"
12#include "chrome/browser/favicon/favicon_tab_helper.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/host_desktop.h"
15#include "chrome/browser/ui/views/apps/shaped_app_window_targeter.h"
16#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
17#include "chrome/browser/ui/views/frame/taskbar_decorator.h"
18#include "chrome/browser/web_applications/web_app.h"
19#include "chrome/common/chrome_switches.h"
20#include "extensions/common/extension.h"
21#include "ui/base/hit_test.h"
22#include "ui/base/models/simple_menu_model.h"
23#include "ui/gfx/image/image_skia.h"
24#include "ui/views/controls/menu/menu_runner.h"
25#include "ui/views/controls/webview/webview.h"
26#include "ui/views/widget/widget.h"
27#include "ui/wm/core/easy_resize_window_targeter.h"
28#include "ui/wm/core/shadow_types.h"
29
30#if defined(OS_LINUX)
31#include "chrome/browser/shell_integration_linux.h"
32#endif
33
34#if defined(USE_ASH)
35#include "ash/ash_constants.h"
36#include "ash/ash_switches.h"
37#include "ash/frame/custom_frame_view_ash.h"
38#include "ash/screen_util.h"
39#include "ash/shell.h"
40#include "ash/wm/immersive_fullscreen_controller.h"
41#include "ash/wm/panels/panel_frame_view.h"
42#include "ash/wm/window_state.h"
43#include "ash/wm/window_state_delegate.h"
44#include "ash/wm/window_state_observer.h"
45#include "chrome/browser/ui/ash/ash_util.h"
46#include "chrome/browser/ui/ash/multi_user/multi_user_context_menu.h"
47#include "ui/aura/client/aura_constants.h"
48#include "ui/aura/client/window_tree_client.h"
49#include "ui/aura/window.h"
50#include "ui/aura/window_observer.h"
51#endif
52
53#if defined(USE_AURA)
54#include "ui/aura/window.h"
55#endif
56
57using apps::AppWindow;
58
59namespace {
60
61const int kMinPanelWidth = 100;
62const int kMinPanelHeight = 100;
63const int kDefaultPanelWidth = 200;
64const int kDefaultPanelHeight = 300;
65const int kResizeInsideBoundsSize = 5;
66const int kResizeAreaCornerSize = 16;
67
68struct AcceleratorMapping {
69  ui::KeyboardCode keycode;
70  int modifiers;
71  int command_id;
72};
73
74const AcceleratorMapping kAppWindowAcceleratorMap[] = {
75  { ui::VKEY_W, ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
76  { ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
77  { ui::VKEY_F4, ui::EF_ALT_DOWN, IDC_CLOSE_WINDOW },
78};
79
80// These accelerators will only be available in kiosk mode. These allow the
81// user to manually zoom app windows. This is only necessary in kiosk mode
82// (in normal mode, the user can zoom via the screen magnifier).
83// TODO(xiyuan): Write a test for kiosk accelerators.
84const AcceleratorMapping kAppWindowKioskAppModeAcceleratorMap[] = {
85  { ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
86  { ui::VKEY_OEM_MINUS, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
87    IDC_ZOOM_MINUS },
88  { ui::VKEY_SUBTRACT, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
89  { ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
90  { ui::VKEY_OEM_PLUS, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
91  { ui::VKEY_ADD, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
92  { ui::VKEY_0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
93  { ui::VKEY_NUMPAD0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
94};
95
96void AddAcceleratorsFromMapping(const AcceleratorMapping mapping[],
97                                size_t mapping_length,
98                                std::map<ui::Accelerator, int>* accelerators) {
99  for (size_t i = 0; i < mapping_length; ++i) {
100    ui::Accelerator accelerator(mapping[i].keycode, mapping[i].modifiers);
101    (*accelerators)[accelerator] = mapping[i].command_id;
102  }
103}
104
105const std::map<ui::Accelerator, int>& GetAcceleratorTable() {
106  typedef std::map<ui::Accelerator, int> AcceleratorMap;
107  CR_DEFINE_STATIC_LOCAL(AcceleratorMap, accelerators, ());
108  if (accelerators.empty()) {
109    AddAcceleratorsFromMapping(
110        kAppWindowAcceleratorMap,
111        arraysize(kAppWindowAcceleratorMap),
112        &accelerators);
113
114    // Add accelerators for kiosk mode.
115    if (chrome::IsRunningInForcedAppMode()) {
116      AddAcceleratorsFromMapping(
117          kAppWindowKioskAppModeAcceleratorMap,
118          arraysize(kAppWindowKioskAppModeAcceleratorMap),
119          &accelerators);
120    }
121  }
122  return accelerators;
123}
124
125#if defined(USE_ASH)
126// This class handles a user's fullscreen request (Shift+F4/F4).
127class NativeAppWindowStateDelegate : public ash::wm::WindowStateDelegate,
128                                     public ash::wm::WindowStateObserver,
129                                     public aura::WindowObserver {
130 public:
131  NativeAppWindowStateDelegate(AppWindow* app_window,
132                               apps::NativeAppWindow* native_app_window)
133      : app_window_(app_window),
134        window_state_(
135            ash::wm::GetWindowState(native_app_window->GetNativeWindow())) {
136    // Add a window state observer to exit fullscreen properly in case
137    // fullscreen is exited without going through AppWindow::Restore(). This
138    // is the case when exiting immersive fullscreen via the "Restore" window
139    // control.
140    // TODO(pkotwicz): This is a hack. Remove ASAP. http://crbug.com/319048
141    window_state_->AddObserver(this);
142    window_state_->window()->AddObserver(this);
143  }
144  virtual ~NativeAppWindowStateDelegate(){
145    if (window_state_) {
146      window_state_->RemoveObserver(this);
147      window_state_->window()->RemoveObserver(this);
148    }
149  }
150
151 private:
152  // Overridden from ash::wm::WindowStateDelegate.
153  virtual bool ToggleFullscreen(ash::wm::WindowState* window_state) OVERRIDE {
154    // Windows which cannot be maximized should not be fullscreened.
155    DCHECK(window_state->IsFullscreen() || window_state->CanMaximize());
156    if (window_state->IsFullscreen())
157      app_window_->Restore();
158    else if (window_state->CanMaximize())
159      app_window_->OSFullscreen();
160    return true;
161  }
162
163  // Overridden from ash::wm::WindowStateObserver:
164  virtual void OnPostWindowStateTypeChange(
165      ash::wm::WindowState* window_state,
166      ash::wm::WindowStateType old_type) OVERRIDE {
167    // Since the window state might get set by a window manager, it is possible
168    // to come here before the application set its |BaseWindow|.
169    if (!window_state->IsFullscreen() && !window_state->IsMinimized() &&
170        app_window_->GetBaseWindow() &&
171        app_window_->GetBaseWindow()->IsFullscreenOrPending()) {
172      app_window_->Restore();
173      // Usually OnNativeWindowChanged() is called when the window bounds are
174      // changed as a result of a state type change. Because the change in state
175      // type has already occurred, we need to call OnNativeWindowChanged()
176      // explicitly.
177      app_window_->OnNativeWindowChanged();
178    }
179  }
180
181  // Overridden from aura::WindowObserver:
182  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
183    window_state_->RemoveObserver(this);
184    window_state_->window()->RemoveObserver(this);
185    window_state_ = NULL;
186  }
187
188  // Not owned.
189  AppWindow* app_window_;
190  ash::wm::WindowState* window_state_;
191
192  DISALLOW_COPY_AND_ASSIGN(NativeAppWindowStateDelegate);
193};
194#endif  // USE_ASH
195
196}  // namespace
197
198ChromeNativeAppWindowViews::ChromeNativeAppWindowViews()
199    : is_fullscreen_(false),
200      has_frame_color_(false),
201      frame_color_(SK_ColorBLACK) {}
202
203ChromeNativeAppWindowViews::~ChromeNativeAppWindowViews() {}
204
205void ChromeNativeAppWindowViews::OnBeforeWidgetInit(
206    views::Widget::InitParams* init_params,
207    views::Widget* widget) {}
208
209void ChromeNativeAppWindowViews::InitializeDefaultWindow(
210    const AppWindow::CreateParams& create_params) {
211  std::string app_name =
212      web_app::GenerateApplicationNameFromExtensionId(
213          app_window()->extension()->id());
214
215  views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_WINDOW);
216  init_params.delegate = this;
217  init_params.remove_standard_frame = IsFrameless() || has_frame_color_;
218  init_params.use_system_default_icon = true;
219  // TODO(erg): Conceptually, these are toplevel windows, but we theoretically
220  // could plumb context through to here in some cases.
221  init_params.top_level = true;
222  if (create_params.transparent_background)
223    init_params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
224  init_params.keep_on_top = create_params.always_on_top;
225
226#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
227  // Set up a custom WM_CLASS for app windows. This allows task switchers in
228  // X11 environments to distinguish them from main browser windows.
229  init_params.wm_class_name = web_app::GetWMClassFromAppName(app_name);
230  init_params.wm_class_class = ShellIntegrationLinux::GetProgramClassName();
231  const char kX11WindowRoleApp[] = "app";
232  init_params.wm_role_name = std::string(kX11WindowRoleApp);
233#endif
234
235  OnBeforeWidgetInit(&init_params, widget());
236  widget()->Init(init_params);
237
238  // The frame insets are required to resolve the bounds specifications
239  // correctly. So we set the window bounds and constraints now.
240  gfx::Insets frame_insets = GetFrameInsets();
241  gfx::Rect window_bounds = create_params.GetInitialWindowBounds(frame_insets);
242  SetContentSizeConstraints(create_params.GetContentMinimumSize(frame_insets),
243                            create_params.GetContentMaximumSize(frame_insets));
244  if (!window_bounds.IsEmpty()) {
245    typedef apps::AppWindow::BoundsSpecification BoundsSpecification;
246    bool position_specified =
247        window_bounds.x() != BoundsSpecification::kUnspecifiedPosition &&
248        window_bounds.y() != BoundsSpecification::kUnspecifiedPosition;
249    if (!position_specified)
250      widget()->CenterWindow(window_bounds.size());
251    else
252      widget()->SetBounds(window_bounds);
253  }
254
255  if (IsFrameless() &&
256      init_params.opacity == views::Widget::InitParams::TRANSLUCENT_WINDOW &&
257      !create_params.resizable) {
258    // The given window is most likely not rectangular since it uses
259    // transparency, has no standard frame and the user cannot resize it using
260    // the OS supplied methods. Therefore we do not use a shadow for it.
261    // TODO(skuhne): If we run into an application which should have a shadow
262    // but does not have, a new attribute has to be added.
263    wm::SetShadowType(widget()->GetNativeWindow(), wm::SHADOW_TYPE_NONE);
264  }
265
266  // Register accelarators supported by app windows.
267  // TODO(jeremya/stevenjb): should these be registered for panels too?
268  views::FocusManager* focus_manager = GetFocusManager();
269  const std::map<ui::Accelerator, int>& accelerator_table =
270      GetAcceleratorTable();
271  const bool is_kiosk_app_mode = chrome::IsRunningInForcedAppMode();
272
273  // Ensures that kiosk mode accelerators are enabled when in kiosk mode (to be
274  // future proof). This is needed because GetAcceleratorTable() uses a static
275  // to store data and only checks kiosk mode once. If a platform app is
276  // launched before kiosk mode starts, the kiosk accelerators will not be
277  // registered. This DCHECK catches the case.
278  DCHECK(!is_kiosk_app_mode ||
279         accelerator_table.size() ==
280             arraysize(kAppWindowAcceleratorMap) +
281                 arraysize(kAppWindowKioskAppModeAcceleratorMap));
282
283  for (std::map<ui::Accelerator, int>::const_iterator iter =
284           accelerator_table.begin();
285       iter != accelerator_table.end(); ++iter) {
286    if (is_kiosk_app_mode && !chrome::IsCommandAllowedInAppMode(iter->second))
287      continue;
288
289    focus_manager->RegisterAccelerator(
290        iter->first, ui::AcceleratorManager::kNormalPriority, this);
291  }
292}
293
294void ChromeNativeAppWindowViews::InitializePanelWindow(
295    const AppWindow::CreateParams& create_params) {
296  views::Widget::InitParams params(views::Widget::InitParams::TYPE_PANEL);
297  params.delegate = this;
298
299  gfx::Rect initial_window_bounds =
300      create_params.GetInitialWindowBounds(gfx::Insets());
301  preferred_size_ = gfx::Size(initial_window_bounds.width(),
302                              initial_window_bounds.height());
303  if (preferred_size_.width() == 0)
304    preferred_size_.set_width(kDefaultPanelWidth);
305  else if (preferred_size_.width() < kMinPanelWidth)
306    preferred_size_.set_width(kMinPanelWidth);
307
308  if (preferred_size_.height() == 0)
309    preferred_size_.set_height(kDefaultPanelHeight);
310  else if (preferred_size_.height() < kMinPanelHeight)
311    preferred_size_.set_height(kMinPanelHeight);
312#if defined(USE_ASH)
313  if (ash::Shell::HasInstance()) {
314    // Open a new panel on the target root.
315    aura::Window* target = ash::Shell::GetTargetRootWindow();
316    params.bounds = ash::ScreenUtil::ConvertRectToScreen(
317        target, gfx::Rect(preferred_size_));
318  } else {
319    params.bounds = gfx::Rect(preferred_size_);
320  }
321#else
322  params.bounds = gfx::Rect(preferred_size_);
323#endif
324  // TODO(erg): Conceptually, these are toplevel windows, but we theoretically
325  // could plumb context through to here in some cases.
326  params.top_level = true;
327  widget()->Init(params);
328  widget()->set_focus_on_creation(create_params.focused);
329
330#if defined(USE_ASH)
331  if (create_params.state == ui::SHOW_STATE_DETACHED) {
332    gfx::Rect window_bounds(initial_window_bounds.x(),
333                            initial_window_bounds.y(),
334                            preferred_size_.width(),
335                            preferred_size_.height());
336    aura::Window* native_window = GetNativeWindow();
337    ash::wm::GetWindowState(native_window)->set_panel_attached(false);
338    aura::client::ParentWindowWithContext(native_window,
339                                          native_window->GetRootWindow(),
340                                          native_window->GetBoundsInScreen());
341    widget()->SetBounds(window_bounds);
342  }
343#else
344  // TODO(stevenjb): NativeAppWindow panels need to be implemented for other
345  // platforms.
346#endif
347}
348
349void ChromeNativeAppWindowViews::InstallEasyResizeTargeterOnContainer() const {
350  aura::Window* window = widget()->GetNativeWindow();
351  gfx::Insets inset(kResizeInsideBoundsSize, kResizeInsideBoundsSize,
352                    kResizeInsideBoundsSize, kResizeInsideBoundsSize);
353  // Add the EasyResizeWindowTargeter on the window, not its root window. The
354  // root window does not have a delegate, which is needed to handle the event
355  // in Linux.
356  window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
357      new wm::EasyResizeWindowTargeter(window, inset, inset)));
358}
359
360apps::AppWindowFrameView*
361ChromeNativeAppWindowViews::CreateAppWindowFrameView() {
362  // By default the user can resize the window from slightly inside the bounds.
363  int resize_inside_bounds_size = kResizeInsideBoundsSize;
364  int resize_outside_bounds_size = 0;
365  int resize_outside_scale_for_touch = 1;
366  int resize_area_corner_size = kResizeAreaCornerSize;
367#if defined(USE_ASH)
368  // For Aura windows on the Ash desktop the sizes are different and the user
369  // can resize the window from slightly outside the bounds as well.
370  if (chrome::IsNativeWindowInAsh(widget()->GetNativeWindow())) {
371    resize_inside_bounds_size = ash::kResizeInsideBoundsSize;
372    resize_outside_bounds_size = ash::kResizeOutsideBoundsSize;
373    resize_outside_scale_for_touch = ash::kResizeOutsideBoundsScaleForTouch;
374    resize_area_corner_size = ash::kResizeAreaCornerSize;
375  }
376#endif
377  apps::AppWindowFrameView* frame_view = new apps::AppWindowFrameView();
378  frame_view->Init(widget(),
379                   this,
380                   has_frame_color_,
381                   frame_color_,
382                   resize_inside_bounds_size,
383                   resize_outside_bounds_size,
384                   resize_outside_scale_for_touch,
385                   resize_area_corner_size);
386  return frame_view;
387}
388
389// ui::BaseWindow implementation.
390
391ui::WindowShowState ChromeNativeAppWindowViews::GetRestoredState() const {
392  if (IsMaximized())
393    return ui::SHOW_STATE_MAXIMIZED;
394  if (IsFullscreen()) {
395#if defined(USE_ASH)
396    if (immersive_fullscreen_controller_.get() &&
397        immersive_fullscreen_controller_->IsEnabled()) {
398      // Restore windows which were previously in immersive fullscreen to
399      // maximized. Restoring the window to a different fullscreen type
400      // makes for a bad experience.
401      return ui::SHOW_STATE_MAXIMIZED;
402    }
403#endif
404    return ui::SHOW_STATE_FULLSCREEN;
405  }
406#if defined(USE_ASH)
407  // Use kRestoreShowStateKey in case a window is minimized/hidden.
408  ui::WindowShowState restore_state = widget()->GetNativeWindow()->GetProperty(
409      aura::client::kRestoreShowStateKey);
410  // Whitelist states to return so that invalid and transient states
411  // are not saved and used to restore windows when they are recreated.
412  switch (restore_state) {
413    case ui::SHOW_STATE_NORMAL:
414    case ui::SHOW_STATE_MAXIMIZED:
415    case ui::SHOW_STATE_FULLSCREEN:
416    case ui::SHOW_STATE_DETACHED:
417      return restore_state;
418
419    case ui::SHOW_STATE_DEFAULT:
420    case ui::SHOW_STATE_MINIMIZED:
421    case ui::SHOW_STATE_INACTIVE:
422    case ui::SHOW_STATE_END:
423      return ui::SHOW_STATE_NORMAL;
424  }
425#endif
426  return ui::SHOW_STATE_NORMAL;
427}
428
429bool ChromeNativeAppWindowViews::IsAlwaysOnTop() const {
430  if (app_window()->window_type_is_panel()) {
431#if defined(USE_ASH)
432    return ash::wm::GetWindowState(widget()->GetNativeWindow())
433        ->panel_attached();
434#else
435    return true;
436#endif
437  } else {
438    return widget()->IsAlwaysOnTop();
439  }
440}
441
442// views::ContextMenuController implementation.
443
444void ChromeNativeAppWindowViews::ShowContextMenuForView(
445    views::View* source,
446    const gfx::Point& p,
447    ui::MenuSourceType source_type) {
448#if defined(USE_ASH) && defined(OS_CHROMEOS)
449  scoped_ptr<ui::MenuModel> model =
450      CreateMultiUserContextMenu(app_window()->GetNativeWindow());
451  if (!model.get())
452    return;
453
454  // Only show context menu if point is in caption.
455  gfx::Point point_in_view_coords(p);
456  views::View::ConvertPointFromScreen(widget()->non_client_view(),
457                                      &point_in_view_coords);
458  int hit_test =
459      widget()->non_client_view()->NonClientHitTest(point_in_view_coords);
460  if (hit_test == HTCAPTION) {
461    menu_runner_.reset(new views::MenuRunner(model.get()));
462    if (menu_runner_->RunMenuAt(source->GetWidget(), NULL,
463          gfx::Rect(p, gfx::Size(0,0)), views::MenuItemView::TOPLEFT,
464          source_type,
465          views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) ==
466        views::MenuRunner::MENU_DELETED)
467      return;
468  }
469#endif
470}
471
472// views::WidgetDelegate implementation.
473
474gfx::ImageSkia ChromeNativeAppWindowViews::GetWindowAppIcon() {
475  gfx::Image app_icon = app_window()->app_icon();
476  if (app_icon.IsEmpty())
477    return GetWindowIcon();
478  else
479    return *app_icon.ToImageSkia();
480}
481
482gfx::ImageSkia ChromeNativeAppWindowViews::GetWindowIcon() {
483  content::WebContents* web_contents = app_window()->web_contents();
484  if (web_contents) {
485    FaviconTabHelper* favicon_tab_helper =
486        FaviconTabHelper::FromWebContents(web_contents);
487    gfx::Image app_icon = favicon_tab_helper->GetFavicon();
488    if (!app_icon.IsEmpty())
489      return *app_icon.ToImageSkia();
490  }
491  return gfx::ImageSkia();
492}
493
494views::NonClientFrameView* ChromeNativeAppWindowViews::CreateNonClientFrameView(
495    views::Widget* widget) {
496#if defined(USE_ASH)
497  if (chrome::IsNativeViewInAsh(widget->GetNativeView())) {
498    // Set the delegate now because CustomFrameViewAsh sets the
499    // WindowStateDelegate if one is not already set.
500    ash::wm::GetWindowState(GetNativeWindow())->SetDelegate(
501        scoped_ptr<ash::wm::WindowStateDelegate>(
502            new NativeAppWindowStateDelegate(app_window(), this)).Pass());
503
504    if (app_window()->window_type_is_panel()) {
505      ash::PanelFrameView::FrameType frame_type = IsFrameless() ?
506          ash::PanelFrameView::FRAME_NONE : ash::PanelFrameView::FRAME_ASH;
507      views::NonClientFrameView* frame_view =
508          new ash::PanelFrameView(widget, frame_type);
509      frame_view->set_context_menu_controller(this);
510      return frame_view;
511    }
512
513    if (!IsFrameless()) {
514      ash::CustomFrameViewAsh* custom_frame_view =
515          new ash::CustomFrameViewAsh(widget);
516      // Non-frameless app windows can be put into immersive fullscreen.
517      immersive_fullscreen_controller_.reset(
518          new ash::ImmersiveFullscreenController());
519      custom_frame_view->InitImmersiveFullscreenControllerForView(
520          immersive_fullscreen_controller_.get());
521      custom_frame_view->GetHeaderView()->set_context_menu_controller(this);
522      return custom_frame_view;
523    }
524  }
525#endif
526#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
527  return CreateAppWindowFrameView();
528#else
529  if (IsFrameless() || has_frame_color_)
530    return CreateAppWindowFrameView();
531#endif
532  return views::WidgetDelegateView::CreateNonClientFrameView(widget);
533}
534
535bool ChromeNativeAppWindowViews::WidgetHasHitTestMask() const {
536  return shape_ != NULL;
537}
538
539void ChromeNativeAppWindowViews::GetWidgetHitTestMask(gfx::Path* mask) const {
540  shape_->getBoundaryPath(mask);
541}
542
543// views::View implementation.
544
545gfx::Size ChromeNativeAppWindowViews::GetPreferredSize() {
546  if (!preferred_size_.IsEmpty())
547    return preferred_size_;
548  return NativeAppWindowViews::GetPreferredSize();
549}
550
551bool ChromeNativeAppWindowViews::AcceleratorPressed(
552    const ui::Accelerator& accelerator) {
553  const std::map<ui::Accelerator, int>& accelerator_table =
554      GetAcceleratorTable();
555  std::map<ui::Accelerator, int>::const_iterator iter =
556      accelerator_table.find(accelerator);
557  DCHECK(iter != accelerator_table.end());
558  int command_id = iter->second;
559  switch (command_id) {
560    case IDC_CLOSE_WINDOW:
561      Close();
562      return true;
563    case IDC_ZOOM_MINUS:
564      chrome_page_zoom::Zoom(web_view()->GetWebContents(),
565                             content::PAGE_ZOOM_OUT);
566      return true;
567    case IDC_ZOOM_NORMAL:
568      chrome_page_zoom::Zoom(web_view()->GetWebContents(),
569                             content::PAGE_ZOOM_RESET);
570      return true;
571    case IDC_ZOOM_PLUS:
572      chrome_page_zoom::Zoom(web_view()->GetWebContents(),
573                             content::PAGE_ZOOM_IN);
574      return true;
575    default:
576      NOTREACHED() << "Unknown accelerator sent to app window.";
577  }
578  return NativeAppWindowViews::AcceleratorPressed(accelerator);
579}
580
581// NativeAppWindow implementation.
582
583void ChromeNativeAppWindowViews::SetFullscreen(int fullscreen_types) {
584  // Fullscreen not supported by panels.
585  if (app_window()->window_type_is_panel())
586    return;
587  is_fullscreen_ = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
588  widget()->SetFullscreen(is_fullscreen_);
589
590#if defined(USE_ASH)
591  if (immersive_fullscreen_controller_.get()) {
592    // |immersive_fullscreen_controller_| should only be set if immersive
593    // fullscreen is the fullscreen type used by the OS.
594    immersive_fullscreen_controller_->SetEnabled(
595        ash::ImmersiveFullscreenController::WINDOW_TYPE_PACKAGED_APP,
596        (fullscreen_types & AppWindow::FULLSCREEN_TYPE_OS) != 0);
597    // Autohide the shelf instead of hiding the shelf completely when only in
598    // OS fullscreen.
599    ash::wm::WindowState* window_state =
600        ash::wm::GetWindowState(widget()->GetNativeWindow());
601    window_state->set_hide_shelf_when_fullscreen(fullscreen_types !=
602                                                 AppWindow::FULLSCREEN_TYPE_OS);
603    DCHECK(ash::Shell::HasInstance());
604    ash::Shell::GetInstance()->UpdateShelfVisibility();
605  }
606#endif
607
608  // TODO(jeremya) we need to call RenderViewHost::ExitFullscreen() if we
609  // ever drop the window out of fullscreen in response to something that
610  // wasn't the app calling webkitCancelFullScreen().
611}
612
613bool ChromeNativeAppWindowViews::IsFullscreenOrPending() const {
614  return is_fullscreen_;
615}
616
617bool ChromeNativeAppWindowViews::IsDetached() const {
618  if (!app_window()->window_type_is_panel())
619    return false;
620#if defined(USE_ASH)
621  return !ash::wm::GetWindowState(widget()->GetNativeWindow())
622              ->panel_attached();
623#else
624  return false;
625#endif
626}
627
628void ChromeNativeAppWindowViews::UpdateBadgeIcon() {
629  const gfx::Image* icon = NULL;
630  if (!app_window()->badge_icon().IsEmpty()) {
631    icon = &app_window()->badge_icon();
632    // chrome::DrawTaskbarDecoration can do interesting things with non-square
633    // bitmaps.
634    // TODO(benwells): Refactor chrome::DrawTaskbarDecoration to not be avatar
635    // specific, and lift this restriction.
636    if (icon->Width() != icon->Height()) {
637      LOG(ERROR) << "Attempt to set a non-square badge; request ignored.";
638      return;
639    }
640  }
641  chrome::DrawTaskbarDecoration(GetNativeWindow(), icon);
642}
643
644void ChromeNativeAppWindowViews::UpdateShape(scoped_ptr<SkRegion> region) {
645  bool had_shape = shape_;
646  shape_ = region.Pass();
647
648  aura::Window* native_window = widget()->GetNativeWindow();
649  if (shape_) {
650    widget()->SetShape(new SkRegion(*shape_));
651    if (!had_shape) {
652      native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
653          new ShapedAppWindowTargeter(native_window, this)));
654    }
655  } else {
656    widget()->SetShape(NULL);
657    if (had_shape)
658      native_window->SetEventTargeter(scoped_ptr<ui::EventTargeter>());
659  }
660}
661
662bool ChromeNativeAppWindowViews::HasFrameColor() const {
663  return has_frame_color_;
664}
665
666SkColor ChromeNativeAppWindowViews::FrameColor() const { return frame_color_; }
667
668// NativeAppWindowViews implementation.
669
670void ChromeNativeAppWindowViews::InitializeWindow(
671    AppWindow* app_window,
672    const AppWindow::CreateParams& create_params) {
673  DCHECK(widget());
674  has_frame_color_ = create_params.has_frame_color;
675  frame_color_ = create_params.frame_color;
676  if (create_params.window_type == AppWindow::WINDOW_TYPE_PANEL ||
677      create_params.window_type == AppWindow::WINDOW_TYPE_V1_PANEL) {
678    InitializePanelWindow(create_params);
679  } else {
680    InitializeDefaultWindow(create_params);
681  }
682  extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews(
683      Profile::FromBrowserContext(app_window->browser_context()),
684      widget()->GetFocusManager(),
685      extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
686      NULL));
687
688#if !defined(OS_CHROMEOS)
689  if ((IsFrameless() || has_frame_color_) &&
690      chrome::GetHostDesktopTypeForNativeWindow(widget()->GetNativeWindow()) !=
691          chrome::HOST_DESKTOP_TYPE_ASH) {
692    InstallEasyResizeTargeterOnContainer();
693  }
694#endif
695}
696