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/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" 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 26 namespace { 27 // These values are experimental and subjective. 28 const int kDefaultFramerateHz = 50; 29 const int kSetBoundsAnimationMs = 180; 30 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(); 38 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; 45 46 // Overridden from views::WidgetObserver: 47 virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE; 48 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE; 49 50 private: 51 views::Widget* window_; // Weak pointer, own us. 52 NativePanelStackWindowDelegate* delegate_; // Weak pointer. 53 54 DISALLOW_COPY_AND_ASSIGN(PanelStackWindow); 55 }; 56 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(); 71 } 72 73 PanelStackWindow::~PanelStackWindow() { 74 } 75 76 base::string16 PanelStackWindow::GetWindowTitle() const { 77 return delegate_ ? delegate_->GetTitle() : base::string16(); 78 } 79 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(); 85 } 86 return gfx::ImageSkia(); 87 } 88 89 gfx::ImageSkia PanelStackWindow::GetWindowIcon() { 90 return GetWindowAppIcon(); 91 } 92 93 views::Widget* PanelStackWindow::GetWidget() { 94 return window_; 95 } 96 97 const views::Widget* PanelStackWindow::GetWidget() const { 98 return window_; 99 } 100 101 void PanelStackWindow::OnWidgetClosing(views::Widget* widget) { 102 delegate_ = NULL; 103 } 104 105 void PanelStackWindow::OnWidgetDestroying(views::Widget* widget) { 106 window_ = NULL; 107 } 108 109 } 110 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 120 } 121 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); 130 } 131 132 PanelStackView::~PanelStackView() { 133 #if defined(OS_WIN) 134 ui::HWNDSubclass::RemoveFilterFromAllTargets(this); 135 #endif 136 } 137 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); 145 } 146 147 void PanelStackView::AddPanel(Panel* panel) { 148 panels_.push_back(panel); 149 150 EnsureWindowCreated(); 151 MakeStackWindowOwnPanelWindow(panel, this); 152 UpdateStackWindowBounds(); 153 154 window_->UpdateWindowTitle(); 155 window_->UpdateWindowIcon(); 156 } 157 158 void PanelStackView::RemovePanel(Panel* panel) { 159 if (IsAnimatingPanelBounds()) { 160 // This panel is gone. 161 bounds_updates_.erase(panel); 162 163 // Abort the ongoing animation. 164 bounds_animator_->Stop(); 165 } 166 167 panels_.remove(panel); 168 169 MakeStackWindowOwnPanelWindow(panel, NULL); 170 UpdateStackWindowBounds(); 171 } 172 173 void PanelStackView::MergeWith(NativePanelStackWindow* another) { 174 PanelStackView* another_stack = static_cast<PanelStackView*>(another); 175 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); 181 } 182 another_stack->panels_.clear(); 183 184 UpdateStackWindowBounds(); 185 } 186 187 bool PanelStackView::IsEmpty() const { 188 return panels_.empty(); 189 } 190 191 bool PanelStackView::HasPanel(Panel* panel) const { 192 return std::find(panels_.begin(), panels_.end(), panel) != panels_.end(); 193 } 194 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); 201 } 202 EndBatchUpdatePanelBounds(); 203 } 204 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; 212 } 213 } 214 215 void PanelStackView::AddPanelBoundsForBatchUpdate(Panel* panel, 216 const gfx::Rect& new_bounds) { 217 DCHECK(bounds_updates_started_); 218 219 // No need to track it if no change is needed. 220 if (panel->GetBounds() == new_bounds) 221 return; 222 223 // Old bounds are stored as the map value. 224 bounds_updates_[panel] = panel->GetBounds(); 225 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); 230 } 231 232 void PanelStackView::EndBatchUpdatePanelBounds() { 233 DCHECK(bounds_updates_started_); 234 235 if (bounds_updates_.empty() || !animate_bounds_updates_) { 236 if (!bounds_updates_.empty()) { 237 UpdatePanelsBounds(); 238 bounds_updates_.clear(); 239 } 240 241 bounds_updates_started_ = false; 242 NotifyBoundsUpdateCompleted(); 243 return; 244 } 245 246 bounds_animator_.reset(new gfx::LinearAnimation( 247 PanelManager::AdjustTimeInterval(kSetBoundsAnimationMs), 248 kDefaultFramerateHz, 249 this)); 250 bounds_animator_->Start(); 251 } 252 253 void PanelStackView::NotifyBoundsUpdateCompleted() { 254 delegate_->PanelBoundsBatchUpdateCompleted(); 255 256 #if defined(OS_WIN) 257 // Refresh the thumbnail each time when any bounds updates are done. 258 RefreshLivePreviewThumbnail(); 259 #endif 260 } 261 262 bool PanelStackView::IsAnimatingPanelBounds() const { 263 return bounds_updates_started_ && animate_bounds_updates_; 264 } 265 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 273 274 window_->Minimize(); 275 } 276 277 bool PanelStackView::IsMinimized() const { 278 return window_ ? window_->IsMinimized() : false; 279 } 280 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; 287 288 #if defined(OS_WIN) 289 // Refresh the thumbnail when a panel could change something for the 290 // attention. 291 RefreshLivePreviewThumbnail(); 292 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()); 309 310 // New background window should also be minimized if the old one is. 311 if (old_window->IsMinimized()) 312 window_->Minimize(); 313 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); 323 } 324 325 // Serve the snapshot to the new backgroud window. 326 if (thumbnailer_.get()) 327 thumbnailer_->ReplaceWindow(views::HWNDForWidget(window_)); 328 329 window_->UpdateWindowTitle(); 330 window_->UpdateWindowIcon(); 331 old_window->Close(); 332 } 333 #else 334 window_->FlashFrame(draw_attention); 335 #endif 336 } 337 338 void PanelStackView::OnPanelActivated(Panel* panel) { 339 // Nothing to do. 340 } 341 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(); 356 } 357 #endif 358 } 359 360 void PanelStackView::AnimationEnded(const gfx::Animation* animation) { 361 bounds_updates_started_ = false; 362 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); 367 } 368 bounds_updates_.clear(); 369 370 NotifyBoundsUpdateCompleted(); 371 } 372 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(); 377 378 AnimationEnded(animation); 379 } 380 381 void PanelStackView::AnimationProgressed(const gfx::Animation* animation) { 382 UpdatePanelsBounds(); 383 } 384 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 390 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; 403 } 404 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 413 414 enclosing_bounds = UnionRects(enclosing_bounds, current_bounds); 415 } 416 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()); 424 } 425 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 432 433 #if defined(OS_WIN) 434 ::EndDeferWindowPos(defer_update); 435 #endif 436 } 437 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()); 444 } 445 return enclosing_bounds; 446 } 447 448 void PanelStackView::UpdateStackWindowBounds() { 449 window_->SetBounds(GetStackWindowBounds()); 450 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 456 } 457 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; 467 468 HWND native_panel_window = views::HWNDForWidget(panel_window); 469 HWND native_stack_window = 470 stack_window ? views::HWNDForWidget(stack_window->window_) : NULL; 471 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)); 481 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)); 487 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); 492 } 493 494 #else 495 NOTIMPLEMENTED(); 496 #endif 497 } 498 499 views::Widget* PanelStackView::CreateWindowWithBounds(const gfx::Rect& bounds) { 500 PanelStackWindow* stack_window = new PanelStackWindow(bounds, delegate_); 501 views::Widget* window = stack_window->GetWidget(); 502 503 #if defined(OS_WIN) 504 DCHECK(!panels_.empty()); 505 Panel* panel = panels_.front(); 506 ui::win::SetAppIdForWindow( 507 ShellIntegration::GetAppModelIdForProfile(UTF8ToWide(panel->app_name()), 508 panel->profile()->GetPath()), 509 views::HWNDForWidget(window)); 510 511 // Remove the filter for old window in case that we're recreating the window. 512 ui::HWNDSubclass::RemoveFilterFromAllTargets(this); 513 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 519 520 return window; 521 } 522 523 void PanelStackView::EnsureWindowCreated() { 524 if (window_) 525 return; 526 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)); 530 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(); 536 } 537 #endif 538 } 539 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; 554 } 555 return false; 556 } 557 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())); 566 } 567 return native_panel_windows; 568 } 569 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(); 576 } 577 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); 589 } 590 #endif 591