panel_stack_view.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2013 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_stack_view.h"
6
7#include "base/logging.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/profiles/profile.h"
10#include "chrome/browser/ui/panels/panel.h"
11#include "chrome/browser/ui/panels/panel_manager.h"
12#include "chrome/browser/ui/panels/stacked_panel_collection.h"
13#include "chrome/browser/ui/views/panels/panel_view.h"
14#include "ui/base/animation/linear_animation.h"
15#include "ui/gfx/image/image_skia.h"
16#include "ui/gfx/rect.h"
17#include "ui/views/widget/widget.h"
18
19#if defined(OS_WIN)
20#include "base/win/windows_version.h"
21#include "chrome/browser/shell_integration.h"
22#include "ui/base/win/shell.h"
23#include "ui/views/win/hwnd_util.h"
24#endif
25
26namespace {
27// These values are experimental and subjective.
28const int kDefaultFramerateHz = 50;
29const int kSetBoundsAnimationMs = 180;
30}
31
32// static
33NativePanelStackWindow* NativePanelStackWindow::Create(
34    NativePanelStackWindowDelegate* delegate) {
35#if defined(OS_WIN)
36  return new PanelStackView(delegate);
37#else
38  NOTIMPLEMENTED();
39  return NULL;
40#endif
41}
42
43PanelStackView::PanelStackView(NativePanelStackWindowDelegate* delegate)
44    : delegate_(delegate),
45      window_(NULL),
46      is_closing_(false),
47      is_drawing_attention_(false),
48      animate_bounds_updates_(false),
49      bounds_updates_started_(false) {
50  DCHECK(delegate);
51  views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
52}
53
54PanelStackView::~PanelStackView() {
55}
56
57void PanelStackView::Close() {
58  is_closing_ = true;
59  delegate_ = NULL;
60  if (bounds_animator_)
61    bounds_animator_.reset();
62  if (window_)
63    window_->Close();
64  views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
65}
66
67void PanelStackView::AddPanel(Panel* panel) {
68  panels_.push_back(panel);
69
70  EnsureWindowCreated();
71  MakeStackWindowOwnPanelWindow(panel, this);
72  UpdateStackWindowBounds();
73
74  window_->UpdateWindowTitle();
75  window_->UpdateWindowIcon();
76}
77
78void PanelStackView::RemovePanel(Panel* panel) {
79  panels_.remove(panel);
80
81  MakeStackWindowOwnPanelWindow(panel, NULL);
82  UpdateStackWindowBounds();
83}
84
85void PanelStackView::MergeWith(NativePanelStackWindow* another) {
86  PanelStackView* another_stack = static_cast<PanelStackView*>(another);
87
88  for (Panels::const_iterator iter = another_stack->panels_.begin();
89       iter != another_stack->panels_.end(); ++iter) {
90    Panel* panel = *iter;
91    panels_.push_back(panel);
92    MakeStackWindowOwnPanelWindow(panel, this);
93  }
94  another_stack->panels_.clear();
95
96  UpdateStackWindowBounds();
97}
98
99bool PanelStackView::IsEmpty() const {
100  return panels_.empty();
101}
102
103bool PanelStackView::HasPanel(Panel* panel) const {
104  return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
105}
106
107void PanelStackView::MovePanelsBy(const gfx::Vector2d& delta) {
108  BeginBatchUpdatePanelBounds(false);
109  for (Panels::const_iterator iter = panels_.begin();
110       iter != panels_.end(); ++iter) {
111    Panel* panel = *iter;
112    AddPanelBoundsForBatchUpdate(panel, panel->GetBounds() + delta);
113  }
114  EndBatchUpdatePanelBounds();
115}
116
117void PanelStackView::BeginBatchUpdatePanelBounds(bool animate) {
118  // If the batch animation is still in progress, continue the animation
119  // with the new target bounds even we want to update the bounds instantly
120  // this time.
121  if (!bounds_updates_started_) {
122    animate_bounds_updates_ = animate;
123    bounds_updates_started_ = true;
124  }
125}
126
127void PanelStackView::AddPanelBoundsForBatchUpdate(Panel* panel,
128                                                  const gfx::Rect& new_bounds) {
129  DCHECK(bounds_updates_started_);
130
131  // No need to track it if no change is needed.
132  if (panel->GetBounds() == new_bounds)
133    return;
134
135  // Old bounds are stored as the map value.
136  bounds_updates_[panel] = panel->GetBounds();
137
138  // New bounds are directly applied to the valued stored in native panel
139  // window.
140  static_cast<PanelView*>(panel->native_panel())->set_cached_bounds_directly(
141      new_bounds);
142}
143
144void PanelStackView::EndBatchUpdatePanelBounds() {
145  DCHECK(bounds_updates_started_);
146
147  if (bounds_updates_.empty() || !animate_bounds_updates_) {
148    if (!bounds_updates_.empty()) {
149      UpdatePanelsBounds();
150      bounds_updates_.clear();
151    }
152
153    bounds_updates_started_ = false;
154    NotifyBoundsUpdateCompleted();
155    return;
156  }
157
158  bounds_animator_.reset(new ui::LinearAnimation(
159      PanelManager::AdjustTimeInterval(kSetBoundsAnimationMs),
160      kDefaultFramerateHz,
161      this));
162  bounds_animator_->Start();
163}
164
165void PanelStackView::NotifyBoundsUpdateCompleted() {
166  delegate_->PanelBoundsBatchUpdateCompleted();
167
168#if defined(OS_WIN)
169  // Refresh the thumbnail each time when any bounds updates are done.
170  RefreshLivePreviewThumbnail();
171#endif
172}
173
174bool PanelStackView::IsAnimatingPanelBounds() const {
175  return bounds_updates_started_ && animate_bounds_updates_;
176}
177
178void PanelStackView::Minimize() {
179#if defined(OS_WIN)
180  // When the stack window is minimized by the system, its snapshot could not
181  // be obtained. We need to capture the snapshot before the minimization.
182  if (thumbnailer_)
183    thumbnailer_->CaptureSnapshot();
184#endif
185
186  window_->Minimize();
187}
188
189bool PanelStackView::IsMinimized() const {
190  return window_ ? window_->IsMinimized() : false;
191}
192
193void PanelStackView::DrawSystemAttention(bool draw_attention) {
194  // The underlying call of FlashFrame, FlashWindowEx, seems not to work
195  // correctly if it is called more than once consecutively.
196  if (draw_attention == is_drawing_attention_)
197    return;
198  is_drawing_attention_ = draw_attention;
199
200#if defined(OS_WIN)
201  // Refresh the thumbnail when a panel could change something for the
202  // attention.
203  RefreshLivePreviewThumbnail();
204
205  if (draw_attention) {
206    // The default implementation of Widget::FlashFrame only flashes 5 times.
207    // We need more than that.
208    FLASHWINFO fwi;
209    fwi.cbSize = sizeof(fwi);
210    fwi.hwnd = views::HWNDForWidget(window_);
211    fwi.dwFlags = FLASHW_ALL;
212    fwi.uCount = panel::kNumberOfTimesToFlashPanelForAttention;
213    fwi.dwTimeout = 0;
214    ::FlashWindowEx(&fwi);
215  } else {
216    // Calling FlashWindowEx with FLASHW_STOP flag does not always work.
217    // Occasionally the taskbar icon could still remain in the flashed state.
218    // To work around this problem, we recreate the underlying window.
219    views::Widget* old_window = window_;
220    window_ = CreateWindowWithBounds(GetStackWindowBounds());
221
222    // New background window should also be minimized if the old one is.
223    if (old_window->IsMinimized())
224      window_->Minimize();
225
226    // Make sure the new background window stays at the same z-order as the old
227    // one.
228    ::SetWindowPos(views::HWNDForWidget(window_),
229                   views::HWNDForWidget(old_window),
230                   0, 0, 0, 0,
231                   SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
232    for (Panels::const_iterator iter = panels_.begin();
233         iter != panels_.end(); ++iter) {
234      MakeStackWindowOwnPanelWindow(*iter, this);
235    }
236
237    // Serve the snapshot to the new backgroud window.
238    if (thumbnailer_.get())
239      thumbnailer_->ReplaceWindow(views::HWNDForWidget(window_));
240
241    window_->UpdateWindowTitle();
242    window_->UpdateWindowIcon();
243    old_window->Close();
244  }
245#else
246  window_->FlashFrame(draw_attention);
247#endif
248}
249
250void PanelStackView::OnPanelActivated(Panel* panel) {
251  // Nothing to do.
252}
253
254string16 PanelStackView::GetWindowTitle() const {
255  return delegate_->GetTitle();
256}
257
258gfx::ImageSkia PanelStackView::GetWindowAppIcon() {
259  if (panels_.empty())
260    return gfx::ImageSkia();
261
262  Panel* panel = panels_.front();
263  gfx::Image app_icon = panel->app_icon();
264  if (!app_icon.IsEmpty())
265    return *app_icon.ToImageSkia();
266
267  return gfx::ImageSkia();
268}
269
270gfx::ImageSkia PanelStackView::GetWindowIcon() {
271  return GetWindowAppIcon();
272}
273
274views::Widget* PanelStackView::GetWidget() {
275  return window_;
276}
277
278const views::Widget* PanelStackView::GetWidget() const {
279  return window_;
280}
281
282void PanelStackView::DeleteDelegate() {
283  // |window_| could be closed when it is regenerated in order to clear the
284  // taskbar icon flash state. We should only delete this instance when the
285  // window is really being closed.
286  if (is_closing_)
287    delete this;
288}
289
290void PanelStackView::OnWidgetDestroying(views::Widget* widget) {
291  if (widget == window_)
292    window_ = NULL;
293}
294
295void PanelStackView::OnNativeFocusChange(gfx::NativeView focused_before,
296                                         gfx::NativeView focused_now) {
297  // When the user selects the stacked panels via ALT-TAB or WIN-TAB, the
298  // background stack window, instead of the foreground panel window, receives
299  // WM_SETFOCUS message. To deal with this, we listen to the focus change event
300  // and activate the most recently active panel.
301  // Note that OnNativeFocusChange might be called when window_ has not be
302  // created yet.
303#if defined(OS_WIN)
304  if (!panels_.empty() && window_ && focused_now == window_->GetNativeView()) {
305    Panel* panel_to_focus =
306        panels_.front()->stack()->most_recently_active_panel();
307    if (panel_to_focus)
308      panel_to_focus->Activate();
309  }
310#endif
311}
312
313void PanelStackView::AnimationEnded(const ui::Animation* animation) {
314  bounds_updates_started_ = false;
315
316  PanelManager* panel_manager = PanelManager::GetInstance();
317  for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
318       iter != bounds_updates_.end(); ++iter) {
319    panel_manager->OnPanelAnimationEnded(iter->first);
320  }
321  bounds_updates_.clear();
322
323  NotifyBoundsUpdateCompleted();
324}
325
326void PanelStackView::AnimationProgressed(const ui::Animation* animation) {
327  UpdatePanelsBounds();
328}
329
330void PanelStackView::UpdatePanelsBounds() {
331#if defined(OS_WIN)
332  // Add an extra count for the background stack window.
333  HDWP defer_update = ::BeginDeferWindowPos(bounds_updates_.size() + 1);
334#endif
335
336  // Update the bounds for each panel in the update list.
337  gfx::Rect enclosing_bounds;
338  for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
339       iter != bounds_updates_.end(); ++iter) {
340    Panel* panel = iter->first;
341    gfx::Rect target_bounds = panel->GetBounds();
342    gfx::Rect current_bounds;
343    if (bounds_animator_ && bounds_animator_->is_animating()) {
344      current_bounds = bounds_animator_->CurrentValueBetween(
345          iter->second, target_bounds);
346    } else {
347      current_bounds = target_bounds;
348    }
349
350    PanelView* panel_view = static_cast<PanelView*>(panel->native_panel());
351#if defined(OS_WIN)
352    DeferUpdateNativeWindowBounds(defer_update,
353                                  panel_view->window(),
354                                  current_bounds);
355#else
356    panel_view->SetPanelBoundsInstantly(current_bounds);
357#endif
358
359    enclosing_bounds = UnionRects(enclosing_bounds, current_bounds);
360  }
361
362  // Compute the stack window bounds that enclose those panels that are not
363  // in the batch update list.
364  for (Panels::const_iterator iter = panels_.begin();
365       iter != panels_.end(); ++iter) {
366    Panel* panel = *iter;
367    if (bounds_updates_.find(panel) == bounds_updates_.end())
368      enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds());
369  }
370
371  // Update the bounds of the background stack window.
372#if defined(OS_WIN)
373  DeferUpdateNativeWindowBounds(defer_update, window_, enclosing_bounds);
374#else
375  window_->SetBounds(enclosing_bounds);
376#endif
377
378#if defined(OS_WIN)
379  ::EndDeferWindowPos(defer_update);
380#endif
381}
382
383gfx::Rect PanelStackView::GetStackWindowBounds() const {
384  gfx::Rect enclosing_bounds;
385  for (Panels::const_iterator iter = panels_.begin();
386       iter != panels_.end(); ++iter) {
387    Panel* panel = *iter;
388    enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds());
389  }
390  return enclosing_bounds;
391}
392
393void PanelStackView::UpdateStackWindowBounds() {
394  window_->SetBounds(GetStackWindowBounds());
395
396#if defined(OS_WIN)
397  // Refresh the thumbnail each time whne the stack window is changed, due to
398  // adding or removing a panel.
399  RefreshLivePreviewThumbnail();
400#endif
401}
402
403// static
404void PanelStackView::MakeStackWindowOwnPanelWindow(
405    Panel* panel, PanelStackView* stack_window) {
406#if defined(OS_WIN)
407  // The panel widget window might already be gone when a panel is closed.
408  views::Widget* panel_window =
409      static_cast<PanelView*>(panel->native_panel())->window();
410  if (!panel_window)
411    return;
412
413  HWND native_panel_window = views::HWNDForWidget(panel_window);
414  HWND native_stack_window =
415      stack_window ? views::HWNDForWidget(stack_window->window_) : NULL;
416
417  // The extended style WS_EX_APPWINDOW is used to force a top-level window onto
418  // the taskbar. In order for multiple stacked panels to appear as one, this
419  // bit needs to be cleared.
420  int value = ::GetWindowLong(native_panel_window, GWL_EXSTYLE);
421  ::SetWindowLong(
422      native_panel_window,
423      GWL_EXSTYLE,
424      native_stack_window ? (value & ~WS_EX_APPWINDOW)
425                          : (value | WS_EX_APPWINDOW));
426
427  // All the windows that share the same owner window will appear as a single
428  // window on the taskbar.
429  ::SetWindowLongPtr(native_panel_window,
430                     GWLP_HWNDPARENT,
431                     reinterpret_cast<LONG>(native_stack_window));
432
433  // Make sure the background stack window always stays behind the panel window.
434  if (native_stack_window) {
435    ::SetWindowPos(native_stack_window, native_panel_window, 0, 0, 0, 0,
436        SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
437  }
438
439#else
440  NOTIMPLEMENTED();
441#endif
442}
443
444views::Widget* PanelStackView::CreateWindowWithBounds(const gfx::Rect& bounds) {
445  views::Widget* window = new views::Widget;
446  views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
447  params.delegate = this;
448  params.remove_standard_frame = true;
449  params.bounds = bounds;
450  window->Init(params);
451  window->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM);
452  window->set_focus_on_creation(false);
453  window->AddObserver(this);
454  window->ShowInactive();
455
456#if defined(OS_WIN)
457  DCHECK(!panels_.empty());
458  Panel* panel = panels_.front();
459  ui::win::SetAppIdForWindow(
460      ShellIntegration::GetAppModelIdForProfile(UTF8ToWide(panel->app_name()),
461                                                panel->profile()->GetPath()),
462      views::HWNDForWidget(window));
463#endif
464
465  return window;
466}
467
468void PanelStackView::EnsureWindowCreated() {
469  if (window_)
470    return;
471
472  // Empty size is not allowed so a temporary small size is passed. SetBounds
473  // will be called later to update the bounds.
474  window_ = CreateWindowWithBounds(gfx::Rect(0, 0, 1, 1));
475
476#if defined(OS_WIN)
477  if (base::win::GetVersion() >= base::win::VERSION_WIN7) {
478    HWND native_window = views::HWNDForWidget(window_);
479    thumbnailer_.reset(new TaskbarWindowThumbnailerWin(native_window, this));
480    thumbnailer_->Start();
481  }
482#endif
483}
484
485#if defined(OS_WIN)
486std::vector<HWND> PanelStackView::GetSnapshotWindowHandles() const {
487  std::vector<HWND> native_panel_windows;
488  for (Panels::const_iterator iter = panels_.begin();
489       iter != panels_.end(); ++iter) {
490    Panel* panel = *iter;
491    native_panel_windows.push_back(
492        views::HWNDForWidget(
493            static_cast<PanelView*>(panel->native_panel())->window()));
494  }
495  return native_panel_windows;
496}
497
498void PanelStackView::RefreshLivePreviewThumbnail() {
499  // Don't refresh the thumbnail when the stack window is system minimized
500  // because the snapshot could not be retrieved.
501  if (!thumbnailer_.get() || IsMinimized())
502    return;
503  thumbnailer_->InvalidateSnapshot();
504}
505
506void PanelStackView::DeferUpdateNativeWindowBounds(HDWP defer_window_pos_info,
507                                                   views::Widget* window,
508                                                   const gfx::Rect& bounds) {
509  ::DeferWindowPos(defer_window_pos_info,
510                    views::HWNDForWidget(window),
511                    NULL,
512                    bounds.x(),
513                    bounds.y(),
514                    bounds.width(),
515                    bounds.height(),
516                    SWP_NOACTIVATE | SWP_NOZORDER);
517}
518#endif
519