diff --git a/app/src/processing/app/syntax/im/CompositionTextManager.java b/app/src/processing/app/syntax/im/CompositionTextManager.java
index 12a9ad341..04fbe5f74 100644
--- a/app/src/processing/app/syntax/im/CompositionTextManager.java
+++ b/app/src/processing/app/syntax/im/CompositionTextManager.java
@@ -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() {
diff --git a/app/src/processing/app/syntax/im/CompositionTextPainter.java b/app/src/processing/app/syntax/im/CompositionTextPainter.java
index c12d7ed30..bb0e43e98 100644
--- a/app/src/processing/app/syntax/im/CompositionTextPainter.java
+++ b/app/src/processing/app/syntax/im/CompositionTextPainter.java
@@ -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;
}
}
diff --git a/app/src/processing/app/syntax/im/InputMethodSupport.java b/app/src/processing/app/syntax/im/InputMethodSupport.java
index 461be3d16..921cfc83b 100644
--- a/app/src/processing/app/syntax/im/InputMethodSupport.java
+++ b/app/src/processing/app/syntax/im/InputMethodSupport.java
@@ -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 Bug 854 : implement input method support for Japanese (and other languages)
+ * @see Bug 1531 : Can't input full-width space when Japanese IME is on.
+ * @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();
+ }
+ }
}