1 // Copyright (c) 2011 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/find_bar/find_bar_controller.h" 6 7 #include <algorithm> 8 9 #include "base/i18n/rtl.h" 10 #include "build/build_config.h" 11 #include "chrome/browser/ui/find_bar/find_bar.h" 12 #include "chrome/browser/ui/find_bar/find_bar_state.h" 13 #include "chrome/browser/ui/find_bar/find_tab_helper.h" 14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 15 #include "content/browser/tab_contents/navigation_entry.h" 16 #include "content/common/notification_details.h" 17 #include "content/common/notification_source.h" 18 #include "ui/gfx/rect.h" 19 20 // The minimum space between the FindInPage window and the search result. 21 static const int kMinFindWndDistanceFromSelection = 5; 22 23 FindBarController::FindBarController(FindBar* find_bar) 24 : find_bar_(find_bar), 25 tab_contents_(NULL), 26 last_reported_matchcount_(0) { 27 } 28 29 FindBarController::~FindBarController() { 30 DCHECK(!tab_contents_); 31 } 32 33 void FindBarController::Show() { 34 FindTabHelper* find_tab_helper = tab_contents_->find_tab_helper(); 35 36 // Only show the animation if we're not already showing a find bar for the 37 // selected TabContents. 38 if (!find_tab_helper->find_ui_active()) { 39 MaybeSetPrepopulateText(); 40 41 find_tab_helper->set_find_ui_active(true); 42 find_bar_->Show(true); 43 } 44 find_bar_->SetFocusAndSelection(); 45 } 46 47 void FindBarController::EndFindSession(SelectionAction action) { 48 find_bar_->Hide(true); 49 50 // |tab_contents_| can be NULL for a number of reasons, for example when the 51 // tab is closing. We must guard against that case. See issue 8030. 52 if (tab_contents_) { 53 FindTabHelper* find_tab_helper = tab_contents_->find_tab_helper(); 54 55 // When we hide the window, we need to notify the renderer that we are done 56 // for now, so that we can abort the scoping effort and clear all the 57 // tickmarks and highlighting. 58 find_tab_helper->StopFinding(action); 59 60 if (action != kKeepSelection) 61 find_bar_->ClearResults(find_tab_helper->find_result()); 62 63 // When we get dismissed we restore the focus to where it belongs. 64 find_bar_->RestoreSavedFocus(); 65 } 66 } 67 68 void FindBarController::ChangeTabContents(TabContentsWrapper* contents) { 69 if (tab_contents_) { 70 registrar_.RemoveAll(); 71 find_bar_->StopAnimation(); 72 } 73 74 tab_contents_ = contents; 75 76 // Hide any visible find window from the previous tab if NULL |tab_contents| 77 // is passed in or if the find UI is not active in the new tab. 78 if (find_bar_->IsFindBarVisible() && 79 (!tab_contents_ || !tab_contents_->find_tab_helper()->find_ui_active())) { 80 find_bar_->Hide(false); 81 } 82 83 if (!tab_contents_) 84 return; 85 86 registrar_.Add(this, NotificationType::FIND_RESULT_AVAILABLE, 87 Source<TabContents>(tab_contents_->tab_contents())); 88 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, 89 Source<NavigationController>(&tab_contents_->controller())); 90 91 MaybeSetPrepopulateText(); 92 93 if (tab_contents_->find_tab_helper()->find_ui_active()) { 94 // A tab with a visible find bar just got selected and we need to show the 95 // find bar but without animation since it was already animated into its 96 // visible state. We also want to reset the window location so that 97 // we don't surprise the user by popping up to the left for no apparent 98 // reason. 99 find_bar_->Show(false); 100 } 101 102 UpdateFindBarForCurrentResult(); 103 } 104 105 //////////////////////////////////////////////////////////////////////////////// 106 // FindBarHost, NotificationObserver implementation: 107 108 void FindBarController::Observe(NotificationType type, 109 const NotificationSource& source, 110 const NotificationDetails& details) { 111 FindTabHelper* find_tab_helper = tab_contents_->find_tab_helper(); 112 if (type == NotificationType::FIND_RESULT_AVAILABLE) { 113 // Don't update for notifications from TabContentses other than the one we 114 // are actively tracking. 115 if (Source<TabContents>(source).ptr() == tab_contents_->tab_contents()) { 116 UpdateFindBarForCurrentResult(); 117 if (find_tab_helper->find_result().final_update() && 118 find_tab_helper->find_result().number_of_matches() == 0) { 119 const string16& last_search = find_tab_helper->previous_find_text(); 120 const string16& current_search = find_tab_helper->find_text(); 121 if (last_search.find(current_search) != 0) 122 find_bar_->AudibleAlert(); 123 } 124 } 125 } else if (type == NotificationType::NAV_ENTRY_COMMITTED) { 126 NavigationController* source_controller = 127 Source<NavigationController>(source).ptr(); 128 if (source_controller == &tab_contents_->controller()) { 129 NavigationController::LoadCommittedDetails* commit_details = 130 Details<NavigationController::LoadCommittedDetails>(details).ptr(); 131 PageTransition::Type transition_type = 132 commit_details->entry->transition_type(); 133 // We hide the FindInPage window when the user navigates away, except on 134 // reload. 135 if (find_bar_->IsFindBarVisible()) { 136 if (PageTransition::StripQualifier(transition_type) != 137 PageTransition::RELOAD) { 138 EndFindSession(kKeepSelection); 139 } else { 140 // On Reload we want to make sure FindNext is converted to a full Find 141 // to make sure highlights for inactive matches are repainted. 142 find_tab_helper->set_find_op_aborted(true); 143 } 144 } 145 } 146 } 147 } 148 149 // static 150 gfx::Rect FindBarController::GetLocationForFindbarView( 151 gfx::Rect view_location, 152 const gfx::Rect& dialog_bounds, 153 const gfx::Rect& avoid_overlapping_rect) { 154 if (base::i18n::IsRTL()) { 155 int boundary = dialog_bounds.width() - view_location.width(); 156 view_location.set_x(std::min(view_location.x(), boundary)); 157 } else { 158 view_location.set_x(std::max(view_location.x(), dialog_bounds.x())); 159 } 160 161 gfx::Rect new_pos = view_location; 162 163 // If the selection rectangle intersects the current position on screen then 164 // we try to move our dialog to the left (right for RTL) of the selection 165 // rectangle. 166 if (!avoid_overlapping_rect.IsEmpty() && 167 avoid_overlapping_rect.Intersects(new_pos)) { 168 if (base::i18n::IsRTL()) { 169 new_pos.set_x(avoid_overlapping_rect.x() + 170 avoid_overlapping_rect.width() + 171 (2 * kMinFindWndDistanceFromSelection)); 172 173 // If we moved it off-screen to the right, we won't move it at all. 174 if (new_pos.x() + new_pos.width() > dialog_bounds.width()) 175 new_pos = view_location; // Reset. 176 } else { 177 new_pos.set_x(avoid_overlapping_rect.x() - new_pos.width() - 178 kMinFindWndDistanceFromSelection); 179 180 // If we moved it off-screen to the left, we won't move it at all. 181 if (new_pos.x() < 0) 182 new_pos = view_location; // Reset. 183 } 184 } 185 186 return new_pos; 187 } 188 189 void FindBarController::UpdateFindBarForCurrentResult() { 190 FindTabHelper* find_tab_helper = tab_contents_->find_tab_helper(); 191 const FindNotificationDetails& find_result = find_tab_helper->find_result(); 192 193 // Avoid bug 894389: When a new search starts (and finds something) it reports 194 // an interim match count result of 1 before the scoping effort starts. This 195 // is to provide feedback as early as possible that we will find something. 196 // As you add letters to the search term, this creates a flashing effect when 197 // we briefly show "1 of 1" matches because there is a slight delay until 198 // the scoping effort starts updating the match count. We avoid this flash by 199 // ignoring interim results of 1 if we already have a positive number. 200 if (find_result.number_of_matches() > -1) { 201 if (last_reported_matchcount_ > 0 && 202 find_result.number_of_matches() == 1 && 203 !find_result.final_update()) 204 return; // Don't let interim result override match count. 205 last_reported_matchcount_ = find_result.number_of_matches(); 206 } 207 208 find_bar_->UpdateUIForFindResult(find_result, find_tab_helper->find_text()); 209 } 210 211 void FindBarController::MaybeSetPrepopulateText() { 212 #if !defined(OS_MACOSX) 213 // Find out what we should show in the find text box. Usually, this will be 214 // the last search in this tab, but if no search has been issued in this tab 215 // we use the last search string (from any tab). 216 FindTabHelper* find_tab_helper = tab_contents_->find_tab_helper(); 217 string16 find_string = find_tab_helper->find_text(); 218 if (find_string.empty()) 219 find_string = find_tab_helper->previous_find_text(); 220 if (find_string.empty()) { 221 find_string = 222 FindBarState::GetLastPrepopulateText(tab_contents_->profile()); 223 } 224 225 // Update the find bar with existing results and search text, regardless of 226 // whether or not the find bar is visible, so that if it's subsequently 227 // shown it is showing the right state for this tab. We update the find text 228 // _first_ since the FindBarView checks its emptiness to see if it should 229 // clear the result count display when there's nothing in the box. 230 find_bar_->SetFindText(find_string); 231 #else 232 // Having a per-tab find_string is not compatible with OS X's find pasteboard, 233 // so we always have the same find text in all find bars. This is done through 234 // the find pasteboard mechanism, so don't set the text here. 235 #endif 236 } 237