4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sfjp.jindolf.editor;
10 import java.awt.Rectangle;
11 import java.awt.event.InputMethodEvent;
12 import java.awt.event.InputMethodListener;
13 import java.nio.CharBuffer;
14 import java.text.AttributedCharacterIterator;
15 import javax.swing.JTextArea;
16 import javax.swing.text.AbstractDocument;
17 import javax.swing.text.AttributeSet;
18 import javax.swing.text.BadLocationException;
19 import javax.swing.text.Document;
20 import javax.swing.text.DocumentFilter;
21 import javax.swing.text.DocumentFilter.FilterBypass;
22 import javax.swing.text.PlainDocument;
27 @SuppressWarnings("serial")
28 public class TextEditor extends JTextArea
29 implements InputMethodListener {
31 private static final int MAX_DOCUMENT = 10 * 1000;
33 private final DocumentFilter documentFilter = new CustomFilter();
35 private boolean onIMEoperation = false;
40 @SuppressWarnings("LeakingThisInConstructor")
45 setWrapStyleWord(false);
47 Document document = new PlainDocument();
48 setDocument(document);
50 addInputMethodListener(this);
57 * @return IME操作中ならtrue
59 public boolean onIMEoperation(){
60 return this.onIMEoperation;
64 * 現在のカーソルが表示されるようスクロールエリアを操作する。
66 public void scrollCaretToVisible(){
67 int caretPosition = getCaretPosition();
69 Rectangle caretBounds;
71 caretBounds = modelToView(caretPosition);
72 }catch(BadLocationException e){
77 scrollRectToVisible(caretBounds);
84 * Document変更をフックしてフィルタを仕込む。
85 * @param document {@inheritDoc}
88 public final void setDocument(Document document){
89 Document oldDocument = getDocument();
90 if(oldDocument instanceof AbstractDocument){
91 AbstractDocument abstractDocument =
92 (AbstractDocument) oldDocument;
93 abstractDocument.setDocumentFilter(null);
96 super.setDocument(document);
98 if(document instanceof AbstractDocument){
99 AbstractDocument abstractDocument = (AbstractDocument) document;
100 abstractDocument.setDocumentFilter(this.documentFilter);
108 * このエディタ中の指定領域が表示されるようスクロールエリアを操作する。
109 * キーボードフォーカスを保持しないときは無視。
110 * @param rect {@inheritDoc}
113 public void scrollRectToVisible(Rectangle rect){
114 if( ! hasFocus() ) return;
115 super.scrollRectToVisible(rect);
121 * @param event {@inheritDoc}
124 public void caretPositionChanged(InputMethodEvent event){
131 * このテキストエディタで現在IMEの変換中か否か判定する処理を含む。
132 * @param event {@inheritDoc}
135 public void inputMethodTextChanged(InputMethodEvent event){
136 int committed = event.getCommittedCharacterCount();
137 AttributedCharacterIterator aci = event.getText();
139 this.onIMEoperation = false;
142 int begin = aci.getBeginIndex();
143 int end = aci.getEndIndex();
144 int span = end - begin;
146 if(committed >= span) this.onIMEoperation = false;
147 else this.onIMEoperation = true;
153 * 入力文字列に制限を加えるDocumentFilter。
154 * \n,\f 以外の制御文字はタブも含め入力禁止。
155 * U+FFFF はjava.textパッケージで特別扱いなのでこれも入力禁止。
158 private class CustomFilter extends DocumentFilter{
163 public CustomFilter(){
171 * @return 入力禁止ならfalse。ただしIME操作中は必ずtrue。
173 private boolean isValid(char ch){
174 if(onIMEoperation()) return true;
176 if(ch == '\n') return true;
177 // if(ch == '\f') return true;
179 if(ch == '\uffff') return false;
180 if(Character.isISOControl(ch)) return false;
182 // if( ! CodeX0208.isValid(ch) ) return false;
183 if(Character.isHighSurrogate(ch)) return false;
184 if(Character.isLowSurrogate(ch) ) return false;
190 * 与えられた文字列から入力禁止文字を除いた文字列に変換する。
191 * @param input 検査対象文字列
194 private String filter(CharSequence input){
195 if(onIMEoperation()) return input.toString();
197 int length = input.length();
198 CharBuffer buf = CharBuffer.allocate(length);
200 for(int pos = 0; pos < length; pos++){
201 char ch = input.charAt(pos);
202 if(ch == '\u2211') ch = '\u03a3'; // Σ変換
203 if(ch == '\u00ac') ch = '\uffe2'; // ¬変換
204 // if(ch == 0x005c ) ch = '\u00a5'; // バックスラッシュから円へ
205 if(isValid(ch)) buf.append(ch);
209 return buf.toString();
214 * @param fb {@inheritDoc}
215 * @param offset {@inheritDoc}
216 * @param text {@inheritDoc}
217 * @param attrs {@inheritDoc}
218 * @throws javax.swing.text.BadLocationException {@inheritDoc}
221 public void insertString(FilterBypass fb,
225 throws BadLocationException{
226 String filtered = filter(text);
228 if( ! onIMEoperation() ){
229 Document document = fb.getDocument();
230 int docLength = document.getLength();
231 int rest = MAX_DOCUMENT - docLength;
234 }else if(rest < filtered.length()){
235 filtered = filtered.substring(0, rest);
239 fb.insertString(offset, filtered, attrs);
246 * @param fb {@inheritDoc}
247 * @param offset {@inheritDoc}
248 * @param length {@inheritDoc}
249 * @param text {@inheritDoc}
250 * @param attrs {@inheritDoc}
251 * @throws javax.swing.text.BadLocationException {@inheritDoc}
254 public void replace(FilterBypass fb,
259 throws BadLocationException{
260 String filtered = filter(text);
262 if( ! onIMEoperation() ){
263 Document document = fb.getDocument();
264 int docLength = document.getLength();
266 int rest = MAX_DOCUMENT - docLength;
269 }else if(rest < filtered.length()){
270 filtered = filtered.substring(0, rest);
274 fb.replace(offset, length, filtered, attrs);
280 // TODO 禁則チェック。20文字を超える長大なブレーク禁止文字列の出現の監視。
281 // TODO 連続したホワイトスペースに対する警告。
282 // TODO 先頭もしくは末尾のホワイトスペース出現に対する警告。