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