mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
Merge pull request #4293 from satoshiokita/FIX-InputMethodCJKSupport
Fixed InputMethod Chiese/Japanese/Korean support.
This commit is contained in:
@@ -5,6 +5,7 @@ import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
@@ -14,6 +15,9 @@ import java.text.CharacterIterator;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Messages;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.syntax.TextAreaPainter;
|
||||
|
||||
@@ -152,14 +156,28 @@ public class CompositionTextManager {
|
||||
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();
|
||||
|
||||
private TextLayout getTextLayout(AttributedCharacterIterator text, int committedCount) {
|
||||
boolean antialias = Preferences.getBoolean("editor.smooth");
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
|
||||
// create attributed string with font info.
|
||||
AttributedString composed = new AttributedString(text, committedCount, text.getEndIndex());
|
||||
Font font = painter.getFontMetrics().getFont();
|
||||
composed.addAttribute(TextAttribute.FONT, font);
|
||||
TextLayout layout = new TextLayout(composed.getIterator(), context);
|
||||
return layout;
|
||||
|
||||
// set hint of antialiasing to render target.
|
||||
Graphics2D g2d = (Graphics2D)painter.getGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
antialias ?
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON :
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
FontRenderContext frc = g2d.getFontRenderContext();
|
||||
if (Base.DEBUG) {
|
||||
Messages.log("debug: FontRenderContext is Antialiased = " + frc.getAntiAliasingHint());
|
||||
}
|
||||
|
||||
return new TextLayout(composed.getIterator(), frc);
|
||||
}
|
||||
|
||||
private Point getCaretLocation() {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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.Rectangle;
|
||||
import java.awt.font.TextHitInfo;
|
||||
import java.awt.font.TextLayout;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Messages;
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
|
||||
/**
|
||||
@@ -24,8 +27,9 @@ public class CompositionTextPainter {
|
||||
private TextLayout composedTextLayout;
|
||||
private int composedBeginCaretPosition = 0;
|
||||
private JEditTextArea textArea;
|
||||
private boolean caretColorFlag;
|
||||
private TextHitInfo caret;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for painter.
|
||||
* @param textArea textarea used by PDE.
|
||||
@@ -83,45 +87,76 @@ public class CompositionTextPainter {
|
||||
*
|
||||
* 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.
|
||||
* 2. 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);
|
||||
// }
|
||||
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);
|
||||
//assert(composedTextLayout != null);
|
||||
if (composedTextLayout != null) {
|
||||
Point composedLoc = getCaretLocation();
|
||||
//refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y);
|
||||
composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y);
|
||||
|
||||
// draw caret.
|
||||
if (this.caret != null) {
|
||||
int caretLocation = Math.round(composedTextLayout.getCaretInfo(caret)[0]);
|
||||
Rectangle rect = new Rectangle(composedLoc.x + caretLocation,
|
||||
composedLoc.y - (int)composedTextLayout.getAscent(),
|
||||
1,
|
||||
(int)composedTextLayout.getAscent() + (int)composedTextLayout.getDescent());
|
||||
// blink caret.
|
||||
if (Base.DEBUG) {
|
||||
Messages.log(rect.toString());
|
||||
}
|
||||
Color c = gfx.getColor(); // save
|
||||
if (caretColorFlag) {
|
||||
caretColorFlag = false;
|
||||
gfx.setColor(Color.WHITE);
|
||||
} else {
|
||||
caretColorFlag = true;
|
||||
gfx.setColor(Color.BLACK);
|
||||
}
|
||||
gfx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
||||
gfx.setColor(c); // restore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
// /**
|
||||
// * 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() {
|
||||
FontMetrics fm = textArea.getPainter().getFontMetrics();
|
||||
int offsetY = fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT;
|
||||
int lineIndex = textArea.getCaretLine();
|
||||
int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(lineIndex);
|
||||
return new Point(textArea.offsetToX(lineIndex, offsetX),
|
||||
(lineIndex - textArea.getFirstLine()) * fm.getHeight() + offsetY);
|
||||
int line = textArea.getCaretLine();
|
||||
int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(line);
|
||||
// '+1' mean textArea.lineToY(line) + textArea.getPainter().getFontMetrics().getHeight().
|
||||
// TextLayout#draw method need at least one height of font.
|
||||
return new Point(textArea.offsetToX(line, offsetX), textArea.lineToY(line + 1));
|
||||
}
|
||||
|
||||
public void setCaret(TextHitInfo caret) {
|
||||
this.caret = caret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,120 +1,239 @@
|
||||
package processing.app.syntax.im;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
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.AttributedCharacterIterator.Attribute;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import java.text.AttributedString;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Messages;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.syntax.TextAreaPainter;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* http://dev.processing.org/bugs/show_bug.cgi?id=854
|
||||
* on-the-spot style input support for CJK.(Chinese, Japanese, Korean).
|
||||
* This class is implemented to fix Bug #854 from 2010-02-16.
|
||||
*
|
||||
* @see <a href="https://processing.org/bugs/bugzilla/854.html">Bug 854 : implement input method support for Japanese (and other languages)</a>
|
||||
* @see <a href="https://processing.org/bugs/bugzilla/1531.html">Bug 1531 : Can't input full-width space when Japanese IME is on.</a>
|
||||
* @see http://docs.oracle.com/javase/8/docs/technotes/guides/imf/index.html
|
||||
* @see http://docs.oracle.com/javase/tutorial/2d/text/index.html
|
||||
*
|
||||
* @author Takashi Maekawa (takachin@generative.info)
|
||||
* @author Satoshi Okita
|
||||
*/
|
||||
public class InputMethodSupport implements InputMethodRequests,
|
||||
InputMethodListener {
|
||||
|
||||
private static final Attribute[] CUSTOM_IM_ATTRIBUTES = {
|
||||
TextAttribute.INPUT_METHOD_HIGHLIGHT,
|
||||
};
|
||||
|
||||
private int committed_count = 0;
|
||||
private CompositionTextManager textManager;
|
||||
private TextHitInfo caret;
|
||||
private JEditTextArea textArea;
|
||||
private AttributedCharacterIterator composedText;
|
||||
private AttributedString composedTextString;
|
||||
|
||||
public InputMethodSupport(JEditTextArea textArea) {
|
||||
textManager = new CompositionTextManager(textArea);
|
||||
textArea.enableInputMethods(true);
|
||||
textArea.addInputMethodListener(this);
|
||||
this.textArea = textArea;
|
||||
this.textArea.enableInputMethods(true);
|
||||
this.textArea.addInputMethodListener(this);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// InputMethodRequest
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@Override
|
||||
public Rectangle getTextLocation(TextHitInfo offset) {
|
||||
return textManager.getTextLocation();
|
||||
if (Base.DEBUG) {
|
||||
Messages.log("#Called getTextLocation:" + offset);
|
||||
}
|
||||
int line = textArea.getCaretLine();
|
||||
int offsetX = textArea.getCaretPosition() - textArea.getLineStartOffset(line);
|
||||
// '+1' mean textArea.lineToY(line) + textArea.getPainter().getFontMetrics().getHeight().
|
||||
// TextLayout#draw method need at least one height of font.
|
||||
Rectangle rectangle = new Rectangle(textArea.offsetToX(line, offsetX), textArea.lineToY(line + 1), 0, 0);
|
||||
|
||||
Point location = textArea.getPainter().getLocationOnScreen();
|
||||
rectangle.translate(location.x, location.y);
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TextHitInfo getLocationOffset(int x, int y) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInsertPositionOffset() {
|
||||
return textManager.getInsertPositionOffset();
|
||||
return textArea.getCaretPosition() * -1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public AttributedCharacterIterator getCommittedText(int beginIndex,
|
||||
int endIndex, AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return textManager.getCommittedText(beginIndex, endIndex);
|
||||
int length = endIndex - beginIndex;
|
||||
String textAreaString = textArea.getText(beginIndex, length);
|
||||
return new AttributedString(textAreaString).getIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommittedTextLength() {
|
||||
return committed_count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributedCharacterIterator cancelLatestCommittedText(
|
||||
AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributedCharacterIterator getSelectedText(
|
||||
AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// InputMethodListener
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* 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();
|
||||
@Override
|
||||
public void inputMethodTextChanged(InputMethodEvent event) {
|
||||
composedText = null;
|
||||
if (Base.DEBUG) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("#Called inputMethodTextChanged");
|
||||
sb.append("\t ID: " + event.getID());
|
||||
sb.append("\t timestamp: " + new java.util.Date(event.getWhen()));
|
||||
sb.append("\t parmString: " + event.paramString());
|
||||
Messages.log(sb.toString());
|
||||
}
|
||||
|
||||
AttributedCharacterIterator text = event.getText(); // text = composedText + commitedText
|
||||
committed_count = event.getCommittedCharacterCount();
|
||||
if(isFullWidthSpaceInput(text)){
|
||||
textManager.insertFullWidthSpace();
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
|
||||
|
||||
// The caret for Input Method.
|
||||
// if you type a character by a input method, original caret become off.
|
||||
// a JEditTextArea is not implemented by the AttributedStirng and TextLayout.
|
||||
// so JEditTextArea Caret On-off logic.
|
||||
//
|
||||
// japanese : if the enter key pressed, event.getText is null.
|
||||
// japanese : if first space key pressed, event.getText is null.
|
||||
// chinese(pinin) : if a space key pressed, event.getText is null.
|
||||
// taiwan(bopomofo): ?
|
||||
// korean : ?
|
||||
textArea.setCaretVisible(false);
|
||||
// Korean Input Method
|
||||
if (text != null && text.getEndIndex() - (text.getBeginIndex() + committed_count) <= 0) {
|
||||
textArea.setCaretVisible(true);
|
||||
}
|
||||
if(isBeginInputProcess(text, textManager)){
|
||||
textManager.beginCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
// Japanese Input Method
|
||||
if (text == null) {
|
||||
textArea.setCaretVisible(true);
|
||||
}
|
||||
if (isInputProcess(text)){
|
||||
textManager.processCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
return;
|
||||
|
||||
char c;
|
||||
if (text != null) {
|
||||
int toCopy = committed_count;
|
||||
c = text.first();
|
||||
while (toCopy-- > 0) {
|
||||
if (Base.DEBUG) {
|
||||
Messages.log("INSERT:'" + c + "'");
|
||||
}
|
||||
this.insertCharacter(c);
|
||||
c = text.next();
|
||||
}
|
||||
|
||||
CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter();
|
||||
if (Base.DEBUG) {
|
||||
Messages.log(" textArea.getCaretPosition() + committed_count: " + (textArea.getCaretPosition() + committed_count));
|
||||
}
|
||||
compositionPainter.setComposedTextLayout(getTextLayout(text, committed_count), textArea.getCaretPosition() + committed_count);
|
||||
compositionPainter.setCaret(event.getCaret());
|
||||
} else {
|
||||
// hide input method.
|
||||
CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter();
|
||||
compositionPainter.setComposedTextLayout(null, 0);
|
||||
compositionPainter.setCaret(null);
|
||||
}
|
||||
textManager.endCompositionText(text, committed_count);
|
||||
caretPositionChanged(event);
|
||||
caret = event.getCaret();
|
||||
event.consume();
|
||||
textArea.repaint();
|
||||
}
|
||||
|
||||
private boolean isFullWidthSpaceInput(AttributedCharacterIterator text){
|
||||
if(text == null)
|
||||
return false;
|
||||
if(textManager.getIsInputProcess())
|
||||
return false;
|
||||
return (String.valueOf(text.first()).equals("\u3000"));
|
||||
}
|
||||
|
||||
private boolean isBeginInputProcess(AttributedCharacterIterator text, CompositionTextManager textManager){
|
||||
if(text == null)
|
||||
return false;
|
||||
if(textManager.getIsInputProcess())
|
||||
return false;
|
||||
return (isInputProcess(text));
|
||||
}
|
||||
|
||||
private boolean isInputProcess(AttributedCharacterIterator text){
|
||||
if(text == null)
|
||||
return false;
|
||||
return (text.getEndIndex() - (text.getBeginIndex() + committed_count) > 0);
|
||||
private TextLayout getTextLayout(AttributedCharacterIterator text, int committedCount) {
|
||||
boolean antialias = Preferences.getBoolean("editor.smooth");
|
||||
TextAreaPainter painter = textArea.getPainter();
|
||||
|
||||
// create attributed string with font info.
|
||||
//if (text.getEndIndex() - (text.getBeginIndex() + committedCharacterCount) > 0) {
|
||||
if (text.getEndIndex() - (text.getBeginIndex() + committedCount) > 0) {
|
||||
composedTextString = new AttributedString(text, committedCount, text.getEndIndex(), CUSTOM_IM_ATTRIBUTES);
|
||||
Font font = painter.getFontMetrics().getFont();
|
||||
composedTextString.addAttribute(TextAttribute.FONT, font);
|
||||
composedTextString.addAttribute(TextAttribute.BACKGROUND, Color.WHITE);
|
||||
composedText = composedTextString.getIterator();
|
||||
} else {
|
||||
composedTextString = new AttributedString("");
|
||||
return null;
|
||||
}
|
||||
|
||||
// set hint of antialiasing to render target.
|
||||
Graphics2D g2d = (Graphics2D)painter.getGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
antialias ?
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON :
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
FontRenderContext frc = g2d.getFontRenderContext();
|
||||
if (Base.DEBUG) {
|
||||
Messages.log("debug: FontRenderContext is Antialiased = " + frc.getAntiAliasingHint());
|
||||
}
|
||||
|
||||
return new TextLayout(composedTextString.getIterator(), frc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caretPositionChanged(InputMethodEvent event) {
|
||||
caret = event.getCaret();
|
||||
event.consume();
|
||||
}
|
||||
|
||||
private void insertCharacter(char c) {
|
||||
if (Base.DEBUG) {
|
||||
Messages.log("debug: insertCharacter(char c) textArea.getCaretPosition()=" + textArea.getCaretPosition());
|
||||
}
|
||||
try {
|
||||
textArea.getDocument().insertString(textArea.getCaretPosition(), Character.toString(c), null);
|
||||
if (Base.DEBUG) {
|
||||
Messages.log("debug: \t after:insertCharacter(char c) textArea.getCaretPosition()=" + textArea.getCaretPosition());
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user