Home | History | Annotate | Download | only in shelf
      1 // Copyright 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 "ash/shelf/shelf_tooltip_manager.h"
      6 
      7 #include "ash/shelf/shelf_layout_manager.h"
      8 #include "ash/shelf/shelf_view.h"
      9 #include "ash/shell.h"
     10 #include "ash/shell_window_ids.h"
     11 #include "ash/wm/window_animations.h"
     12 #include "base/bind.h"
     13 #include "base/message_loop/message_loop.h"
     14 #include "base/time/time.h"
     15 #include "base/timer/timer.h"
     16 #include "ui/aura/root_window.h"
     17 #include "ui/aura/window.h"
     18 #include "ui/events/event.h"
     19 #include "ui/events/event_constants.h"
     20 #include "ui/gfx/insets.h"
     21 #include "ui/views/bubble/bubble_delegate.h"
     22 #include "ui/views/bubble/bubble_frame_view.h"
     23 #include "ui/views/controls/label.h"
     24 #include "ui/views/layout/fill_layout.h"
     25 #include "ui/views/widget/widget.h"
     26 
     27 namespace ash {
     28 namespace internal {
     29 namespace {
     30 const int kTooltipTopBottomMargin = 3;
     31 const int kTooltipLeftRightMargin = 10;
     32 const int kTooltipAppearanceDelay = 1000;  // msec
     33 const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin;
     34 const SkColor kTooltipTextColor = SkColorSetRGB(0x22, 0x22, 0x22);
     35 
     36 // The maximum width of the tooltip bubble.  Borrowed the value from
     37 // ash/tooltip/tooltip_controller.cc
     38 const int kTooltipMaxWidth = 250;
     39 
     40 // The offset for the tooltip bubble - making sure that the bubble is flush
     41 // with the shelf. The offset includes the arrow size in pixels as well as
     42 // the activation bar and other spacing elements.
     43 const int kArrowOffsetLeftRight = 11;
     44 const int kArrowOffsetTopBottom = 7;
     45 
     46 }  // namespace
     47 
     48 // The implementation of tooltip of the launcher.
     49 class ShelfTooltipManager::ShelfTooltipBubble
     50     : public views::BubbleDelegateView {
     51  public:
     52   ShelfTooltipBubble(views::View* anchor,
     53                         views::BubbleBorder::Arrow arrow,
     54                         ShelfTooltipManager* host);
     55 
     56   void SetText(const base::string16& text);
     57   void Close();
     58 
     59  private:
     60   // views::WidgetDelegate overrides:
     61   virtual void WindowClosing() OVERRIDE;
     62 
     63   // views::View overrides:
     64   virtual gfx::Size GetPreferredSize() OVERRIDE;
     65 
     66   ShelfTooltipManager* host_;
     67   views::Label* label_;
     68 
     69   DISALLOW_COPY_AND_ASSIGN(ShelfTooltipBubble);
     70 };
     71 
     72 ShelfTooltipManager::ShelfTooltipBubble::ShelfTooltipBubble(
     73     views::View* anchor,
     74     views::BubbleBorder::Arrow arrow,
     75     ShelfTooltipManager* host)
     76     : views::BubbleDelegateView(anchor, arrow), host_(host) {
     77   // Make sure that the bubble follows the animation of the shelf.
     78   set_move_with_anchor(true);
     79   gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom,
     80                                    kArrowOffsetLeftRight,
     81                                    kArrowOffsetTopBottom,
     82                                    kArrowOffsetLeftRight);
     83   // Shelf items can have an asymmetrical border for spacing reasons.
     84   // Adjust anchor location for this.
     85   if (anchor->border())
     86     insets += anchor->border()->GetInsets();
     87 
     88   set_anchor_view_insets(insets);
     89   set_close_on_esc(false);
     90   set_close_on_deactivate(false);
     91   set_use_focusless(true);
     92   set_accept_events(false);
     93   set_margins(gfx::Insets(kTooltipTopBottomMargin, kTooltipLeftRightMargin,
     94                           kTooltipTopBottomMargin, kTooltipLeftRightMargin));
     95   set_shadow(views::BubbleBorder::SMALL_SHADOW);
     96   SetLayoutManager(new views::FillLayout());
     97   // The anchor may not have the widget in tests.
     98   if (anchor->GetWidget() && anchor->GetWidget()->GetNativeView()) {
     99     aura::Window* root_window =
    100         anchor->GetWidget()->GetNativeView()->GetRootWindow();
    101     set_parent_window(ash::Shell::GetInstance()->GetContainer(
    102         root_window, ash::internal::kShellWindowId_SettingBubbleContainer));
    103   }
    104   label_ = new views::Label;
    105   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    106   label_->SetEnabledColor(kTooltipTextColor);
    107   AddChildView(label_);
    108   views::BubbleDelegateView::CreateBubble(this);
    109 }
    110 
    111 void ShelfTooltipManager::ShelfTooltipBubble::SetText(
    112     const base::string16& text) {
    113   label_->SetText(text);
    114   SizeToContents();
    115 }
    116 
    117 void ShelfTooltipManager::ShelfTooltipBubble::Close() {
    118   if (GetWidget()) {
    119     host_ = NULL;
    120     GetWidget()->Close();
    121   }
    122 }
    123 
    124 void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() {
    125   views::BubbleDelegateView::WindowClosing();
    126   if (host_)
    127     host_->OnBubbleClosed(this);
    128 }
    129 
    130 gfx::Size ShelfTooltipManager::ShelfTooltipBubble::GetPreferredSize() {
    131   gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize();
    132   if (pref_size.height() < kTooltipMinHeight)
    133     pref_size.set_height(kTooltipMinHeight);
    134   if (pref_size.width() > kTooltipMaxWidth)
    135     pref_size.set_width(kTooltipMaxWidth);
    136   return pref_size;
    137 }
    138 
    139 ShelfTooltipManager::ShelfTooltipManager(
    140     ShelfLayoutManager* shelf_layout_manager,
    141     ShelfView* shelf_view)
    142     : view_(NULL),
    143       widget_(NULL),
    144       anchor_(NULL),
    145       shelf_layout_manager_(shelf_layout_manager),
    146       shelf_view_(shelf_view),
    147       weak_factory_(this) {
    148   if (shelf_layout_manager)
    149     shelf_layout_manager->AddObserver(this);
    150   if (Shell::HasInstance())
    151     Shell::GetInstance()->AddPreTargetHandler(this);
    152 }
    153 
    154 ShelfTooltipManager::~ShelfTooltipManager() {
    155   CancelHidingAnimation();
    156   Close();
    157   if (shelf_layout_manager_)
    158     shelf_layout_manager_->RemoveObserver(this);
    159   if (Shell::HasInstance())
    160     Shell::GetInstance()->RemovePreTargetHandler(this);
    161 }
    162 
    163 void ShelfTooltipManager::ShowDelayed(views::View* anchor,
    164                                       const base::string16& text) {
    165   if (view_) {
    166     if (timer_.get() && timer_->IsRunning()) {
    167       return;
    168     } else {
    169       CancelHidingAnimation();
    170       Close();
    171     }
    172   }
    173 
    174   if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
    175     return;
    176 
    177   CreateBubble(anchor, text);
    178   ResetTimer();
    179 }
    180 
    181 void ShelfTooltipManager::ShowImmediately(views::View* anchor,
    182                                           const base::string16& text) {
    183   if (view_) {
    184     if (timer_.get() && timer_->IsRunning())
    185       StopTimer();
    186     CancelHidingAnimation();
    187     Close();
    188   }
    189 
    190   if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
    191     return;
    192 
    193   CreateBubble(anchor, text);
    194   ShowInternal();
    195 }
    196 
    197 void ShelfTooltipManager::Close() {
    198   StopTimer();
    199   if (view_) {
    200     view_->Close();
    201     view_ = NULL;
    202     widget_ = NULL;
    203   }
    204 }
    205 
    206 void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
    207   if (view == view_) {
    208     view_ = NULL;
    209     widget_ = NULL;
    210   }
    211 }
    212 
    213 void ShelfTooltipManager::UpdateArrow() {
    214   if (view_) {
    215     CancelHidingAnimation();
    216     Close();
    217     ShowImmediately(anchor_, text_);
    218   }
    219 }
    220 
    221 void ShelfTooltipManager::ResetTimer() {
    222   if (timer_.get() && timer_->IsRunning()) {
    223     timer_->Reset();
    224     return;
    225   }
    226 
    227   // We don't start the timer if the shelf isn't visible.
    228   if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
    229     return;
    230 
    231   CreateTimer(kTooltipAppearanceDelay);
    232 }
    233 
    234 void ShelfTooltipManager::StopTimer() {
    235   timer_.reset();
    236 }
    237 
    238 bool ShelfTooltipManager::IsVisible() {
    239   if (timer_.get() && timer_->IsRunning())
    240     return false;
    241 
    242   return widget_ && widget_->IsVisible();
    243 }
    244 
    245 void ShelfTooltipManager::CreateZeroDelayTimerForTest() {
    246   CreateTimer(0);
    247 }
    248 
    249 void ShelfTooltipManager::OnMouseEvent(ui::MouseEvent* event) {
    250   DCHECK(event);
    251   DCHECK(event->target());
    252   if (!widget_ || !widget_->IsVisible())
    253     return;
    254 
    255   DCHECK(view_);
    256   DCHECK(shelf_view_);
    257 
    258   // Pressing the mouse button anywhere should close the tooltip.
    259   if (event->type() == ui::ET_MOUSE_PRESSED) {
    260     CloseSoon();
    261     return;
    262   }
    263 
    264   aura::Window* target = static_cast<aura::Window*>(event->target());
    265   if (widget_->GetNativeWindow()->GetRootWindow() != target->GetRootWindow()) {
    266     CloseSoon();
    267     return;
    268   }
    269 
    270   gfx::Point location_in_shelf_view = event->location();
    271   aura::Window::ConvertPointToTarget(
    272       target, shelf_view_->GetWidget()->GetNativeWindow(),
    273       &location_in_shelf_view);
    274 
    275   if (shelf_view_->ShouldHideTooltip(location_in_shelf_view)) {
    276     // Because this mouse event may arrive to |view_|, here we just schedule
    277     // the closing event rather than directly calling Close().
    278     CloseSoon();
    279   }
    280 }
    281 
    282 void ShelfTooltipManager::OnTouchEvent(ui::TouchEvent* event) {
    283   aura::Window* target = static_cast<aura::Window*>(event->target());
    284   if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target)
    285     Close();
    286 }
    287 
    288 void ShelfTooltipManager::OnGestureEvent(ui::GestureEvent* event) {
    289   if (widget_ && widget_->IsVisible()) {
    290     // Because this mouse event may arrive to |view_|, here we just schedule
    291     // the closing event rather than directly calling Close().
    292     CloseSoon();
    293   }
    294 }
    295 
    296 void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) {
    297   Close();
    298 }
    299 
    300 void ShelfTooltipManager::WillDeleteShelf() {
    301   shelf_layout_manager_ = NULL;
    302 }
    303 
    304 void ShelfTooltipManager::WillChangeVisibilityState(
    305     ShelfVisibilityState new_state) {
    306   if (new_state == SHELF_HIDDEN) {
    307     StopTimer();
    308     Close();
    309   }
    310 }
    311 
    312 void ShelfTooltipManager::OnAutoHideStateChanged(ShelfAutoHideState new_state) {
    313   if (new_state == SHELF_AUTO_HIDE_HIDDEN) {
    314     StopTimer();
    315     // AutoHide state change happens during an event filter, so immediate close
    316     // may cause a crash in the HandleMouseEvent() after the filter.  So we just
    317     // schedule the Close here.
    318     CloseSoon();
    319   }
    320 }
    321 
    322 void ShelfTooltipManager::CancelHidingAnimation() {
    323   if (!widget_ || !widget_->GetNativeView())
    324     return;
    325 
    326   gfx::NativeView native_view = widget_->GetNativeView();
    327   views::corewm::SetWindowVisibilityAnimationTransition(
    328       native_view, views::corewm::ANIMATE_NONE);
    329 }
    330 
    331 void ShelfTooltipManager::CloseSoon() {
    332   base::MessageLoopForUI::current()->PostTask(
    333       FROM_HERE,
    334       base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr()));
    335 }
    336 
    337 void ShelfTooltipManager::ShowInternal() {
    338   if (view_)
    339     view_->GetWidget()->Show();
    340 
    341   timer_.reset();
    342 }
    343 
    344 void ShelfTooltipManager::CreateBubble(views::View* anchor,
    345                                        const base::string16& text) {
    346   DCHECK(!view_);
    347 
    348   anchor_ = anchor;
    349   text_ = text;
    350   views::BubbleBorder::Arrow arrow =
    351       shelf_layout_manager_->SelectValueForShelfAlignment(
    352           views::BubbleBorder::BOTTOM_CENTER,
    353           views::BubbleBorder::LEFT_CENTER,
    354           views::BubbleBorder::RIGHT_CENTER,
    355           views::BubbleBorder::TOP_CENTER);
    356 
    357   view_ = new ShelfTooltipBubble(anchor, arrow, this);
    358   widget_ = view_->GetWidget();
    359   view_->SetText(text_);
    360 
    361   gfx::NativeView native_view = widget_->GetNativeView();
    362   views::corewm::SetWindowVisibilityAnimationType(
    363       native_view, views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
    364   views::corewm::SetWindowVisibilityAnimationTransition(
    365       native_view, views::corewm::ANIMATE_HIDE);
    366 }
    367 
    368 void ShelfTooltipManager::CreateTimer(int delay_in_ms) {
    369   base::OneShotTimer<ShelfTooltipManager>* new_timer =
    370       new base::OneShotTimer<ShelfTooltipManager>();
    371   new_timer->Start(FROM_HERE,
    372                    base::TimeDelta::FromMilliseconds(delay_in_ms),
    373                    this,
    374                    &ShelfTooltipManager::ShowInternal);
    375   timer_.reset(new_timer);
    376 }
    377 
    378 }  // namespace internal
    379 }  // namespace ash
    380