mirror of
https://github.com/processing/processing4.git
synced 2026-02-13 18:35:37 +01:00
updates for input method support (bug #854)
This commit is contained in:
@@ -1,70 +1,38 @@
|
||||
package processing.app.syntax;
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.InputMethodEvent;
|
||||
import java.awt.event.InputMethodListener;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextHitInfo;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.awt.im.InputMethodRequests;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.AttributedString;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
|
||||
/**
|
||||
* Support in-line Japanese input for PDE. (Maybe Chinese, Korean and more)
|
||||
* This class is implemented by Java Input Method Framework and handles
|
||||
* If you would like to know more about Java Input Method Framework,
|
||||
* Please see http://java.sun.com/j2se/1.5.0/docs/guide/imf/
|
||||
*
|
||||
* This class is implemented to fix Bug 854.
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
*/
|
||||
public class InputMethodSupport implements InputMethodRequests,
|
||||
InputMethodListener {
|
||||
|
||||
private JEditTextArea textArea;
|
||||
|
||||
private TextLayout composedTextLayout = null;
|
||||
|
||||
private int committed_count = 0;
|
||||
|
||||
private static final int COMPOSING_UNDERBAR_HEIGHT = 5;
|
||||
|
||||
private boolean isComposing;
|
||||
private CompositionTextManager textManager;
|
||||
|
||||
public InputMethodSupport(JEditTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
textManager = new CompositionTextManager(textArea);
|
||||
textArea.enableInputMethods(true);
|
||||
textArea.addInputMethodListener(this);
|
||||
isComposing = false;
|
||||
}
|
||||
|
||||
public boolean getIsComposing() {
|
||||
return isComposing;
|
||||
}
|
||||
|
||||
private Point getCaretLocation() {
|
||||
Point loc = new Point();
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
FontMetrics fm = painter.getFontMetrics();
|
||||
int offsetY = fm.getHeight() - 5;
|
||||
int lineIndex = textArea.getCaretLine();
|
||||
loc.y = lineIndex * fm.getHeight() + offsetY;
|
||||
int offsetX = textArea.getCaretPosition()
|
||||
- textArea.getLineStartOffset(lineIndex);
|
||||
loc.x = textArea.offsetToX(lineIndex, offsetX);
|
||||
return loc;
|
||||
}
|
||||
|
||||
public Rectangle getTextLocation(TextHitInfo offset) {
|
||||
Point caret = getCaretLocation();
|
||||
return getCaretRectangle(caret.x, caret.y);
|
||||
}
|
||||
|
||||
private Rectangle getCaretRectangle(int x, int y) {
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
Point origin = painter.getLocationOnScreen();
|
||||
int height = painter.getFontMetrics().getHeight();
|
||||
return new Rectangle(origin.x + x, origin.y + y, 0, height);
|
||||
return textManager.getTextLocation();
|
||||
}
|
||||
|
||||
public TextHitInfo getLocationOffset(int x, int y) {
|
||||
@@ -72,16 +40,12 @@ public class InputMethodSupport implements InputMethodRequests,
|
||||
}
|
||||
|
||||
public int getInsertPositionOffset() {
|
||||
if (isComposing) {
|
||||
isComposing = false;
|
||||
}
|
||||
return textArea.getCaretPosition();
|
||||
return textManager.getInsertPositionOffset();
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator getCommittedText(int beginIndex,
|
||||
int endIndex, AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return (new AttributedString(textArea.getText(beginIndex, endIndex
|
||||
- beginIndex))).getIterator();
|
||||
return textManager.getCommittedText(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public int getCommittedTextLength() {
|
||||
@@ -98,73 +62,144 @@ public class InputMethodSupport implements InputMethodRequests,
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles events from InputMethod.
|
||||
* This method judges whether beginning of input or
|
||||
* progress of input or end and call related method.
|
||||
*
|
||||
* @param event event from Input Method.
|
||||
*/
|
||||
public void inputMethodTextChanged(InputMethodEvent event) {
|
||||
composedTextLayout = null;
|
||||
AttributedCharacterIterator text = event.getText();
|
||||
committed_count = event.getCommittedCharacterCount();
|
||||
if (committed_count == 0) {
|
||||
if (text.getEndIndex() == 0) {
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
}
|
||||
if (text.getEndIndex() < text.getBeginIndex()) {
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
}
|
||||
isComposing = true;
|
||||
drawComposingText(text, committed_count);
|
||||
if(isBeginInputProcess(text, textManager)){
|
||||
textManager.beginCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
}
|
||||
commitText(text, committed_count);
|
||||
isComposing = false;
|
||||
if (isInputProcess(text)){
|
||||
textManager.processCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
}
|
||||
textManager.endCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
}
|
||||
|
||||
private void drawComposingText(AttributedCharacterIterator text, int count) {
|
||||
assert ((count == 0 && text.getEndIndex() > 0));
|
||||
Point textLocation = getCaretLocation();
|
||||
invalidateComposingLine(textArea.getPainter(), textLocation.x,
|
||||
textLocation.y);
|
||||
composedTextLayout = getTextLayout(text, count);
|
||||
composedTextLayout.draw((Graphics2D) (textArea.getPainter().getGraphics()),
|
||||
textLocation.x, textLocation.y);
|
||||
private boolean isBeginInputProcess(AttributedCharacterIterator text, CompositionTextManager textManager){
|
||||
if(text == null)
|
||||
return false;
|
||||
return (isInputProcess(text) && !textManager.getIsInputProcess());
|
||||
}
|
||||
|
||||
private void invalidateComposingLine(TextAreaPainter painter, int x, int y) {
|
||||
Graphics gfx = painter.getGraphics();
|
||||
gfx.setColor(painter.lineHighlightColor);
|
||||
gfx.fillRect(x, y
|
||||
- (painter.getFontMetrics().getHeight() - COMPOSING_UNDERBAR_HEIGHT), //
|
||||
painter.getWidth(), painter.getFontMetrics().getHeight());
|
||||
}
|
||||
|
||||
private void commitText(AttributedCharacterIterator text, int count) {
|
||||
char c;
|
||||
StringBuffer committing = new StringBuffer(count);
|
||||
for (c = text.first(); c != AttributedCharacterIterator.DONE && count > 0; c = text
|
||||
.next(), --count) {
|
||||
committing.append(c);
|
||||
}
|
||||
int caret = textArea.getCaretPosition();
|
||||
String committing_text = committing.toString();
|
||||
try {
|
||||
textArea.getDocument().insertString(caret, committing_text, null);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private TextLayout getTextLayout(AttributedCharacterIterator text,
|
||||
int committed_count) {
|
||||
AttributedString composed = new AttributedString(text, committed_count,
|
||||
text.getEndIndex());
|
||||
Font font = textArea.getPainter().getFont();
|
||||
FontRenderContext context = ((Graphics2D) (textArea.getPainter()
|
||||
.getGraphics())).getFontRenderContext();
|
||||
composed.addAttribute(TextAttribute.FONT, font);
|
||||
TextLayout layout = new TextLayout(composed.getIterator(), context);
|
||||
return layout;
|
||||
private boolean isInputProcess(AttributedCharacterIterator text){
|
||||
if(text == null)
|
||||
return false;
|
||||
return (text.getEndIndex() - (text.getBeginIndex() + committed_count) > 0);
|
||||
}
|
||||
|
||||
public void caretPositionChanged(InputMethodEvent event) {
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.InputMethodEvent;
|
||||
import java.awt.event.InputMethodListener;
|
||||
import java.awt.font.TextHitInfo;
|
||||
import java.awt.im.InputMethodRequests;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
|
||||
/**
|
||||
* Support in-line Japanese input for PDE. (Maybe Chinese, Korean and more)
|
||||
* This class is implemented by Java Input Method Framework and handles
|
||||
* If you would like to know more about Java Input Method Framework,
|
||||
* Please see http://java.sun.com/j2se/1.5.0/docs/guide/imf/
|
||||
*
|
||||
* This class is implemented to fix Bug 854.
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
*/
|
||||
public class InputMethodSupport implements InputMethodRequests,
|
||||
InputMethodListener {
|
||||
|
||||
private int committed_count = 0;
|
||||
private CompositionTextManager textManager;
|
||||
|
||||
public InputMethodSupport(JEditTextArea textArea) {
|
||||
textManager = new CompositionTextManager(textArea);
|
||||
textArea.enableInputMethods(true);
|
||||
textArea.addInputMethodListener(this);
|
||||
}
|
||||
|
||||
public Rectangle getTextLocation(TextHitInfo offset) {
|
||||
return textManager.getTextLocation();
|
||||
}
|
||||
|
||||
public TextHitInfo getLocationOffset(int x, int y) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getInsertPositionOffset() {
|
||||
return textManager.getInsertPositionOffset();
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator getCommittedText(int beginIndex,
|
||||
int endIndex, AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return textManager.getCommittedText(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public int getCommittedTextLength() {
|
||||
return committed_count;
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator cancelLatestCommittedText(
|
||||
AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator getSelectedText(
|
||||
AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles events from InputMethod.
|
||||
* This method judges whether beginning of input or
|
||||
* progress of input or end and call related method.
|
||||
*
|
||||
* @param event event from Input Method.
|
||||
*/
|
||||
public void inputMethodTextChanged(InputMethodEvent event) {
|
||||
AttributedCharacterIterator text = event.getText();
|
||||
committed_count = event.getCommittedCharacterCount();
|
||||
if(isBeginInputProcess(text, textManager)){
|
||||
textManager.beginCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
}
|
||||
if (isInputProcess(text)){
|
||||
textManager.processCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
}
|
||||
textManager.endCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
}
|
||||
|
||||
private boolean isBeginInputProcess(AttributedCharacterIterator text, CompositionTextManager textManager){
|
||||
if(text == null)
|
||||
return false;
|
||||
return (isInputProcess(text) && !textManager.getIsInputProcess());
|
||||
}
|
||||
|
||||
private boolean isInputProcess(AttributedCharacterIterator text){
|
||||
if(text == null)
|
||||
return false;
|
||||
return (text.getEndIndex() - (text.getBeginIndex() + committed_count) > 0);
|
||||
}
|
||||
|
||||
public void caretPositionChanged(InputMethodEvent event) {
|
||||
|
||||
@@ -24,6 +24,8 @@ import java.util.Enumeration;
|
||||
import java.util.Vector;
|
||||
import java.awt.im.InputMethodRequests;
|
||||
|
||||
import processing.app.syntax.im.InputMethodSupport;
|
||||
|
||||
/**
|
||||
* jEdit's text area component. It is more suited for editing program
|
||||
* source code than JEditorPane, because it drops the unnecessary features
|
||||
@@ -227,7 +229,7 @@ public class JEditTextArea extends JComponent
|
||||
* Blinks the caret.
|
||||
*/
|
||||
public final void blinkCaret() {
|
||||
if (caretBlinks && !((InputMethodSupport)getInputMethodRequests()).getIsComposing()) {
|
||||
if (caretBlinks) {
|
||||
blink = !blink;
|
||||
painter.invalidateSelectedLines();
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
package processing.app.syntax;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.syntax.im.CompositionTextPainter;
|
||||
|
||||
import javax.swing.ToolTipManager;
|
||||
import javax.swing.text.*;
|
||||
@@ -33,6 +34,9 @@ implements TabExpander, Printable
|
||||
/** Current setting for editor.antialias preference */
|
||||
boolean antialias;
|
||||
|
||||
/** A specific painter composed by the InputMethod.*/
|
||||
protected CompositionTextPainter compositionTextPainter;
|
||||
|
||||
/**
|
||||
* Creates a new repaint manager. This should be not be called
|
||||
* directly.
|
||||
@@ -72,6 +76,16 @@ implements TabExpander, Printable
|
||||
eolMarkerColor = defaults.eolMarkerColor;
|
||||
eolMarkers = defaults.eolMarkers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CompositionTextPainter. if CompositionTextPainter is not created, create it.
|
||||
*/
|
||||
public CompositionTextPainter getCompositionTextpainter(){
|
||||
if(compositionTextPainter == null){
|
||||
compositionTextPainter = new CompositionTextPainter(textArea);
|
||||
}
|
||||
return compositionTextPainter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this component can be traversed by pressing the
|
||||
@@ -602,7 +616,12 @@ implements TabExpander, Printable
|
||||
|
||||
y += fm.getHeight();
|
||||
x = Utilities.drawTabbedText(currentLine,x,y,gfx,this,0);
|
||||
|
||||
/*
|
||||
* Draw characters via input method.
|
||||
*/
|
||||
if (compositionTextPainter != null && compositionTextPainter.hasComposedTextLayout()) {
|
||||
compositionTextPainter.draw(gfx, lineHighlightColor);
|
||||
}
|
||||
if (eolMarkers) {
|
||||
gfx.setColor(eolMarkerColor);
|
||||
gfx.drawString(".",x,y);
|
||||
@@ -625,7 +644,12 @@ implements TabExpander, Printable
|
||||
x = SyntaxUtilities.paintSyntaxLine(currentLine,
|
||||
currentLineTokens,
|
||||
styles, this, gfx, x, y);
|
||||
|
||||
/*
|
||||
* Draw characters via input method.
|
||||
*/
|
||||
if (compositionTextPainter != null && compositionTextPainter.hasComposedTextLayout()) {
|
||||
compositionTextPainter.draw(gfx, lineHighlightColor);
|
||||
}
|
||||
if (eolMarkers) {
|
||||
gfx.setColor(eolMarkerColor);
|
||||
gfx.drawString(".",x,y);
|
||||
|
||||
374
app/src/processing/app/syntax/im/CompositionTextManager.java
Normal file
374
app/src/processing/app/syntax/im/CompositionTextManager.java
Normal file
@@ -0,0 +1,374 @@
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.AttributedString;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.syntax.TextAreaPainter;
|
||||
|
||||
/**
|
||||
* This class Manage texts from input method
|
||||
* by begin-process-end steps.
|
||||
*
|
||||
* First, if a user start inputing via input method,
|
||||
* beginCompositionText is called from InputMethodSupport.
|
||||
* Second, the user continues from input method, processCompositionText is called
|
||||
* and reflect user inputs to text area.
|
||||
* Finally the user try to commit text, endCompositionText is called.
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
*/
|
||||
|
||||
public class CompositionTextManager {
|
||||
private JEditTextArea textArea;
|
||||
private String prevComposeString;
|
||||
private int prevCommittedCount;
|
||||
private boolean isInputProcess;
|
||||
private int initialCaretPosition;
|
||||
public static final int COMPOSING_UNDERBAR_HEIGHT = 5;
|
||||
|
||||
/**
|
||||
* Create text manager class with a textarea.
|
||||
* @param textArea texarea component for PDE.
|
||||
*/
|
||||
public CompositionTextManager(JEditTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
prevComposeString = "";
|
||||
isInputProcess = false;
|
||||
prevCommittedCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this text manager is whether in input process or not.
|
||||
*/
|
||||
public boolean getIsInputProcess() {
|
||||
return isInputProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user begins input from input method.
|
||||
* This method initializes text manager.
|
||||
*
|
||||
* @param text Text from InputMethodEvent.
|
||||
* @param commited_count Numbers of committed characters in text.
|
||||
*/
|
||||
public void beginCompositionText(AttributedCharacterIterator text, int committed_count) {
|
||||
isInputProcess = true;
|
||||
prevComposeString = "";
|
||||
initialCaretPosition = textArea.getCaretPosition();
|
||||
processCompositionText(text, committed_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user processing input characters and
|
||||
* select candidates from input method.
|
||||
*
|
||||
* @param text Text from InputMethodEvent.
|
||||
* @param commited_count Numbers of committed characters in text.
|
||||
*/
|
||||
public void processCompositionText(AttributedCharacterIterator text, int committed_count) {
|
||||
int layoutCaretPosition = initialCaretPosition + committed_count;
|
||||
CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter();
|
||||
compositionPainter.setComposedTextLayout(getTextLayout(text, committed_count), layoutCaretPosition);
|
||||
int textLength = text.getEndIndex() - text.getBeginIndex() - committed_count;
|
||||
StringBuffer unCommitedStringBuf = new StringBuffer(textLength);
|
||||
char c;
|
||||
for (c = text.setIndex(committed_count); c != AttributedCharacterIterator.DONE
|
||||
&& textLength > 0; c = text.next(), --textLength) {
|
||||
unCommitedStringBuf.append(c);
|
||||
}
|
||||
String unCommittedString = unCommitedStringBuf.toString();
|
||||
try {
|
||||
if(canRemovePreviousInput(committed_count)){
|
||||
textArea.getDocument().remove(layoutCaretPosition, prevComposeString.length());
|
||||
}
|
||||
textArea.getDocument().insertString(layoutCaretPosition, unCommittedString, null);
|
||||
if(committed_count > 0){
|
||||
initialCaretPosition = initialCaretPosition + committed_count;
|
||||
}
|
||||
prevComposeString = unCommittedString;
|
||||
prevCommittedCount = committed_count;
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canRemovePreviousInput(int committed_count){
|
||||
return (prevCommittedCount == committed_count || prevCommittedCount > committed_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user fixed text from input method or delete all
|
||||
* composition text. This method resets CompositionTextPainter.
|
||||
*
|
||||
* @param text Text from InputMethodEvent.
|
||||
* @param commited_count Numbers of committed characters in text.
|
||||
*/
|
||||
public void endCompositionText(AttributedCharacterIterator text, int committed_count) {
|
||||
isInputProcess = false;
|
||||
/*
|
||||
* If there are no committed characters, remove it all from textarea.
|
||||
* This case will happen if a user delete all composing characters by backspace or delete key.
|
||||
* If it does, these previous characters are needed to be deleted.
|
||||
*/
|
||||
if(committed_count == 0){
|
||||
removeNotCommittedText(text);
|
||||
}
|
||||
CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter();
|
||||
compositionPainter.invalidateComposedTextLayout(initialCaretPosition + committed_count);
|
||||
prevComposeString = "";
|
||||
isInputProcess = false;
|
||||
}
|
||||
|
||||
private void removeNotCommittedText(AttributedCharacterIterator text){
|
||||
if (prevComposeString.length() == 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
textArea.getDocument().remove(initialCaretPosition, prevComposeString.length());
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private TextLayout getTextLayout(AttributedCharacterIterator text, int committed_count) {
|
||||
AttributedString composed = new AttributedString(text, committed_count, text.getEndIndex());
|
||||
Font font = textArea.getPainter().getFont();
|
||||
FontRenderContext context = ((Graphics2D) (textArea.getPainter().getGraphics())).getFontRenderContext();
|
||||
composed.addAttribute(TextAttribute.FONT, font);
|
||||
TextLayout layout = new TextLayout(composed.getIterator(), context);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Point getCaretLocation() {
|
||||
Point loc = new Point();
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
FontMetrics fm = painter.getFontMetrics();
|
||||
int offsetY = fm.getHeight() - COMPOSING_UNDERBAR_HEIGHT;
|
||||
int lineIndex = textArea.getCaretLine();
|
||||
loc.y = lineIndex * fm.getHeight() + offsetY;
|
||||
int offsetX = textArea.getCaretPosition()
|
||||
- textArea.getLineStartOffset(lineIndex);
|
||||
loc.x = textArea.offsetToX(lineIndex, offsetX);
|
||||
return loc;
|
||||
}
|
||||
|
||||
public Rectangle getTextLocation() {
|
||||
Point caret = getCaretLocation();
|
||||
return getCaretRectangle(caret.x, caret.y);
|
||||
}
|
||||
|
||||
private Rectangle getCaretRectangle(int x, int y) {
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
Point origin = painter.getLocationOnScreen();
|
||||
int height = painter.getFontMetrics().getHeight();
|
||||
return new Rectangle(origin.x + x, origin.y + y, 0, height);
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex) {
|
||||
int length = endIndex - beginIndex;
|
||||
String textAreaString = textArea.getText(beginIndex, length);
|
||||
return new AttributedString(textAreaString).getIterator();
|
||||
}
|
||||
|
||||
public int getInsertPositionOffset() {
|
||||
return textArea.getCaretPosition() * -1;
|
||||
}
|
||||
}
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.AttributedString;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.syntax.TextAreaPainter;
|
||||
|
||||
/**
|
||||
* This class Manage texts from input method
|
||||
* by begin-process-end steps.
|
||||
*
|
||||
* First, if a user start inputing via input method,
|
||||
* beginCompositionText is called from InputMethodSupport.
|
||||
* Second, the user continues from input method, processCompositionText is called
|
||||
* and reflect user inputs to text area.
|
||||
* Finally the user try to commit text, endCompositionText is called.
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
*/
|
||||
|
||||
public class CompositionTextManager {
|
||||
private JEditTextArea textArea;
|
||||
private String prevComposeString;
|
||||
private int prevCommittedCount;
|
||||
private boolean isInputProcess;
|
||||
private int initialCaretPosition;
|
||||
public static final int COMPOSING_UNDERBAR_HEIGHT = 5;
|
||||
|
||||
/**
|
||||
* Create text manager class with a textarea.
|
||||
* @param textArea texarea component for PDE.
|
||||
*/
|
||||
public CompositionTextManager(JEditTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
prevComposeString = "";
|
||||
isInputProcess = false;
|
||||
prevCommittedCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this text manager is whether in input process or not.
|
||||
*/
|
||||
public boolean getIsInputProcess() {
|
||||
return isInputProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user begins input from input method.
|
||||
* This method initializes text manager.
|
||||
*
|
||||
* @param text Text from InputMethodEvent.
|
||||
* @param commited_count Numbers of committed characters in text.
|
||||
*/
|
||||
public void beginCompositionText(AttributedCharacterIterator text, int committed_count) {
|
||||
isInputProcess = true;
|
||||
prevComposeString = "";
|
||||
initialCaretPosition = textArea.getCaretPosition();
|
||||
processCompositionText(text, committed_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user processing input characters and
|
||||
* select candidates from input method.
|
||||
*
|
||||
* @param text Text from InputMethodEvent.
|
||||
* @param commited_count Numbers of committed characters in text.
|
||||
*/
|
||||
public void processCompositionText(AttributedCharacterIterator text, int committed_count) {
|
||||
int layoutCaretPosition = initialCaretPosition + committed_count;
|
||||
CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter();
|
||||
compositionPainter.setComposedTextLayout(getTextLayout(text, committed_count), layoutCaretPosition);
|
||||
int textLength = text.getEndIndex() - text.getBeginIndex() - committed_count;
|
||||
StringBuffer unCommitedStringBuf = new StringBuffer(textLength);
|
||||
char c;
|
||||
for (c = text.setIndex(committed_count); c != AttributedCharacterIterator.DONE
|
||||
&& textLength > 0; c = text.next(), --textLength) {
|
||||
unCommitedStringBuf.append(c);
|
||||
}
|
||||
String unCommittedString = unCommitedStringBuf.toString();
|
||||
try {
|
||||
if(canRemovePreviousInput(committed_count)){
|
||||
textArea.getDocument().remove(layoutCaretPosition, prevComposeString.length());
|
||||
}
|
||||
textArea.getDocument().insertString(layoutCaretPosition, unCommittedString, null);
|
||||
if(committed_count > 0){
|
||||
initialCaretPosition = initialCaretPosition + committed_count;
|
||||
}
|
||||
prevComposeString = unCommittedString;
|
||||
prevCommittedCount = committed_count;
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canRemovePreviousInput(int committed_count){
|
||||
return (prevCommittedCount == committed_count || prevCommittedCount > committed_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user fixed text from input method or delete all
|
||||
* composition text. This method resets CompositionTextPainter.
|
||||
*
|
||||
* @param text Text from InputMethodEvent.
|
||||
* @param commited_count Numbers of committed characters in text.
|
||||
*/
|
||||
public void endCompositionText(AttributedCharacterIterator text, int committed_count) {
|
||||
isInputProcess = false;
|
||||
/*
|
||||
* If there are no committed characters, remove it all from textarea.
|
||||
* This case will happen if a user delete all composing characters by backspace or delete key.
|
||||
* If it does, these previous characters are needed to be deleted.
|
||||
*/
|
||||
if(committed_count == 0){
|
||||
removeNotCommittedText(text);
|
||||
}
|
||||
CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter();
|
||||
compositionPainter.invalidateComposedTextLayout(initialCaretPosition + committed_count);
|
||||
prevComposeString = "";
|
||||
isInputProcess = false;
|
||||
}
|
||||
|
||||
private void removeNotCommittedText(AttributedCharacterIterator text){
|
||||
if (prevComposeString.length() == 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
textArea.getDocument().remove(initialCaretPosition, prevComposeString.length());
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private TextLayout getTextLayout(AttributedCharacterIterator text, int committed_count) {
|
||||
AttributedString composed = new AttributedString(text, committed_count, text.getEndIndex());
|
||||
Font font = textArea.getPainter().getFont();
|
||||
FontRenderContext context = ((Graphics2D) (textArea.getPainter().getGraphics())).getFontRenderContext();
|
||||
composed.addAttribute(TextAttribute.FONT, font);
|
||||
TextLayout layout = new TextLayout(composed.getIterator(), context);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Point getCaretLocation() {
|
||||
Point loc = new Point();
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
FontMetrics fm = painter.getFontMetrics();
|
||||
int offsetY = fm.getHeight() - COMPOSING_UNDERBAR_HEIGHT;
|
||||
int lineIndex = textArea.getCaretLine();
|
||||
loc.y = lineIndex * fm.getHeight() + offsetY;
|
||||
int offsetX = textArea.getCaretPosition()
|
||||
- textArea.getLineStartOffset(lineIndex);
|
||||
loc.x = textArea.offsetToX(lineIndex, offsetX);
|
||||
return loc;
|
||||
}
|
||||
|
||||
public Rectangle getTextLocation() {
|
||||
Point caret = getCaretLocation();
|
||||
return getCaretRectangle(caret.x, caret.y);
|
||||
}
|
||||
|
||||
private Rectangle getCaretRectangle(int x, int y) {
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
Point origin = painter.getLocationOnScreen();
|
||||
int height = painter.getFontMetrics().getHeight();
|
||||
return new Rectangle(origin.x + x, origin.y + y, 0, height);
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex) {
|
||||
int length = endIndex - beginIndex;
|
||||
String textAreaString = textArea.getText(beginIndex, length);
|
||||
return new AttributedString(textAreaString).getIterator();
|
||||
}
|
||||
|
||||
public int getInsertPositionOffset() {
|
||||
return textArea.getCaretPosition() * -1;
|
||||
}
|
||||
}
|
||||
250
app/src/processing/app/syntax/im/CompositionTextPainter.java
Normal file
250
app/src/processing/app/syntax/im/CompositionTextPainter.java
Normal file
@@ -0,0 +1,250 @@
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.font.TextLayout;
|
||||
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.syntax.TextAreaPainter;
|
||||
|
||||
/**
|
||||
* Paint texts from input method.
|
||||
* Text via input method are transmitted by AttributedCaharacterIterator.
|
||||
* But current PDE's TextAreaPainter can't treat AttributedCaharacterIterator directly.
|
||||
* So this class helps to treat it and paint text.
|
||||
*
|
||||
* For practical purposes, paint to textarea is done by TextLayout class.
|
||||
* Because TextLayout class is easy to draw composing texts.
|
||||
* (For example, draw underline composing texts, focus when select from candidates text.)
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
*/
|
||||
public class CompositionTextPainter {
|
||||
private TextLayout composedTextLayout;
|
||||
private int composedBeginCaretPosition = 0;
|
||||
private JEditTextArea textArea;
|
||||
|
||||
/**
|
||||
* Constructor for painter.
|
||||
* @param textarea textarea used by PDE.
|
||||
*/
|
||||
public CompositionTextPainter(JEditTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
composedTextLayout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the painter has TextLayout.
|
||||
* If a user input via InputMethod, this result will return true.
|
||||
* @param textarea textarea used by PDE.
|
||||
*/
|
||||
public boolean hasComposedTextLayout() {
|
||||
return (composedTextLayout != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set TextLayout to the painter.
|
||||
* TextLayout will be created and set by CompositionTextManager.
|
||||
*
|
||||
* @see CompositionTextManager
|
||||
* @param textarea textarea used by PDE.
|
||||
*/
|
||||
public void setComposedTextLayout(TextLayout composedTextLayout, int composedStartCaretPosition) {
|
||||
this.composedTextLayout = composedTextLayout;
|
||||
this.composedBeginCaretPosition = composedStartCaretPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate this TextLayout to set null.
|
||||
* If a user end input via InputMethod, this method will called from CompositionTextManager.endCompositionText
|
||||
*/
|
||||
public void invalidateComposedTextLayout(int composedEndCaretPosition) {
|
||||
this.composedTextLayout = null;
|
||||
this.composedBeginCaretPosition = composedEndCaretPosition;
|
||||
//this.composedBeginCaretPosition = textArea.getCaretPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text via input method with composed text information.
|
||||
* This method can draw texts with some underlines to illustrate converting characters.
|
||||
*
|
||||
* This method is workaround for TextAreaPainter.
|
||||
* Because, TextAreaPainter can't treat AttributedCharacterIterator directly.
|
||||
* AttributedCharacterIterator has very important information when composing text.
|
||||
* It has a map where are converted characters and committed characters.
|
||||
* Ideally, changing TextAreaPainter method can treat AttributedCharacterIterator is better. But it's very tough!!
|
||||
* So I choose to write some code as a workaround.
|
||||
*
|
||||
* This draw method is proceeded with the following steps.
|
||||
* 1. Original TextAreaPainter draws characters.
|
||||
* 2. This refillComposedArea method erase previous paint characters by textarea's background color.
|
||||
* The refill area is only square that width and height defined by characters with input method.
|
||||
* 3. CompositionTextPainter.draw method paints composed text. It was actually drawn by TextLayout.
|
||||
*
|
||||
* @param gfx set TextAreaPainter's Graphics object.
|
||||
* @param fillBackGroundColor set textarea's background.
|
||||
*/
|
||||
public void draw(Graphics gfx, Color fillBackGroundColor) {
|
||||
assert(composedTextLayout != null);
|
||||
Point composedLoc = getCaretLocation();
|
||||
refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y);
|
||||
composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill color to erase characters drawn by original TextAreaPainter.
|
||||
*
|
||||
* @param fillColor fill color to erase characters drawn by original TextAreaPainter method.
|
||||
* @param x x-coordinate where to fill.
|
||||
* @param y y-coordinate where to fill.
|
||||
*/
|
||||
private void refillComposedArea(Color fillColor, int x, int y) {
|
||||
Graphics gfx = textArea.getPainter().getGraphics();
|
||||
gfx.setColor(fillColor);
|
||||
FontMetrics fm = textArea.getPainter().getFontMetrics();
|
||||
int newY = y - (fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT);
|
||||
int paintHeight = fm.getHeight();
|
||||
int paintWidth = (int) composedTextLayout.getBounds().getWidth();
|
||||
gfx.fillRect(x, newY, paintWidth, paintHeight);
|
||||
}
|
||||
|
||||
private Point getCaretLocation() {
|
||||
Point loc = new Point();
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
FontMetrics fm = painter.getFontMetrics();
|
||||
int offsetY = fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT;
|
||||
int lineIndex = textArea.getCaretLine();
|
||||
loc.y = lineIndex * fm.getHeight() + offsetY;
|
||||
int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(lineIndex);
|
||||
loc.x = textArea.offsetToX(lineIndex, offsetX);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.font.TextLayout;
|
||||
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.syntax.TextAreaPainter;
|
||||
|
||||
/**
|
||||
* Paint texts from input method.
|
||||
* Text via input method are transmitted by AttributedCaharacterIterator.
|
||||
* But current PDE's TextAreaPainter can't treat AttributedCaharacterIterator directly.
|
||||
* So this class helps to treat it and paint text.
|
||||
*
|
||||
* For practical purposes, paint to textarea is done by TextLayout class.
|
||||
* Because TextLayout class is easy to draw composing texts.
|
||||
* (For example, draw underline composing texts, focus when select from candidates text.)
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
*/
|
||||
public class CompositionTextPainter {
|
||||
private TextLayout composedTextLayout;
|
||||
private int composedBeginCaretPosition = 0;
|
||||
private JEditTextArea textArea;
|
||||
|
||||
/**
|
||||
* Constructor for painter.
|
||||
* @param textarea textarea used by PDE.
|
||||
*/
|
||||
public CompositionTextPainter(JEditTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
composedTextLayout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the painter has TextLayout.
|
||||
* If a user input via InputMethod, this result will return true.
|
||||
* @param textarea textarea used by PDE.
|
||||
*/
|
||||
public boolean hasComposedTextLayout() {
|
||||
return (composedTextLayout != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set TextLayout to the painter.
|
||||
* TextLayout will be created and set by CompositionTextManager.
|
||||
*
|
||||
* @see CompositionTextManager
|
||||
* @param textarea textarea used by PDE.
|
||||
*/
|
||||
public void setComposedTextLayout(TextLayout composedTextLayout, int composedStartCaretPosition) {
|
||||
this.composedTextLayout = composedTextLayout;
|
||||
this.composedBeginCaretPosition = composedStartCaretPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate this TextLayout to set null.
|
||||
* If a user end input via InputMethod, this method will called from CompositionTextManager.endCompositionText
|
||||
*/
|
||||
public void invalidateComposedTextLayout(int composedEndCaretPosition) {
|
||||
this.composedTextLayout = null;
|
||||
this.composedBeginCaretPosition = composedEndCaretPosition;
|
||||
//this.composedBeginCaretPosition = textArea.getCaretPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text via input method with composed text information.
|
||||
* This method can draw texts with some underlines to illustrate converting characters.
|
||||
*
|
||||
* This method is workaround for TextAreaPainter.
|
||||
* Because, TextAreaPainter can't treat AttributedCharacterIterator directly.
|
||||
* AttributedCharacterIterator has very important information when composing text.
|
||||
* It has a map where are converted characters and committed characters.
|
||||
* Ideally, changing TextAreaPainter method can treat AttributedCharacterIterator is better. But it's very tough!!
|
||||
* So I choose to write some code as a workaround.
|
||||
*
|
||||
* This draw method is proceeded with the following steps.
|
||||
* 1. Original TextAreaPainter draws characters.
|
||||
* 2. This refillComposedArea method erase previous paint characters by textarea's background color.
|
||||
* The refill area is only square that width and height defined by characters with input method.
|
||||
* 3. CompositionTextPainter.draw method paints composed text. It was actually drawn by TextLayout.
|
||||
*
|
||||
* @param gfx set TextAreaPainter's Graphics object.
|
||||
* @param fillBackGroundColor set textarea's background.
|
||||
*/
|
||||
public void draw(Graphics gfx, Color fillBackGroundColor) {
|
||||
assert(composedTextLayout != null);
|
||||
Point composedLoc = getCaretLocation();
|
||||
refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y);
|
||||
composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill color to erase characters drawn by original TextAreaPainter.
|
||||
*
|
||||
* @param fillColor fill color to erase characters drawn by original TextAreaPainter method.
|
||||
* @param x x-coordinate where to fill.
|
||||
* @param y y-coordinate where to fill.
|
||||
*/
|
||||
private void refillComposedArea(Color fillColor, int x, int y) {
|
||||
Graphics gfx = textArea.getPainter().getGraphics();
|
||||
gfx.setColor(fillColor);
|
||||
FontMetrics fm = textArea.getPainter().getFontMetrics();
|
||||
int newY = y - (fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT);
|
||||
int paintHeight = fm.getHeight();
|
||||
int paintWidth = (int) composedTextLayout.getBounds().getWidth();
|
||||
gfx.fillRect(x, newY, paintWidth, paintHeight);
|
||||
}
|
||||
|
||||
private Point getCaretLocation() {
|
||||
Point loc = new Point();
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
FontMetrics fm = painter.getFontMetrics();
|
||||
int offsetY = fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT;
|
||||
int lineIndex = textArea.getCaretLine();
|
||||
loc.y = lineIndex * fm.getHeight() + offsetY;
|
||||
int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(lineIndex);
|
||||
loc.x = textArea.offsetToX(lineIndex, offsetX);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user