Home | History | Annotate | Download | only in tray
      1 // Copyright (c) 2012 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 "ash/system/tray/system_tray_bubble.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/system/tray/system_tray.h"
      9 #include "ash/system/tray/system_tray_delegate.h"
     10 #include "ash/system/tray/system_tray_item.h"
     11 #include "ash/system/tray/tray_bubble_wrapper.h"
     12 #include "ash/system/tray/tray_constants.h"
     13 #include "base/message_loop/message_loop.h"
     14 #include "ui/aura/window.h"
     15 #include "ui/compositor/layer.h"
     16 #include "ui/compositor/layer_animation_observer.h"
     17 #include "ui/compositor/scoped_layer_animation_settings.h"
     18 #include "ui/gfx/canvas.h"
     19 #include "ui/views/layout/box_layout.h"
     20 #include "ui/views/view.h"
     21 #include "ui/views/widget/widget.h"
     22 
     23 using views::TrayBubbleView;
     24 
     25 namespace ash {
     26 
     27 namespace {
     28 
     29 // Normally a detailed view is the same size as the default view. However,
     30 // when showing a detailed view directly (e.g. clicking on a notification),
     31 // we may not know the height of the default view, or the default view may
     32 // be too short, so we use this as a default and minimum height for any
     33 // detailed view.
     34 const int kDetailedBubbleMaxHeight = kTrayPopupItemHeight * 5;
     35 
     36 // Duration of swipe animation used when transitioning from a default to
     37 // detailed view or vice versa.
     38 const int kSwipeDelayMS = 150;
     39 
     40 // A view with some special behaviour for tray items in the popup:
     41 // - optionally changes background color on hover.
     42 class TrayPopupItemContainer : public views::View {
     43  public:
     44   TrayPopupItemContainer(views::View* view,
     45                          bool change_background,
     46                          bool draw_border)
     47       : hover_(false),
     48         change_background_(change_background) {
     49     set_notify_enter_exit_on_child(true);
     50     if (draw_border) {
     51       SetBorder(
     52           views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
     53     }
     54     views::BoxLayout* layout = new views::BoxLayout(
     55         views::BoxLayout::kVertical, 0, 0, 0);
     56     layout->SetDefaultFlex(1);
     57     SetLayoutManager(layout);
     58     SetPaintToLayer(view->layer() != NULL);
     59     if (view->layer())
     60       SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
     61     AddChildView(view);
     62     SetVisible(view->visible());
     63   }
     64 
     65   virtual ~TrayPopupItemContainer() {}
     66 
     67  private:
     68   // Overridden from views::View.
     69   virtual void ChildVisibilityChanged(View* child) OVERRIDE {
     70     if (visible() == child->visible())
     71       return;
     72     SetVisible(child->visible());
     73     PreferredSizeChanged();
     74   }
     75 
     76   virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
     77     PreferredSizeChanged();
     78   }
     79 
     80   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
     81     hover_ = true;
     82     SchedulePaint();
     83   }
     84 
     85   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
     86     hover_ = false;
     87     SchedulePaint();
     88   }
     89 
     90   virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
     91     if (child_count() == 0)
     92       return;
     93 
     94     views::View* view = child_at(0);
     95     if (!view->background()) {
     96       canvas->FillRect(gfx::Rect(size()), (hover_ && change_background_) ?
     97           kHoverBackgroundColor : kBackgroundColor);
     98     }
     99   }
    100 
    101   bool hover_;
    102   bool change_background_;
    103 
    104   DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
    105 };
    106 
    107 // Implicit animation observer that deletes itself and the layer at the end of
    108 // the animation.
    109 class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver {
    110  public:
    111   explicit AnimationObserverDeleteLayer(ui::Layer* layer)
    112       : layer_(layer) {
    113   }
    114 
    115   virtual ~AnimationObserverDeleteLayer() {
    116   }
    117 
    118   virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    119     base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
    120   }
    121 
    122  private:
    123   scoped_ptr<ui::Layer> layer_;
    124 
    125   DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer);
    126 };
    127 
    128 }  // namespace
    129 
    130 // SystemTrayBubble
    131 
    132 SystemTrayBubble::SystemTrayBubble(
    133     ash::SystemTray* tray,
    134     const std::vector<ash::SystemTrayItem*>& items,
    135     BubbleType bubble_type)
    136     : tray_(tray),
    137       bubble_view_(NULL),
    138       items_(items),
    139       bubble_type_(bubble_type),
    140       autoclose_delay_(0) {
    141 }
    142 
    143 SystemTrayBubble::~SystemTrayBubble() {
    144   DestroyItemViews();
    145   // Reset the host pointer in bubble_view_ in case its destruction is deferred.
    146   if (bubble_view_)
    147     bubble_view_->reset_delegate();
    148 }
    149 
    150 void SystemTrayBubble::UpdateView(
    151     const std::vector<ash::SystemTrayItem*>& items,
    152     BubbleType bubble_type) {
    153   DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION);
    154 
    155   scoped_ptr<ui::Layer> scoped_layer;
    156   if (bubble_type != bubble_type_) {
    157     base::TimeDelta swipe_duration =
    158         base::TimeDelta::FromMilliseconds(kSwipeDelayMS);
    159     scoped_layer = bubble_view_->RecreateLayer();
    160     // Keep the reference to layer as we need it after releasing it.
    161     ui::Layer* layer = scoped_layer.get();
    162     DCHECK(layer);
    163     layer->SuppressPaint();
    164 
    165     // When transitioning from detailed view to default view, animate the
    166     // existing view (slide out towards the right).
    167     if (bubble_type == BUBBLE_TYPE_DEFAULT) {
    168       ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
    169       settings.AddObserver(
    170           new AnimationObserverDeleteLayer(scoped_layer.release()));
    171       settings.SetTransitionDuration(swipe_duration);
    172       settings.SetTweenType(gfx::Tween::EASE_OUT);
    173       gfx::Transform transform;
    174       transform.Translate(layer->bounds().width(), 0.0);
    175       layer->SetTransform(transform);
    176     }
    177 
    178     {
    179       // Add a shadow layer to make the old layer darker as the animation
    180       // progresses.
    181       ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR);
    182       shadow->SetColor(SK_ColorBLACK);
    183       shadow->SetOpacity(0.01f);
    184       shadow->SetBounds(layer->bounds());
    185       layer->Add(shadow);
    186       layer->StackAtTop(shadow);
    187       {
    188         // Animate the darkening effect a little longer than the swipe-in. This
    189         // is to make sure the darkening animation does not end up finishing
    190         // early, because the dark layer goes away at the end of the animation,
    191         // and there is a brief moment when the old view is still visible, but
    192         // it does not have the shadow layer on top.
    193         ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator());
    194         settings.AddObserver(new AnimationObserverDeleteLayer(shadow));
    195         settings.SetTransitionDuration(swipe_duration +
    196                                        base::TimeDelta::FromMilliseconds(150));
    197         settings.SetTweenType(gfx::Tween::LINEAR);
    198         shadow->SetOpacity(0.15f);
    199       }
    200     }
    201   }
    202 
    203   DestroyItemViews();
    204   bubble_view_->RemoveAllChildViews(true);
    205 
    206   items_ = items;
    207   bubble_type_ = bubble_type;
    208   CreateItemViews(
    209       Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
    210 
    211   // Close bubble view if we failed to create the item view.
    212   if (!bubble_view_->has_children()) {
    213     Close();
    214     return;
    215   }
    216 
    217   bubble_view_->GetWidget()->GetContentsView()->Layout();
    218   // Make sure that the bubble is large enough for the default view.
    219   if (bubble_type_ == BUBBLE_TYPE_DEFAULT) {
    220     bubble_view_->SetMaxHeight(0);  // Clear max height limit.
    221   }
    222 
    223   if (scoped_layer) {
    224     // When transitioning from default view to detailed view, animate the new
    225     // view (slide in from the right).
    226     if (bubble_type == BUBBLE_TYPE_DETAILED) {
    227       ui::Layer* new_layer = bubble_view_->layer();
    228 
    229       // Make sure the new layer is stacked above the old layer during the
    230       // animation.
    231       new_layer->parent()->StackAbove(new_layer, scoped_layer.get());
    232 
    233       gfx::Rect bounds = new_layer->bounds();
    234       gfx::Transform transform;
    235       transform.Translate(bounds.width(), 0.0);
    236       new_layer->SetTransform(transform);
    237       {
    238         ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator());
    239         settings.AddObserver(
    240             new AnimationObserverDeleteLayer(scoped_layer.release()));
    241         settings.SetTransitionDuration(
    242             base::TimeDelta::FromMilliseconds(kSwipeDelayMS));
    243         settings.SetTweenType(gfx::Tween::EASE_OUT);
    244         new_layer->SetTransform(gfx::Transform());
    245       }
    246     }
    247   }
    248 }
    249 
    250 void SystemTrayBubble::InitView(views::View* anchor,
    251                                 user::LoginStatus login_status,
    252                                 TrayBubbleView::InitParams* init_params) {
    253   DCHECK(bubble_view_ == NULL);
    254 
    255   if (bubble_type_ == BUBBLE_TYPE_DETAILED &&
    256       init_params->max_height < kDetailedBubbleMaxHeight) {
    257     init_params->max_height = kDetailedBubbleMaxHeight;
    258   } else if (bubble_type_ == BUBBLE_TYPE_NOTIFICATION) {
    259     init_params->close_on_deactivate = false;
    260   }
    261   bubble_view_ = TrayBubbleView::Create(
    262       tray_->GetBubbleWindowContainer(), anchor, tray_, init_params);
    263   bubble_view_->set_adjust_if_offscreen(false);
    264   CreateItemViews(login_status);
    265 
    266   if (bubble_view_->CanActivate()) {
    267     bubble_view_->NotifyAccessibilityEvent(
    268         ui::AX_EVENT_ALERT, true);
    269   }
    270 }
    271 
    272 void SystemTrayBubble::FocusDefaultIfNeeded() {
    273   views::FocusManager* manager = bubble_view_->GetFocusManager();
    274   if (!manager || manager->GetFocusedView())
    275     return;
    276 
    277   views::View* view = manager->GetNextFocusableView(NULL, NULL, false, false);
    278   if (view)
    279     view->RequestFocus();
    280 }
    281 
    282 void SystemTrayBubble::DestroyItemViews() {
    283   for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
    284        it != items_.end();
    285        ++it) {
    286     switch (bubble_type_) {
    287       case BUBBLE_TYPE_DEFAULT:
    288         (*it)->DestroyDefaultView();
    289         break;
    290       case BUBBLE_TYPE_DETAILED:
    291         (*it)->DestroyDetailedView();
    292         break;
    293       case BUBBLE_TYPE_NOTIFICATION:
    294         (*it)->DestroyNotificationView();
    295         break;
    296     }
    297   }
    298 }
    299 
    300 void SystemTrayBubble::BubbleViewDestroyed() {
    301   bubble_view_ = NULL;
    302 }
    303 
    304 void SystemTrayBubble::StartAutoCloseTimer(int seconds) {
    305   autoclose_.Stop();
    306   autoclose_delay_ = seconds;
    307   if (autoclose_delay_) {
    308     autoclose_.Start(FROM_HERE,
    309                      base::TimeDelta::FromSeconds(autoclose_delay_),
    310                      this, &SystemTrayBubble::Close);
    311   }
    312 }
    313 
    314 void SystemTrayBubble::StopAutoCloseTimer() {
    315   autoclose_.Stop();
    316 }
    317 
    318 void SystemTrayBubble::RestartAutoCloseTimer() {
    319   if (autoclose_delay_)
    320     StartAutoCloseTimer(autoclose_delay_);
    321 }
    322 
    323 void SystemTrayBubble::Close() {
    324   tray_->HideBubbleWithView(bubble_view());
    325 }
    326 
    327 void SystemTrayBubble::SetVisible(bool is_visible) {
    328   if (!bubble_view_)
    329     return;
    330   views::Widget* bubble_widget = bubble_view_->GetWidget();
    331   if (is_visible)
    332     bubble_widget->Show();
    333   else
    334     bubble_widget->Hide();
    335 }
    336 
    337 bool SystemTrayBubble::IsVisible() {
    338   return bubble_view() && bubble_view()->GetWidget()->IsVisible();
    339 }
    340 
    341 bool SystemTrayBubble::ShouldShowShelf() const {
    342   for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin();
    343        it != items_.end();
    344        ++it) {
    345     if ((*it)->ShouldShowShelf())
    346       return true;
    347   }
    348   return false;
    349 }
    350 
    351 void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
    352   std::vector<views::View*> item_views;
    353   // If a system modal dialog is present, create the same tray as
    354   // in locked state.
    355   if (Shell::GetInstance()->IsSystemModalWindowOpen() &&
    356       login_status != user::LOGGED_IN_NONE) {
    357     login_status = user::LOGGED_IN_LOCKED;
    358   }
    359 
    360   views::View* focus_view = NULL;
    361   for (size_t i = 0; i < items_.size(); ++i) {
    362     views::View* view = NULL;
    363     switch (bubble_type_) {
    364       case BUBBLE_TYPE_DEFAULT:
    365         view = items_[i]->CreateDefaultView(login_status);
    366         if (items_[i]->restore_focus())
    367           focus_view = view;
    368         break;
    369       case BUBBLE_TYPE_DETAILED:
    370         view = items_[i]->CreateDetailedView(login_status);
    371         break;
    372       case BUBBLE_TYPE_NOTIFICATION:
    373         view = items_[i]->CreateNotificationView(login_status);
    374         break;
    375     }
    376     if (view)
    377       item_views.push_back(view);
    378   }
    379 
    380   bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT;
    381   for (size_t i = 0; i < item_views.size(); ++i) {
    382     // For default view, draw bottom border for each item, except the last
    383     // 2 items, which are the bottom header row and the one just above it.
    384     bubble_view_->AddChildView(new TrayPopupItemContainer(
    385         item_views[i], is_default_bubble,
    386         is_default_bubble && (i < item_views.size() - 2)));
    387   }
    388   if (focus_view)
    389     focus_view->RequestFocus();
    390 }
    391 
    392 }  // namespace ash
    393