001: /*
002: * InputMethodSupport.java - Input method support for JEditTextArea
003: *
004: * :tabSize=8:indentSize=8:noTabs=false:
005: * :folding=explicit:collapseFolds=1:
006: *
007: * Copyright (C) 2006 Kazutoshi Satoda
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023:
024: package org.gjt.sp.jedit.textarea;
025:
026: // {{{ Imports
027: import java.text.AttributedString;
028: import java.text.AttributedCharacterIterator;
029: import java.awt.Point;
030: import java.awt.Rectangle;
031: import java.awt.Graphics2D;
032: import java.awt.FontMetrics;
033: import java.awt.im.InputMethodRequests;
034: import java.awt.event.InputMethodListener;
035: import java.awt.event.InputMethodEvent;
036: import java.awt.font.TextLayout;
037: import java.awt.font.TextAttribute;
038: import java.awt.font.TextHitInfo;
039:
040: // }}}
041:
042: /**
043: * Input method support for JEditTextArea
044: *
045: * @author Kazutoshi Satoda
046: * @since jEdit 4.3pre7
047: */
048:
049: class InputMethodSupport extends TextAreaExtension implements
050: InputMethodRequests, InputMethodListener {
051: // The owner.
052: private TextArea owner;
053: // The composed text layout which was built from last InputMethodEvent.
054: private TextLayout composedTextLayout = null;
055: // The X offset to the caret in the composed text.
056: private int composedCaretX = 0;
057: // Last committed information to support cancelLatestCommittedText()
058: private int lastCommittedAt = 0;
059: private String lastCommittedText = null;
060:
061: public InputMethodSupport(TextArea owner) {
062: this .owner = owner;
063: owner.addInputMethodListener(this );
064: owner.getPainter().addExtension(TextAreaPainter.HIGHEST_LAYER,
065: this );
066: }
067:
068: // {{{ Private utilities
069: // Compute return value of getTextLocation() from (x, y).
070: private Rectangle getCaretRectangle(int x, int y) {
071: TextAreaPainter painter = owner.getPainter();
072: Point origin = painter.getLocationOnScreen();
073: int height = painter.getFontMetrics().getHeight();
074: return new Rectangle(origin.x + x, origin.y + y, 0, height);
075: }
076:
077: // }}}
078:
079: // {{{ extends TextAreaExtension
080: public void paintValidLine(Graphics2D gfx, int screenLine,
081: int physicalLine, int start, int end, int y) {
082: if (composedTextLayout != null) {
083: int caret = owner.getCaretPosition();
084: if (start <= caret && caret < end) {
085: TextAreaPainter painter = owner.getPainter();
086: // The hight and baseline are taken from
087: // painter's FontMetrics instead of TextLayout
088: // so that the composed text is rendered at
089: // the same position with text in the TextArea.
090: FontMetrics fm = painter.getFontMetrics();
091: int x = owner.offsetToXY(caret).x;
092: int width = Math.round(composedTextLayout.getAdvance());
093: int height = fm.getHeight();
094: int offset_to_baseline = height - fm.getLeading()
095: - fm.getDescent();
096: int caret_x = x + composedCaretX;
097:
098: gfx.setColor(painter.getBackground());
099: gfx.fillRect(x, y, width, height);
100: gfx.setColor(painter.getForeground());
101: composedTextLayout.draw(gfx, x, y + offset_to_baseline);
102: gfx.setColor(painter.getCaretColor());
103: gfx.drawLine(caret_x, y, caret_x, y + height - 1);
104: }
105: }
106: }
107:
108: // }}}
109:
110: // {{{ implements InputMethodRequests
111: public Rectangle getTextLocation(TextHitInfo offset) {
112: if (composedTextLayout != null) {
113: // return location of composed text.
114: Point caret = owner.offsetToXY(owner.getCaretPosition());
115: return getCaretRectangle(caret.x + composedCaretX, caret.y);
116: } else {
117: // return location of selected text.
118: Selection selection_on_caret = owner
119: .getSelectionAtOffset(owner.getCaretPosition());
120: if (selection_on_caret != null) {
121: Point selection_start = owner
122: .offsetToXY(selection_on_caret.getStart());
123: return getCaretRectangle(selection_start.x,
124: selection_start.y);
125: }
126: }
127: return null;
128: }
129:
130: public TextHitInfo getLocationOffset(int x, int y) {
131: if (composedTextLayout != null) {
132: Point origin = owner.getPainter().getLocationOnScreen();
133: Point caret = owner.offsetToXY(owner.getCaretPosition());
134: float local_x = x - origin.x - caret.x;
135: float local_y = y - origin.y - caret.y
136: - composedTextLayout.getLeading()
137: - composedTextLayout.getAscent();
138: return composedTextLayout.hitTestChar(local_x, local_y);
139: }
140: return null;
141: }
142:
143: public int getInsertPositionOffset() {
144: return owner.getCaretPosition();
145: }
146:
147: public AttributedCharacterIterator getCommittedText(int beginIndex,
148: int endIndex,
149: AttributedCharacterIterator.Attribute[] attributes) {
150: return (new AttributedString(owner.getText(beginIndex, endIndex
151: - beginIndex))).getIterator();
152: }
153:
154: public int getCommittedTextLength() {
155: return owner.getBufferLength();
156: }
157:
158: public AttributedCharacterIterator cancelLatestCommittedText(
159: AttributedCharacterIterator.Attribute[] attributes) {
160: if (lastCommittedText != null) {
161: int offset = lastCommittedAt;
162: int length = lastCommittedText.length();
163: String sample = owner.getText(offset, length);
164: if (sample != null && sample.equals(lastCommittedText)) {
165: AttributedCharacterIterator canceled = (new AttributedString(
166: sample)).getIterator();
167: owner.getBuffer().remove(offset, length);
168: owner.setCaretPosition(offset);
169: lastCommittedText = null;
170: return canceled;
171: }
172: // Cleare last committed information to prevent
173: // accidental match.
174: lastCommittedText = null;
175: }
176: return null;
177: }
178:
179: public AttributedCharacterIterator getSelectedText(
180: AttributedCharacterIterator.Attribute[] attributes) {
181: Selection selection_on_caret = owner.getSelectionAtOffset(owner
182: .getCaretPosition());
183: if (selection_on_caret != null) {
184: return (new AttributedString(owner
185: .getSelectedText(selection_on_caret)))
186: .getIterator();
187: }
188: return null;
189: }
190:
191: // }}}
192:
193: // {{{ implements InputMethodListener
194: public void inputMethodTextChanged(InputMethodEvent event) {
195: composedTextLayout = null;
196: AttributedCharacterIterator text = event.getText();
197: if (text != null) {
198: int committed_count = event.getCommittedCharacterCount();
199: if (committed_count > 0) {
200: lastCommittedText = null;
201: lastCommittedAt = owner.getCaretPosition();
202: StringBuffer committed = new StringBuffer(
203: committed_count);
204: char c;
205: int count;
206: for (c = text.first(), count = committed_count; c != AttributedCharacterIterator.DONE
207: && count > 0; c = text.next(), --count) {
208: owner.userInput(c);
209: committed.append(c);
210: }
211: lastCommittedText = committed.toString();
212: }
213: int end_index = text.getEndIndex();
214: if (committed_count < end_index) {
215: AttributedString composed = new AttributedString(text,
216: committed_count, end_index);
217: TextAreaPainter painter = owner.getPainter();
218: composed.addAttribute(TextAttribute.FONT, painter
219: .getFont());
220: composedTextLayout = new TextLayout(composed
221: .getIterator(), painter.getFontRenderContext());
222: }
223: }
224: // Also updates caret.
225: caretPositionChanged(event);
226: }
227:
228: public void caretPositionChanged(InputMethodEvent event) {
229: composedCaretX = 0;
230: if (composedTextLayout != null) {
231: TextHitInfo caret = event.getCaret();
232: if (caret != null) {
233: composedCaretX = Math.round(composedTextLayout
234: .getCaretInfo(caret)[0]);
235: }
236: // Adjust visiblity.
237: int insertion_x = owner
238: .offsetToXY(owner.getCaretPosition()).x;
239: TextHitInfo visible = event.getVisiblePosition();
240: int composed_visible_x = (visible != null) ? Math
241: .round(composedTextLayout.getCaretInfo(visible)[0])
242: : composedCaretX;
243: int visible_x = insertion_x + composed_visible_x;
244: int painter_width = owner.getPainter().getWidth();
245: int adjustment = 0;
246: if (visible_x < 0) {
247: adjustment = visible_x;
248: }
249: if (visible_x >= painter_width) {
250: adjustment = visible_x - (painter_width - 1);
251: }
252: if (adjustment != 0) {
253: owner.setHorizontalOffset(owner.getHorizontalOffset()
254: - adjustment);
255: }
256: } else {
257: /* Cancel horizontal adjustment for composed text.
258: FIXME:
259: The horizontal offset may be beyond the max
260: value of owner's horizontal scroll bar.
261: */
262: owner.scrollToCaret(false);
263: }
264: /* Invalidate one more line below the caret because
265: the underline for composed text goes beyond the caret
266: line in some font settings. */
267: int caret_line = owner.getCaretLine();
268: owner.invalidateLineRange(caret_line, caret_line + 1);
269: event.consume();
270: }
271: // }}}
272: }
|