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/geometry/rect.h"
16 #include "ui/gfx/image/image_skia.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 ~PanelStackWindow() override
;
39 // Overridden from views::WidgetDelegate:
40 base::string16
GetWindowTitle() const override
;
41 gfx::ImageSkia
GetWindowAppIcon() override
;
42 gfx::ImageSkia
GetWindowIcon() override
;
43 views::Widget
* GetWidget() override
;
44 const views::Widget
* GetWidget() const override
;
46 // Overridden from views::WidgetObserver:
47 void OnWidgetClosing(views::Widget
* widget
) override
;
48 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::OnNativeFocusChanged(gfx::NativeView focused_now
) {
343 // When the user selects the stacked panels via ALT-TAB or WIN-TAB, the
344 // background stack window, instead of the foreground panel window, receives
345 // WM_SETFOCUS message. To deal with this, we listen to the focus change event
346 // and activate the most recently active panel.
347 // Note that OnNativeFocusChanged might be called when window_ has not been
350 if (!panels_
.empty() && window_
&& focused_now
== window_
->GetNativeView()) {
351 Panel
* panel_to_focus
=
352 panels_
.front()->stack()->most_recently_active_panel();
354 panel_to_focus
->Activate();
359 void PanelStackView::AnimationEnded(const gfx::Animation
* animation
) {
360 bounds_updates_started_
= false;
362 PanelManager
* panel_manager
= PanelManager::GetInstance();
363 for (BoundsUpdates::const_iterator iter
= bounds_updates_
.begin();
364 iter
!= bounds_updates_
.end(); ++iter
) {
365 panel_manager
->OnPanelAnimationEnded(iter
->first
);
367 bounds_updates_
.clear();
369 NotifyBoundsUpdateCompleted();
372 void PanelStackView::AnimationCanceled(const gfx::Animation
* animation
) {
373 // When the animation is aborted due to something like one of panels is gone,
374 // update panels to their taget bounds immediately.
375 UpdatePanelsBounds();
377 AnimationEnded(animation
);
380 void PanelStackView::AnimationProgressed(const gfx::Animation
* animation
) {
381 UpdatePanelsBounds();
384 void PanelStackView::UpdatePanelsBounds() {
386 // Add an extra count for the background stack window.
387 HDWP defer_update
= ::BeginDeferWindowPos(bounds_updates_
.size() + 1);
390 // Update the bounds for each panel in the update list.
391 gfx::Rect enclosing_bounds
;
392 for (BoundsUpdates::const_iterator iter
= bounds_updates_
.begin();
393 iter
!= bounds_updates_
.end(); ++iter
) {
394 Panel
* panel
= iter
->first
;
395 gfx::Rect target_bounds
= panel
->GetBounds();
396 gfx::Rect current_bounds
;
397 if (bounds_animator_
&& bounds_animator_
->is_animating()) {
398 current_bounds
= bounds_animator_
->CurrentValueBetween(
399 iter
->second
, target_bounds
);
401 current_bounds
= target_bounds
;
404 PanelView
* panel_view
= static_cast<PanelView
*>(panel
->native_panel());
406 DeferUpdateNativeWindowBounds(defer_update
,
407 panel_view
->window(),
410 panel_view
->SetPanelBoundsInstantly(current_bounds
);
413 enclosing_bounds
= UnionRects(enclosing_bounds
, current_bounds
);
416 // Compute the stack window bounds that enclose those panels that are not
417 // in the batch update list.
418 for (Panels::const_iterator iter
= panels_
.begin();
419 iter
!= panels_
.end(); ++iter
) {
420 Panel
* panel
= *iter
;
421 if (bounds_updates_
.find(panel
) == bounds_updates_
.end())
422 enclosing_bounds
= UnionRects(enclosing_bounds
, panel
->GetBounds());
425 // Update the bounds of the background stack window.
427 DeferUpdateNativeWindowBounds(defer_update
, window_
, enclosing_bounds
);
429 window_
->SetBounds(enclosing_bounds
);
433 ::EndDeferWindowPos(defer_update
);
437 gfx::Rect
PanelStackView::GetStackWindowBounds() const {
438 gfx::Rect enclosing_bounds
;
439 for (Panels::const_iterator iter
= panels_
.begin();
440 iter
!= panels_
.end(); ++iter
) {
441 Panel
* panel
= *iter
;
442 enclosing_bounds
= UnionRects(enclosing_bounds
, panel
->GetBounds());
444 return enclosing_bounds
;
447 void PanelStackView::UpdateStackWindowBounds() {
448 window_
->SetBounds(GetStackWindowBounds());
451 // Refresh the thumbnail each time whne the stack window is changed, due to
452 // adding or removing a panel.
453 RefreshLivePreviewThumbnail();
458 void PanelStackView::MakeStackWindowOwnPanelWindow(
459 Panel
* panel
, PanelStackView
* stack_window
) {
461 // The panel widget window might already be gone when a panel is closed.
462 views::Widget
* panel_window
=
463 static_cast<PanelView
*>(panel
->native_panel())->window();
467 HWND native_panel_window
= views::HWNDForWidget(panel_window
);
468 HWND native_stack_window
=
469 stack_window
? views::HWNDForWidget(stack_window
->window_
) : NULL
;
471 // The extended style WS_EX_APPWINDOW is used to force a top-level window onto
472 // the taskbar. In order for multiple stacked panels to appear as one, this
473 // bit needs to be cleared.
474 int value
= ::GetWindowLong(native_panel_window
, GWL_EXSTYLE
);
478 native_stack_window
? (value
& ~WS_EX_APPWINDOW
)
479 : (value
| WS_EX_APPWINDOW
));
481 // All the windows that share the same owner window will appear as a single
482 // window on the taskbar.
483 ::SetWindowLongPtr(native_panel_window
,
485 reinterpret_cast<LONG_PTR
>(native_stack_window
));
487 // Make sure the background stack window always stays behind the panel window.
488 if (native_stack_window
) {
489 ::SetWindowPos(native_stack_window
, native_panel_window
, 0, 0, 0, 0,
490 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOSIZE
);
498 views::Widget
* PanelStackView::CreateWindowWithBounds(const gfx::Rect
& bounds
) {
499 PanelStackWindow
* stack_window
= new PanelStackWindow(bounds
, delegate_
);
500 views::Widget
* window
= stack_window
->GetWidget();
503 DCHECK(!panels_
.empty());
504 Panel
* panel
= panels_
.front();
505 ui::win::SetAppIdForWindow(
506 ShellIntegration::GetAppModelIdForProfile(
507 base::UTF8ToWide(panel
->app_name()), panel
->profile()->GetPath()),
508 views::HWNDForWidget(window
));
510 // Remove the filter for old window in case that we're recreating the window.
511 ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
513 // Listen to WM_MOVING message in order to move all panels windows on top of
514 // the background window altogether when the background window is being moved
516 ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window
), this);
522 void PanelStackView::EnsureWindowCreated() {
526 // Empty size is not allowed so a temporary small size is passed. SetBounds
527 // will be called later to update the bounds.
528 window_
= CreateWindowWithBounds(gfx::Rect(0, 0, 1, 1));
531 if (base::win::GetVersion() >= base::win::VERSION_WIN7
) {
532 HWND native_window
= views::HWNDForWidget(window_
);
533 thumbnailer_
.reset(new TaskbarWindowThumbnailerWin(native_window
, this));
534 thumbnailer_
->Start();
540 bool PanelStackView::FilterMessage(HWND hwnd
,
547 // When the background window is being moved by the user, all panels
549 gfx::Rect
new_stack_bounds(*(reinterpret_cast<LPRECT
>(l_param
)));
551 new_stack_bounds
.origin() - panels_
.front()->GetBounds().origin());
557 std::vector
<HWND
> PanelStackView::GetSnapshotWindowHandles() const {
558 std::vector
<HWND
> native_panel_windows
;
559 for (Panels::const_iterator iter
= panels_
.begin();
560 iter
!= panels_
.end(); ++iter
) {
561 Panel
* panel
= *iter
;
562 native_panel_windows
.push_back(
563 views::HWNDForWidget(
564 static_cast<PanelView
*>(panel
->native_panel())->window()));
566 return native_panel_windows
;
569 void PanelStackView::RefreshLivePreviewThumbnail() {
570 // Don't refresh the thumbnail when the stack window is system minimized
571 // because the snapshot could not be retrieved.
572 if (!thumbnailer_
.get() || IsMinimized())
574 thumbnailer_
->InvalidateSnapshot();
577 void PanelStackView::DeferUpdateNativeWindowBounds(HDWP defer_window_pos_info
,
578 views::Widget
* window
,
579 const gfx::Rect
& bounds
) {
580 ::DeferWindowPos(defer_window_pos_info
,
581 views::HWNDForWidget(window
),
587 SWP_NOACTIVATE
| SWP_NOZORDER
);