panel_view.cc revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright (c) 2012 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/panels/panel_view.h"
6
7#include <map>
8#include "base/logging.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/app/chrome_command_ids.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/ui/host_desktop.h"
14#include "chrome/browser/ui/panels/panel.h"
15#include "chrome/browser/ui/panels/panel_bounds_animation.h"
16#include "chrome/browser/ui/panels/panel_manager.h"
17#include "chrome/browser/ui/panels/stacked_panel_collection.h"
18#include "chrome/browser/ui/views/auto_keep_alive.h"
19#include "chrome/browser/ui/views/panels/panel_frame_view.h"
20#include "content/public/browser/render_view_host.h"
21#include "content/public/browser/render_widget_host_view.h"
22#include "content/public/browser/web_contents.h"
23#include "ui/aura/window.h"
24#include "ui/aura/window_tree_host.h"
25#include "ui/gfx/image/image.h"
26#include "ui/gfx/path.h"
27#include "ui/gfx/screen.h"
28#include "ui/views/controls/button/image_button.h"
29#include "ui/views/controls/webview/webview.h"
30#include "ui/views/widget/widget.h"
31
32#if defined(OS_WIN)
33#include "base/win/windows_version.h"
34#include "chrome/browser/shell_integration.h"
35#include "chrome/browser/ui/views/panels/taskbar_window_thumbnailer_win.h"
36#include "ui/base/win/shell.h"
37#include "ui/gfx/icon_util.h"
38#include "ui/views/win/hwnd_util.h"
39#endif
40
41#if defined(USE_X11) && !defined(OS_CHROMEOS)
42#include "chrome/browser/shell_integration_linux.h"
43#include "chrome/browser/ui/views/panels/x11_panel_resizer.h"
44#include "chrome/browser/web_applications/web_app.h"
45#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
46#endif
47
48namespace {
49
50#if defined(OS_WIN)
51// If the height of a stacked panel shrinks below this threshold during the
52// user resizing, it will be treated as minimized.
53const int kStackedPanelHeightShrinkThresholdToBecomeMinimized =
54    panel::kTitlebarHeight + 20;
55#endif
56
57// Supported accelerators.
58// Note: We can't use the acclerator table defined in chrome/browser/ui/views
59// due to checkdeps violation.
60struct AcceleratorMapping {
61  ui::KeyboardCode keycode;
62  int modifiers;
63  int command_id;
64};
65const AcceleratorMapping kPanelAcceleratorMap[] = {
66  { ui::VKEY_W, ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
67  { ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
68  { ui::VKEY_F4, ui::EF_ALT_DOWN, IDC_CLOSE_WINDOW },
69  { ui::VKEY_R, ui::EF_CONTROL_DOWN, IDC_RELOAD },
70  { ui::VKEY_F5, ui::EF_NONE, IDC_RELOAD },
71  { ui::VKEY_R, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
72      IDC_RELOAD_IGNORING_CACHE },
73  { ui::VKEY_F5, ui::EF_CONTROL_DOWN, IDC_RELOAD_IGNORING_CACHE },
74  { ui::VKEY_F5, ui::EF_SHIFT_DOWN, IDC_RELOAD_IGNORING_CACHE },
75  { ui::VKEY_ESCAPE, ui::EF_NONE, IDC_STOP },
76  { ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
77  { ui::VKEY_SUBTRACT, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
78  { ui::VKEY_0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
79  { ui::VKEY_NUMPAD0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
80  { ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
81  { ui::VKEY_ADD, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
82  { ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_DEV_TOOLS },
83  { ui::VKEY_J, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
84    IDC_DEV_TOOLS_CONSOLE },
85};
86
87const std::map<ui::Accelerator, int>& GetAcceleratorTable() {
88  static std::map<ui::Accelerator, int>* accelerators = NULL;
89  if (!accelerators) {
90    accelerators = new std::map<ui::Accelerator, int>();
91    for (size_t i = 0; i < arraysize(kPanelAcceleratorMap); ++i) {
92      ui::Accelerator accelerator(kPanelAcceleratorMap[i].keycode,
93                                  kPanelAcceleratorMap[i].modifiers);
94      (*accelerators)[accelerator] = kPanelAcceleratorMap[i].command_id;
95    }
96  }
97  return *accelerators;
98}
99
100// NativePanelTesting implementation.
101class NativePanelTestingViews : public NativePanelTesting {
102 public:
103  explicit NativePanelTestingViews(PanelView* panel_view);
104  virtual ~NativePanelTestingViews();
105
106 private:
107  virtual void PressLeftMouseButtonTitlebar(
108      const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
109  virtual void ReleaseMouseButtonTitlebar(
110      panel::ClickModifier modifier) OVERRIDE;
111  virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
112  virtual void CancelDragTitlebar() OVERRIDE;
113  virtual void FinishDragTitlebar() OVERRIDE;
114  virtual bool VerifyDrawingAttention() const OVERRIDE;
115  virtual bool VerifyActiveState(bool is_active) OVERRIDE;
116  virtual bool VerifyAppIcon() const OVERRIDE;
117  virtual bool VerifySystemMinimizeState() const OVERRIDE;
118  virtual bool IsWindowVisible() const OVERRIDE;
119  virtual bool IsWindowSizeKnown() const OVERRIDE;
120  virtual bool IsAnimatingBounds() const OVERRIDE;
121  virtual bool IsButtonVisible(
122      panel::TitlebarButtonType button_type) const OVERRIDE;
123  virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE;
124  virtual bool EnsureApplicationRunOnForeground() OVERRIDE;
125
126  PanelView* panel_view_;
127};
128
129NativePanelTestingViews::NativePanelTestingViews(PanelView* panel_view)
130    : panel_view_(panel_view) {
131}
132
133NativePanelTestingViews::~NativePanelTestingViews() {
134}
135
136void NativePanelTestingViews::PressLeftMouseButtonTitlebar(
137    const gfx::Point& mouse_location, panel::ClickModifier modifier) {
138  panel_view_->OnTitlebarMousePressed(mouse_location);
139}
140
141void NativePanelTestingViews::ReleaseMouseButtonTitlebar(
142    panel::ClickModifier modifier) {
143  panel_view_->OnTitlebarMouseReleased(modifier);
144}
145
146void NativePanelTestingViews::DragTitlebar(const gfx::Point& mouse_location) {
147  panel_view_->OnTitlebarMouseDragged(mouse_location);
148}
149
150void NativePanelTestingViews::CancelDragTitlebar() {
151  panel_view_->OnTitlebarMouseCaptureLost();
152}
153
154void NativePanelTestingViews::FinishDragTitlebar() {
155  panel_view_->OnTitlebarMouseReleased(panel::NO_MODIFIER);
156}
157
158bool NativePanelTestingViews::VerifyDrawingAttention() const {
159  base::MessageLoop::current()->RunUntilIdle();
160  return panel_view_->GetFrameView()->GetPaintState() ==
161         PanelFrameView::PAINT_FOR_ATTENTION;
162}
163
164bool NativePanelTestingViews::VerifyActiveState(bool is_active) {
165  return panel_view_->GetFrameView()->GetPaintState() ==
166         (is_active ? PanelFrameView::PAINT_AS_ACTIVE
167                    : PanelFrameView::PAINT_AS_INACTIVE);
168}
169
170bool NativePanelTestingViews::VerifyAppIcon() const {
171#if defined(OS_WIN)
172  // We only care about Windows 7 and later.
173  if (base::win::GetVersion() < base::win::VERSION_WIN7)
174    return true;
175
176  HWND native_window = views::HWNDForWidget(panel_view_->window());
177  HICON app_icon = reinterpret_cast<HICON>(
178      ::SendMessage(native_window, WM_GETICON, ICON_BIG, 0L));
179  if (!app_icon)
180    return false;
181  scoped_ptr<SkBitmap> bitmap(IconUtil::CreateSkBitmapFromHICON(app_icon));
182  return bitmap.get() &&
183         bitmap->width() == panel::kPanelAppIconSize &&
184         bitmap->height() == panel::kPanelAppIconSize;
185#else
186  return true;
187#endif
188}
189
190bool NativePanelTestingViews::VerifySystemMinimizeState() const {
191#if defined(OS_WIN)
192  HWND native_window = views::HWNDForWidget(panel_view_->window());
193  WINDOWPLACEMENT placement;
194  if (!::GetWindowPlacement(native_window, &placement))
195    return false;
196  if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED)
197    return true;
198
199  // If the panel window has owner window, as in stacked mode, check its owner
200  // window. Note that owner window, instead of parent window, is returned
201  // though GWL_HWNDPARENT contains 'parent'.
202  HWND owner_window =
203      reinterpret_cast<HWND>(::GetWindowLongPtr(native_window,
204                                                GWLP_HWNDPARENT));
205  if (!owner_window || !::GetWindowPlacement(owner_window, &placement))
206    return false;
207  return placement.showCmd == SW_MINIMIZE ||
208         placement.showCmd == SW_SHOWMINIMIZED;
209#else
210  return true;
211#endif
212}
213
214bool NativePanelTestingViews::IsWindowVisible() const {
215  return panel_view_->window()->IsVisible();
216}
217
218bool NativePanelTestingViews::IsWindowSizeKnown() const {
219  return true;
220}
221
222bool NativePanelTestingViews::IsAnimatingBounds() const {
223  return panel_view_->IsAnimatingBounds();
224}
225
226bool NativePanelTestingViews::IsButtonVisible(
227    panel::TitlebarButtonType button_type) const {
228  PanelFrameView* frame_view = panel_view_->GetFrameView();
229
230  switch (button_type) {
231    case panel::CLOSE_BUTTON:
232      return frame_view->close_button()->visible();
233    case panel::MINIMIZE_BUTTON:
234      return frame_view->minimize_button()->visible();
235    case panel::RESTORE_BUTTON:
236      return frame_view->restore_button()->visible();
237    default:
238      NOTREACHED();
239  }
240  return false;
241}
242
243panel::CornerStyle NativePanelTestingViews::GetWindowCornerStyle() const {
244  return panel_view_->GetFrameView()->corner_style();
245}
246
247bool NativePanelTestingViews::EnsureApplicationRunOnForeground() {
248  // Not needed on views.
249  return true;
250}
251
252}  // namespace
253
254// static
255NativePanel* Panel::CreateNativePanel(Panel* panel,
256                                      const gfx::Rect& bounds,
257                                      bool always_on_top) {
258  return new PanelView(panel, bounds, always_on_top);
259}
260
261// The panel window has to be created as always-on-top. We cannot create it
262// as non-always-on-top and then change it to always-on-top because Windows
263// system might deny making a window always-on-top if the application is not
264// a foreground application.
265PanelView::PanelView(Panel* panel, const gfx::Rect& bounds, bool always_on_top)
266    : panel_(panel),
267      bounds_(bounds),
268      window_(NULL),
269      window_closed_(false),
270      web_view_(NULL),
271      always_on_top_(always_on_top),
272      focused_(false),
273      user_resizing_(false),
274#if defined(OS_WIN)
275      user_resizing_interior_stacked_panel_edge_(false),
276#endif
277      mouse_pressed_(false),
278      mouse_dragging_state_(NO_DRAGGING),
279      is_drawing_attention_(false),
280      force_to_paint_as_inactive_(false),
281      old_focused_view_(NULL) {
282  window_ = new views::Widget;
283  views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
284  params.delegate = this;
285  params.remove_standard_frame = true;
286  params.keep_on_top = always_on_top;
287  params.visible_on_all_workspaces = always_on_top;
288  params.bounds = bounds;
289
290#if defined(USE_X11) && !defined(OS_CHROMEOS)
291  params.wm_class_name = web_app::GetWMClassFromAppName(panel->app_name());
292  params.wm_class_class = shell_integration_linux::GetProgramClassName();
293#endif
294
295  window_->Init(params);
296  window_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM);
297  window_->set_focus_on_creation(false);
298  window_->AddObserver(this);
299
300  // Prevent the browser process from shutting down while this window is open.
301  keep_alive_.reset(new AutoKeepAlive(GetNativePanelWindow()));
302
303  web_view_ = new views::WebView(NULL);
304  AddChildView(web_view_);
305
306  // Register accelarators supported by panels.
307  views::FocusManager* focus_manager = GetFocusManager();
308  const std::map<ui::Accelerator, int>& accelerator_table =
309      GetAcceleratorTable();
310  for (std::map<ui::Accelerator, int>::const_iterator iter =
311           accelerator_table.begin();
312       iter != accelerator_table.end(); ++iter) {
313    focus_manager->RegisterAccelerator(
314        iter->first, ui::AcceleratorManager::kNormalPriority, this);
315  }
316
317#if defined(OS_WIN)
318  ui::win::SetAppIdForWindow(
319      ShellIntegration::GetAppModelIdForProfile(
320          base::UTF8ToWide(panel->app_name()), panel->profile()->GetPath()),
321      views::HWNDForWidget(window_));
322  ui::win::PreventWindowFromPinning(views::HWNDForWidget(window_));
323#endif
324
325#if defined(USE_X11) && !defined(OS_CHROMEOS)
326  // Swap the default non client event handler with one which handles resizes
327  // for panels entirely within Chrome. This is needed because it is not
328  // possible to tell when a resize performed by the window manager ends.
329  views::DesktopWindowTreeHostX11* host =
330      views::DesktopWindowTreeHostX11::GetHostForXID(
331          window_->GetNativeView()->GetHost()->GetAcceleratedWidget());
332  scoped_ptr<ui::EventHandler> resizer(
333      new X11PanelResizer(panel_.get(), window_->GetNativeWindow()));
334  host->SwapNonClientEventHandler(resizer.Pass());
335#endif
336}
337
338PanelView::~PanelView() {
339}
340
341void PanelView::ShowPanel() {
342  ShowPanelInactive();
343  ActivatePanel();
344}
345
346void PanelView::ShowPanelInactive() {
347  if (window_->IsVisible())
348    return;
349  window_->ShowInactive();
350  // No animation is used for initial creation of a panel on Win.
351  // Signal immediately that pending actions can be performed.
352  panel_->manager()->OnPanelAnimationEnded(panel_.get());
353}
354
355gfx::Rect PanelView::GetPanelBounds() const {
356  return bounds_;
357}
358
359void PanelView::SetPanelBounds(const gfx::Rect& bounds) {
360  SetBoundsInternal(bounds, true);
361}
362
363void PanelView::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
364  SetBoundsInternal(bounds, false);
365}
366
367void PanelView::SetBoundsInternal(const gfx::Rect& new_bounds, bool animate) {
368  if (bounds_ == new_bounds)
369    return;
370
371  bounds_ = new_bounds;
372
373  if (!animate) {
374    // If no animation is in progress, apply bounds change instantly. Otherwise,
375    // continue the animation with new target bounds.
376    if (!IsAnimatingBounds())
377      SetWidgetBounds(bounds_);
378    return;
379  }
380
381  animation_start_bounds_ = window_->GetWindowBoundsInScreen();
382
383  bounds_animator_.reset(new PanelBoundsAnimation(
384      this, panel_.get(), animation_start_bounds_, new_bounds));
385  bounds_animator_->Start();
386}
387
388#if defined(OS_WIN)
389bool PanelView::FilterMessage(HWND hwnd,
390                              UINT message,
391                              WPARAM w_param,
392                              LPARAM l_param,
393                              LRESULT* l_result) {
394  switch (message) {
395    case WM_SIZING:
396      if (w_param == WMSZ_BOTTOM)
397        user_resizing_interior_stacked_panel_edge_ = true;
398      break;
399  }
400  return false;
401}
402#endif
403
404void PanelView::AnimationEnded(const gfx::Animation* animation) {
405  panel_->manager()->OnPanelAnimationEnded(panel_.get());
406}
407
408void PanelView::AnimationProgressed(const gfx::Animation* animation) {
409  gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween(
410      animation_start_bounds_, bounds_);
411  SetWidgetBounds(new_bounds);
412}
413
414void PanelView::SetWidgetBounds(const gfx::Rect& new_bounds) {
415#if defined(OS_WIN)
416  // An overlapped window is a top-level window that has a titlebar, border,
417  // and client area. The Windows system will automatically put the shadow
418  // around the whole window. Also the system will enforce the minimum height
419  // (38 pixels based on observation) for the overlapped window such that it
420  // will always has the space for the titlebar.
421  //
422  // On contrast, a popup window is a bare minimum window without border and
423  // titlebar by default. It is often used for the popup menu and the window
424  // with short life. The Windows system does not add the shadow around the
425  // whole window though CS_DROPSHADOW class style could be passed to add the
426  // drop shadow which is only around the right and bottom edges.
427  //
428  // The height of the title-only or minimized panel is smaller than the minimum
429  // overlapped window height. If the panel still uses the overlapped window
430  // style, Windows system will automatically increase the window height. To
431  // work around this limitation, we temporarily change the window style to
432  // popup when the height to set is smaller than the minimum overlapped window
433  // height and then restore the window style to overlapped when the height
434  // grows.
435  static const int kMinimumOverlappedWindowHeight = 38;
436  gfx::Rect old_bounds = GetWidget()->GetRestoredBounds();
437  if (old_bounds.height() > kMinimumOverlappedWindowHeight &&
438      new_bounds.height() <= kMinimumOverlappedWindowHeight) {
439    // When the panel height shrinks below the minimum overlapped window height,
440    // change the window style to popup such that we can show the title-only
441    // and minimized panel without additional height being added by the system.
442    UpdateWindowAttribute(GWL_STYLE,
443                          WS_POPUP,
444                          WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU,
445                          true);
446  } else if (old_bounds.height() <= kMinimumOverlappedWindowHeight &&
447             new_bounds.height() > kMinimumOverlappedWindowHeight) {
448    // Change the window style back to overlappped when the panel height grow
449    // taller than the minimum overlapped window height.
450    UpdateWindowAttribute(GWL_STYLE,
451                          WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU,
452                          WS_POPUP,
453                          true);
454  }
455#endif
456
457  GetWidget()->SetBounds(new_bounds);
458}
459
460void PanelView::ClosePanel() {
461  // We're already closing. Do nothing.
462  if (window_closed_)
463    return;
464
465  if (!panel_->ShouldCloseWindow())
466    return;
467
468  // Cancel any currently running animation since we're closing down.
469  if (bounds_animator_.get())
470    bounds_animator_.reset();
471
472  if (panel_->GetWebContents()) {
473    // Still have web contents. Allow renderer to shut down.
474    // When web contents are destroyed, we will be called back again.
475    panel_->OnWindowClosing();
476    return;
477  }
478
479  panel_->OnNativePanelClosed();
480  if (window_)
481    window_->Close();
482  window_closed_ = true;
483}
484
485void PanelView::ActivatePanel() {
486  window_->Activate();
487}
488
489void PanelView::DeactivatePanel() {
490  if (!focused_)
491    return;
492
493#if defined(OS_WIN)
494  // Need custom behavior for always-on-top panels to avoid
495  // the OS activating a minimized panel when this one is
496  // deactivated.
497  if (always_on_top_) {
498    ::SetForegroundWindow(::GetDesktopWindow());
499    return;
500  }
501#endif
502
503  window_->Deactivate();
504}
505
506bool PanelView::IsPanelActive() const {
507  return focused_;
508}
509
510void PanelView::PreventActivationByOS(bool prevent_activation) {
511#if defined(OS_WIN)
512  // Set the flags "NoActivate" to make sure the minimized panels do not get
513  // activated by the OS. In addition, set "AppWindow" to make sure the
514  // minimized panels do appear in the taskbar and Alt-Tab menu if it is not
515  // in a stack.
516  int value_to_change = WS_EX_NOACTIVATE;
517  if (!panel_->stack())
518    value_to_change |= WS_EX_APPWINDOW;
519  if (prevent_activation)
520    UpdateWindowAttribute(GWL_EXSTYLE, value_to_change, 0, false);
521  else
522    UpdateWindowAttribute(GWL_EXSTYLE, 0, value_to_change, false);
523#endif
524}
525
526gfx::NativeWindow PanelView::GetNativePanelWindow() {
527  return window_->GetNativeWindow();
528}
529
530void PanelView::UpdatePanelTitleBar() {
531  UpdateWindowTitle();
532  UpdateWindowIcon();
533}
534
535void PanelView::UpdatePanelLoadingAnimations(bool should_animate) {
536  GetFrameView()->UpdateThrobber();
537}
538
539void PanelView::PanelWebContentsFocused(content::WebContents* contents) {
540  web_view_->OnWebContentsFocused(contents);
541}
542
543void PanelView::PanelCut() {
544  // Nothing to do since we do not have panel-specific system menu.
545  NOTREACHED();
546}
547
548void PanelView::PanelCopy() {
549  // Nothing to do since we do not have panel-specific system menu.
550  NOTREACHED();
551}
552
553void PanelView::PanelPaste() {
554  // Nothing to do since we do not have panel-specific system menu.
555  NOTREACHED();
556}
557
558void PanelView::DrawAttention(bool draw_attention) {
559  DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0);
560
561  if (is_drawing_attention_ == draw_attention)
562    return;
563  is_drawing_attention_ = draw_attention;
564  GetFrameView()->SchedulePaint();
565
566  if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) {
567#if defined(OS_WIN)
568    // The default implementation of Widget::FlashFrame only flashes 5 times.
569    // We need more than that.
570    FLASHWINFO fwi;
571    fwi.cbSize = sizeof(fwi);
572    fwi.hwnd = views::HWNDForWidget(window_);
573    if (draw_attention) {
574      fwi.dwFlags = FLASHW_ALL;
575      fwi.uCount = panel::kNumberOfTimesToFlashPanelForAttention;
576      fwi.dwTimeout = 0;
577    } else {
578      // TODO(jianli): calling FlashWindowEx with FLASHW_STOP flag for the
579      // panel window has the same problem as the stack window. However,
580      // we cannot take the similar fix since there is no background window
581      // to replace for the regular panel window. More investigation is needed.
582      fwi.dwFlags = FLASHW_STOP;
583    }
584    ::FlashWindowEx(&fwi);
585#else
586    window_->FlashFrame(draw_attention);
587#endif
588  }
589}
590
591bool PanelView::IsDrawingAttention() const {
592  return is_drawing_attention_;
593}
594
595void PanelView::HandlePanelKeyboardEvent(
596    const content::NativeWebKeyboardEvent& event) {
597  views::FocusManager* focus_manager = GetFocusManager();
598  if (focus_manager->shortcut_handling_suspended())
599    return;
600
601  ui::Accelerator accelerator(
602      static_cast<ui::KeyboardCode>(event.windowsKeyCode),
603      content::GetModifiersFromNativeWebKeyboardEvent(event));
604  if (event.type == blink::WebInputEvent::KeyUp)
605    accelerator.set_type(ui::ET_KEY_RELEASED);
606  focus_manager->ProcessAccelerator(accelerator);
607}
608
609void PanelView::FullScreenModeChanged(bool is_full_screen) {
610  if (is_full_screen) {
611    if (window_->IsVisible() && always_on_top_)
612      window_->Hide();
613  } else {
614    if (!window_->IsVisible()) {
615      ShowPanelInactive();
616
617#if defined(OS_WIN)
618      // When hiding and showing again a top-most window that belongs to a
619      // background application (i.e. the application is not a foreground one),
620      // the window may loose top-most placement even though its WS_EX_TOPMOST
621      // bit is still set. Re-issuing SetWindowsPos() returns the window to its
622      // top-most placement.
623      if (always_on_top_)
624        window_->SetAlwaysOnTop(true);
625#endif
626    }
627  }
628}
629
630bool PanelView::IsPanelAlwaysOnTop() const {
631  return always_on_top_;
632}
633
634void PanelView::SetPanelAlwaysOnTop(bool on_top) {
635  if (always_on_top_ == on_top)
636    return;
637  always_on_top_ = on_top;
638
639  window_->SetAlwaysOnTop(on_top);
640  window_->SetVisibleOnAllWorkspaces(on_top);
641  window_->non_client_view()->Layout();
642  window_->client_view()->Layout();
643}
644
645void PanelView::UpdatePanelMinimizeRestoreButtonVisibility() {
646  GetFrameView()->UpdateTitlebarMinimizeRestoreButtonVisibility();
647}
648
649void PanelView::SetWindowCornerStyle(panel::CornerStyle corner_style) {
650  GetFrameView()->SetWindowCornerStyle(corner_style);
651}
652
653void PanelView::PanelExpansionStateChanging(Panel::ExpansionState old_state,
654                                            Panel::ExpansionState new_state) {
655#if defined(OS_WIN)
656  // Live preview is only available since Windows 7.
657  if (base::win::GetVersion() < base::win::VERSION_WIN7)
658    return;
659
660  if (panel_->collection()->type() != PanelCollection::DOCKED)
661    return;
662
663  bool is_minimized = old_state != Panel::EXPANDED;
664  bool will_be_minimized = new_state != Panel::EXPANDED;
665  if (is_minimized == will_be_minimized)
666    return;
667
668  HWND native_window = views::HWNDForWidget(window_);
669
670  if (!thumbnailer_.get()) {
671    DCHECK(native_window);
672    thumbnailer_.reset(new TaskbarWindowThumbnailerWin(native_window, NULL));
673  }
674
675  // Cache the image at this point.
676  if (will_be_minimized) {
677    // If the panel is still active (we will deactivate the minimizd panel at
678    // later time), we need to paint it immediately as inactive so that we can
679    // take a snapshot of inactive panel.
680    if (focused_) {
681      force_to_paint_as_inactive_ = true;
682      ::RedrawWindow(native_window, NULL, NULL,
683                     RDW_NOCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW);
684    }
685
686    // Start the thumbnailer and capture the snapshot now.
687    thumbnailer_->Start();
688    thumbnailer_->CaptureSnapshot();
689  } else {
690    force_to_paint_as_inactive_ = false;
691    thumbnailer_->Stop();
692  }
693
694#endif
695}
696
697gfx::Size PanelView::WindowSizeFromContentSize(
698    const gfx::Size& content_size) const {
699  gfx::Size frame = GetFrameView()->NonClientAreaSize();
700  return gfx::Size(content_size.width() + frame.width(),
701                   content_size.height() + frame.height());
702}
703
704gfx::Size PanelView::ContentSizeFromWindowSize(
705    const gfx::Size& window_size) const {
706  gfx::Size frame = GetFrameView()->NonClientAreaSize();
707  return gfx::Size(window_size.width() - frame.width(),
708                   window_size.height() - frame.height());
709}
710
711int PanelView::TitleOnlyHeight() const {
712  return panel::kTitlebarHeight;
713}
714
715void PanelView::MinimizePanelBySystem() {
716  window_->Minimize();
717}
718
719bool PanelView::IsPanelMinimizedBySystem() const {
720  return window_->IsMinimized();
721}
722
723bool PanelView::IsPanelShownOnActiveDesktop() const {
724#if defined(OS_WIN)
725  // Virtual desktop is not supported by the native Windows system.
726  return true;
727#else
728  NOTIMPLEMENTED();
729  return true;
730#endif
731}
732
733void PanelView::ShowShadow(bool show) {
734#if defined(OS_WIN)
735  // The overlapped window has the shadow while the popup window does not have
736  // the shadow.
737  int overlap_style = WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU;
738  int popup_style = WS_POPUP;
739  UpdateWindowAttribute(GWL_STYLE,
740                        show ? overlap_style : popup_style,
741                        show ? popup_style : overlap_style,
742                        true);
743#endif
744}
745
746void PanelView::AttachWebContents(content::WebContents* contents) {
747  web_view_->SetWebContents(contents);
748}
749
750void PanelView::DetachWebContents(content::WebContents* contents) {
751  web_view_->SetWebContents(NULL);
752}
753
754NativePanelTesting* PanelView::CreateNativePanelTesting() {
755  return new NativePanelTestingViews(this);
756}
757
758void PanelView::OnDisplayChanged() {
759  panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged();
760}
761
762void PanelView::OnWorkAreaChanged() {
763  panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged();
764}
765
766bool PanelView::WillProcessWorkAreaChange() const {
767  return true;
768}
769
770views::View* PanelView::GetContentsView() {
771  return this;
772}
773
774views::NonClientFrameView* PanelView::CreateNonClientFrameView(
775    views::Widget* widget) {
776  PanelFrameView* frame_view = new PanelFrameView(this);
777  frame_view->Init();
778  return frame_view;
779}
780
781bool PanelView::CanResize() const {
782  return true;
783}
784
785bool PanelView::CanMaximize() const {
786  return false;
787}
788
789base::string16 PanelView::GetWindowTitle() const {
790  return panel_->GetWindowTitle();
791}
792
793gfx::ImageSkia PanelView::GetWindowAppIcon() {
794  gfx::Image app_icon = panel_->app_icon();
795  if (app_icon.IsEmpty())
796    return GetWindowIcon();
797  else
798    return *app_icon.ToImageSkia();
799}
800
801gfx::ImageSkia PanelView::GetWindowIcon() {
802  gfx::Image icon = panel_->GetCurrentPageIcon();
803  return icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia();
804}
805
806void PanelView::WindowClosing() {
807  // When closing a panel via window.close, API or the close button,
808  // ClosePanel() is called first, destroying the native |window_|
809  // which results in this method being called. ClosePanel() sets
810  // |window_closed_| to NULL.
811  // If we still have a |window_closed_| here, the close was triggered by the
812  // OS, (e.g. clicking on taskbar menu), which destroys the native |window_|
813  // without invoking ClosePanel() beforehand.
814  if (!window_closed_) {
815    panel_->OnWindowClosing();
816    ClosePanel();
817    DCHECK(window_closed_);
818  }
819}
820
821void PanelView::DeleteDelegate() {
822  delete this;
823}
824
825void PanelView::OnWindowBeginUserBoundsChange() {
826  user_resizing_ = true;
827  panel_->OnPanelStartUserResizing();
828
829#if defined(OS_WIN)
830  StackedPanelCollection* stack = panel_->stack();
831  if (stack) {
832    // Listen to WM_SIZING message in order to find out whether the interior
833    // edge is being resized such that the specific maximum size could be
834    // passed to the system.
835    if (panel_->stack()->GetPanelBelow(panel_.get())) {
836      ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window_), this);
837      user_resizing_interior_stacked_panel_edge_ = false;
838    }
839
840    // Keep track of the original full size of the resizing panel such that it
841    // can be restored to this size once it is shrunk to minimized state.
842    original_full_size_of_resizing_panel_ = panel_->full_size();
843
844    // Keep track of the original full size of the panel below the resizing
845    // panel such that it can be restored to this size once it is shrunk to
846    // minimized state.
847    Panel* below_panel = stack->GetPanelBelow(panel_.get());
848    if (below_panel && !below_panel->IsMinimized()) {
849      original_full_size_of_panel_below_resizing_panel_ =
850          below_panel->full_size();
851    }
852  }
853#endif
854}
855
856void PanelView::OnWindowEndUserBoundsChange() {
857  user_resizing_ = false;
858  panel_->OnPanelEndUserResizing();
859
860  // No need to proceed with post-resizing update when there is no size change.
861  gfx::Rect new_bounds = window_->GetWindowBoundsInScreen();
862  if (bounds_ == new_bounds)
863    return;
864  bounds_ = new_bounds;
865
866  panel_->IncreaseMaxSize(bounds_.size());
867  panel_->set_full_size(bounds_.size());
868
869#if defined(OS_WIN)
870  StackedPanelCollection* stack = panel_->stack();
871  if (stack) {
872    // No need to listen to WM_SIZING message any more.
873    ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
874
875    // If the height of resizing panel shrinks close to the titlebar height,
876    // treate it as minimized. This could occur when the user is dragging
877    // 1) the top edge of the top panel downward to shrink it; or
878    // 2) the bottom edge of any panel upward to shrink it.
879    if (panel_->GetBounds().height() <
880            kStackedPanelHeightShrinkThresholdToBecomeMinimized) {
881      stack->MinimizePanel(panel_.get());
882      panel_->set_full_size(original_full_size_of_resizing_panel_);
883    }
884
885    // If the height of panel below the resizing panel shrinks close to the
886    // titlebar height, treat it as minimized. This could occur when the user
887    // is dragging the bottom edge of non-bottom panel downward to expand it
888    // and also shrink the panel below.
889    Panel* below_panel = stack->GetPanelBelow(panel_.get());
890    if (below_panel && !below_panel->IsMinimized() &&
891        below_panel->GetBounds().height() <
892            kStackedPanelHeightShrinkThresholdToBecomeMinimized) {
893      stack->MinimizePanel(below_panel);
894      below_panel->set_full_size(
895          original_full_size_of_panel_below_resizing_panel_);
896    }
897  }
898#endif
899
900  panel_->collection()->RefreshLayout();
901}
902
903views::Widget* PanelView::GetWidget() {
904  return window_;
905}
906
907const views::Widget* PanelView::GetWidget() const {
908  return window_;
909}
910
911void PanelView::UpdateLoadingAnimations(bool should_animate) {
912  GetFrameView()->UpdateThrobber();
913}
914
915void PanelView::UpdateWindowTitle() {
916  window_->UpdateWindowTitle();
917  GetFrameView()->UpdateTitle();
918}
919
920void PanelView::UpdateWindowIcon() {
921  window_->UpdateWindowIcon();
922  GetFrameView()->UpdateIcon();
923}
924
925void PanelView::Layout() {
926  // |web_view_| might not be created yet when the window is first created.
927  if (web_view_)
928    web_view_->SetBounds(0, 0, width(), height());
929}
930
931gfx::Size PanelView::GetMinimumSize() const {
932  // If the panel is minimized, it can be rendered to very small size, like
933  // 4-pixel lines when it is docked. Otherwise, its size should not be less
934  // than its minimum size.
935  return panel_->IsMinimized() ? gfx::Size() :
936      gfx::Size(panel::kPanelMinWidth, panel::kPanelMinHeight);
937}
938
939gfx::Size PanelView::GetMaximumSize() const {
940  // If the user is resizing a stacked panel by its bottom edge, make sure its
941  // height cannot grow more than what the panel below it could offer. This is
942  // because growing a stacked panel by y amount will shrink the panel below it
943  // by same amount and we do not want the panel below it being shrunk to be
944  // smaller than the titlebar.
945#if defined(OS_WIN)
946  if (panel_->stack() && user_resizing_interior_stacked_panel_edge_) {
947    Panel* below_panel = panel_->stack()->GetPanelBelow(panel_.get());
948    if (below_panel && !below_panel->IsMinimized()) {
949      return gfx::Size(0, below_panel->GetBounds().bottom() -
950          panel_->GetBounds().y() - panel::kTitlebarHeight);
951    }
952  }
953#endif
954  return gfx::Size();
955}
956
957bool PanelView::AcceleratorPressed(const ui::Accelerator& accelerator) {
958  if (mouse_pressed_ && accelerator.key_code() == ui::VKEY_ESCAPE) {
959    OnTitlebarMouseCaptureLost();
960    return true;
961  }
962
963  // No other accelerator is allowed when the drag begins.
964  if (mouse_dragging_state_ == DRAGGING_STARTED)
965    return true;
966
967  const std::map<ui::Accelerator, int>& accelerator_table =
968      GetAcceleratorTable();
969  std::map<ui::Accelerator, int>::const_iterator iter =
970      accelerator_table.find(accelerator);
971  DCHECK(iter != accelerator_table.end());
972  return panel_->ExecuteCommandIfEnabled(iter->second);
973}
974
975void PanelView::OnWidgetDestroying(views::Widget* widget) {
976  window_ = NULL;
977}
978
979void PanelView::OnWidgetActivationChanged(views::Widget* widget, bool active) {
980#if defined(OS_WIN)
981  // WM_NCACTIVATED could be sent when an active window is being destroyed on
982  // Windows. We need to guard against this.
983  if (window_closed_)
984    return;
985
986  bool focused = active;
987  if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_NATIVE) {
988    // The panel window is in focus (actually accepting keystrokes) if it is
989    // active and belongs to a foreground application.
990    focused = active &&
991        views::HWNDForWidget(widget) == ::GetForegroundWindow();
992  }
993#else
994  bool focused = active;
995#endif
996
997  if (focused_ == focused)
998    return;
999  focused_ = focused;
1000
1001  // Expand the panel if the minimized panel is activated by means other than
1002  // clicking on its titlebar. This is the workaround to support restoring the
1003  // minimized panel by other means, like alt-tabbing, win-tabbing, or clicking
1004  // the taskbar icon. Note that this workaround does not work for one edge
1005  // case: the mouse happens to be at the minimized panel when the user tries to
1006  // bring up the panel with the above alternatives.
1007  // When the user clicks on the minimized panel, the panel expansion will be
1008  // done when we process the mouse button pressed message.
1009#if defined(OS_WIN)
1010  if (focused_ && panel_->IsMinimized() &&
1011      panel_->collection()->type() == PanelCollection::DOCKED &&
1012      gfx::Screen::GetScreenFor(widget->GetNativeWindow())->
1013          GetWindowUnderCursor() != widget->GetNativeWindow()) {
1014    panel_->Restore();
1015  }
1016#endif
1017
1018  panel()->OnActiveStateChanged(focused);
1019
1020   // Give web contents view a chance to set focus to the appropriate element.
1021  if (focused_) {
1022    content::WebContents* web_contents = panel_->GetWebContents();
1023    if (web_contents)
1024      web_contents->RestoreFocus();
1025  }
1026}
1027
1028void PanelView::OnWidgetBoundsChanged(views::Widget* widget,
1029                                      const gfx::Rect& new_bounds) {
1030  if (user_resizing_)
1031    panel()->collection()->OnPanelResizedByMouse(panel(), new_bounds);
1032}
1033
1034bool PanelView::OnTitlebarMousePressed(const gfx::Point& mouse_location) {
1035  mouse_pressed_ = true;
1036  mouse_dragging_state_ = NO_DRAGGING;
1037  last_mouse_location_ = mouse_location;
1038  return true;
1039}
1040
1041bool PanelView::OnTitlebarMouseDragged(const gfx::Point& mouse_location) {
1042  if (!mouse_pressed_)
1043    return false;
1044
1045  if (mouse_dragging_state_ == NO_DRAGGING &&
1046      ExceededDragThreshold(mouse_location - last_mouse_location_)) {
1047    // When a drag begins, we do not want to the client area to still receive
1048    // the focus. We do not need to do this for the unfocused minimized panel.
1049    if (!panel_->IsMinimized()) {
1050      old_focused_view_ = GetFocusManager()->GetFocusedView();
1051      GetFocusManager()->SetFocusedView(GetFrameView());
1052    }
1053
1054    panel_->manager()->StartDragging(panel_.get(), last_mouse_location_);
1055    mouse_dragging_state_ = DRAGGING_STARTED;
1056  }
1057  if (mouse_dragging_state_ == DRAGGING_STARTED) {
1058    panel_->manager()->Drag(mouse_location);
1059
1060    // Once in drag, update |last_mouse_location_| on each drag fragment, since
1061    // we already dragged the panel up to the current mouse location.
1062    last_mouse_location_ = mouse_location;
1063  }
1064  return true;
1065}
1066
1067bool PanelView::OnTitlebarMouseReleased(panel::ClickModifier modifier) {
1068  if (mouse_dragging_state_ != NO_DRAGGING) {
1069    // Ensure dragging a minimized panel does not leave it activated.
1070    // Windows activates a panel on mouse-down, regardless of our attempts
1071    // to prevent activation of a minimized panel. Now that we know mouse-down
1072    // resulted in a mouse-drag, we need to ensure the minimized panel is
1073    // deactivated.
1074    if (panel_->IsMinimized() && focused_)
1075      panel_->Deactivate();
1076
1077    if (mouse_dragging_state_ == DRAGGING_STARTED) {
1078      // When a drag ends, restore the focus.
1079      if (old_focused_view_) {
1080        GetFocusManager()->SetFocusedView(old_focused_view_);
1081        old_focused_view_ = NULL;
1082      }
1083      return EndDragging(false);
1084    }
1085
1086    // The panel drag was cancelled before the mouse is released. Do not
1087    // treat this as a click.
1088    return true;
1089  }
1090
1091  panel_->OnTitlebarClicked(modifier);
1092  return true;
1093}
1094
1095bool PanelView::OnTitlebarMouseCaptureLost() {
1096  if (mouse_dragging_state_ == DRAGGING_STARTED)
1097    return EndDragging(true);
1098  return true;
1099}
1100
1101bool PanelView::EndDragging(bool cancelled) {
1102  // Only handle clicks that started in our window.
1103  if (!mouse_pressed_)
1104    return false;
1105  mouse_pressed_ = false;
1106
1107  mouse_dragging_state_ = DRAGGING_ENDED;
1108  panel_->manager()->EndDragging(cancelled);
1109  return true;
1110}
1111
1112PanelFrameView* PanelView::GetFrameView() const {
1113  return static_cast<PanelFrameView*>(window_->non_client_view()->frame_view());
1114}
1115
1116bool PanelView::IsAnimatingBounds() const {
1117  if (bounds_animator_.get() && bounds_animator_->is_animating())
1118    return true;
1119  StackedPanelCollection* stack = panel_->stack();
1120  if (!stack)
1121    return false;
1122  return stack->IsAnimatingPanelBounds(panel_.get());
1123}
1124
1125bool PanelView::IsWithinResizingArea(const gfx::Point& mouse_location) const {
1126  gfx::Rect bounds = window_->GetWindowBoundsInScreen();
1127  DCHECK(bounds.Contains(mouse_location));
1128  return mouse_location.x() < bounds.x() + kResizeInsideBoundsSize ||
1129         mouse_location.x() >= bounds.right() - kResizeInsideBoundsSize ||
1130         mouse_location.y() < bounds.y() + kResizeInsideBoundsSize ||
1131         mouse_location.y() >= bounds.bottom() - kResizeInsideBoundsSize;
1132}
1133
1134#if defined(OS_WIN)
1135void PanelView::UpdateWindowAttribute(int attribute_index,
1136                                      int attribute_value_to_set,
1137                                      int attribute_value_to_reset,
1138                                      bool update_frame) {
1139  HWND native_window = views::HWNDForWidget(window_);
1140  int value = ::GetWindowLong(native_window, attribute_index);
1141  int expected_value = value;
1142  if (attribute_value_to_set)
1143    expected_value |=  attribute_value_to_set;
1144  if (attribute_value_to_reset)
1145    expected_value &=  ~attribute_value_to_reset;
1146  if (value != expected_value)
1147    ::SetWindowLong(native_window, attribute_index, expected_value);
1148
1149  // Per MSDN, if any of the frame styles is changed, SetWindowPos with the
1150  // SWP_FRAMECHANGED flag must be called in order for the cached window data
1151  // to be updated properly.
1152  // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx
1153  if (update_frame) {
1154    ::SetWindowPos(native_window, NULL, 0, 0, 0, 0,
1155                   SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
1156                       SWP_NOZORDER | SWP_NOACTIVATE);
1157  }
1158}
1159#endif
1160