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