[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / panels / panel_stack_view.cc
blob057bac28137e11f3105837ab6376135d0fb2793c
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"
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
26 namespace {
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 {
34 public:
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;
50 private:
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)
59 : window_(NULL),
60 delegate_(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() {
81 if (delegate_) {
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() {
94 return window_;
97 const views::Widget* PanelStackWindow::GetWidget() const {
98 return window_;
101 void PanelStackWindow::OnWidgetClosing(views::Widget* widget) {
102 delegate_ = NULL;
105 void PanelStackWindow::OnWidgetDestroying(views::Widget* widget) {
106 window_ = NULL;
111 // static
112 NativePanelStackWindow* NativePanelStackWindow::Create(
113 NativePanelStackWindowDelegate* delegate) {
114 #if defined(OS_WIN)
115 return new PanelStackView(delegate);
116 #else
117 NOTIMPLEMENTED();
118 return NULL;
119 #endif
122 PanelStackView::PanelStackView(NativePanelStackWindowDelegate* delegate)
123 : delegate_(delegate),
124 window_(NULL),
125 is_drawing_attention_(false),
126 animate_bounds_updates_(false),
127 bounds_updates_started_(false) {
128 DCHECK(delegate);
129 views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
132 PanelStackView::~PanelStackView() {
133 #if defined(OS_WIN)
134 ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
135 #endif
138 void PanelStackView::Close() {
139 delegate_ = NULL;
140 if (bounds_animator_)
141 bounds_animator_.reset();
142 if (window_)
143 window_->Close();
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
208 // this time.
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)
221 return;
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
227 // window.
228 static_cast<PanelView*>(panel->native_panel())->set_cached_bounds_directly(
229 new_bounds);
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();
243 return;
246 bounds_animator_.reset(new gfx::LinearAnimation(
247 PanelManager::AdjustTimeInterval(kSetBoundsAnimationMs),
248 kDefaultFramerateHz,
249 this));
250 bounds_animator_->Start();
253 void PanelStackView::NotifyBoundsUpdateCompleted() {
254 delegate_->PanelBoundsBatchUpdateCompleted();
256 #if defined(OS_WIN)
257 // Refresh the thumbnail each time when any bounds updates are done.
258 RefreshLivePreviewThumbnail();
259 #endif
262 bool PanelStackView::IsAnimatingPanelBounds() const {
263 return bounds_updates_started_ && animate_bounds_updates_;
266 void PanelStackView::Minimize() {
267 #if defined(OS_WIN)
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.
270 if (thumbnailer_)
271 thumbnailer_->CaptureSnapshot();
272 #endif
274 window_->Minimize();
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_)
285 return;
286 is_drawing_attention_ = draw_attention;
288 #if defined(OS_WIN)
289 // Refresh the thumbnail when a panel could change something for the
290 // attention.
291 RefreshLivePreviewThumbnail();
293 if (draw_attention) {
294 // The default implementation of Widget::FlashFrame only flashes 5 times.
295 // We need more than that.
296 FLASHWINFO fwi;
297 fwi.cbSize = sizeof(fwi);
298 fwi.hwnd = views::HWNDForWidget(window_);
299 fwi.dwFlags = FLASHW_ALL;
300 fwi.uCount = panel::kNumberOfTimesToFlashPanelForAttention;
301 fwi.dwTimeout = 0;
302 ::FlashWindowEx(&fwi);
303 } else {
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())
312 window_->Minimize();
314 // Make sure the new background window stays at the same z-order as the old
315 // one.
316 ::SetWindowPos(views::HWNDForWidget(window_),
317 views::HWNDForWidget(old_window),
318 0, 0, 0, 0,
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();
331 old_window->Close();
333 #else
334 window_->FlashFrame(draw_attention);
335 #endif
338 void PanelStackView::OnPanelActivated(Panel* panel) {
339 // Nothing to do.
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
349 // created yet.
350 #if defined(OS_WIN)
351 if (!panels_.empty() && window_ && focused_now == window_->GetNativeView()) {
352 Panel* panel_to_focus =
353 panels_.front()->stack()->most_recently_active_panel();
354 if (panel_to_focus)
355 panel_to_focus->Activate();
357 #endif
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() {
386 #if defined(OS_WIN)
387 // Add an extra count for the background stack window.
388 HDWP defer_update = ::BeginDeferWindowPos(bounds_updates_.size() + 1);
389 #endif
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);
401 } else {
402 current_bounds = target_bounds;
405 PanelView* panel_view = static_cast<PanelView*>(panel->native_panel());
406 #if defined(OS_WIN)
407 DeferUpdateNativeWindowBounds(defer_update,
408 panel_view->window(),
409 current_bounds);
410 #else
411 panel_view->SetPanelBoundsInstantly(current_bounds);
412 #endif
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.
427 #if defined(OS_WIN)
428 DeferUpdateNativeWindowBounds(defer_update, window_, enclosing_bounds);
429 #else
430 window_->SetBounds(enclosing_bounds);
431 #endif
433 #if defined(OS_WIN)
434 ::EndDeferWindowPos(defer_update);
435 #endif
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());
451 #if defined(OS_WIN)
452 // Refresh the thumbnail each time whne the stack window is changed, due to
453 // adding or removing a panel.
454 RefreshLivePreviewThumbnail();
455 #endif
458 // static
459 void PanelStackView::MakeStackWindowOwnPanelWindow(
460 Panel* panel, PanelStackView* stack_window) {
461 #if defined(OS_WIN)
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();
465 if (!panel_window)
466 return;
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);
476 ::SetWindowLong(
477 native_panel_window,
478 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,
485 GWLP_HWNDPARENT,
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);
494 #else
495 NOTIMPLEMENTED();
496 #endif
499 views::Widget* PanelStackView::CreateWindowWithBounds(const gfx::Rect& bounds) {
500 PanelStackWindow* stack_window = new PanelStackWindow(bounds, delegate_);
501 views::Widget* window = stack_window->GetWidget();
503 #if defined(OS_WIN)
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
516 // by the user.
517 ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window), this);
518 #endif
520 return window;
523 void PanelStackView::EnsureWindowCreated() {
524 if (window_)
525 return;
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));
531 #if defined(OS_WIN)
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();
537 #endif
540 #if defined(OS_WIN)
541 bool PanelStackView::FilterMessage(HWND hwnd,
542 UINT message,
543 WPARAM w_param,
544 LPARAM l_param,
545 LRESULT* l_result) {
546 switch (message) {
547 case WM_MOVING:
548 // When the background window is being moved by the user, all panels
549 // should also move.
550 gfx::Rect new_stack_bounds(*(reinterpret_cast<LPRECT>(l_param)));
551 MovePanelsBy(
552 new_stack_bounds.origin() - panels_.front()->GetBounds().origin());
553 break;
555 return false;
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())
574 return;
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),
583 NULL,
584 bounds.x(),
585 bounds.y(),
586 bounds.width(),
587 bounds.height(),
588 SWP_NOACTIVATE | SWP_NOZORDER);
590 #endif