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