Home | History | Annotate | Download | only in find_bar
      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