Home | History | Annotate | Download | only in event
      1 /*
      2  * Copyright (C) 2014 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 com.android.inputmethod.event;
     18 
     19 import android.text.SpannableStringBuilder;
     20 import android.text.TextUtils;
     21 
     22 import com.android.inputmethod.latin.common.Constants;
     23 
     24 import java.util.ArrayList;
     25 
     26 import javax.annotation.Nonnull;
     27 
     28 /**
     29  * This class implements the logic chain between receiving events and generating code points.
     30  *
     31  * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard,
     32  * or any exotic input source.
     33  * This class will orchestrate the composing chain that starts with an event as its input. Each
     34  * composer will be given turns one after the other.
     35  * The output is composed of two sequences of code points: the first, representing the already
     36  * finished combining part, will be shown normally as the composing string, while the second is
     37  * feedback on the composing state and will typically be shown with different styling such as
     38  * a colored background.
     39  */
     40 public class CombinerChain {
     41     // The already combined text, as described above
     42     private StringBuilder mCombinedText;
     43     // The feedback on the composing state, as described above
     44     private SpannableStringBuilder mStateFeedback;
     45     private final ArrayList<Combiner> mCombiners;
     46 
     47     /**
     48      * Create an combiner chain.
     49      *
     50      * The combiner chain takes events as inputs and outputs code points and combining state.
     51      * For example, if the input language is Japanese, the combining chain will typically perform
     52      * kana conversion. This takes a string for initial text, taken to be present before the
     53      * cursor: we'll start after this.
     54      *
     55      * @param initialText The text that has already been combined so far.
     56      */
     57     public CombinerChain(final String initialText) {
     58         mCombiners = new ArrayList<>();
     59         // The dead key combiner is always active, and always first
     60         mCombiners.add(new DeadKeyCombiner());
     61         mCombinedText = new StringBuilder(initialText);
     62         mStateFeedback = new SpannableStringBuilder();
     63     }
     64 
     65     public void reset() {
     66         mCombinedText.setLength(0);
     67         mStateFeedback.clear();
     68         for (final Combiner c : mCombiners) {
     69             c.reset();
     70         }
     71     }
     72 
     73     private void updateStateFeedback() {
     74         mStateFeedback.clear();
     75         for (int i = mCombiners.size() - 1; i >= 0; --i) {
     76             mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback());
     77         }
     78     }
     79 
     80     /**
     81      * Process an event through the combining chain, and return a processed event to apply.
     82      * @param previousEvents the list of previous events in this composition
     83      * @param newEvent the new event to process
     84      * @return the processed event. It may be the same event, or a consumed event, or a completely
     85      *   new event. However it may never be null.
     86      */
     87     @Nonnull
     88     public Event processEvent(final ArrayList<Event> previousEvents,
     89             @Nonnull final Event newEvent) {
     90         final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents);
     91         Event event = newEvent;
     92         for (final Combiner combiner : mCombiners) {
     93             // A combiner can never return more than one event; it can return several
     94             // code points, but they should be encapsulated within one event.
     95             event = combiner.processEvent(modifiablePreviousEvents, event);
     96             if (event.isConsumed()) {
     97                 // If the event is consumed, then we don't pass it to subsequent combiners:
     98                 // they should not see it at all.
     99                 break;
    100             }
    101         }
    102         updateStateFeedback();
    103         return event;
    104     }
    105 
    106     /**
    107      * Apply a processed event.
    108      * @param event the event to be applied
    109      */
    110     public void applyProcessedEvent(final Event event) {
    111         if (null != event) {
    112             // TODO: figure out the generic way of doing this
    113             if (Constants.CODE_DELETE == event.mKeyCode) {
    114                 final int length = mCombinedText.length();
    115                 if (length > 0) {
    116                     final int lastCodePoint = mCombinedText.codePointBefore(length);
    117                     mCombinedText.delete(length - Character.charCount(lastCodePoint), length);
    118                 }
    119             } else {
    120                 final CharSequence textToCommit = event.getTextToCommit();
    121                 if (!TextUtils.isEmpty(textToCommit)) {
    122                     mCombinedText.append(textToCommit);
    123                 }
    124             }
    125         }
    126         updateStateFeedback();
    127     }
    128 
    129     /**
    130      * Get the char sequence that should be displayed as the composing word. It may include
    131      * styling spans.
    132      */
    133     public CharSequence getComposingWordWithCombiningFeedback() {
    134         final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
    135         return s.append(mStateFeedback);
    136     }
    137 }
    138