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/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(
    508           base::UTF8ToWide(panel->app_name()), 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