| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.inputmethod.event; |
| |
| import android.text.SpannableStringBuilder; |
| import android.text.TextUtils; |
| |
| import com.android.inputmethod.latin.Constants; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| |
| /** |
| * This class implements the logic chain between receiving events and generating code points. |
| * |
| * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard, |
| * or any exotic input source. |
| * This class will orchestrate the composing chain that starts with an event as its input. Each |
| * composer will be given turns one after the other. |
| * The output is composed of two sequences of code points: the first, representing the already |
| * finished combining part, will be shown normally as the composing string, while the second is |
| * feedback on the composing state and will typically be shown with different styling such as |
| * a colored background. |
| */ |
| public class CombinerChain { |
| // The already combined text, as described above |
| private StringBuilder mCombinedText; |
| // The feedback on the composing state, as described above |
| private SpannableStringBuilder mStateFeedback; |
| private final ArrayList<Combiner> mCombiners; |
| |
| private static final HashMap<String, Class<? extends Combiner>> IMPLEMENTED_COMBINERS = |
| new HashMap<>(); |
| static { |
| IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class); |
| } |
| private static final String COMBINER_SPEC_SEPARATOR = ";"; |
| |
| /** |
| * Create an combiner chain. |
| * |
| * The combiner chain takes events as inputs and outputs code points and combining state. |
| * For example, if the input language is Japanese, the combining chain will typically perform |
| * kana conversion. This takes a string for initial text, taken to be present before the |
| * cursor: we'll start after this. |
| * |
| * @param initialText The text that has already been combined so far. |
| * @param combinerList A list of combiners to be applied in order. |
| */ |
| public CombinerChain(final String initialText, final Combiner... combinerList) { |
| mCombiners = new ArrayList<>(); |
| // The dead key combiner is always active, and always first |
| mCombiners.add(new DeadKeyCombiner()); |
| for (final Combiner combiner : combinerList) { |
| mCombiners.add(combiner); |
| } |
| mCombinedText = new StringBuilder(initialText); |
| mStateFeedback = new SpannableStringBuilder(); |
| } |
| |
| public void reset() { |
| mCombinedText.setLength(0); |
| mStateFeedback.clear(); |
| for (final Combiner c : mCombiners) { |
| c.reset(); |
| } |
| } |
| |
| /** |
| * Process an event through the combining chain, and return a processed event to apply. |
| * @param previousEvents the list of previous events in this composition |
| * @param newEvent the new event to process |
| */ |
| public Event processEvent(final ArrayList<Event> previousEvents, final Event newEvent) { |
| final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents); |
| Event event = newEvent; |
| for (final Combiner combiner : mCombiners) { |
| // A combiner can never return more than one event; it can return several |
| // code points, but they should be encapsulated within one event. |
| event = combiner.processEvent(modifiablePreviousEvents, event); |
| if (null == event) { |
| // Combiners return null if they eat the event. |
| break; |
| } |
| } |
| return event; |
| } |
| |
| /** |
| * Apply a processed event. |
| * @param event the event to be applied |
| */ |
| public void applyProcessedEvent(final Event event) { |
| if (null != event) { |
| // TODO: figure out the generic way of doing this |
| if (Constants.CODE_DELETE == event.mKeyCode) { |
| final int length = mCombinedText.length(); |
| if (length > 0) { |
| final int lastCodePoint = mCombinedText.codePointBefore(length); |
| mCombinedText.delete(length - Character.charCount(lastCodePoint), length); |
| } |
| } else { |
| final CharSequence textToCommit = event.getTextToCommit(); |
| if (!TextUtils.isEmpty(textToCommit)) { |
| mCombinedText.append(textToCommit); |
| } |
| } |
| } |
| mStateFeedback.clear(); |
| for (int i = mCombiners.size() - 1; i >= 0; --i) { |
| mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback()); |
| } |
| } |
| |
| /** |
| * Get the char sequence that should be displayed as the composing word. It may include |
| * styling spans. |
| */ |
| public CharSequence getComposingWordWithCombiningFeedback() { |
| final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText); |
| return s.append(mStateFeedback); |
| } |
| |
| public static Combiner[] createCombiners(final String spec) { |
| if (TextUtils.isEmpty(spec)) { |
| return new Combiner[0]; |
| } |
| final String[] combinerDescriptors = spec.split(COMBINER_SPEC_SEPARATOR); |
| final Combiner[] combiners = new Combiner[combinerDescriptors.length]; |
| int i = 0; |
| for (final String combinerDescriptor : combinerDescriptors) { |
| final Class<? extends Combiner> combinerClass = |
| IMPLEMENTED_COMBINERS.get(combinerDescriptor); |
| if (null == combinerClass) { |
| throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor); |
| } |
| try { |
| combiners[i++] = combinerClass.newInstance(); |
| } catch (InstantiationException e) { |
| throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor, |
| e); |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor, |
| e); |
| } |
| } |
| return combiners; |
| } |
| } |