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