• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ShelfTooltipBubble(views::View * anchor,views::BubbleBorder::Arrow arrow,ShelfTooltipManager * host)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 
SetText(const base::string16 & text)108 void ShelfTooltipManager::ShelfTooltipBubble::SetText(
109     const base::string16& text) {
110   label_->SetText(text);
111   SizeToContents();
112 }
113 
Close()114 void ShelfTooltipManager::ShelfTooltipBubble::Close() {
115   if (GetWidget()) {
116     host_ = NULL;
117     GetWidget()->Close();
118   }
119 }
120 
WindowClosing()121 void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() {
122   views::BubbleDelegateView::WindowClosing();
123   if (host_)
124     host_->OnBubbleClosed(this);
125 }
126 
GetPreferredSize() const127 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 
ShelfTooltipManager(ShelfLayoutManager * shelf_layout_manager,ShelfView * shelf_view)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 
~ShelfTooltipManager()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 
ShowDelayed(views::View * anchor,const base::string16 & text)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 
ShowImmediately(views::View * anchor,const base::string16 & text)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 
Close()194 void ShelfTooltipManager::Close() {
195   StopTimer();
196   if (view_) {
197     view_->Close();
198     view_ = NULL;
199     widget_ = NULL;
200   }
201 }
202 
OnBubbleClosed(views::BubbleDelegateView * view)203 void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
204   if (view == view_) {
205     view_ = NULL;
206     widget_ = NULL;
207   }
208 }
209 
UpdateArrow()210 void ShelfTooltipManager::UpdateArrow() {
211   if (view_) {
212     CancelHidingAnimation();
213     Close();
214     ShowImmediately(anchor_, text_);
215   }
216 }
217 
ResetTimer()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 
StopTimer()231 void ShelfTooltipManager::StopTimer() {
232   timer_.reset();
233 }
234 
IsVisible()235 bool ShelfTooltipManager::IsVisible() {
236   if (timer_.get() && timer_->IsRunning())
237     return false;
238 
239   return widget_ && widget_->IsVisible();
240 }
241 
CreateZeroDelayTimerForTest()242 void ShelfTooltipManager::CreateZeroDelayTimerForTest() {
243   CreateTimer(0);
244 }
245 
OnMouseEvent(ui::MouseEvent * event)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 
OnTouchEvent(ui::TouchEvent * event)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 
OnGestureEvent(ui::GestureEvent * event)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 
OnCancelMode(ui::CancelModeEvent * event)293 void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) {
294   Close();
295 }
296 
WillDeleteShelf()297 void ShelfTooltipManager::WillDeleteShelf() {
298   shelf_layout_manager_ = NULL;
299 }
300 
WillChangeVisibilityState(ShelfVisibilityState new_state)301 void ShelfTooltipManager::WillChangeVisibilityState(
302     ShelfVisibilityState new_state) {
303   if (new_state == SHELF_HIDDEN) {
304     StopTimer();
305     Close();
306   }
307 }
308 
OnAutoHideStateChanged(ShelfAutoHideState new_state)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 
CancelHidingAnimation()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 
CloseSoon()328 void ShelfTooltipManager::CloseSoon() {
329   base::MessageLoopForUI::current()->PostTask(
330       FROM_HERE,
331       base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr()));
332 }
333 
ShowInternal()334 void ShelfTooltipManager::ShowInternal() {
335   if (view_)
336     view_->GetWidget()->Show();
337 
338   timer_.reset();
339 }
340 
CreateBubble(views::View * anchor,const base::string16 & text)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 
CreateTimer(int delay_in_ms)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