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
FindBarController(FindBar * find_bar)23 FindBarController::FindBarController(FindBar* find_bar)
24 : find_bar_(find_bar),
25 tab_contents_(NULL),
26 last_reported_matchcount_(0) {
27 }
28
~FindBarController()29 FindBarController::~FindBarController() {
30 DCHECK(!tab_contents_);
31 }
32
Show()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
EndFindSession(SelectionAction action)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
ChangeTabContents(TabContentsWrapper * contents)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
GetLocationForFindbarView(gfx::Rect view_location,const gfx::Rect & dialog_bounds,const gfx::Rect & avoid_overlapping_rect)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
UpdateFindBarForCurrentResult()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
MaybeSetPrepopulateText()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