tab_scrubber.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1// Copyright (c) 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 "chrome/browser/ui/views/ash/tab_scrubber.h"
6
7#include "ash/shell.h"
8#include "ash/wm/window_util.h"
9#include "base/metrics/histogram.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "chrome/browser/ui/browser.h"
12#include "chrome/browser/ui/browser_finder.h"
13#include "chrome/browser/ui/tabs/tab_strip_model.h"
14#include "chrome/browser/ui/views/frame/browser_view.h"
15#include "chrome/browser/ui/views/tabs/tab.h"
16#include "chrome/browser/ui/views/tabs/tab_strip.h"
17#include "chrome/common/pref_names.h"
18#include "content/public/browser/notification_service.h"
19#include "content/public/browser/notification_source.h"
20#include "ui/aura/window.h"
21#include "ui/base/events/event.h"
22#include "ui/base/events/event_utils.h"
23#include "ui/base/gestures/gesture_configuration.h"
24#include "ui/views/controls/glow_hover_controller.h"
25
26namespace {
27const int64 kActivationDelayMS = 200;
28}
29
30// static
31TabScrubber* TabScrubber::GetInstance() {
32  static TabScrubber* instance = NULL;
33  if (!instance)
34    instance = new TabScrubber();
35  return instance;
36}
37
38// static
39gfx::Point TabScrubber::GetStartPoint(
40    TabStrip* tab_strip,
41    int index,
42    TabScrubber::Direction direction) {
43  int initial_tab_offset = Tab::GetMiniWidth() / 2;
44  gfx::Rect tab_bounds = tab_strip->tab_at(index)->bounds();
45  float x = direction == LEFT ?
46      tab_bounds.x() + initial_tab_offset :
47          tab_bounds.right() - initial_tab_offset;
48  return gfx::Point(x, tab_bounds.CenterPoint().y());
49}
50
51bool TabScrubber::IsActivationPending() {
52  return activate_timer_.IsRunning();
53}
54
55TabScrubber::TabScrubber()
56    : scrubbing_(false),
57      browser_(NULL),
58      swipe_x_(-1),
59      swipe_y_(-1),
60      swipe_direction_(LEFT),
61      highlighted_tab_(-1),
62      activate_timer_(true, false),
63      activation_delay_(kActivationDelayMS),
64      use_default_activation_delay_(true),
65      weak_ptr_factory_(this) {
66  ash::Shell::GetInstance()->AddPreTargetHandler(this);
67  registrar_.Add(
68      this,
69      chrome::NOTIFICATION_BROWSER_CLOSED,
70      content::NotificationService::AllSources());
71}
72
73TabScrubber::~TabScrubber() {
74  // Note: The weak_ptr_factory_ should invalidate  its weak pointers before
75  // any other members are destroyed.
76  weak_ptr_factory_.InvalidateWeakPtrs();
77}
78
79void TabScrubber::OnScrollEvent(ui::ScrollEvent* event) {
80  if (event->type() == ui::ET_SCROLL_FLING_CANCEL ||
81      event->type() == ui::ET_SCROLL_FLING_START) {
82    FinishScrub(true);
83    immersive_reveal_lock_.reset();
84    return;
85  }
86
87  if (event->finger_count() != 3)
88    return;
89
90  Browser* browser = GetActiveBrowser();
91  if (!browser || (scrubbing_ && browser_ && browser != browser_) ||
92      (highlighted_tab_ != -1 &&
93          highlighted_tab_ >= browser->tab_strip_model()->count())) {
94    FinishScrub(false);
95    return;
96  }
97
98  BrowserView* browser_view =
99      BrowserView::GetBrowserViewForNativeWindow(
100          browser->window()->GetNativeWindow());
101  TabStrip* tab_strip = browser_view->tabstrip();
102
103  if (tab_strip->IsAnimating()) {
104    FinishScrub(false);
105    return;
106  }
107
108  // We are handling the event.
109  event->StopPropagation();
110
111  float x_offset = event->x_offset();
112  if (!ui::IsNaturalScrollEnabled())
113    x_offset = -x_offset;
114  int last_tab_index = highlighted_tab_ == -1 ?
115      browser->tab_strip_model()->active_index() : highlighted_tab_;
116  if (!scrubbing_) {
117    swipe_direction_ = (x_offset < 0) ? LEFT : RIGHT;
118    const gfx::Point start_point =
119        GetStartPoint(tab_strip,
120                      browser->tab_strip_model()->active_index(),
121                      swipe_direction_);
122    browser_ = browser;
123    scrubbing_ = true;
124
125    swipe_x_ = start_point.x();
126    swipe_y_ = start_point.y();
127    ImmersiveModeController* immersive_controller =
128        browser_view->immersive_mode_controller();
129    if (immersive_controller->IsEnabled()) {
130      immersive_reveal_lock_.reset(immersive_controller->GetRevealedLock(
131          ImmersiveModeController::ANIMATE_REVEAL_YES));
132    }
133    tab_strip->AddObserver(this);
134  } else if (highlighted_tab_ == -1) {
135    Direction direction = (x_offset < 0) ? LEFT : RIGHT;
136    if (direction != swipe_direction_) {
137      const gfx::Point start_point =
138          GetStartPoint(tab_strip,
139                        browser->tab_strip_model()->active_index(),
140                        direction);
141      swipe_x_ = start_point.x();
142      swipe_y_ = start_point.y();
143      swipe_direction_ = direction;
144    }
145  }
146
147  swipe_x_ += x_offset;
148  Tab* first_tab = tab_strip->tab_at(0);
149  int first_tab_center = first_tab->bounds().CenterPoint().x();
150  Tab* last_tab = tab_strip->tab_at(tab_strip->tab_count() - 1);
151  int last_tab_tab_center = last_tab->bounds().CenterPoint().x();
152  if (swipe_x_ < first_tab_center)
153    swipe_x_ = first_tab_center;
154  if (swipe_x_ > last_tab_tab_center)
155    swipe_x_ = last_tab_tab_center;
156
157  Tab* initial_tab = tab_strip->tab_at(last_tab_index);
158  gfx::Point tab_point(swipe_x_, swipe_y_);
159  views::View::ConvertPointToTarget(tab_strip, initial_tab, &tab_point);
160  Tab* new_tab = tab_strip->GetTabAt(initial_tab, tab_point);
161  if (!new_tab)
162    return;
163
164  int new_index = tab_strip->GetModelIndexOfTab(new_tab);
165  if (highlighted_tab_ == -1 &&
166      new_index == browser->tab_strip_model()->active_index())
167    return;
168
169  if (new_index != highlighted_tab_) {
170    if (activate_timer_.IsRunning()) {
171      activate_timer_.Reset();
172    } else {
173      int delay = use_default_activation_delay_ ?
174          ui::GestureConfiguration::tab_scrub_activation_delay_in_ms() :
175          activation_delay_;
176      if (delay >= 0) {
177        activate_timer_.Start(FROM_HERE,
178                              base::TimeDelta::FromMilliseconds(delay),
179                              base::Bind(&TabScrubber::FinishScrub,
180                                         weak_ptr_factory_.GetWeakPtr(),
181                                         true));
182      }
183    }
184    if (highlighted_tab_ != -1) {
185      Tab* tab = tab_strip->tab_at(highlighted_tab_);
186      tab->hover_controller()->HideImmediately();
187    }
188    if (new_index == browser->tab_strip_model()->active_index()) {
189      highlighted_tab_ = -1;
190    } else {
191      highlighted_tab_ = new_index;
192      new_tab->hover_controller()->Show(views::GlowHoverController::PRONOUNCED);
193    }
194  }
195  if (highlighted_tab_ != -1) {
196    gfx::Point hover_point(swipe_x_, swipe_y_);
197    views::View::ConvertPointToTarget(tab_strip, new_tab, &hover_point);
198    new_tab->hover_controller()->SetLocation(hover_point);
199  }
200}
201
202void TabScrubber::Observe(int type,
203                          const content::NotificationSource& source,
204                          const content::NotificationDetails& details) {
205  if (content::Source<Browser>(source).ptr() == browser_) {
206    activate_timer_.Stop();
207    swipe_x_ = -1;
208    swipe_y_ = -1;
209    scrubbing_ = false;
210    highlighted_tab_ = -1;
211    browser_ = NULL;
212  }
213}
214
215void TabScrubber::TabStripAddedTabAt(TabStrip* tab_strip, int index) {
216  if (highlighted_tab_ == -1)
217    return;
218
219  if (index < highlighted_tab_)
220    ++highlighted_tab_;
221}
222
223void TabScrubber::TabStripMovedTab(TabStrip* tab_strip,
224                                   int from_index,
225                                   int to_index) {
226  if (highlighted_tab_ == -1)
227    return;
228
229  if (from_index == highlighted_tab_)
230    highlighted_tab_ = to_index;
231  else if (from_index < highlighted_tab_&& highlighted_tab_<= to_index)
232    --highlighted_tab_;
233  else if (from_index > highlighted_tab_ && highlighted_tab_ >= to_index)
234    ++highlighted_tab_;
235}
236
237void TabScrubber::TabStripRemovedTabAt(TabStrip* tab_strip, int index) {
238  if (highlighted_tab_ == -1)
239    return;
240  if (index == highlighted_tab_) {
241    FinishScrub(false);
242    return;
243  }
244  if (index < highlighted_tab_)
245    --highlighted_tab_;
246}
247
248void TabScrubber::TabStripDeleted(TabStrip* tab_strip) {
249  if (highlighted_tab_ == -1)
250    return;
251}
252
253Browser* TabScrubber::GetActiveBrowser() {
254  aura::Window* active_window = ash::wm::GetActiveWindow();
255  if (!active_window)
256    return NULL;
257
258  Browser* browser = chrome::FindBrowserWithWindow(active_window);
259  if (!browser || browser->type() != Browser::TYPE_TABBED)
260    return NULL;
261
262  return browser;
263}
264
265void TabScrubber::FinishScrub(bool activate) {
266  activate_timer_.Stop();
267
268  if (browser_ && browser_->window()) {
269    BrowserView* browser_view =
270        BrowserView::GetBrowserViewForNativeWindow(
271            browser_->window()->GetNativeWindow());
272    TabStrip* tab_strip = browser_view->tabstrip();
273    if (activate && highlighted_tab_ != -1) {
274      Tab* tab = tab_strip->tab_at(highlighted_tab_);
275      tab->hover_controller()->HideImmediately();
276      int distance =
277          std::abs(
278              highlighted_tab_ - browser_->tab_strip_model()->active_index());
279      UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.ScrubDistance", distance, 0, 20, 20);
280      browser_->tab_strip_model()->ActivateTabAt(highlighted_tab_, true);
281    }
282    tab_strip->RemoveObserver(this);
283  }
284  swipe_x_ = -1;
285  swipe_y_ = -1;
286  scrubbing_ = false;
287  highlighted_tab_ = -1;
288}
289