find_bar_host.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/find_bar_host.h"
6
7#include <algorithm>
8
9#include "chrome/browser/ui/find_bar/find_bar_controller.h"
10#include "chrome/browser/ui/find_bar/find_tab_helper.h"
11#include "chrome/browser/ui/view_ids.h"
12#include "chrome/browser/ui/views/find_bar_view.h"
13#include "chrome/browser/ui/views/frame/browser_view.h"
14#include "content/public/browser/render_view_host.h"
15#include "content/public/browser/web_contents.h"
16#include "ui/events/event.h"
17#include "ui/events/keycodes/keyboard_codes.h"
18#include "ui/views/focus/external_focus_tracker.h"
19#include "ui/views/focus/view_storage.h"
20#include "ui/views/widget/root_view.h"
21#include "ui/views/widget/widget.h"
22
23using content::NativeWebKeyboardEvent;
24
25namespace chrome {
26
27// Declared in browser_dialogs.h so others don't have to depend on our header.
28FindBar* CreateFindBar(BrowserView* browser_view) {
29  return new FindBarHost(browser_view);
30}
31
32}  // namespace chrome
33
34////////////////////////////////////////////////////////////////////////////////
35// FindBarHost, public:
36
37FindBarHost::FindBarHost(BrowserView* browser_view)
38    : DropdownBarHost(browser_view),
39      find_bar_controller_(NULL) {
40  FindBarView* find_bar_view = new FindBarView(this);
41  Init(browser_view->find_bar_host_view(), find_bar_view, find_bar_view);
42}
43
44FindBarHost::~FindBarHost() {
45}
46
47bool FindBarHost::MaybeForwardKeyEventToWebpage(
48    const ui::KeyEvent& key_event) {
49  if (!ShouldForwardKeyEventToWebpageNative(key_event)) {
50    // Native implementation says not to forward these events.
51    return false;
52  }
53
54  switch (key_event.key_code()) {
55    case ui::VKEY_DOWN:
56    case ui::VKEY_UP:
57    case ui::VKEY_PRIOR:
58    case ui::VKEY_NEXT:
59      break;
60    case ui::VKEY_HOME:
61    case ui::VKEY_END:
62      if (key_event.IsControlDown())
63        break;
64    // Fall through.
65    default:
66      return false;
67  }
68
69  content::WebContents* contents = find_bar_controller_->web_contents();
70  if (!contents)
71    return false;
72
73  content::RenderViewHost* render_view_host = contents->GetRenderViewHost();
74
75  // Make sure we don't have a text field element interfering with keyboard
76  // input. Otherwise Up and Down arrow key strokes get eaten. "Nom Nom Nom".
77  render_view_host->ClearFocusedElement();
78  NativeWebKeyboardEvent event = GetKeyboardEvent(contents, key_event);
79  render_view_host->ForwardKeyboardEvent(event);
80  return true;
81}
82
83FindBarController* FindBarHost::GetFindBarController() const {
84  return find_bar_controller_;
85}
86
87void FindBarHost::SetFindBarController(FindBarController* find_bar_controller) {
88  find_bar_controller_ = find_bar_controller;
89}
90
91void FindBarHost::Show(bool animate) {
92  DropdownBarHost::Show(animate);
93}
94
95void FindBarHost::Hide(bool animate) {
96  DropdownBarHost::Hide(animate);
97}
98
99void FindBarHost::SetFocusAndSelection() {
100  DropdownBarHost::SetFocusAndSelection();
101}
102
103void FindBarHost::ClearResults(const FindNotificationDetails& results) {
104  find_bar_view()->UpdateForResult(results, base::string16());
105}
106
107void FindBarHost::StopAnimation() {
108  DropdownBarHost::StopAnimation();
109}
110
111void FindBarHost::MoveWindowIfNecessary(const gfx::Rect& selection_rect,
112                                        bool no_redraw) {
113  // We only move the window if one is active for the current WebContents. If we
114  // don't check this, then SetWidgetPosition below will end up making the Find
115  // Bar visible.
116  content::WebContents* web_contents = find_bar_controller_->web_contents();
117  if (!web_contents)
118    return;
119
120  FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);
121  if (!find_tab_helper || !find_tab_helper->find_ui_active())
122    return;
123
124  gfx::Rect new_pos = GetDialogPosition(selection_rect);
125  SetDialogPosition(new_pos, no_redraw);
126
127  // May need to redraw our frame to accommodate bookmark bar styles.
128  view()->Layout();  // Bounds may have changed.
129  view()->SchedulePaint();
130}
131
132void FindBarHost::SetFindTextAndSelectedRange(
133    const base::string16& find_text,
134    const gfx::Range& selected_range) {
135  find_bar_view()->SetFindTextAndSelectedRange(find_text, selected_range);
136}
137
138base::string16 FindBarHost::GetFindText() {
139  return find_bar_view()->GetFindText();
140}
141
142gfx::Range FindBarHost::GetSelectedRange() {
143  return find_bar_view()->GetSelectedRange();
144}
145
146void FindBarHost::UpdateUIForFindResult(const FindNotificationDetails& result,
147                                        const base::string16& find_text) {
148  // Make sure match count is clear. It may get set again in UpdateForResult
149  // if enough data is available.
150  find_bar_view()->ClearMatchCount();
151
152  if (!find_text.empty())
153    find_bar_view()->UpdateForResult(result, find_text);
154
155  // We now need to check if the window is obscuring the search results.
156  MoveWindowIfNecessary(result.selection_rect(), false);
157
158  // Once we find a match we no longer want to keep track of what had
159  // focus. EndFindSession will then set the focus to the page content.
160  if (result.number_of_matches() > 0)
161    ResetFocusTracker();
162}
163
164bool FindBarHost::IsFindBarVisible() {
165  return DropdownBarHost::IsVisible();
166}
167
168void FindBarHost::RestoreSavedFocus() {
169  if (focus_tracker() == NULL) {
170    // TODO(brettw): Focus() should be on WebContentsView.
171    find_bar_controller_->web_contents()->Focus();
172  } else {
173    focus_tracker()->FocusLastFocusedExternalView();
174  }
175}
176
177bool FindBarHost::HasGlobalFindPasteboard() {
178  return false;
179}
180
181void FindBarHost::UpdateFindBarForChangedWebContents() {
182}
183
184FindBarTesting* FindBarHost::GetFindBarTesting() {
185  return this;
186}
187
188////////////////////////////////////////////////////////////////////////////////
189// FindBarWin, ui::AcceleratorTarget implementation:
190
191bool FindBarHost::AcceleratorPressed(const ui::Accelerator& accelerator) {
192  ui::KeyboardCode key = accelerator.key_code();
193  if (key == ui::VKEY_RETURN && accelerator.IsCtrlDown()) {
194    // Ctrl+Enter closes the Find session and navigates any link that is active.
195    find_bar_controller_->EndFindSession(
196        FindBarController::kActivateSelectionOnPage,
197        FindBarController::kClearResultsInFindBox);
198    return true;
199  } else if (key == ui::VKEY_ESCAPE) {
200    // This will end the Find session and hide the window, causing it to loose
201    // focus and in the process unregister us as the handler for the Escape
202    // accelerator through the OnWillChangeFocus event.
203    find_bar_controller_->EndFindSession(
204        FindBarController::kKeepSelectionOnPage,
205        FindBarController::kKeepResultsInFindBox);
206    return true;
207  } else {
208    NOTREACHED() << "Unknown accelerator";
209  }
210
211  return false;
212}
213
214bool FindBarHost::CanHandleAccelerators() const {
215  return true;
216}
217
218////////////////////////////////////////////////////////////////////////////////
219// FindBarTesting implementation:
220
221bool FindBarHost::GetFindBarWindowInfo(gfx::Point* position,
222                                      bool* fully_visible) {
223  if (!find_bar_controller_ ||
224#if defined(OS_WIN) && !defined(USE_AURA)
225      !::IsWindow(host()->GetNativeView())) {
226#else
227      false) {
228      // TODO(sky): figure out linux side.
229      // This is tricky due to asynchronous nature of x11.
230      // See bug http://crbug.com/28629.
231#endif
232    if (position)
233      *position = gfx::Point();
234    if (fully_visible)
235      *fully_visible = false;
236    return false;
237  }
238
239  gfx::Rect window_rect = host()->GetWindowBoundsInScreen();
240  if (position)
241    *position = window_rect.origin();
242  if (fully_visible)
243    *fully_visible = IsVisible() && !IsAnimating();
244  return true;
245}
246
247base::string16 FindBarHost::GetFindSelectedText() {
248  return find_bar_view()->GetFindSelectedText();
249}
250
251base::string16 FindBarHost::GetMatchCountText() {
252  return find_bar_view()->GetMatchCountText();
253}
254
255int FindBarHost::GetWidth() {
256  return view()->width();
257}
258
259////////////////////////////////////////////////////////////////////////////////
260// Overridden from DropdownBarHost:
261
262gfx::Rect FindBarHost::GetDialogPosition(gfx::Rect avoid_overlapping_rect) {
263  // Find the area we have to work with (after accounting for scrollbars, etc).
264  gfx::Rect widget_bounds;
265  GetWidgetBounds(&widget_bounds);
266  if (widget_bounds.IsEmpty())
267    return gfx::Rect();
268
269  // Ask the view how large an area it needs to draw on.
270  gfx::Size prefsize = view()->GetPreferredSize();
271
272  // Limit width to the available area.
273  if (widget_bounds.width() < prefsize.width())
274    prefsize.set_width(widget_bounds.width());
275
276  // Don't show the find bar if |widget_bounds| is not tall enough.
277  if (widget_bounds.height() < prefsize.height())
278    return gfx::Rect();
279
280  // Place the view in the top right corner of the widget boundaries (top left
281  // for RTL languages).
282  gfx::Rect view_location;
283  int x = widget_bounds.x();
284  if (!base::i18n::IsRTL())
285    x += widget_bounds.width() - prefsize.width();
286  int y = widget_bounds.y();
287  view_location.SetRect(x, y, prefsize.width(), prefsize.height());
288
289  // When we get Find results back, we specify a selection rect, which we
290  // should strive to avoid overlapping. But first, we need to offset the
291  // selection rect (if one was provided).
292  if (!avoid_overlapping_rect.IsEmpty()) {
293    // For comparison (with the Intersects function below) we need to account
294    // for the fact that we draw the Find widget relative to the Chrome frame,
295    // whereas the selection rect is relative to the page.
296    GetWidgetPositionNative(&avoid_overlapping_rect);
297  }
298
299  gfx::Rect new_pos = FindBarController::GetLocationForFindbarView(
300      view_location, widget_bounds, avoid_overlapping_rect);
301
302  // While we are animating, the Find window will grow bottoms up so we need to
303  // re-position the widget so that it appears to grow out of the toolbar.
304  if (animation_offset() > 0)
305    new_pos.Offset(0, std::min(0, -animation_offset()));
306
307  return new_pos;
308}
309
310void FindBarHost::SetDialogPosition(const gfx::Rect& new_pos, bool no_redraw) {
311  if (new_pos.IsEmpty())
312    return;
313
314  SetWidgetPositionNative(new_pos, no_redraw);
315
316  // Tell the immersive mode controller about the find bar's new bounds. The
317  // immersive mode controller uses the bounds to keep the top-of-window views
318  // revealed when the mouse is hovered over the find bar.
319  browser_view()->immersive_mode_controller()->OnFindBarVisibleBoundsChanged(
320      host()->GetWindowBoundsInScreen());
321}
322
323void FindBarHost::GetWidgetBounds(gfx::Rect* bounds) {
324  DCHECK(bounds);
325  // The BrowserView does Layout for the components that we care about
326  // positioning relative to, so we ask it to tell us where we should go.
327  *bounds = browser_view()->GetFindBarBoundingBox();
328}
329
330void FindBarHost::RegisterAccelerators() {
331  DropdownBarHost::RegisterAccelerators();
332
333  // Register for Ctrl+Return.
334  ui::Accelerator escape(ui::VKEY_RETURN, ui::EF_CONTROL_DOWN);
335  focus_manager()->RegisterAccelerator(
336      escape, ui::AcceleratorManager::kNormalPriority, this);
337}
338
339void FindBarHost::UnregisterAccelerators() {
340  // Unregister Ctrl+Return.
341  ui::Accelerator escape(ui::VKEY_RETURN, ui::EF_CONTROL_DOWN);
342  focus_manager()->UnregisterAccelerator(escape, this);
343
344  DropdownBarHost::UnregisterAccelerators();
345}
346
347void FindBarHost::OnVisibilityChanged() {
348  // Tell the immersive mode controller about the find bar's bounds. The
349  // immersive mode controller uses the bounds to keep the top-of-window views
350  // revealed when the mouse is hovered over the find bar.
351  gfx::Rect visible_bounds;
352  if (IsVisible())
353    visible_bounds = host()->GetWindowBoundsInScreen();
354  browser_view()->immersive_mode_controller()->OnFindBarVisibleBoundsChanged(
355      visible_bounds);
356}
357
358////////////////////////////////////////////////////////////////////////////////
359// private:
360
361void FindBarHost::GetWidgetPositionNative(gfx::Rect* avoid_overlapping_rect) {
362  gfx::Rect frame_rect = host()->GetTopLevelWidget()->GetWindowBoundsInScreen();
363  gfx::Rect webcontents_rect =
364      find_bar_controller_->web_contents()->GetViewBounds();
365  avoid_overlapping_rect->Offset(0, webcontents_rect.y() - frame_rect.y());
366}
367