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