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 23 using content::NativeWebKeyboardEvent; 24 25 namespace chrome { 26 27 // Declared in browser_dialogs.h so others don't have to depend on our header. 28 FindBar* CreateFindBar(BrowserView* browser_view) { 29 return new FindBarHost(browser_view); 30 } 31 32 } // namespace chrome 33 34 //////////////////////////////////////////////////////////////////////////////// 35 // FindBarHost, public: 36 37 FindBarHost::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 44 FindBarHost::~FindBarHost() { 45 } 46 47 bool 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 83 FindBarController* FindBarHost::GetFindBarController() const { 84 return find_bar_controller_; 85 } 86 87 void FindBarHost::SetFindBarController(FindBarController* find_bar_controller) { 88 find_bar_controller_ = find_bar_controller; 89 } 90 91 void FindBarHost::Show(bool animate) { 92 DropdownBarHost::Show(animate); 93 } 94 95 void FindBarHost::Hide(bool animate) { 96 DropdownBarHost::Hide(animate); 97 } 98 99 void FindBarHost::SetFocusAndSelection() { 100 DropdownBarHost::SetFocusAndSelection(); 101 } 102 103 void FindBarHost::ClearResults(const FindNotificationDetails& results) { 104 find_bar_view()->UpdateForResult(results, base::string16()); 105 } 106 107 void FindBarHost::StopAnimation() { 108 DropdownBarHost::StopAnimation(); 109 } 110 111 void 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 132 void 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 138 base::string16 FindBarHost::GetFindText() { 139 return find_bar_view()->GetFindText(); 140 } 141 142 gfx::Range FindBarHost::GetSelectedRange() { 143 return find_bar_view()->GetSelectedRange(); 144 } 145 146 void 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 164 bool FindBarHost::IsFindBarVisible() { 165 return DropdownBarHost::IsVisible(); 166 } 167 168 void 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 177 bool FindBarHost::HasGlobalFindPasteboard() { 178 return false; 179 } 180 181 void FindBarHost::UpdateFindBarForChangedWebContents() { 182 } 183 184 FindBarTesting* FindBarHost::GetFindBarTesting() { 185 return this; 186 } 187 188 //////////////////////////////////////////////////////////////////////////////// 189 // FindBarWin, ui::AcceleratorTarget implementation: 190 191 bool 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 214 bool FindBarHost::CanHandleAccelerators() const { 215 return true; 216 } 217 218 //////////////////////////////////////////////////////////////////////////////// 219 // FindBarTesting implementation: 220 221 bool 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 247 base::string16 FindBarHost::GetFindSelectedText() { 248 return find_bar_view()->GetFindSelectedText(); 249 } 250 251 base::string16 FindBarHost::GetMatchCountText() { 252 return find_bar_view()->GetMatchCountText(); 253 } 254 255 int FindBarHost::GetWidth() { 256 return view()->width(); 257 } 258 259 //////////////////////////////////////////////////////////////////////////////// 260 // Overridden from DropdownBarHost: 261 262 gfx::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 310 void FindBarHost::SetDialogPosition(const gfx::Rect& new_pos, bool no_redraw) { 311 if (new_pos.IsEmpty()) 312 return; 313 314 // Make sure the window edges are clipped to just the visible region. We need 315 // to do this before changing position, so that when we animate the closure 316 // of it it doesn't look like the window crumbles into the toolbar. 317 UpdateWindowEdges(new_pos); 318 319 SetWidgetPositionNative(new_pos, no_redraw); 320 321 // Tell the immersive mode controller about the find bar's new bounds. The 322 // immersive mode controller uses the bounds to keep the top-of-window views 323 // revealed when the mouse is hovered over the find bar. 324 browser_view()->immersive_mode_controller()->OnFindBarVisibleBoundsChanged( 325 host()->GetWindowBoundsInScreen()); 326 } 327 328 void FindBarHost::GetWidgetBounds(gfx::Rect* bounds) { 329 DCHECK(bounds); 330 // The BrowserView does Layout for the components that we care about 331 // positioning relative to, so we ask it to tell us where we should go. 332 *bounds = browser_view()->GetFindBarBoundingBox(); 333 } 334 335 void FindBarHost::RegisterAccelerators() { 336 DropdownBarHost::RegisterAccelerators(); 337 338 // Register for Ctrl+Return. 339 ui::Accelerator escape(ui::VKEY_RETURN, ui::EF_CONTROL_DOWN); 340 focus_manager()->RegisterAccelerator( 341 escape, ui::AcceleratorManager::kNormalPriority, this); 342 } 343 344 void FindBarHost::UnregisterAccelerators() { 345 // Unregister Ctrl+Return. 346 ui::Accelerator escape(ui::VKEY_RETURN, ui::EF_CONTROL_DOWN); 347 focus_manager()->UnregisterAccelerator(escape, this); 348 349 DropdownBarHost::UnregisterAccelerators(); 350 } 351 352 void FindBarHost::OnVisibilityChanged() { 353 // Tell the immersive mode controller about the find bar's bounds. The 354 // immersive mode controller uses the bounds to keep the top-of-window views 355 // revealed when the mouse is hovered over the find bar. 356 gfx::Rect visible_bounds; 357 if (IsVisible()) 358 visible_bounds = host()->GetWindowBoundsInScreen(); 359 browser_view()->immersive_mode_controller()->OnFindBarVisibleBoundsChanged( 360 visible_bounds); 361 } 362 363 //////////////////////////////////////////////////////////////////////////////// 364 // private: 365 366 void FindBarHost::GetWidgetPositionNative(gfx::Rect* avoid_overlapping_rect) { 367 gfx::Rect frame_rect = host()->GetTopLevelWidget()->GetWindowBoundsInScreen(); 368 gfx::Rect webcontents_rect = 369 find_bar_controller_->web_contents()->GetViewBounds(); 370 avoid_overlapping_rect->Offset(0, webcontents_rect.y() - frame_rect.y()); 371 } 372