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