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