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