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