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
27namespace ash {
28namespace {
29const int kTooltipTopBottomMargin = 3;
30const int kTooltipLeftRightMargin = 10;
31const int kTooltipAppearanceDelay = 1000;  // msec
32const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin;
33const 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
37const 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.
42const int kArrowOffsetLeftRight = 11;
43const int kArrowOffsetTopBottom = 7;
44
45}  // namespace
46
47// The implementation of tooltip of the launcher.
48class 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
71ShelfTooltipManager::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
108void ShelfTooltipManager::ShelfTooltipBubble::SetText(
109    const base::string16& text) {
110  label_->SetText(text);
111  SizeToContents();
112}
113
114void ShelfTooltipManager::ShelfTooltipBubble::Close() {
115  if (GetWidget()) {
116    host_ = NULL;
117    GetWidget()->Close();
118  }
119}
120
121void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() {
122  views::BubbleDelegateView::WindowClosing();
123  if (host_)
124    host_->OnBubbleClosed(this);
125}
126
127gfx::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
136ShelfTooltipManager::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
151ShelfTooltipManager::~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
160void 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
178void 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
194void ShelfTooltipManager::Close() {
195  StopTimer();
196  if (view_) {
197    view_->Close();
198    view_ = NULL;
199    widget_ = NULL;
200  }
201}
202
203void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
204  if (view == view_) {
205    view_ = NULL;
206    widget_ = NULL;
207  }
208}
209
210void ShelfTooltipManager::UpdateArrow() {
211  if (view_) {
212    CancelHidingAnimation();
213    Close();
214    ShowImmediately(anchor_, text_);
215  }
216}
217
218void 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
231void ShelfTooltipManager::StopTimer() {
232  timer_.reset();
233}
234
235bool ShelfTooltipManager::IsVisible() {
236  if (timer_.get() && timer_->IsRunning())
237    return false;
238
239  return widget_ && widget_->IsVisible();
240}
241
242void ShelfTooltipManager::CreateZeroDelayTimerForTest() {
243  CreateTimer(0);
244}
245
246void 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
279void 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
285void 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
293void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) {
294  Close();
295}
296
297void ShelfTooltipManager::WillDeleteShelf() {
298  shelf_layout_manager_ = NULL;
299}
300
301void ShelfTooltipManager::WillChangeVisibilityState(
302    ShelfVisibilityState new_state) {
303  if (new_state == SHELF_HIDDEN) {
304    StopTimer();
305    Close();
306  }
307}
308
309void 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
319void 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
328void ShelfTooltipManager::CloseSoon() {
329  base::MessageLoopForUI::current()->PostTask(
330      FROM_HERE,
331      base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr()));
332}
333
334void ShelfTooltipManager::ShowInternal() {
335  if (view_)
336    view_->GetWidget()->Show();
337
338  timer_.reset();
339}
340
341void 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
365void 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