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