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