panel_stack_view.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 // Make sure the new background window stays at the same z-order as the old 222 // one. 223 ::SetWindowPos(views::HWNDForWidget(window_), 224 views::HWNDForWidget(old_window), 225 0, 0, 0, 0, 226 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 227 for (Panels::const_iterator iter = panels_.begin(); 228 iter != panels_.end(); ++iter) { 229 MakeStackWindowOwnPanelWindow(*iter, this); 230 } 231 window_->UpdateWindowTitle(); 232 window_->UpdateWindowIcon(); 233 old_window->Close(); 234 } 235#else 236 window_->FlashFrame(draw_attention); 237#endif 238} 239 240void PanelStackView::OnPanelActivated(Panel* panel) { 241 // Nothing to do. 242} 243 244string16 PanelStackView::GetWindowTitle() const { 245 return delegate_->GetTitle(); 246} 247 248gfx::ImageSkia PanelStackView::GetWindowAppIcon() { 249 if (panels_.empty()) 250 return gfx::ImageSkia(); 251 252 Panel* panel = panels_.front(); 253 gfx::Image app_icon = panel->app_icon(); 254 if (!app_icon.IsEmpty()) 255 return *app_icon.ToImageSkia(); 256 257 return gfx::ImageSkia(); 258} 259 260gfx::ImageSkia PanelStackView::GetWindowIcon() { 261 return GetWindowAppIcon(); 262} 263 264views::Widget* PanelStackView::GetWidget() { 265 return window_; 266} 267 268const views::Widget* PanelStackView::GetWidget() const { 269 return window_; 270} 271 272void PanelStackView::DeleteDelegate() { 273 // |window_| could be closed when it is regenerated in order to clear the 274 // taskbar icon flash state. We should only delete this instance when the 275 // window is really being closed. 276 if (is_closing_) 277 delete this; 278} 279 280void PanelStackView::OnWidgetDestroying(views::Widget* widget) { 281 if (widget == window_) 282 window_ = NULL; 283} 284 285void PanelStackView::OnNativeFocusChange(gfx::NativeView focused_before, 286 gfx::NativeView focused_now) { 287 // When the user selects the stacked panels via ALT-TAB or WIN-TAB, the 288 // background stack window, instead of the foreground panel window, receives 289 // WM_SETFOCUS message. To deal with this, we listen to the focus change event 290 // and activate the most recently active panel. 291 // Note that OnNativeFocusChange might be called when window_ has not be 292 // created yet. 293#if defined(OS_WIN) 294 if (!panels_.empty() && window_ && focused_now == window_->GetNativeView()) { 295 Panel* panel_to_focus = 296 panels_.front()->stack()->most_recently_active_panel(); 297 if (panel_to_focus) 298 panel_to_focus->Activate(); 299 } 300#endif 301} 302 303void PanelStackView::AnimationEnded(const ui::Animation* animation) { 304 bounds_updates_started_ = false; 305 306 PanelManager* panel_manager = PanelManager::GetInstance(); 307 for (BoundsUpdates::const_iterator iter = bounds_updates_.begin(); 308 iter != bounds_updates_.end(); ++iter) { 309 panel_manager->OnPanelAnimationEnded(iter->first); 310 } 311 bounds_updates_.clear(); 312 313 NotifyBoundsUpdateCompleted(); 314} 315 316void PanelStackView::AnimationProgressed(const ui::Animation* animation) { 317 UpdatePanelsBounds(); 318} 319 320void PanelStackView::UpdatePanelsBounds() { 321#if defined(OS_WIN) 322 // Add an extra count for the background stack window. 323 HDWP defer_update = ::BeginDeferWindowPos(bounds_updates_.size() + 1); 324#endif 325 326 // Update the bounds for each panel in the update list. 327 gfx::Rect enclosing_bounds; 328 for (BoundsUpdates::const_iterator iter = bounds_updates_.begin(); 329 iter != bounds_updates_.end(); ++iter) { 330 Panel* panel = iter->first; 331 gfx::Rect target_bounds = panel->GetBounds(); 332 gfx::Rect current_bounds; 333 if (bounds_animator_ && bounds_animator_->is_animating()) { 334 current_bounds = bounds_animator_->CurrentValueBetween( 335 iter->second, target_bounds); 336 } else { 337 current_bounds = target_bounds; 338 } 339 340 PanelView* panel_view = static_cast<PanelView*>(panel->native_panel()); 341#if defined(OS_WIN) 342 DeferUpdateNativeWindowBounds(defer_update, 343 panel_view->window(), 344 current_bounds); 345#else 346 panel_view->SetPanelBoundsInstantly(current_bounds); 347#endif 348 349 enclosing_bounds = UnionRects(enclosing_bounds, current_bounds); 350 } 351 352 // Compute the stack window bounds that enclose those panels that are not 353 // in the batch update list. 354 for (Panels::const_iterator iter = panels_.begin(); 355 iter != panels_.end(); ++iter) { 356 Panel* panel = *iter; 357 if (bounds_updates_.find(panel) == bounds_updates_.end()) 358 enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds()); 359 } 360 361 // Update the bounds of the background stack window. 362#if defined(OS_WIN) 363 DeferUpdateNativeWindowBounds(defer_update, window_, enclosing_bounds); 364#else 365 window_->SetBounds(enclosing_bounds); 366#endif 367 368#if defined(OS_WIN) 369 ::EndDeferWindowPos(defer_update); 370#endif 371} 372 373gfx::Rect PanelStackView::GetStackWindowBounds() const { 374 gfx::Rect enclosing_bounds; 375 for (Panels::const_iterator iter = panels_.begin(); 376 iter != panels_.end(); ++iter) { 377 Panel* panel = *iter; 378 enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds()); 379 } 380 return enclosing_bounds; 381} 382 383void PanelStackView::UpdateStackWindowBounds() { 384 window_->SetBounds(GetStackWindowBounds()); 385 386#if defined(OS_WIN) 387 // Refresh the thumbnail each time whne the stack window is changed, due to 388 // adding or removing a panel. 389 RefreshLivePreviewThumbnail(); 390#endif 391} 392 393// static 394void PanelStackView::MakeStackWindowOwnPanelWindow( 395 Panel* panel, PanelStackView* stack_window) { 396#if defined(OS_WIN) 397 // The panel widget window might already be gone when a panel is closed. 398 views::Widget* panel_window = 399 static_cast<PanelView*>(panel->native_panel())->window(); 400 if (!panel_window) 401 return; 402 403 HWND native_panel_window = views::HWNDForWidget(panel_window); 404 HWND native_stack_window = 405 stack_window ? views::HWNDForWidget(stack_window->window_) : NULL; 406 407 // The extended style WS_EX_APPWINDOW is used to force a top-level window onto 408 // the taskbar. In order for multiple stacked panels to appear as one, this 409 // bit needs to be cleared. 410 int value = ::GetWindowLong(native_panel_window, GWL_EXSTYLE); 411 ::SetWindowLong( 412 native_panel_window, 413 GWL_EXSTYLE, 414 native_stack_window ? (value & ~WS_EX_APPWINDOW) 415 : (value | WS_EX_APPWINDOW)); 416 417 // All the windows that share the same owner window will appear as a single 418 // window on the taskbar. 419 ::SetWindowLongPtr(native_panel_window, 420 GWLP_HWNDPARENT, 421 reinterpret_cast<LONG>(native_stack_window)); 422 423 // Make sure the background stack window always stays behind the panel window. 424 if (native_stack_window) { 425 ::SetWindowPos(native_stack_window, native_panel_window, 0, 0, 0, 0, 426 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 427 } 428 429#else 430 NOTIMPLEMENTED(); 431#endif 432} 433 434views::Widget* PanelStackView::CreateWindowWithBounds(const gfx::Rect& bounds) { 435 views::Widget* window = new views::Widget; 436 views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); 437 params.delegate = this; 438 params.remove_standard_frame = true; 439 params.bounds = bounds; 440 window->Init(params); 441 window->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM); 442 window->set_focus_on_creation(false); 443 window->AddObserver(this); 444 window->ShowInactive(); 445 446#if defined(OS_WIN) 447 DCHECK(!panels_.empty()); 448 Panel* panel = panels_.front(); 449 ui::win::SetAppIdForWindow( 450 ShellIntegration::GetAppModelIdForProfile(UTF8ToWide(panel->app_name()), 451 panel->profile()->GetPath()), 452 views::HWNDForWidget(window)); 453 454 if (base::win::GetVersion() >= base::win::VERSION_WIN7) { 455 HWND native_window = views::HWNDForWidget(window); 456 thumbnailer_.reset(new TaskbarWindowThumbnailerWin(native_window, this)); 457 thumbnailer_->Start(); 458 } 459#endif 460 461 return window; 462} 463 464void PanelStackView::EnsureWindowCreated() { 465 if (window_) 466 return; 467 468 // Empty size is not allowed so a temporary small size is passed. SetBounds 469 // will be called later to update the bounds. 470 window_ = CreateWindowWithBounds(gfx::Rect(0, 0, 1, 1)); 471} 472 473#if defined(OS_WIN) 474std::vector<HWND> PanelStackView::GetSnapshotWindowHandles() const { 475 std::vector<HWND> native_panel_windows; 476 for (Panels::const_iterator iter = panels_.begin(); 477 iter != panels_.end(); ++iter) { 478 Panel* panel = *iter; 479 native_panel_windows.push_back( 480 views::HWNDForWidget( 481 static_cast<PanelView*>(panel->native_panel())->window())); 482 } 483 return native_panel_windows; 484} 485 486void PanelStackView::RefreshLivePreviewThumbnail() { 487 if (!thumbnailer_.get()) 488 return; 489 thumbnailer_->InvalidateSnapshot(); 490} 491 492void PanelStackView::DeferUpdateNativeWindowBounds(HDWP defer_window_pos_info, 493 views::Widget* window, 494 const gfx::Rect& bounds) { 495 ::DeferWindowPos(defer_window_pos_info, 496 views::HWNDForWidget(window), 497 NULL, 498 bounds.x(), 499 bounds.y(), 500 bounds.width(), 501 bounds.height(), 502 SWP_NOACTIVATE | SWP_NOZORDER); 503} 504#endif 505