shelf_tooltip_manager.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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
27namespace ash {
28namespace internal {
29namespace {
30const int kTooltipTopBottomMargin = 3;
31const int kTooltipLeftRightMargin = 10;
32const int kTooltipAppearanceDelay = 1000;  // msec
33const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin;
34const 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
38const 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.
43const int kArrowOffsetLeftRight = 11;
44const int kArrowOffsetTopBottom = 7;
45
46}  // namespace
47
48// The implementation of tooltip of the launcher.
49class 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
72ShelfTooltipManager::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
111void ShelfTooltipManager::ShelfTooltipBubble::SetText(
112    const base::string16& text) {
113  label_->SetText(text);
114  SizeToContents();
115}
116
117void ShelfTooltipManager::ShelfTooltipBubble::Close() {
118  if (GetWidget()) {
119    host_ = NULL;
120    GetWidget()->Close();
121  }
122}
123
124void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() {
125  views::BubbleDelegateView::WindowClosing();
126  if (host_)
127    host_->OnBubbleClosed(this);
128}
129
130gfx::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
139ShelfTooltipManager::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
154ShelfTooltipManager::~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
163void 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
181void 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
197void ShelfTooltipManager::Close() {
198  StopTimer();
199  if (view_) {
200    view_->Close();
201    view_ = NULL;
202    widget_ = NULL;
203  }
204}
205
206void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
207  if (view == view_) {
208    view_ = NULL;
209    widget_ = NULL;
210  }
211}
212
213void ShelfTooltipManager::UpdateArrow() {
214  if (view_) {
215    CancelHidingAnimation();
216    Close();
217    ShowImmediately(anchor_, text_);
218  }
219}
220
221void 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
234void ShelfTooltipManager::StopTimer() {
235  timer_.reset();
236}
237
238bool ShelfTooltipManager::IsVisible() {
239  if (timer_.get() && timer_->IsRunning())
240    return false;
241
242  return widget_ && widget_->IsVisible();
243}
244
245void ShelfTooltipManager::CreateZeroDelayTimerForTest() {
246  CreateTimer(0);
247}
248
249void 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
282void 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
288void 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
296void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) {
297  Close();
298}
299
300void ShelfTooltipManager::WillDeleteShelf() {
301  shelf_layout_manager_ = NULL;
302}
303
304void ShelfTooltipManager::WillChangeVisibilityState(
305    ShelfVisibilityState new_state) {
306  if (new_state == SHELF_HIDDEN) {
307    StopTimer();
308    Close();
309  }
310}
311
312void 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
322void 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
331void ShelfTooltipManager::CloseSoon() {
332  base::MessageLoopForUI::current()->PostTask(
333      FROM_HERE,
334      base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr()));
335}
336
337void ShelfTooltipManager::ShowInternal() {
338  if (view_)
339    view_->GetWidget()->Show();
340
341  timer_.reset();
342}
343
344void 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
368void 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