Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.webkit;
     18 
     19 import android.annotation.SystemApi;
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.graphics.Point;
     23 import android.graphics.Rect;
     24 import android.text.Editable;
     25 import android.text.Selection;
     26 import android.text.Spannable;
     27 import android.text.TextWatcher;
     28 import android.view.ActionMode;
     29 import android.view.LayoutInflater;
     30 import android.view.Menu;
     31 import android.view.MenuItem;
     32 import android.view.View;
     33 import android.view.inputmethod.InputMethodManager;
     34 import android.widget.EditText;
     35 import android.widget.TextView;
     36 
     37 /**
     38  * @hide
     39  */
     40 @SystemApi
     41 public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
     42         View.OnClickListener, WebView.FindListener {
     43     private View mCustomView;
     44     private EditText mEditText;
     45     private TextView mMatches;
     46     private WebView mWebView;
     47     private InputMethodManager mInput;
     48     private Resources mResources;
     49     private boolean mMatchesFound;
     50     private int mNumberOfMatches;
     51     private int mActiveMatchIndex;
     52     private ActionMode mActionMode;
     53 
     54     public FindActionModeCallback(Context context) {
     55         mCustomView = LayoutInflater.from(context).inflate(
     56                 com.android.internal.R.layout.webview_find, null);
     57         mEditText = (EditText) mCustomView.findViewById(
     58                 com.android.internal.R.id.edit);
     59         mEditText.setCustomSelectionActionModeCallback(new NoAction());
     60         mEditText.setOnClickListener(this);
     61         setText("");
     62         mMatches = (TextView) mCustomView.findViewById(
     63                 com.android.internal.R.id.matches);
     64         mInput = (InputMethodManager)
     65                 context.getSystemService(Context.INPUT_METHOD_SERVICE);
     66         mResources = context.getResources();
     67     }
     68 
     69     public void finish() {
     70         mActionMode.finish();
     71     }
     72 
     73     /*
     74      * Place text in the text field so it can be searched for.  Need to press
     75      * the find next or find previous button to find all of the matches.
     76      */
     77     public void setText(String text) {
     78         mEditText.setText(text);
     79         Spannable span = (Spannable) mEditText.getText();
     80         int length = span.length();
     81         // Ideally, we would like to set the selection to the whole field,
     82         // but this brings up the Text selection CAB, which dismisses this
     83         // one.
     84         Selection.setSelection(span, length, length);
     85         // Necessary each time we set the text, so that this will watch
     86         // changes to it.
     87         span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
     88         mMatchesFound = false;
     89     }
     90 
     91     /*
     92      * Set the WebView to search.  Must be non null.
     93      */
     94     public void setWebView(WebView webView) {
     95         if (null == webView) {
     96             throw new AssertionError("WebView supplied to "
     97                     + "FindActionModeCallback cannot be null");
     98         }
     99         mWebView = webView;
    100         mWebView.setFindDialogFindListener(this);
    101     }
    102 
    103     @Override
    104     public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
    105             boolean isDoneCounting) {
    106         if (isDoneCounting) {
    107             updateMatchCount(activeMatchOrdinal, numberOfMatches, numberOfMatches == 0);
    108         }
    109     }
    110 
    111     /*
    112      * Move the highlight to the next match.
    113      * @param next If true, find the next match further down in the document.
    114      *             If false, find the previous match, up in the document.
    115      */
    116     private void findNext(boolean next) {
    117         if (mWebView == null) {
    118             throw new AssertionError(
    119                     "No WebView for FindActionModeCallback::findNext");
    120         }
    121         if (!mMatchesFound) {
    122             findAll();
    123             return;
    124         }
    125         if (0 == mNumberOfMatches) {
    126             // There are no matches, so moving to the next match will not do
    127             // anything.
    128             return;
    129         }
    130         mWebView.findNext(next);
    131         updateMatchesString();
    132     }
    133 
    134     /*
    135      * Highlight all the instances of the string from mEditText in mWebView.
    136      */
    137     public void findAll() {
    138         if (mWebView == null) {
    139             throw new AssertionError(
    140                     "No WebView for FindActionModeCallback::findAll");
    141         }
    142         CharSequence find = mEditText.getText();
    143         if (0 == find.length()) {
    144             mWebView.clearMatches();
    145             mMatches.setVisibility(View.GONE);
    146             mMatchesFound = false;
    147             mWebView.findAll(null);
    148         } else {
    149             mMatchesFound = true;
    150             mMatches.setVisibility(View.INVISIBLE);
    151             mNumberOfMatches = 0;
    152             mWebView.findAllAsync(find.toString());
    153         }
    154     }
    155 
    156     public void showSoftInput() {
    157         if (mEditText.requestFocus()) {
    158             mInput.showSoftInput(mEditText, 0);
    159         }
    160     }
    161 
    162     public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) {
    163         if (!isEmptyFind) {
    164             mNumberOfMatches = matchCount;
    165             mActiveMatchIndex = matchIndex;
    166             updateMatchesString();
    167         } else {
    168             mMatches.setVisibility(View.GONE);
    169             mNumberOfMatches = 0;
    170         }
    171     }
    172 
    173     /*
    174      * Update the string which tells the user how many matches were found, and
    175      * which match is currently highlighted.
    176      */
    177     private void updateMatchesString() {
    178         if (mNumberOfMatches == 0) {
    179             mMatches.setText(com.android.internal.R.string.no_matches);
    180         } else {
    181             mMatches.setText(mResources.getQuantityString(
    182                 com.android.internal.R.plurals.matches_found, mNumberOfMatches,
    183                 mActiveMatchIndex + 1, mNumberOfMatches));
    184         }
    185         mMatches.setVisibility(View.VISIBLE);
    186     }
    187 
    188     // OnClickListener implementation
    189 
    190     @Override
    191     public void onClick(View v) {
    192         findNext(true);
    193     }
    194 
    195     // ActionMode.Callback implementation
    196 
    197     @Override
    198     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    199         if (!mode.isUiFocusable()) {
    200             // If the action mode we're running in is not focusable the user
    201             // will not be able to type into the find on page field. This
    202             // should only come up when we're running in a dialog which is
    203             // already less than ideal; disable the option for now.
    204             return false;
    205         }
    206 
    207         mode.setCustomView(mCustomView);
    208         mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
    209                 menu);
    210         mActionMode = mode;
    211         Editable edit = mEditText.getText();
    212         Selection.setSelection(edit, edit.length());
    213         mMatches.setVisibility(View.GONE);
    214         mMatchesFound = false;
    215         mMatches.setText("0");
    216         mEditText.requestFocus();
    217         return true;
    218     }
    219 
    220     @Override
    221     public void onDestroyActionMode(ActionMode mode) {
    222         mActionMode = null;
    223         mWebView.notifyFindDialogDismissed();
    224         mWebView.setFindDialogFindListener(null);
    225         mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
    226     }
    227 
    228     @Override
    229     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    230         return false;
    231     }
    232 
    233     @Override
    234     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    235         if (mWebView == null) {
    236             throw new AssertionError(
    237                     "No WebView for FindActionModeCallback::onActionItemClicked");
    238         }
    239         mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
    240         switch(item.getItemId()) {
    241             case com.android.internal.R.id.find_prev:
    242                 findNext(false);
    243                 break;
    244             case com.android.internal.R.id.find_next:
    245                 findNext(true);
    246                 break;
    247             default:
    248                 return false;
    249         }
    250         return true;
    251     }
    252 
    253     // TextWatcher implementation
    254 
    255     @Override
    256     public void beforeTextChanged(CharSequence s,
    257                                   int start,
    258                                   int count,
    259                                   int after) {
    260         // Does nothing.  Needed to implement TextWatcher.
    261     }
    262 
    263     @Override
    264     public void onTextChanged(CharSequence s,
    265                               int start,
    266                               int before,
    267                               int count) {
    268         findAll();
    269     }
    270 
    271     @Override
    272     public void afterTextChanged(Editable s) {
    273         // Does nothing.  Needed to implement TextWatcher.
    274     }
    275 
    276     private Rect mGlobalVisibleRect = new Rect();
    277     private Point mGlobalVisibleOffset = new Point();
    278     public int getActionModeGlobalBottom() {
    279         if (mActionMode == null) {
    280             return 0;
    281         }
    282         View view = (View) mCustomView.getParent();
    283         if (view == null) {
    284             view = mCustomView;
    285         }
    286         view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
    287         return mGlobalVisibleRect.bottom;
    288     }
    289 
    290     public static class NoAction implements ActionMode.Callback {
    291         @Override
    292         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    293             return false;
    294         }
    295 
    296         @Override
    297         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    298             return false;
    299         }
    300 
    301         @Override
    302         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    303             return false;
    304         }
    305 
    306         @Override
    307         public void onDestroyActionMode(ActionMode mode) {
    308         }
    309     }
    310 }
    311