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.
5 #include "chrome/browser/ui/views/panels/panel_stack_view.h"
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/gfx/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"
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"
27 // These values are experimental and subjective.
28 const int kDefaultFramerateHz
= 50;
29 const int kSetBoundsAnimationMs
= 180;
31 // The widget window that acts as a background window for the stack of panels.
32 class PanelStackWindow
: public views::WidgetObserver
,
33 public views::WidgetDelegateView
{
35 PanelStackWindow(const gfx::Rect
& bounds
,
36 NativePanelStackWindowDelegate
* delegate
);
37 virtual ~PanelStackWindow();
39 // Overridden from views::WidgetDelegate:
40 virtual base::string16
GetWindowTitle() const OVERRIDE
;
41 virtual gfx::ImageSkia
GetWindowAppIcon() OVERRIDE
;
42 virtual gfx::ImageSkia
GetWindowIcon() OVERRIDE
;
43 virtual views::Widget
* GetWidget() OVERRIDE
;
44 virtual const views::Widget
* GetWidget() const OVERRIDE
;
46 // Overridden from views::WidgetObserver:
47 virtual void OnWidgetClosing(views::Widget
* widget
) OVERRIDE
;
48 virtual void OnWidgetDestroying(views::Widget
* widget
) OVERRIDE
;
51 views::Widget
* window_
; // Weak pointer, own us.
52 NativePanelStackWindowDelegate
* delegate_
; // Weak pointer.
54 DISALLOW_COPY_AND_ASSIGN(PanelStackWindow
);
57 PanelStackWindow::PanelStackWindow(const gfx::Rect
& bounds
,
58 NativePanelStackWindowDelegate
* delegate
)
61 window_
= new views::Widget
;
62 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_WINDOW
);
63 params
.delegate
= this;
64 params
.remove_standard_frame
= true;
65 params
.bounds
= bounds
;
66 window_
->Init(params
);
67 window_
->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM
);
68 window_
->set_focus_on_creation(false);
69 window_
->AddObserver(this);
70 window_
->ShowInactive();
73 PanelStackWindow::~PanelStackWindow() {
76 base::string16
PanelStackWindow::GetWindowTitle() const {
77 return delegate_
? delegate_
->GetTitle() : base::string16();
80 gfx::ImageSkia
PanelStackWindow::GetWindowAppIcon() {
82 gfx::Image app_icon
= delegate_
->GetIcon();
83 if (!app_icon
.IsEmpty())
84 return *app_icon
.ToImageSkia();
86 return gfx::ImageSkia();
89 gfx::ImageSkia
PanelStackWindow::GetWindowIcon() {
90 return GetWindowAppIcon();
93 views::Widget
* PanelStackWindow::GetWidget() {
97 const views::Widget
* PanelStackWindow::GetWidget() const {
101 void PanelStackWindow::OnWidgetClosing(views::Widget
* widget
) {
105 void PanelStackWindow::OnWidgetDestroying(views::Widget
* widget
) {
112 NativePanelStackWindow
* NativePanelStackWindow::Create(
113 NativePanelStackWindowDelegate
* delegate
) {
115 return new PanelStackView(delegate
);
122 PanelStackView::PanelStackView(NativePanelStackWindowDelegate
* delegate
)
123 : delegate_(delegate
),
125 is_drawing_attention_(false),
126 animate_bounds_updates_(false),
127 bounds_updates_started_(false) {
129 views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
132 PanelStackView::~PanelStackView() {
134 ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
138 void PanelStackView::Close() {
140 if (bounds_animator_
)
141 bounds_animator_
.reset();
144 views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
147 void PanelStackView::AddPanel(Panel
* panel
) {
148 panels_
.push_back(panel
);
150 EnsureWindowCreated();
151 MakeStackWindowOwnPanelWindow(panel
, this);
152 UpdateStackWindowBounds();
154 window_
->UpdateWindowTitle();
155 window_
->UpdateWindowIcon();
158 void PanelStackView::RemovePanel(Panel
* panel
) {
159 if (IsAnimatingPanelBounds()) {
160 // This panel is gone.
161 bounds_updates_
.erase(panel
);
163 // Abort the ongoing animation.
164 bounds_animator_
->Stop();
167 panels_
.remove(panel
);
169 MakeStackWindowOwnPanelWindow(panel
, NULL
);
170 UpdateStackWindowBounds();
173 void PanelStackView::MergeWith(NativePanelStackWindow
* another
) {
174 PanelStackView
* another_stack
= static_cast<PanelStackView
*>(another
);
176 for (Panels::const_iterator iter
= another_stack
->panels_
.begin();
177 iter
!= another_stack
->panels_
.end(); ++iter
) {
178 Panel
* panel
= *iter
;
179 panels_
.push_back(panel
);
180 MakeStackWindowOwnPanelWindow(panel
, this);
182 another_stack
->panels_
.clear();
184 UpdateStackWindowBounds();
187 bool PanelStackView::IsEmpty() const {
188 return panels_
.empty();
191 bool PanelStackView::HasPanel(Panel
* panel
) const {
192 return std::find(panels_
.begin(), panels_
.end(), panel
) != panels_
.end();
195 void PanelStackView::MovePanelsBy(const gfx::Vector2d
& delta
) {
196 BeginBatchUpdatePanelBounds(false);
197 for (Panels::const_iterator iter
= panels_
.begin();
198 iter
!= panels_
.end(); ++iter
) {
199 Panel
* panel
= *iter
;
200 AddPanelBoundsForBatchUpdate(panel
, panel
->GetBounds() + delta
);
202 EndBatchUpdatePanelBounds();
205 void PanelStackView::BeginBatchUpdatePanelBounds(bool animate
) {
206 // If the batch animation is still in progress, continue the animation
207 // with the new target bounds even we want to update the bounds instantly
209 if (!bounds_updates_started_
) {
210 animate_bounds_updates_
= animate
;
211 bounds_updates_started_
= true;
215 void PanelStackView::AddPanelBoundsForBatchUpdate(Panel
* panel
,
216 const gfx::Rect
& new_bounds
) {
217 DCHECK(bounds_updates_started_
);
219 // No need to track it if no change is needed.
220 if (panel
->GetBounds() == new_bounds
)
223 // Old bounds are stored as the map value.
224 bounds_updates_
[panel
] = panel
->GetBounds();
226 // New bounds are directly applied to the valued stored in native panel
228 static_cast<PanelView
*>(panel
->native_panel())->set_cached_bounds_directly(
232 void PanelStackView::EndBatchUpdatePanelBounds() {
233 DCHECK(bounds_updates_started_
);
235 if (bounds_updates_
.empty() || !animate_bounds_updates_
) {
236 if (!bounds_updates_
.empty()) {
237 UpdatePanelsBounds();
238 bounds_updates_
.clear();
241 bounds_updates_started_
= false;
242 NotifyBoundsUpdateCompleted();
246 bounds_animator_
.reset(new gfx::LinearAnimation(
247 PanelManager::AdjustTimeInterval(kSetBoundsAnimationMs
),
250 bounds_animator_
->Start();
253 void PanelStackView::NotifyBoundsUpdateCompleted() {
254 delegate_
->PanelBoundsBatchUpdateCompleted();
257 // Refresh the thumbnail each time when any bounds updates are done.
258 RefreshLivePreviewThumbnail();
262 bool PanelStackView::IsAnimatingPanelBounds() const {
263 return bounds_updates_started_
&& animate_bounds_updates_
;
266 void PanelStackView::Minimize() {
268 // When the stack window is minimized by the system, its snapshot could not
269 // be obtained. We need to capture the snapshot before the minimization.
271 thumbnailer_
->CaptureSnapshot();
277 bool PanelStackView::IsMinimized() const {
278 return window_
? window_
->IsMinimized() : false;
281 void PanelStackView::DrawSystemAttention(bool draw_attention
) {
282 // The underlying call of FlashFrame, FlashWindowEx, seems not to work
283 // correctly if it is called more than once consecutively.
284 if (draw_attention
== is_drawing_attention_
)
286 is_drawing_attention_
= draw_attention
;
289 // Refresh the thumbnail when a panel could change something for the
291 RefreshLivePreviewThumbnail();
293 if (draw_attention
) {
294 // The default implementation of Widget::FlashFrame only flashes 5 times.
295 // We need more than that.
297 fwi
.cbSize
= sizeof(fwi
);
298 fwi
.hwnd
= views::HWNDForWidget(window_
);
299 fwi
.dwFlags
= FLASHW_ALL
;
300 fwi
.uCount
= panel::kNumberOfTimesToFlashPanelForAttention
;
302 ::FlashWindowEx(&fwi
);
304 // Calling FlashWindowEx with FLASHW_STOP flag does not always work.
305 // Occasionally the taskbar icon could still remain in the flashed state.
306 // To work around this problem, we recreate the underlying window.
307 views::Widget
* old_window
= window_
;
308 window_
= CreateWindowWithBounds(GetStackWindowBounds());
310 // New background window should also be minimized if the old one is.
311 if (old_window
->IsMinimized())
314 // Make sure the new background window stays at the same z-order as the old
316 ::SetWindowPos(views::HWNDForWidget(window_
),
317 views::HWNDForWidget(old_window
),
319 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOSIZE
);
320 for (Panels::const_iterator iter
= panels_
.begin();
321 iter
!= panels_
.end(); ++iter
) {
322 MakeStackWindowOwnPanelWindow(*iter
, this);
325 // Serve the snapshot to the new backgroud window.
326 if (thumbnailer_
.get())
327 thumbnailer_
->ReplaceWindow(views::HWNDForWidget(window_
));
329 window_
->UpdateWindowTitle();
330 window_
->UpdateWindowIcon();
334 window_
->FlashFrame(draw_attention
);
338 void PanelStackView::OnPanelActivated(Panel
* panel
) {
342 void PanelStackView::OnNativeFocusChange(gfx::NativeView focused_before
,
343 gfx::NativeView focused_now
) {
344 // When the user selects the stacked panels via ALT-TAB or WIN-TAB, the
345 // background stack window, instead of the foreground panel window, receives
346 // WM_SETFOCUS message. To deal with this, we listen to the focus change event
347 // and activate the most recently active panel.
348 // Note that OnNativeFocusChange might be called when window_ has not be
351 if (!panels_
.empty() && window_
&& focused_now
== window_
->GetNativeView()) {
352 Panel
* panel_to_focus
=
353 panels_
.front()->stack()->most_recently_active_panel();
355 panel_to_focus
->Activate();
360 void PanelStackView::AnimationEnded(const gfx::Animation
* animation
) {
361 bounds_updates_started_
= false;
363 PanelManager
* panel_manager
= PanelManager::GetInstance();
364 for (BoundsUpdates::const_iterator iter
= bounds_updates_
.begin();
365 iter
!= bounds_updates_
.end(); ++iter
) {
366 panel_manager
->OnPanelAnimationEnded(iter
->first
);
368 bounds_updates_
.clear();
370 NotifyBoundsUpdateCompleted();
373 void PanelStackView::AnimationCanceled(const gfx::Animation
* animation
) {
374 // When the animation is aborted due to something like one of panels is gone,
375 // update panels to their taget bounds immediately.
376 UpdatePanelsBounds();
378 AnimationEnded(animation
);
381 void PanelStackView::AnimationProgressed(const gfx::Animation
* animation
) {
382 UpdatePanelsBounds();
385 void PanelStackView::UpdatePanelsBounds() {
387 // Add an extra count for the background stack window.
388 HDWP defer_update
= ::BeginDeferWindowPos(bounds_updates_
.size() + 1);
391 // Update the bounds for each panel in the update list.
392 gfx::Rect enclosing_bounds
;
393 for (BoundsUpdates::const_iterator iter
= bounds_updates_
.begin();
394 iter
!= bounds_updates_
.end(); ++iter
) {
395 Panel
* panel
= iter
->first
;
396 gfx::Rect target_bounds
= panel
->GetBounds();
397 gfx::Rect current_bounds
;
398 if (bounds_animator_
&& bounds_animator_
->is_animating()) {
399 current_bounds
= bounds_animator_
->CurrentValueBetween(
400 iter
->second
, target_bounds
);
402 current_bounds
= target_bounds
;
405 PanelView
* panel_view
= static_cast<PanelView
*>(panel
->native_panel());
407 DeferUpdateNativeWindowBounds(defer_update
,
408 panel_view
->window(),
411 panel_view
->SetPanelBoundsInstantly(current_bounds
);
414 enclosing_bounds
= UnionRects(enclosing_bounds
, current_bounds
);
417 // Compute the stack window bounds that enclose those panels that are not
418 // in the batch update list.
419 for (Panels::const_iterator iter
= panels_
.begin();
420 iter
!= panels_
.end(); ++iter
) {
421 Panel
* panel
= *iter
;
422 if (bounds_updates_
.find(panel
) == bounds_updates_
.end())
423 enclosing_bounds
= UnionRects(enclosing_bounds
, panel
->GetBounds());
426 // Update the bounds of the background stack window.
428 DeferUpdateNativeWindowBounds(defer_update
, window_
, enclosing_bounds
);
430 window_
->SetBounds(enclosing_bounds
);
434 ::EndDeferWindowPos(defer_update
);
438 gfx::Rect
PanelStackView::GetStackWindowBounds() const {
439 gfx::Rect enclosing_bounds
;
440 for (Panels::const_iterator iter
= panels_
.begin();
441 iter
!= panels_
.end(); ++iter
) {
442 Panel
* panel
= *iter
;
443 enclosing_bounds
= UnionRects(enclosing_bounds
, panel
->GetBounds());
445 return enclosing_bounds
;
448 void PanelStackView::UpdateStackWindowBounds() {
449 window_
->SetBounds(GetStackWindowBounds());
452 // Refresh the thumbnail each time whne the stack window is changed, due to
453 // adding or removing a panel.
454 RefreshLivePreviewThumbnail();
459 void PanelStackView::MakeStackWindowOwnPanelWindow(
460 Panel
* panel
, PanelStackView
* stack_window
) {
462 // The panel widget window might already be gone when a panel is closed.
463 views::Widget
* panel_window
=
464 static_cast<PanelView
*>(panel
->native_panel())->window();
468 HWND native_panel_window
= views::HWNDForWidget(panel_window
);
469 HWND native_stack_window
=
470 stack_window
? views::HWNDForWidget(stack_window
->window_
) : NULL
;
472 // The extended style WS_EX_APPWINDOW is used to force a top-level window onto
473 // the taskbar. In order for multiple stacked panels to appear as one, this
474 // bit needs to be cleared.
475 int value
= ::GetWindowLong(native_panel_window
, GWL_EXSTYLE
);
479 native_stack_window
? (value
& ~WS_EX_APPWINDOW
)
480 : (value
| WS_EX_APPWINDOW
));
482 // All the windows that share the same owner window will appear as a single
483 // window on the taskbar.
484 ::SetWindowLongPtr(native_panel_window
,
486 reinterpret_cast<LONG_PTR
>(native_stack_window
));
488 // Make sure the background stack window always stays behind the panel window.
489 if (native_stack_window
) {
490 ::SetWindowPos(native_stack_window
, native_panel_window
, 0, 0, 0, 0,
491 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOSIZE
);
499 views::Widget
* PanelStackView::CreateWindowWithBounds(const gfx::Rect
& bounds
) {
500 PanelStackWindow
* stack_window
= new PanelStackWindow(bounds
, delegate_
);
501 views::Widget
* window
= stack_window
->GetWidget();
504 DCHECK(!panels_
.empty());
505 Panel
* panel
= panels_
.front();
506 ui::win::SetAppIdForWindow(
507 ShellIntegration::GetAppModelIdForProfile(
508 base::UTF8ToWide(panel
->app_name()), panel
->profile()->GetPath()),
509 views::HWNDForWidget(window
));
511 // Remove the filter for old window in case that we're recreating the window.
512 ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
514 // Listen to WM_MOVING message in order to move all panels windows on top of
515 // the background window altogether when the background window is being moved
517 ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window
), this);
523 void PanelStackView::EnsureWindowCreated() {
527 // Empty size is not allowed so a temporary small size is passed. SetBounds
528 // will be called later to update the bounds.
529 window_
= CreateWindowWithBounds(gfx::Rect(0, 0, 1, 1));
532 if (base::win::GetVersion() >= base::win::VERSION_WIN7
) {
533 HWND native_window
= views::HWNDForWidget(window_
);
534 thumbnailer_
.reset(new TaskbarWindowThumbnailerWin(native_window
, this));
535 thumbnailer_
->Start();
541 bool PanelStackView::FilterMessage(HWND hwnd
,
548 // When the background window is being moved by the user, all panels
550 gfx::Rect
new_stack_bounds(*(reinterpret_cast<LPRECT
>(l_param
)));
552 new_stack_bounds
.origin() - panels_
.front()->GetBounds().origin());
558 std::vector
<HWND
> PanelStackView::GetSnapshotWindowHandles() const {
559 std::vector
<HWND
> native_panel_windows
;
560 for (Panels::const_iterator iter
= panels_
.begin();
561 iter
!= panels_
.end(); ++iter
) {
562 Panel
* panel
= *iter
;
563 native_panel_windows
.push_back(
564 views::HWNDForWidget(
565 static_cast<PanelView
*>(panel
->native_panel())->window()));
567 return native_panel_windows
;
570 void PanelStackView::RefreshLivePreviewThumbnail() {
571 // Don't refresh the thumbnail when the stack window is system minimized
572 // because the snapshot could not be retrieved.
573 if (!thumbnailer_
.get() || IsMinimized())
575 thumbnailer_
->InvalidateSnapshot();
578 void PanelStackView::DeferUpdateNativeWindowBounds(HDWP defer_window_pos_info
,
579 views::Widget
* window
,
580 const gfx::Rect
& bounds
) {
581 ::DeferWindowPos(defer_window_pos_info
,
582 views::HWNDForWidget(window
),
588 SWP_NOACTIVATE
| SWP_NOZORDER
);