/** * MIT License * * Copyright (c) 2017 zgqq * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package mah.ui.input; import java.util.LinkedList; /** * Created by zgq on 2017-01-09 14:03 */ public abstract class AbstractInput implements Input { private final LinkedList<TextState> undoStack = new LinkedList<>(); private final LinkedList<TextState> redoStack = new LinkedList<>(); private TextState textState; protected boolean canBackward() { return getCaretPosition() > 0; } private void push() { pushToUndo(newTextState(getText(), getCaretPosition())); } private void pushToUndo(TextState textState) { if (undoStack.size() > 100) { undoStack.pollLast(); } undoStack.push(textState); } protected boolean canMotionForward() { return getCaretPosition() < textLength(); } protected boolean canActionForward() { return getCaretPosition() < textLength(); } @Override public void beginningOfLine() { setCaretPosition(0); } @Override public void endOfLine() { setCaretPosition(textLength()); } @Override public void forwardChar() { if (canMotionForward()) { setCaretPosition(getCaretPosition() + 1); } } public void backwardChar() { if (getCaretPosition() > 0) { setCaretPosition(getCaretPosition() - 1); } } public void deleteChar() { int caretPosition = getCaretPosition(); if (caretPosition >= textLength()) { return; } push(); remove(caretPosition, 1); } @Override public void forwardWord() { if (!canMotionForward()) { return; } int caretPosition = forwardByWord(); setCaretPosition(caretPosition); } protected int forwardByWord() { String text = getText(); int caretPosition = getCaretPosition(); boolean hasLetter = false; for (int i = caretPosition; i < motionLength(); i++) { char c = text.charAt(i); if (hasLetter) { if (!isWordLetter(c)) { break; } } else { if (isWordLetter(c)) { hasLetter = true; } } caretPosition++; } return caretPosition; } protected boolean isWordLetter(char c) { return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; } @Override public void backwardWord() { if (!canBackward()) { return; } int caretPosition = backwardWord2(); setCaretPosition(caretPosition); } protected int backwardWord2() { String text = getText(); int caretPosition = getCaretPosition(); int caret = caretPosition; boolean hasLetter = false; for (int i = caret - 1; i >= 0; i--) { char c = text.charAt(i); if (c == ' ' && i != caret - 1) { break; } if (hasLetter) { if (!isWordLetter(c)) { break; } } else { if (isWordLetter(c)) { hasLetter = true; } } caretPosition--; } return caretPosition; } public void killWord() { if (!canActionForward()) { return; } int origin = getCaretPosition(); int forward = forwardByWord(); if (forward > origin) { remove(origin, forward - origin); push(); } } @Override public void killWholeLine() { String text = getText(); if (text != null && textLength() > 0) { push(); remove(0, textLength()); } } @Override public void killLine() { int caret = getCaretPosition(); if (caret < textLength()) { push(); remove(caret, textLength() - caret); } } @Override public void backwardKillWord() { if (!canBackward()) { return; } int origin = getCaretPosition(); int backwardWord = backwardWord2(); if (backwardWord == origin) { return; } push(); remove(backwardWord, origin - backwardWord); } @Override public void backwardDeleteChar() { if (!canBackward()) { return; } push(); int caret = getCaretPosition(); remove(caret - 1, 1); setCaretPosition(caret - 1); } @Override public final void undo() { TextState textState = undoStack.pollFirst(); if (textState != null) { pushToRedo(); setText(textState.getText()); setCaretPosition(textState.getPosition()); } } @Override public final void redo() { TextState textState = redoStack.pollFirst(); if (textState != null) { setText(textState.getText()); setCaretPosition(textState.getPosition()); pushToUndo(textState); } } @Override public void clear() { String text = getText(); if (text != null && !text.isEmpty()) { remove(0, textLength()); } } public void setTextState(TextState textState) { this.textState = textState; setText(textState.getText()); setCaretPosition(textState.getPosition()); } public TextState getTextState() { return textState; } protected abstract int motionLength(); protected abstract int textLength(); private TextState newTextState(String text, int position) { return new TextState.Builder(text, position).build(); } private void pushToRedo() { pushToRedo(newTextState(getText(), getCaretPosition())); } private void pushToRedo(TextState textState) { if (redoStack.size() > 100) { redoStack.pollLast(); } redoStack.push(textState); } }