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 26 namespace { 27 // These values are experimental and subjective. 28 const int kDefaultFramerateHz = 50; 29 const int kSetBoundsAnimationMs = 180; 30 } 31 32 // static 33 NativePanelStackWindow* 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 43 PanelStackView::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 54 PanelStackView::~PanelStackView() { 55 #if defined(OS_WIN) 56 ui::HWNDSubclass::RemoveFilterFromAllTargets(this); 57 #endif 58 } 59 60 void PanelStackView::Close() { 61 is_closing_ = true; 62 delegate_ = NULL; 63 if (bounds_animator_) 64 bounds_animator_.reset(); 65 if (window_) 66 window_->Close(); 67 views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this); 68 } 69 70 void PanelStackView::AddPanel(Panel* panel) { 71 panels_.push_back(panel); 72 73 EnsureWindowCreated(); 74 MakeStackWindowOwnPanelWindow(panel, this); 75 UpdateStackWindowBounds(); 76 77 window_->UpdateWindowTitle(); 78 window_->UpdateWindowIcon(); 79 } 80 81 void PanelStackView::RemovePanel(Panel* panel) { 82 panels_.remove(panel); 83 84 MakeStackWindowOwnPanelWindow(panel, NULL); 85 UpdateStackWindowBounds(); 86 } 87 88 void PanelStackView::MergeWith(NativePanelStackWindow* another) { 89 PanelStackView* another_stack = static_cast<PanelStackView*>(another); 90 91 for (Panels::const_iterator iter = another_stack->panels_.begin(); 92 iter != another_stack->panels_.end(); ++iter) { 93 Panel* panel = *iter; 94 panels_.push_back(panel); 95 MakeStackWindowOwnPanelWindow(panel, this); 96 } 97 another_stack->panels_.clear(); 98 99 UpdateStackWindowBounds(); 100 } 101 102 bool PanelStackView::IsEmpty() const { 103 return panels_.empty(); 104 } 105 106 bool PanelStackView::HasPanel(Panel* panel) const { 107 return std::find(panels_.begin(), panels_.end(), panel) != panels_.end(); 108 } 109 110 void PanelStackView::MovePanelsBy(const gfx::Vector2d& delta) { 111 BeginBatchUpdatePanelBounds(false); 112 for (Panels::const_iterator iter = panels_.begin(); 113 iter != panels_.end(); ++iter) { 114 Panel* panel = *iter; 115 AddPanelBoundsForBatchUpdate(panel, panel->GetBounds() + delta); 116 } 117 EndBatchUpdatePanelBounds(); 118 } 119 120 void PanelStackView::BeginBatchUpdatePanelBounds(bool animate) { 121 // If the batch animation is still in progress, continue the animation 122 // with the new target bounds even we want to update the bounds instantly 123 // this time. 124 if (!bounds_updates_started_) { 125 animate_bounds_updates_ = animate; 126 bounds_updates_started_ = true; 127 } 128 } 129 130 void PanelStackView::AddPanelBoundsForBatchUpdate(Panel* panel, 131 const gfx::Rect& new_bounds) { 132 DCHECK(bounds_updates_started_); 133 134 // No need to track it if no change is needed. 135 if (panel->GetBounds() == new_bounds) 136 return; 137 138 // Old bounds are stored as the map value. 139 bounds_updates_[panel] = panel->GetBounds(); 140 141 // New bounds are directly applied to the valued stored in native panel 142 // window. 143 static_cast<PanelView*>(panel->native_panel())->set_cached_bounds_directly( 144 new_bounds); 145 } 146 147 void PanelStackView::EndBatchUpdatePanelBounds() { 148 DCHECK(bounds_updates_started_); 149 150 if (bounds_updates_.empty() || !animate_bounds_updates_) { 151 if (!bounds_updates_.empty()) { 152 UpdatePanelsBounds(); 153 bounds_updates_.clear(); 154 } 155 156 bounds_updates_started_ = false; 157 NotifyBoundsUpdateCompleted(); 158 return; 159 } 160 161 bounds_animator_.reset(new ui::LinearAnimation( 162 PanelManager::AdjustTimeInterval(kSetBoundsAnimationMs), 163 kDefaultFramerateHz, 164 this)); 165 bounds_animator_->Start(); 166 } 167 168 void PanelStackView::NotifyBoundsUpdateCompleted() { 169 delegate_->PanelBoundsBatchUpdateCompleted(); 170 171 #if defined(OS_WIN) 172 // Refresh the thumbnail each time when any bounds updates are done. 173 RefreshLivePreviewThumbnail(); 174 #endif 175 } 176 177 bool PanelStackView::IsAnimatingPanelBounds() const { 178 return bounds_updates_started_ && animate_bounds_updates_; 179 } 180 181 void PanelStackView::Minimize() { 182 #if defined(OS_WIN) 183 // When the stack window is minimized by the system, its snapshot could not 184 // be obtained. We need to capture the snapshot before the minimization. 185 if (thumbnailer_) 186 thumbnailer_->CaptureSnapshot(); 187 #endif 188 189 window_->Minimize(); 190 } 191 192 bool PanelStackView::IsMinimized() const { 193 return window_ ? window_->IsMinimized() : false; 194 } 195 196 void PanelStackView::DrawSystemAttention(bool draw_attention) { 197 // The underlying call of FlashFrame, FlashWindowEx, seems not to work 198 // correctly if it is called more than once consecutively. 199 if (draw_attention == is_drawing_attention_) 200 return; 201 is_drawing_attention_ = draw_attention; 202 203 #if defined(OS_WIN) 204 // Refresh the thumbnail when a panel could change something for the 205 // attention. 206 RefreshLivePreviewThumbnail(); 207 208 if (draw_attention) { 209 // The default implementation of Widget::FlashFrame only flashes 5 times. 210 // We need more than that. 211 FLASHWINFO fwi; 212 fwi.cbSize = sizeof(fwi); 213 fwi.hwnd = views::HWNDForWidget(window_); 214 fwi.dwFlags = FLASHW_ALL; 215 fwi.uCount = panel::kNumberOfTimesToFlashPanelForAttention; 216 fwi.dwTimeout = 0; 217 ::FlashWindowEx(&fwi); 218 } else { 219 // Calling FlashWindowEx with FLASHW_STOP flag does not always work. 220 // Occasionally the taskbar icon could still remain in the flashed state. 221 // To work around this problem, we recreate the underlying window. 222 views::Widget* old_window = window_; 223 window_ = CreateWindowWithBounds(GetStackWindowBounds()); 224 225 // New background window should also be minimized if the old one is. 226 if (old_window->IsMinimized()) 227 window_->Minimize(); 228 229 // Make sure the new background window stays at the same z-order as the old 230 // one. 231 ::SetWindowPos(views::HWNDForWidget(window_), 232 views::HWNDForWidget(old_window), 233 0, 0, 0, 0, 234 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 235 for (Panels::const_iterator iter = panels_.begin(); 236 iter != panels_.end(); ++iter) { 237 MakeStackWindowOwnPanelWindow(*iter, this); 238 } 239 240 // Serve the snapshot to the new backgroud window. 241 if (thumbnailer_.get()) 242 thumbnailer_->ReplaceWindow(views::HWNDForWidget(window_)); 243 244 window_->UpdateWindowTitle(); 245 window_->UpdateWindowIcon(); 246 old_window->Close(); 247 } 248 #else 249 window_->FlashFrame(draw_attention); 250 #endif 251 } 252 253 void PanelStackView::OnPanelActivated(Panel* panel) { 254 // Nothing to do. 255 } 256 257 string16 PanelStackView::GetWindowTitle() const { 258 return delegate_->GetTitle(); 259 } 260 261 gfx::ImageSkia PanelStackView::GetWindowAppIcon() { 262 if (panels_.empty()) 263 return gfx::ImageSkia(); 264 265 Panel* panel = panels_.front(); 266 gfx::Image app_icon = panel->app_icon(); 267 if (!app_icon.IsEmpty()) 268 return *app_icon.ToImageSkia(); 269 270 return gfx::ImageSkia(); 271 } 272 273 gfx::ImageSkia PanelStackView::GetWindowIcon() { 274 return GetWindowAppIcon(); 275 } 276 277 views::Widget* PanelStackView::GetWidget() { 278 return window_; 279 } 280 281 const views::Widget* PanelStackView::GetWidget() const { 282 return window_; 283 } 284 285 void PanelStackView::DeleteDelegate() { 286 // |window_| could be closed when it is regenerated in order to clear the 287 // taskbar icon flash state. We should only delete this instance when the 288 // window is really being closed. 289 if (is_closing_) 290 delete this; 291 } 292 293 void PanelStackView::OnWidgetDestroying(views::Widget* widget) { 294 if (widget == window_) 295 window_ = NULL; 296 } 297 298 void PanelStackView::OnNativeFocusChange(gfx::NativeView focused_before, 299 gfx::NativeView focused_now) { 300 // When the user selects the stacked panels via ALT-TAB or WIN-TAB, the 301 // background stack window, instead of the foreground panel window, receives 302 // WM_SETFOCUS message. To deal with this, we listen to the focus change event 303 // and activate the most recently active panel. 304 // Note that OnNativeFocusChange might be called when window_ has not be 305 // created yet. 306 #if defined(OS_WIN) 307 if (!panels_.empty() && window_ && focused_now == window_->GetNativeView()) { 308 Panel* panel_to_focus = 309 panels_.front()->stack()->most_recently_active_panel(); 310 if (panel_to_focus) 311 panel_to_focus->Activate(); 312 } 313 #endif 314 } 315 316 void PanelStackView::AnimationEnded(const ui::Animation* animation) { 317 bounds_updates_started_ = false; 318 319 PanelManager* panel_manager = PanelManager::GetInstance(); 320 for (BoundsUpdates::const_iterator iter = bounds_updates_.begin(); 321 iter != bounds_updates_.end(); ++iter) { 322 panel_manager->OnPanelAnimationEnded(iter->first); 323 } 324 bounds_updates_.clear(); 325 326 NotifyBoundsUpdateCompleted(); 327 } 328 329 void PanelStackView::AnimationProgressed(const ui::Animation* animation) { 330 UpdatePanelsBounds(); 331 } 332 333 void PanelStackView::UpdatePanelsBounds() { 334 #if defined(OS_WIN) 335 // Add an extra count for the background stack window. 336 HDWP defer_update = ::BeginDeferWindowPos(bounds_updates_.size() + 1); 337 #endif 338 339 // Update the bounds for each panel in the update list. 340 gfx::Rect enclosing_bounds; 341 for (BoundsUpdates::const_iterator iter = bounds_updates_.begin(); 342 iter != bounds_updates_.end(); ++iter) { 343 Panel* panel = iter->first; 344 gfx::Rect target_bounds = panel->GetBounds(); 345 gfx::Rect current_bounds; 346 if (bounds_animator_ && bounds_animator_->is_animating()) { 347 current_bounds = bounds_animator_->CurrentValueBetween( 348 iter->second, target_bounds); 349 } else { 350 current_bounds = target_bounds; 351 } 352 353 PanelView* panel_view = static_cast<PanelView*>(panel->native_panel()); 354 #if defined(OS_WIN) 355 DeferUpdateNativeWindowBounds(defer_update, 356 panel_view->window(), 357 current_bounds); 358 #else 359 panel_view->SetPanelBoundsInstantly(current_bounds); 360 #endif 361 362 enclosing_bounds = UnionRects(enclosing_bounds, current_bounds); 363 } 364 365 // Compute the stack window bounds that enclose those panels that are not 366 // in the batch update list. 367 for (Panels::const_iterator iter = panels_.begin(); 368 iter != panels_.end(); ++iter) { 369 Panel* panel = *iter; 370 if (bounds_updates_.find(panel) == bounds_updates_.end()) 371 enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds()); 372 } 373 374 // Update the bounds of the background stack window. 375 #if defined(OS_WIN) 376 DeferUpdateNativeWindowBounds(defer_update, window_, enclosing_bounds); 377 #else 378 window_->SetBounds(enclosing_bounds); 379 #endif 380 381 #if defined(OS_WIN) 382 ::EndDeferWindowPos(defer_update); 383 #endif 384 } 385 386 gfx::Rect PanelStackView::GetStackWindowBounds() const { 387 gfx::Rect enclosing_bounds; 388 for (Panels::const_iterator iter = panels_.begin(); 389 iter != panels_.end(); ++iter) { 390 Panel* panel = *iter; 391 enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds()); 392 } 393 return enclosing_bounds; 394 } 395 396 void PanelStackView::UpdateStackWindowBounds() { 397 window_->SetBounds(GetStackWindowBounds()); 398 399 #if defined(OS_WIN) 400 // Refresh the thumbnail each time whne the stack window is changed, due to 401 // adding or removing a panel. 402 RefreshLivePreviewThumbnail(); 403 #endif 404 } 405 406 // static 407 void PanelStackView::MakeStackWindowOwnPanelWindow( 408 Panel* panel, PanelStackView* stack_window) { 409 #if defined(OS_WIN) 410 // The panel widget window might already be gone when a panel is closed. 411 views::Widget* panel_window = 412 static_cast<PanelView*>(panel->native_panel())->window(); 413 if (!panel_window) 414 return; 415 416 HWND native_panel_window = views::HWNDForWidget(panel_window); 417 HWND native_stack_window = 418 stack_window ? views::HWNDForWidget(stack_window->window_) : NULL; 419 420 // The extended style WS_EX_APPWINDOW is used to force a top-level window onto 421 // the taskbar. In order for multiple stacked panels to appear as one, this 422 // bit needs to be cleared. 423 int value = ::GetWindowLong(native_panel_window, GWL_EXSTYLE); 424 ::SetWindowLong( 425 native_panel_window, 426 GWL_EXSTYLE, 427 native_stack_window ? (value & ~WS_EX_APPWINDOW) 428 : (value | WS_EX_APPWINDOW)); 429 430 // All the windows that share the same owner window will appear as a single 431 // window on the taskbar. 432 ::SetWindowLongPtr(native_panel_window, 433 GWLP_HWNDPARENT, 434 reinterpret_cast<LONG>(native_stack_window)); 435 436 // Make sure the background stack window always stays behind the panel window. 437 if (native_stack_window) { 438 ::SetWindowPos(native_stack_window, native_panel_window, 0, 0, 0, 0, 439 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 440 } 441 442 #else 443 NOTIMPLEMENTED(); 444 #endif 445 } 446 447 views::Widget* PanelStackView::CreateWindowWithBounds(const gfx::Rect& bounds) { 448 views::Widget* window = new views::Widget; 449 views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); 450 params.delegate = this; 451 params.remove_standard_frame = true; 452 params.bounds = bounds; 453 window->Init(params); 454 window->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM); 455 window->set_focus_on_creation(false); 456 window->AddObserver(this); 457 window->ShowInactive(); 458 459 #if defined(OS_WIN) 460 DCHECK(!panels_.empty()); 461 Panel* panel = panels_.front(); 462 ui::win::SetAppIdForWindow( 463 ShellIntegration::GetAppModelIdForProfile(UTF8ToWide(panel->app_name()), 464 panel->profile()->GetPath()), 465 views::HWNDForWidget(window)); 466 467 // Remove the filter for old window in case that we're recreating the window. 468 ui::HWNDSubclass::RemoveFilterFromAllTargets(this); 469 470 // Listen to WM_MOVING message in order to move all panels windows on top of 471 // the background window altogether when the background window is being moved 472 // by the user. 473 ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window), this); 474 #endif 475 476 return window; 477 } 478 479 void PanelStackView::EnsureWindowCreated() { 480 if (window_) 481 return; 482 483 // Empty size is not allowed so a temporary small size is passed. SetBounds 484 // will be called later to update the bounds. 485 window_ = CreateWindowWithBounds(gfx::Rect(0, 0, 1, 1)); 486 487 #if defined(OS_WIN) 488 if (base::win::GetVersion() >= base::win::VERSION_WIN7) { 489 HWND native_window = views::HWNDForWidget(window_); 490 thumbnailer_.reset(new TaskbarWindowThumbnailerWin(native_window, this)); 491 thumbnailer_->Start(); 492 } 493 #endif 494 } 495 496 #if defined(OS_WIN) 497 bool PanelStackView::FilterMessage(HWND hwnd, 498 UINT message, 499 WPARAM w_param, 500 LPARAM l_param, 501 LRESULT* l_result) { 502 switch (message) { 503 case WM_MOVING: 504 // When the background window is being moved by the user, all panels 505 // should also move. 506 gfx::Rect new_stack_bounds(*(reinterpret_cast<LPRECT>(l_param))); 507 MovePanelsBy( 508 new_stack_bounds.origin() - panels_.front()->GetBounds().origin()); 509 break; 510 } 511 return false; 512 } 513 514 std::vector<HWND> PanelStackView::GetSnapshotWindowHandles() const { 515 std::vector<HWND> native_panel_windows; 516 for (Panels::const_iterator iter = panels_.begin(); 517 iter != panels_.end(); ++iter) { 518 Panel* panel = *iter; 519 native_panel_windows.push_back( 520 views::HWNDForWidget( 521 static_cast<PanelView*>(panel->native_panel())->window())); 522 } 523 return native_panel_windows; 524 } 525 526 void PanelStackView::RefreshLivePreviewThumbnail() { 527 // Don't refresh the thumbnail when the stack window is system minimized 528 // because the snapshot could not be retrieved. 529 if (!thumbnailer_.get() || IsMinimized()) 530 return; 531 thumbnailer_->InvalidateSnapshot(); 532 } 533 534 void PanelStackView::DeferUpdateNativeWindowBounds(HDWP defer_window_pos_info, 535 views::Widget* window, 536 const gfx::Rect& bounds) { 537 ::DeferWindowPos(defer_window_pos_info, 538 views::HWNDForWidget(window), 539 NULL, 540 bounds.x(), 541 bounds.y(), 542 bounds.width(), 543 bounds.height(), 544 SWP_NOACTIVATE | SWP_NOZORDER); 545 } 546 #endif 547