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 int last_tab_index = highlighted_tab_ == -1 ? 113 browser->tab_strip_model()->active_index() : highlighted_tab_; 114 if (!scrubbing_) { 115 swipe_direction_ = (x_offset < 0) ? LEFT : RIGHT; 116 const gfx::Point start_point = 117 GetStartPoint(tab_strip, 118 browser->tab_strip_model()->active_index(), 119 swipe_direction_); 120 browser_ = browser; 121 scrubbing_ = true; 122 123 swipe_x_ = start_point.x(); 124 swipe_y_ = start_point.y(); 125 ImmersiveModeController* immersive_controller = 126 browser_view->immersive_mode_controller(); 127 if (immersive_controller->IsEnabled()) { 128 immersive_reveal_lock_.reset(immersive_controller->GetRevealedLock( 129 ImmersiveModeController::ANIMATE_REVEAL_YES)); 130 } 131 tab_strip->AddObserver(this); 132 } else if (highlighted_tab_ == -1) { 133 Direction direction = (x_offset < 0) ? LEFT : RIGHT; 134 if (direction != swipe_direction_) { 135 const gfx::Point start_point = 136 GetStartPoint(tab_strip, 137 browser->tab_strip_model()->active_index(), 138 direction); 139 swipe_x_ = start_point.x(); 140 swipe_y_ = start_point.y(); 141 swipe_direction_ = direction; 142 } 143 } 144 145 swipe_x_ += x_offset; 146 Tab* first_tab = tab_strip->tab_at(0); 147 int first_tab_center = first_tab->bounds().CenterPoint().x(); 148 Tab* last_tab = tab_strip->tab_at(tab_strip->tab_count() - 1); 149 int last_tab_tab_center = last_tab->bounds().CenterPoint().x(); 150 if (swipe_x_ < first_tab_center) 151 swipe_x_ = first_tab_center; 152 if (swipe_x_ > last_tab_tab_center) 153 swipe_x_ = last_tab_tab_center; 154 155 Tab* initial_tab = tab_strip->tab_at(last_tab_index); 156 gfx::Point tab_point(swipe_x_, swipe_y_); 157 views::View::ConvertPointToTarget(tab_strip, initial_tab, &tab_point); 158 Tab* new_tab = tab_strip->GetTabAt(initial_tab, tab_point); 159 if (!new_tab) 160 return; 161 162 int new_index = tab_strip->GetModelIndexOfTab(new_tab); 163 if (highlighted_tab_ == -1 && 164 new_index == browser->tab_strip_model()->active_index()) 165 return; 166 167 if (new_index != highlighted_tab_) { 168 if (activate_timer_.IsRunning()) { 169 activate_timer_.Reset(); 170 } else { 171 int delay = use_default_activation_delay_ ? 172 ui::GestureConfiguration::tab_scrub_activation_delay_in_ms() : 173 activation_delay_; 174 if (delay >= 0) { 175 activate_timer_.Start(FROM_HERE, 176 base::TimeDelta::FromMilliseconds(delay), 177 base::Bind(&TabScrubber::FinishScrub, 178 weak_ptr_factory_.GetWeakPtr(), 179 true)); 180 } 181 } 182 if (highlighted_tab_ != -1) { 183 Tab* tab = tab_strip->tab_at(highlighted_tab_); 184 tab->hover_controller()->HideImmediately(); 185 } 186 if (new_index == browser->tab_strip_model()->active_index()) { 187 highlighted_tab_ = -1; 188 } else { 189 highlighted_tab_ = new_index; 190 new_tab->hover_controller()->Show(views::GlowHoverController::PRONOUNCED); 191 } 192 } 193 if (highlighted_tab_ != -1) { 194 gfx::Point hover_point(swipe_x_, swipe_y_); 195 views::View::ConvertPointToTarget(tab_strip, new_tab, &hover_point); 196 new_tab->hover_controller()->SetLocation(hover_point); 197 } 198 } 199 200 void TabScrubber::Observe(int type, 201 const content::NotificationSource& source, 202 const content::NotificationDetails& details) { 203 if (content::Source<Browser>(source).ptr() == browser_) { 204 activate_timer_.Stop(); 205 swipe_x_ = -1; 206 swipe_y_ = -1; 207 scrubbing_ = false; 208 highlighted_tab_ = -1; 209 browser_ = NULL; 210 } 211 } 212 213 void TabScrubber::TabStripAddedTabAt(TabStrip* tab_strip, int index) { 214 if (highlighted_tab_ == -1) 215 return; 216 217 if (index < highlighted_tab_) 218 ++highlighted_tab_; 219 } 220 221 void TabScrubber::TabStripMovedTab(TabStrip* tab_strip, 222 int from_index, 223 int to_index) { 224 if (highlighted_tab_ == -1) 225 return; 226 227 if (from_index == highlighted_tab_) 228 highlighted_tab_ = to_index; 229 else if (from_index < highlighted_tab_&& highlighted_tab_<= to_index) 230 --highlighted_tab_; 231 else if (from_index > highlighted_tab_ && highlighted_tab_ >= to_index) 232 ++highlighted_tab_; 233 } 234 235 void TabScrubber::TabStripRemovedTabAt(TabStrip* tab_strip, int index) { 236 if (highlighted_tab_ == -1) 237 return; 238 if (index == highlighted_tab_) { 239 FinishScrub(false); 240 return; 241 } 242 if (index < highlighted_tab_) 243 --highlighted_tab_; 244 } 245 246 void TabScrubber::TabStripDeleted(TabStrip* tab_strip) { 247 if (highlighted_tab_ == -1) 248 return; 249 } 250 251 Browser* TabScrubber::GetActiveBrowser() { 252 aura::Window* active_window = ash::wm::GetActiveWindow(); 253 if (!active_window) 254 return NULL; 255 256 Browser* browser = chrome::FindBrowserWithWindow(active_window); 257 if (!browser || browser->type() != Browser::TYPE_TABBED) 258 return NULL; 259 260 return browser; 261 } 262 263 void TabScrubber::FinishScrub(bool activate) { 264 activate_timer_.Stop(); 265 266 if (browser_ && browser_->window()) { 267 BrowserView* browser_view = 268 BrowserView::GetBrowserViewForNativeWindow( 269 browser_->window()->GetNativeWindow()); 270 TabStrip* tab_strip = browser_view->tabstrip(); 271 if (activate && highlighted_tab_ != -1) { 272 Tab* tab = tab_strip->tab_at(highlighted_tab_); 273 tab->hover_controller()->HideImmediately(); 274 int distance = 275 std::abs( 276 highlighted_tab_ - browser_->tab_strip_model()->active_index()); 277 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.ScrubDistance", distance, 0, 20, 21); 278 browser_->tab_strip_model()->ActivateTabAt(highlighted_tab_, true); 279 } 280 tab_strip->RemoveObserver(this); 281 } 282 swipe_x_ = -1; 283 swipe_y_ = -1; 284 scrubbing_ = false; 285 highlighted_tab_ = -1; 286 } 287