package processing.app.syntax.im; import java.awt.Color; 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.syntax.JEditTextArea; /** * Paint texts from input method. Text via input method are transmitted by * AttributedCaharacterIterator. This class helps the PDE's TextAreaPainter * to handle AttributedCaharacterIterator. * * 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; private boolean caretColorFlag; private TextHitInfo caret; /** * 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. */ public boolean hasComposedTextLayout() { return (composedTextLayout != null); } /** * Set TextLayout to the painter. * TextLayout will be created and set by CompositionTextManager. * * @see CompositionTextManager */ 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. 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); 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()); 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); // } private Point getCaretLocation() { 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; } }