browser_root_view.cc revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright (c) 2012 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/frame/browser_root_view.h"
6
7#include "chrome/browser/autocomplete/autocomplete_classifier.h"
8#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
9#include "chrome/browser/autocomplete/autocomplete_match.h"
10#include "chrome/browser/defaults.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/ui/browser_commands.h"
13#include "chrome/browser/ui/tabs/tab_strip_model.h"
14#include "chrome/browser/ui/views/frame/browser_frame.h"
15#include "chrome/browser/ui/views/frame/browser_view.h"
16#include "chrome/browser/ui/views/tabs/tab_strip.h"
17#include "chrome/browser/ui/views/touch_uma/touch_uma.h"
18#include "components/metrics/proto/omnibox_event.pb.h"
19#include "ui/base/dragdrop/drag_drop_types.h"
20#include "ui/base/dragdrop/os_exchange_data.h"
21#include "ui/base/hit_test.h"
22
23// static
24const char BrowserRootView::kViewClassName[] =
25    "browser/ui/views/frame/BrowserRootView";
26
27BrowserRootView::BrowserRootView(BrowserView* browser_view,
28                                 views::Widget* widget)
29    : views::internal::RootView(widget),
30      browser_view_(browser_view),
31      forwarding_to_tab_strip_(false) { }
32
33bool BrowserRootView::GetDropFormats(
34      int* formats,
35      std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
36  if (tabstrip() && tabstrip()->visible()) {
37    *formats = ui::OSExchangeData::URL | ui::OSExchangeData::STRING;
38    return true;
39  }
40  return false;
41}
42
43bool BrowserRootView::AreDropTypesRequired() {
44  return true;
45}
46
47bool BrowserRootView::CanDrop(const ui::OSExchangeData& data) {
48  if (!tabstrip() || !tabstrip()->visible())
49    return false;
50
51  // If there is a URL, we'll allow the drop.
52  if (data.HasURL(ui::OSExchangeData::CONVERT_FILENAMES))
53    return true;
54
55  // If there isn't a URL, see if we can 'paste and go'.
56  return GetPasteAndGoURL(data, NULL);
57}
58
59void BrowserRootView::OnDragEntered(const ui::DropTargetEvent& event) {
60  if (ShouldForwardToTabStrip(event)) {
61    forwarding_to_tab_strip_ = true;
62    scoped_ptr<ui::DropTargetEvent> mapped_event(
63        MapEventToTabStrip(event, event.data()));
64    tabstrip()->OnDragEntered(*mapped_event.get());
65  }
66}
67
68int BrowserRootView::OnDragUpdated(const ui::DropTargetEvent& event) {
69  if (ShouldForwardToTabStrip(event)) {
70    scoped_ptr<ui::DropTargetEvent> mapped_event(
71        MapEventToTabStrip(event, event.data()));
72    if (!forwarding_to_tab_strip_) {
73      tabstrip()->OnDragEntered(*mapped_event.get());
74      forwarding_to_tab_strip_ = true;
75    }
76    return tabstrip()->OnDragUpdated(*mapped_event.get());
77  } else if (forwarding_to_tab_strip_) {
78    forwarding_to_tab_strip_ = false;
79    tabstrip()->OnDragExited();
80  }
81  return ui::DragDropTypes::DRAG_NONE;
82}
83
84void BrowserRootView::OnDragExited() {
85  if (forwarding_to_tab_strip_) {
86    forwarding_to_tab_strip_ = false;
87    tabstrip()->OnDragExited();
88  }
89}
90
91int BrowserRootView::OnPerformDrop(const ui::DropTargetEvent& event) {
92  if (!forwarding_to_tab_strip_)
93    return ui::DragDropTypes::DRAG_NONE;
94
95  // Extract the URL and create a new ui::OSExchangeData containing the URL. We
96  // do this as the TabStrip doesn't know about the autocomplete edit and needs
97  // to know about it to handle 'paste and go'.
98  GURL url;
99  base::string16 title;
100  ui::OSExchangeData mapped_data;
101  if (!event.data().GetURLAndTitle(
102           ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
103      !url.is_valid()) {
104    // The url isn't valid. Use the paste and go url.
105    if (GetPasteAndGoURL(event.data(), &url))
106      mapped_data.SetURL(url, base::string16());
107    // else case: couldn't extract a url or 'paste and go' url. This ends up
108    // passing through an ui::OSExchangeData with nothing in it. We need to do
109    // this so that the tab strip cleans up properly.
110  } else {
111    mapped_data.SetURL(url, base::string16());
112  }
113  forwarding_to_tab_strip_ = false;
114  scoped_ptr<ui::DropTargetEvent> mapped_event(
115      MapEventToTabStrip(event, mapped_data));
116  return tabstrip()->OnPerformDrop(*mapped_event);
117}
118
119const char* BrowserRootView::GetClassName() const {
120  return kViewClassName;
121}
122
123bool BrowserRootView::OnMouseWheel(const ui::MouseWheelEvent& event) {
124  if (browser_defaults::kScrollEventChangesTab) {
125    // Switch to the left/right tab if the wheel-scroll happens over the
126    // tabstrip, or the empty space beside the tabstrip.
127    views::View* hit_view = GetEventHandlerForPoint(event.location());
128    int hittest =
129        GetWidget()->non_client_view()->NonClientHitTest(event.location());
130    if (tabstrip()->Contains(hit_view) ||
131        hittest == HTCAPTION ||
132        hittest == HTTOP) {
133      int scroll_offset = abs(event.y_offset()) > abs(event.x_offset()) ?
134          event.y_offset() : -event.x_offset();
135      Browser* browser = browser_view_->browser();
136      TabStripModel* model = browser->tab_strip_model();
137      // Switch to the next tab only if not at the end of the tab-strip.
138      if (scroll_offset < 0 && model->active_index() + 1 < model->count()) {
139        chrome::SelectNextTab(browser);
140        return true;
141      }
142
143      // Switch to the previous tab only if not at the beginning of the
144      // tab-strip.
145      if (scroll_offset > 0 && model->active_index() > 0) {
146        chrome::SelectPreviousTab(browser);
147        return true;
148      }
149    }
150  }
151  return RootView::OnMouseWheel(event);
152}
153
154ui::EventDispatchDetails BrowserRootView::OnEventFromSource(ui::Event* event) {
155  if (event->IsGestureEvent()) {
156    ui::GestureEvent* gesture_event = event->AsGestureEvent();
157    if (gesture_event->type() == ui::ET_GESTURE_TAP &&
158        gesture_event->location().y() <= 0 &&
159        gesture_event->location().x() <= browser_view_->GetBounds().width()) {
160      TouchUMA::RecordGestureAction(TouchUMA::GESTURE_ROOTVIEWTOP_TAP);
161    }
162  }
163
164  return RootView::OnEventFromSource(event);
165}
166
167bool BrowserRootView::ShouldForwardToTabStrip(
168    const ui::DropTargetEvent& event) {
169  if (!tabstrip()->visible())
170    return false;
171
172  // Allow the drop as long as the mouse is over the tabstrip or vertically
173  // before it.
174  gfx::Point tab_loc_in_host;
175  ConvertPointToTarget(tabstrip(), this, &tab_loc_in_host);
176  return event.y() < tab_loc_in_host.y() + tabstrip()->height();
177}
178
179ui::DropTargetEvent* BrowserRootView::MapEventToTabStrip(
180    const ui::DropTargetEvent& event,
181    const ui::OSExchangeData& data) {
182  gfx::Point tab_strip_loc(event.location());
183  ConvertPointToTarget(this, tabstrip(), &tab_strip_loc);
184  return new ui::DropTargetEvent(data, tab_strip_loc, tab_strip_loc,
185                                 event.source_operations());
186}
187
188TabStrip* BrowserRootView::tabstrip() const {
189  return browser_view_->tabstrip();
190}
191
192bool BrowserRootView::GetPasteAndGoURL(const ui::OSExchangeData& data,
193                                       GURL* url) {
194  if (!data.HasString())
195    return false;
196
197  base::string16 text;
198  if (!data.GetString(&text) || text.empty())
199    return false;
200  text = AutocompleteMatch::SanitizeString(text);
201
202  AutocompleteMatch match;
203  AutocompleteClassifierFactory::GetForProfile(
204      browser_view_->browser()->profile())->Classify(
205          text, false, false, metrics::OmniboxEventProto::INVALID_SPEC, &match,
206          NULL);
207  if (!match.destination_url.is_valid())
208    return false;
209
210  if (url)
211    *url = match.destination_url;
212  return true;
213}
214