blob: d31eed969320d5cbc60656c48e094c52e1393a9c [file] [log] [blame]
/*
* (C) Copyright IBM Corp. 1998-2008. All Rights Reserved.
*
* The program is provided "as is" without any warranty express or
* implied, including the warranty of non-infringement and the implied
* warranties of merchantibility and fitness for a particular purpose.
* IBM will not be liable for any damages suffered by you as a result
* of using the Program. In no event will IBM be liable for any
* special, indirect or consequential damages or lost profits even if
* IBM has been advised of the possibility of their occurrence. IBM
* will not be liable for any third party claims against you.
*/
/*
2/25/99 - Now processing characters from keyTyped method (not keyPressed).
This new way is input-method friendly on 1.2, and is generally
more correct.
7/7/97 - the mouseDidSomething methods used to remove the typing interactor.
This is definitely wrong, but maybe that made sense at one time. Anyway,
now the mousePressed / mouseReleased methods remove the interactor; the
others do nothing.
*/
package com.ibm.richtext.textpanel;
import com.ibm.richtext.textlayout.attributes.AttributeMap;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.FocusEvent;
import java.text.BreakIterator;
import com.ibm.richtext.styledtext.StyleModifier;
import com.ibm.richtext.styledtext.MConstText;
import com.ibm.richtext.styledtext.MText;
import com.ibm.richtext.styledtext.StyledText;
import com.ibm.richtext.textformat.TextOffset;
final class TypingInteractor extends Behavior {
// static final String COPYRIGHT =
// "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
private static final char BACKSPACE = 8;
private static final char TAB = '\t';
private static final char RETURN = '\r';
private static final char LINE_FEED = '\n';
// private static final char PARAGRAPH_SEP = '\u2029';
// private TextComponent fTextComponent;
private TextSelection fSelection;
private AttributeMap fTypingStyle;
private MConstText fText;
private TextEditBehavior fParent;
private TextChangeCommand fCommand = null;
private SimpleCommandLog fCommandLog;
private PanelEventBroadcaster fListener;
private BreakIterator fCharBreak = null;
/**
* Not all characters that come from the keyboard are handled
* as input. For example, ctrl-c is not a typable character.
* This method determines whether a particular character from
* the keyboard will affect the text.
*/
private static boolean isTypingInteractorChar(char ch) {
return ch >= ' ' ||
ch == LINE_FEED ||
ch == RETURN ||
ch == TAB ||
ch == BACKSPACE;
}
/**
* This method determines whether a TypingInteractor should
* handle the given KeyEvent.
*/
static boolean handledByTypingInteractor(KeyEvent event) {
final int id = event.getID();
if (id == KeyEvent.KEY_TYPED) {
return isTypingInteractorChar(event.getKeyChar());
}
else {
return (id == KeyEvent.KEY_PRESSED &&
event.getKeyCode() == KeyEvent.VK_DELETE);
}
}
public TypingInteractor(TextComponent textComponent,
TextSelection selection,
AttributeMap typingStyle,
TextEditBehavior parent,
SimpleCommandLog commandLog,
PanelEventBroadcaster listener) {
// fTextComponent = textComponent;
fText = textComponent.getText();
fSelection = selection;
fTypingStyle = typingStyle;
fParent = parent;
fCommandLog = commandLog;
fListener = listener;
fParent.setTypingInteractor(this);
}
private void endInteraction() {
removeFromOwner();
postTextChangeCommand();
int selStart = fSelection.getStart().fOffset;
int selLimit = fSelection.getEnd().fOffset;
fParent.setSavedTypingStyle(selStart==selLimit? fTypingStyle : null, selStart);
fParent.setTypingInteractor(null);
}
public boolean textControlEventOccurred(Behavior.EventType event, Object what) {
if (fCommand == null && event == Behavior.CHARACTER_STYLE_MOD) {
pickUpTypingStyle();
fTypingStyle = ((StyleModifier)what).modifyStyle(fTypingStyle);
fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);
return true;
}
else {
Behavior next = nextBehavior(); // save because removeFromOwner() will trash this
endInteraction();
if (next != null)
return next.textControlEventOccurred(event, what);
else
return false;
}
}
private void doBackspace() {
int selStart = fSelection.getStart().fOffset;
int selLimit = fSelection.getEnd().fOffset;
if (selStart == selLimit) {
if (selStart != 0) {
fTypingStyle = null;
pickUpTypingStyle();
makeTextChangeCommand();
if (selStart <= fCommand.affectedRangeStart()) {
fCommand.prependToOldText(fText.extract(selStart - 1, selStart));
}
TextOffset insPt = new TextOffset(selStart - 1);
fParent.doReplaceText(selStart - 1, selStart, null, insPt, insPt);
}
}
else {
fTypingStyle = null;
makeTextChangeCommand();
TextOffset insPt = new TextOffset(selStart);
fParent.doReplaceText(selStart, selLimit, null, insPt, insPt);
}
}
private void doFwdDelete(boolean ignoreCharBreak) {
int selStart = fSelection.getStart().fOffset;
int selLimit = fSelection.getEnd().fOffset;
TextOffset insPt = new TextOffset(selStart);
if (selStart == selLimit) {
if (selStart != fText.length()) {
fTypingStyle = null;
makeTextChangeCommand();
int numChars;
if (ignoreCharBreak) {
numChars = 1;
}
else {
if (fCharBreak == null) {
fCharBreak = BreakIterator.getCharacterInstance();
}
fCharBreak.setText(fText.createCharacterIterator());
numChars = fCharBreak.following(selStart) - selStart;
}
fCommand.appendToOldText(fText.extract(selStart, selStart + numChars));
fParent.doReplaceText(selStart, selStart + numChars, null, insPt, insPt);
}
}
else {
fTypingStyle = null;
makeTextChangeCommand();
fParent.doReplaceText(selStart, selLimit, null, insPt, insPt);
}
}
private void doNormalKey(char ch) {
// Sigh - 1.1 reports enter key events as return chars, but
// 1.2 reports them as linefeeds.
if (ch == RETURN) {
ch = LINE_FEED;
}
pickUpTypingStyle();
makeTextChangeCommand();
fParent.doReplaceSelectedText(ch, fTypingStyle);
}
public boolean focusGained(FocusEvent e) {
// pass through, but stick around...
return super.focusGained(e);
}
public boolean focusLost(FocusEvent e) {
// pass through, but stick around...
return super.focusLost(e);
}
public boolean keyTyped(KeyEvent e) {
if (e.getKeyChar() == BACKSPACE) {
doBackspace();
}
else {
if (isTypingInteractorChar(e.getKeyChar())) {
KeyRemap remap = fParent.getKeyRemap();
doNormalKey(remap.remap(e));
}
}
return true;
}
public boolean keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_DELETE) {
doFwdDelete(e.isShiftDown());
return true;
}
Behavior next = nextBehavior();
if (TextSelection.keyAffectsSelection(e)) {
endInteraction();
}
return next.keyPressed(e);
}
public boolean keyReleased(KeyEvent e) {
return true;
}
private void makeTextChangeCommand() {
if (fCommand == null) {
TextOffset selStart = fSelection.getStart();
TextOffset selEnd = fSelection.getEnd();
MText writableText = new StyledText();
writableText.replace(0, 0, fText, selStart.fOffset, selEnd.fOffset);
fCommand = new TextChangeCommand(fParent,
writableText,
null, selStart.fOffset, selStart, selEnd,
new TextOffset(), new TextOffset());
fListener.textStateChanged(TextPanelEvent.UNDO_STATE_CHANGED);
}
}
public boolean mouseDragged(MouseEvent e) {
return true;
}
public boolean mouseEntered(MouseEvent e) {
return true;
}
public boolean mouseExited(MouseEvent e) {
return true;
}
public boolean mouseMoved(MouseEvent e) {
return true;
}
public boolean mousePressed(MouseEvent e) {
Behavior next = nextBehavior(); // save because removeFromOwner() will trash this
endInteraction();
if (next != null)
return next.mousePressed(e);
else
return false;
}
public boolean mouseReleased(MouseEvent e) {
Behavior next = nextBehavior(); // save because removeFromOwner() will trash this
endInteraction();
if (next != null)
return next.mouseReleased(e);
else
return false;
}
private void pickUpTypingStyle() {
if (fTypingStyle == null) {
int selStart = fSelection.getStart().fOffset;
int selLimit = fSelection.getEnd().fOffset;
fTypingStyle = TextEditBehavior.typingStyleAt(fText, selStart, selLimit);
}
}
private void postTextChangeCommand() {
if (fCommand != null) {
TextOffset selStart = fSelection.getStart();
TextOffset selEnd = fSelection.getEnd();
fCommand.setNewText(fText.extract(fCommand.affectedRangeStart(), selStart.fOffset));
fCommand.setSelRangeAfter(selStart, selEnd);
fCommandLog.add(fCommand);
}
}
boolean hasPendingCommand() {
return fCommand != null;
}
AttributeMap getTypingStyle() {
pickUpTypingStyle();
return fTypingStyle;
}
}