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