Home | History | Annotate | Download | only in panels
      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