PageRenderTime 195ms CodeModel.GetById 129ms app.highlight 23ms RepoModel.GetById 39ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/textarea/InputMethodSupport.java

#
Java | 290 lines | 208 code | 21 blank | 61 comment | 33 complexity | 975c49a9a3dbb18f710a1e4ee5e05f3c MD5 | raw file
  1/*
  2 * InputMethodSupport.java - Input method support for JEditTextArea
  3 *
  4 * :tabSize=8:indentSize=8:noTabs=false:
  5 * :folding=explicit:collapseFolds=1:
  6 *
  7 * Copyright (C) 2006 Kazutoshi Satoda
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit.textarea;
 25
 26// {{{ Imports
 27import java.text.AttributedString;
 28import java.text.AttributedCharacterIterator;
 29import java.awt.Point;
 30import java.awt.Rectangle;
 31import java.awt.Graphics2D;
 32import java.awt.FontMetrics;
 33import java.awt.im.InputMethodRequests;
 34import java.awt.event.InputMethodListener;
 35import java.awt.event.InputMethodEvent;
 36import java.awt.font.TextLayout;
 37import java.awt.font.TextAttribute;
 38import java.awt.font.TextHitInfo;
 39// }}}
 40
 41/**
 42 * Input method support for JEditTextArea
 43 *
 44 * @author Kazutoshi Satoda
 45 * @since jEdit 4.3pre7
 46 */
 47
 48class InputMethodSupport
 49	extends TextAreaExtension
 50	implements InputMethodRequests, InputMethodListener
 51{
 52	// The owner.
 53	private TextArea owner;
 54	// The composed text layout which was built from last InputMethodEvent.
 55	private TextLayout composedTextLayout = null;
 56	// The X offset to the caret in the composed text.
 57	private int composedCaretX = 0;
 58	// Last committed information to support cancelLatestCommittedText()
 59	private int lastCommittedAt = 0;
 60	private String lastCommittedText = null;
 61
 62	public InputMethodSupport(TextArea owner)
 63	{
 64		this.owner = owner;
 65		owner.addInputMethodListener(this);
 66		owner.getPainter().addExtension(TextAreaPainter.HIGHEST_LAYER, this);
 67	}
 68
 69
 70	// {{{ Private utilities
 71	// Compute return value of getTextLocation() from (x, y).
 72	private Rectangle getCaretRectangle(int x, int y)
 73	{
 74		TextAreaPainter painter = owner.getPainter();
 75		Point origin = painter.getLocationOnScreen();
 76		int height = painter.getLineHeight();
 77		return new Rectangle(origin.x + x, origin.y + y, 0, height);
 78	}
 79	// }}}
 80
 81
 82	// {{{ extends TextAreaExtension
 83	public void paintValidLine(Graphics2D gfx, int screenLine,
 84				   int physicalLine, int start, int end, int y)
 85	{
 86		if(composedTextLayout != null)
 87		{
 88			int caret = owner.getCaretPosition();
 89			if(start <= caret && caret < end)
 90			{
 91				TextAreaPainter painter = owner.getPainter();
 92				// The hight and baseline are taken from
 93				// painter's FontMetrics instead of TextLayout
 94				// so that the composed text is rendered at
 95				// the same position with text in the TextArea.
 96				FontMetrics fm = painter.getFontMetrics();
 97				int x = owner.offsetToXY(caret).x;
 98				int width = Math.round(composedTextLayout.getAdvance());
 99				int height = painter.getLineHeight();
100				int offset_to_baseline = height
101					- (fm.getLeading()+1) - fm.getDescent();
102				int caret_x = x + composedCaretX;
103
104				gfx.setColor(painter.getBackground());
105				gfx.fillRect(x, y, width, height);
106				gfx.setColor(painter.getForeground());
107				composedTextLayout.draw(gfx, x, y + offset_to_baseline);
108				gfx.setColor(painter.getCaretColor());
109				gfx.drawLine(caret_x, y, caret_x, y + height - 1);
110			}
111		}
112	}
113	// }}}
114
115
116	// {{{ implements InputMethodRequests
117	public Rectangle getTextLocation(TextHitInfo offset)
118	{
119		if(composedTextLayout != null)
120		{
121			// return location of composed text.
122			Point caret = owner.offsetToXY(owner.getCaretPosition());
123			return getCaretRectangle(caret.x + composedCaretX, caret.y);
124		}
125		else
126		{
127			// return location of selected text.
128			Selection selection_on_caret = owner.getSelectionAtOffset(owner.getCaretPosition());
129			if(selection_on_caret != null)
130			{
131				Point selection_start = owner.offsetToXY(selection_on_caret.getStart());
132				return getCaretRectangle(selection_start.x, selection_start.y);
133			}
134		}
135		return null;
136	}
137
138	public TextHitInfo getLocationOffset(int x, int y)
139	{
140		if(composedTextLayout != null)
141		{
142			Point origin = owner.getPainter().getLocationOnScreen();
143			Point caret = owner.offsetToXY(owner.getCaretPosition());
144			float local_x = x - origin.x - caret.x;
145			float local_y = y - origin.y - caret.y
146				- (composedTextLayout.getLeading()+1)
147				- composedTextLayout.getAscent();
148			return composedTextLayout.hitTestChar(local_x, local_y);
149		}
150		return null;
151	}
152
153	public int getInsertPositionOffset()
154	{
155		return owner.getCaretPosition();
156	}
157
158	public AttributedCharacterIterator getCommittedText(int beginIndex , int endIndex
159		, AttributedCharacterIterator.Attribute[] attributes)
160	{
161		return (new AttributedString(owner.getText(beginIndex, endIndex - beginIndex))).getIterator();
162	}
163
164	public int getCommittedTextLength()
165	{
166		return owner.getBufferLength();
167	}
168
169	public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes)
170	{
171		if(lastCommittedText != null)
172		{
173			int offset = lastCommittedAt;
174			int length = lastCommittedText.length();
175			String sample = owner.getText(offset, length);
176			if(sample != null && sample.equals(lastCommittedText))
177			{
178				AttributedCharacterIterator canceled = (new AttributedString(sample)).getIterator();
179				owner.getBuffer().remove(offset, length);
180				owner.setCaretPosition(offset);
181				lastCommittedText = null;
182				return canceled;
183			}
184			// Cleare last committed information to prevent
185			// accidental match.
186			lastCommittedText = null;
187		}
188		return null;
189	}
190
191	public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes)
192	{
193		Selection selection_on_caret = owner.getSelectionAtOffset(owner.getCaretPosition());
194		if(selection_on_caret != null)
195		{
196			return (new AttributedString(owner.getSelectedText(selection_on_caret))).getIterator();
197		}
198		return null;
199	}
200	// }}}
201
202
203	// {{{ implements InputMethodListener
204	public void inputMethodTextChanged(InputMethodEvent event)
205	{
206		composedTextLayout = null;
207		AttributedCharacterIterator text = event.getText();
208		if(text != null)
209		{
210			int committed_count = event.getCommittedCharacterCount();
211			if(committed_count > 0)
212			{
213				lastCommittedText = null;
214				lastCommittedAt = owner.getCaretPosition();
215				StringBuilder committed = new StringBuilder(committed_count);
216				char c;
217				int count;
218				for(c = text.first(), count = committed_count
219					; c != AttributedCharacterIterator.DONE && count > 0
220					; c = text.next(), --count)
221				{
222					owner.userInput(c);
223					committed.append(c);
224				}
225				lastCommittedText = committed.toString();
226			}
227			int end_index = text.getEndIndex();
228			if(committed_count < end_index)
229			{
230				AttributedString composed = new AttributedString(text, committed_count, end_index);
231				TextAreaPainter painter = owner.getPainter();
232				composed.addAttribute(TextAttribute.FONT, painter.getFont());
233				composedTextLayout = new TextLayout(composed.getIterator()
234					, painter.getFontRenderContext());
235			}
236		}
237		// Also updates caret.
238		caretPositionChanged(event);
239	}
240
241	public void caretPositionChanged(InputMethodEvent event)
242	{
243		composedCaretX = 0;
244		if(composedTextLayout != null)
245		{
246			TextHitInfo caret = event.getCaret();
247			if(caret != null)
248			{
249				composedCaretX = Math.round(composedTextLayout.getCaretInfo(caret)[0]);
250			}
251			// Adjust visiblity.
252			int insertion_x = owner.offsetToXY(owner.getCaretPosition()).x;
253			TextHitInfo visible = event.getVisiblePosition();
254			int composed_visible_x = (visible != null)
255				? Math.round(composedTextLayout.getCaretInfo(visible)[0])
256				: composedCaretX;
257			int visible_x = insertion_x + composed_visible_x;
258			int painter_width = owner.getPainter().getWidth();
259			int adjustment = 0;
260			if(visible_x < 0)
261			{
262				adjustment = visible_x;
263			}
264			if(visible_x >= painter_width)
265			{
266				adjustment = visible_x - (painter_width - 1);
267			}
268			if(adjustment != 0)
269			{
270				owner.setHorizontalOffset(owner.getHorizontalOffset() - adjustment);
271			}
272		}
273		else
274		{
275			/* Cancel horizontal adjustment for composed text.
276			   FIXME:
277			     The horizontal offset may be beyond the max
278			     value of owner's horizontal scroll bar.
279			*/
280			owner.scrollToCaret(false);
281		}
282		/* Invalidate one more line below the caret because
283		   the underline for composed text goes beyond the caret
284		   line in some font settings. */
285		int caret_line = owner.getCaretLine();
286		owner.invalidateLineRange(caret_line, caret_line + 1);
287		event.consume();
288	}
289	// }}}
290}