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/events/event.h" 22 #include "ui/events/event_utils.h" 23 #include "ui/events/gestures/gesture_configuration.h" 24 #include "ui/views/controls/glow_hover_controller.h" 25 26 namespace { 27 const int64 kActivationDelayMS = 200; 28 } 29 30 // static 31 TabScrubber* TabScrubber::GetInstance() { 32 static TabScrubber* instance = NULL; 33 if (!instance) 34 instance = new TabScrubber(); 35 return instance; 36 } 37 38 // static 39 gfx::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 51 bool TabScrubber::IsActivationPending() { 52 return activate_timer_.IsRunning(); 53 } 54 55 TabScrubber::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 73 TabScrubber::~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 79 void 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 202 void 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 215 void 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 223 void 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 237 void 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 248 void TabScrubber::TabStripDeleted(TabStrip* tab_strip) { 249 if (highlighted_tab_ == -1) 250 return; 251 } 252 253 Browser* 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 265 void 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