| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/ash/tab_scrubber.h" |
| |
| #include "ash/shell.h" |
| #include "ash/wm/window_util.h" |
| #include "base/metrics/histogram.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/tabs/tab.h" |
| #include "chrome/browser/ui/views/tabs/tab_strip.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "ui/aura/window.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/gestures/gesture_configuration.h" |
| #include "ui/views/controls/glow_hover_controller.h" |
| |
| namespace { |
| const int64 kActivationDelayMS = 200; |
| } |
| |
| // static |
| TabScrubber* TabScrubber::GetInstance() { |
| static TabScrubber* instance = NULL; |
| if (!instance) |
| instance = new TabScrubber(); |
| return instance; |
| } |
| |
| // static |
| gfx::Point TabScrubber::GetStartPoint( |
| TabStrip* tab_strip, |
| int index, |
| TabScrubber::Direction direction) { |
| int initial_tab_offset = Tab::GetMiniWidth() / 2; |
| gfx::Rect tab_bounds = tab_strip->tab_at(index)->bounds(); |
| float x = direction == LEFT ? |
| tab_bounds.x() + initial_tab_offset : |
| tab_bounds.right() - initial_tab_offset; |
| return gfx::Point(x, tab_bounds.CenterPoint().y()); |
| } |
| |
| bool TabScrubber::IsActivationPending() { |
| return activate_timer_.IsRunning(); |
| } |
| |
| TabScrubber::TabScrubber() |
| : scrubbing_(false), |
| browser_(NULL), |
| swipe_x_(-1), |
| swipe_y_(-1), |
| swipe_direction_(LEFT), |
| highlighted_tab_(-1), |
| activate_timer_(true, false), |
| activation_delay_(kActivationDelayMS), |
| use_default_activation_delay_(true), |
| weak_ptr_factory_(this) { |
| ash::Shell::GetInstance()->AddPreTargetHandler(this); |
| registrar_.Add( |
| this, |
| chrome::NOTIFICATION_BROWSER_CLOSED, |
| content::NotificationService::AllSources()); |
| } |
| |
| TabScrubber::~TabScrubber() { |
| // Note: The weak_ptr_factory_ should invalidate its weak pointers before |
| // any other members are destroyed. |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void TabScrubber::OnScrollEvent(ui::ScrollEvent* event) { |
| if (event->type() == ui::ET_SCROLL_FLING_CANCEL || |
| event->type() == ui::ET_SCROLL_FLING_START) { |
| FinishScrub(true); |
| immersive_reveal_lock_.reset(); |
| return; |
| } |
| |
| if (event->finger_count() != 3) |
| return; |
| |
| Browser* browser = GetActiveBrowser(); |
| if (!browser || (scrubbing_ && browser_ && browser != browser_) || |
| (highlighted_tab_ != -1 && |
| highlighted_tab_ >= browser->tab_strip_model()->count())) { |
| FinishScrub(false); |
| return; |
| } |
| |
| BrowserView* browser_view = |
| BrowserView::GetBrowserViewForNativeWindow( |
| browser->window()->GetNativeWindow()); |
| TabStrip* tab_strip = browser_view->tabstrip(); |
| |
| if (tab_strip->IsAnimating()) { |
| FinishScrub(false); |
| return; |
| } |
| |
| // We are handling the event. |
| event->StopPropagation(); |
| |
| float x_offset = event->x_offset(); |
| int last_tab_index = highlighted_tab_ == -1 ? |
| browser->tab_strip_model()->active_index() : highlighted_tab_; |
| if (!scrubbing_) { |
| swipe_direction_ = (x_offset < 0) ? LEFT : RIGHT; |
| const gfx::Point start_point = |
| GetStartPoint(tab_strip, |
| browser->tab_strip_model()->active_index(), |
| swipe_direction_); |
| browser_ = browser; |
| scrubbing_ = true; |
| |
| swipe_x_ = start_point.x(); |
| swipe_y_ = start_point.y(); |
| ImmersiveModeController* immersive_controller = |
| browser_view->immersive_mode_controller(); |
| if (immersive_controller->IsEnabled()) { |
| immersive_reveal_lock_.reset(immersive_controller->GetRevealedLock( |
| ImmersiveModeController::ANIMATE_REVEAL_YES)); |
| } |
| tab_strip->AddObserver(this); |
| } else if (highlighted_tab_ == -1) { |
| Direction direction = (x_offset < 0) ? LEFT : RIGHT; |
| if (direction != swipe_direction_) { |
| const gfx::Point start_point = |
| GetStartPoint(tab_strip, |
| browser->tab_strip_model()->active_index(), |
| direction); |
| swipe_x_ = start_point.x(); |
| swipe_y_ = start_point.y(); |
| swipe_direction_ = direction; |
| } |
| } |
| |
| swipe_x_ += x_offset; |
| Tab* first_tab = tab_strip->tab_at(0); |
| int first_tab_center = first_tab->bounds().CenterPoint().x(); |
| Tab* last_tab = tab_strip->tab_at(tab_strip->tab_count() - 1); |
| int last_tab_tab_center = last_tab->bounds().CenterPoint().x(); |
| if (swipe_x_ < first_tab_center) |
| swipe_x_ = first_tab_center; |
| if (swipe_x_ > last_tab_tab_center) |
| swipe_x_ = last_tab_tab_center; |
| |
| Tab* initial_tab = tab_strip->tab_at(last_tab_index); |
| gfx::Point tab_point(swipe_x_, swipe_y_); |
| views::View::ConvertPointToTarget(tab_strip, initial_tab, &tab_point); |
| Tab* new_tab = tab_strip->GetTabAt(initial_tab, tab_point); |
| if (!new_tab) |
| return; |
| |
| int new_index = tab_strip->GetModelIndexOfTab(new_tab); |
| if (highlighted_tab_ == -1 && |
| new_index == browser->tab_strip_model()->active_index()) |
| return; |
| |
| if (new_index != highlighted_tab_) { |
| if (activate_timer_.IsRunning()) { |
| activate_timer_.Reset(); |
| } else { |
| int delay = use_default_activation_delay_ ? |
| ui::GestureConfiguration::tab_scrub_activation_delay_in_ms() : |
| activation_delay_; |
| if (delay >= 0) { |
| activate_timer_.Start(FROM_HERE, |
| base::TimeDelta::FromMilliseconds(delay), |
| base::Bind(&TabScrubber::FinishScrub, |
| weak_ptr_factory_.GetWeakPtr(), |
| true)); |
| } |
| } |
| if (highlighted_tab_ != -1) { |
| Tab* tab = tab_strip->tab_at(highlighted_tab_); |
| tab->hover_controller()->HideImmediately(); |
| } |
| if (new_index == browser->tab_strip_model()->active_index()) { |
| highlighted_tab_ = -1; |
| } else { |
| highlighted_tab_ = new_index; |
| new_tab->hover_controller()->Show(views::GlowHoverController::PRONOUNCED); |
| } |
| } |
| if (highlighted_tab_ != -1) { |
| gfx::Point hover_point(swipe_x_, swipe_y_); |
| views::View::ConvertPointToTarget(tab_strip, new_tab, &hover_point); |
| new_tab->hover_controller()->SetLocation(hover_point); |
| } |
| } |
| |
| void TabScrubber::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| if (content::Source<Browser>(source).ptr() == browser_) { |
| activate_timer_.Stop(); |
| swipe_x_ = -1; |
| swipe_y_ = -1; |
| scrubbing_ = false; |
| highlighted_tab_ = -1; |
| browser_ = NULL; |
| } |
| } |
| |
| void TabScrubber::TabStripAddedTabAt(TabStrip* tab_strip, int index) { |
| if (highlighted_tab_ == -1) |
| return; |
| |
| if (index < highlighted_tab_) |
| ++highlighted_tab_; |
| } |
| |
| void TabScrubber::TabStripMovedTab(TabStrip* tab_strip, |
| int from_index, |
| int to_index) { |
| if (highlighted_tab_ == -1) |
| return; |
| |
| if (from_index == highlighted_tab_) |
| highlighted_tab_ = to_index; |
| else if (from_index < highlighted_tab_&& highlighted_tab_<= to_index) |
| --highlighted_tab_; |
| else if (from_index > highlighted_tab_ && highlighted_tab_ >= to_index) |
| ++highlighted_tab_; |
| } |
| |
| void TabScrubber::TabStripRemovedTabAt(TabStrip* tab_strip, int index) { |
| if (highlighted_tab_ == -1) |
| return; |
| if (index == highlighted_tab_) { |
| FinishScrub(false); |
| return; |
| } |
| if (index < highlighted_tab_) |
| --highlighted_tab_; |
| } |
| |
| void TabScrubber::TabStripDeleted(TabStrip* tab_strip) { |
| if (highlighted_tab_ == -1) |
| return; |
| } |
| |
| Browser* TabScrubber::GetActiveBrowser() { |
| aura::Window* active_window = ash::wm::GetActiveWindow(); |
| if (!active_window) |
| return NULL; |
| |
| Browser* browser = chrome::FindBrowserWithWindow(active_window); |
| if (!browser || browser->type() != Browser::TYPE_TABBED) |
| return NULL; |
| |
| return browser; |
| } |
| |
| void TabScrubber::FinishScrub(bool activate) { |
| activate_timer_.Stop(); |
| |
| if (browser_ && browser_->window()) { |
| BrowserView* browser_view = |
| BrowserView::GetBrowserViewForNativeWindow( |
| browser_->window()->GetNativeWindow()); |
| TabStrip* tab_strip = browser_view->tabstrip(); |
| if (activate && highlighted_tab_ != -1) { |
| Tab* tab = tab_strip->tab_at(highlighted_tab_); |
| tab->hover_controller()->HideImmediately(); |
| int distance = |
| std::abs( |
| highlighted_tab_ - browser_->tab_strip_model()->active_index()); |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.ScrubDistance", distance, 0, 20, 20); |
| browser_->tab_strip_model()->ActivateTabAt(highlighted_tab_, true); |
| } |
| tab_strip->RemoveObserver(this); |
| } |
| swipe_x_ = -1; |
| swipe_y_ = -1; |
| scrubbing_ = false; |
| highlighted_tab_ = -1; |
| } |