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