OSDN Git Service

remove subversion keyword
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / TextEditor.java
1 /*\r
2  * 原稿作成支援用テキストコンポーネント\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2008 olyutorskii\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf;\r
9 \r
10 import java.awt.Rectangle;\r
11 import java.awt.event.InputMethodEvent;\r
12 import java.awt.event.InputMethodListener;\r
13 import java.nio.CharBuffer;\r
14 import java.text.AttributedCharacterIterator;\r
15 import javax.swing.JTextArea;\r
16 import javax.swing.text.AbstractDocument;\r
17 import javax.swing.text.AttributeSet;\r
18 import javax.swing.text.BadLocationException;\r
19 import javax.swing.text.Document;\r
20 import javax.swing.text.DocumentFilter;\r
21 import javax.swing.text.DocumentFilter.FilterBypass;\r
22 import javax.swing.text.PlainDocument;\r
23 \r
24 /**\r
25  * 原稿作成支援用テキストコンポーネント。\r
26  */\r
27 @SuppressWarnings("serial")\r
28 public class TextEditor extends JTextArea\r
29         implements InputMethodListener {\r
30 \r
31     private static final int MAX_DOCUMENT = 10 * 1000;\r
32 \r
33     private final DocumentFilter documentFilter = new CustomFilter();\r
34 \r
35     private boolean onIMEoperation = false;\r
36 \r
37     /**\r
38      * コンストラクタ。\r
39      */\r
40     public TextEditor(){\r
41         super();\r
42 \r
43         setLineWrap(true);\r
44         setWrapStyleWord(false);\r
45 \r
46         Document document = new PlainDocument();\r
47         setDocument(document);\r
48 \r
49         addInputMethodListener(this);\r
50 \r
51         return;\r
52     }\r
53 \r
54     /**\r
55      * エディタが現在IME操作中か判定する。\r
56      * @return IME操作中ならtrue\r
57      */\r
58     public boolean onIMEoperation(){\r
59         return this.onIMEoperation;\r
60     }\r
61 \r
62     /**\r
63      * 現在のカーソルが表示されるようスクロールエリアを操作する。\r
64      */\r
65     public void scrollCaretToVisible(){\r
66         int caretPosition = getCaretPosition();\r
67 \r
68         Rectangle caretBounds;\r
69         try{\r
70             caretBounds = modelToView(caretPosition);\r
71         }catch(BadLocationException e){\r
72             assert false;\r
73             return;\r
74         }\r
75 \r
76         scrollRectToVisible(caretBounds);\r
77 \r
78         return;\r
79     }\r
80 \r
81     /**\r
82      * {@inheritDoc}\r
83      * Document変更をフックしてフィルタを仕込む。\r
84      * @param document {@inheritDoc}\r
85      */\r
86     @Override\r
87     public final void setDocument(Document document){\r
88         Document oldDocument = getDocument();\r
89         if(oldDocument instanceof AbstractDocument){\r
90             AbstractDocument abstractDocument =\r
91                     (AbstractDocument) oldDocument;\r
92             abstractDocument.setDocumentFilter(null);\r
93         }\r
94 \r
95         super.setDocument(document);\r
96 \r
97         if(document instanceof AbstractDocument){\r
98             AbstractDocument abstractDocument = (AbstractDocument) document;\r
99             abstractDocument.setDocumentFilter(this.documentFilter);\r
100         }\r
101 \r
102         return;\r
103     }\r
104 \r
105     /**\r
106      * {@inheritDoc}\r
107      * このエディタ中の指定領域が表示されるようスクロールエリアを操作する。\r
108      * キーボードフォーカスを保持しないときは無視。\r
109      * @param rect {@inheritDoc}\r
110      */\r
111     @Override\r
112     public void scrollRectToVisible(Rectangle rect){\r
113         if( ! hasFocus() ) return;\r
114         super.scrollRectToVisible(rect);\r
115         return;\r
116     }\r
117 \r
118     /**\r
119      * {@inheritDoc}\r
120      * @param event {@inheritDoc}\r
121      */\r
122     public void caretPositionChanged(InputMethodEvent event){\r
123         // NOTHING\r
124         return;\r
125     }\r
126 \r
127     /**\r
128      * {@inheritDoc}\r
129      * このテキストエディタで現在IMEの変換中か否か判定する処理を含む。\r
130      * @param event {@inheritDoc}\r
131      */\r
132     public void inputMethodTextChanged(InputMethodEvent event){\r
133         int committed = event.getCommittedCharacterCount();\r
134         AttributedCharacterIterator aci = event.getText();\r
135         if(aci == null){\r
136             this.onIMEoperation = false;\r
137             return;\r
138         }\r
139         int begin = aci.getBeginIndex();\r
140         int end   = aci.getEndIndex();\r
141         int span = end - begin;\r
142 \r
143         if(committed >= span) this.onIMEoperation = false;\r
144         else                  this.onIMEoperation = true;\r
145 \r
146         return;\r
147     }\r
148 \r
149     /**\r
150      * 入力文字列に制限を加えるDocumentFilter。\r
151      * \n,\f 以外の制御文字はタブも含め入力禁止。\r
152      * U+FFFF はjava.textパッケージで特別扱いなのでこれも入力禁止。\r
153      * ※ ただしIME操作中は制限なし。\r
154      */\r
155     private class CustomFilter extends DocumentFilter{\r
156 \r
157         /**\r
158          * 入力禁止文字の判定。\r
159          * @param ch 検査対象文字\r
160          * @return 入力禁止ならfalse。ただしIME操作中は必ずtrue。\r
161          */\r
162         private boolean isValid(char ch){\r
163             if(onIMEoperation()) return true;\r
164 \r
165             if(ch == '\n') return true;\r
166 //          if(ch == '\f') return true;\r
167 \r
168             if(ch == '\uffff')             return false;\r
169             if(Character.isISOControl(ch)) return false;\r
170 \r
171 //          if( ! CodeX0208.isValid(ch) ) return false;\r
172             if(Character.isHighSurrogate(ch)) return false;\r
173             if(Character.isLowSurrogate(ch) ) return false;\r
174 \r
175             return true;\r
176         }\r
177 \r
178         /**\r
179          * 与えられた文字列から入力禁止文字を除いた文字列に変換する。\r
180          * @param input 検査対象文字列\r
181          * @return 除去済み文字列\r
182          */\r
183         private String filter(CharSequence input){\r
184             if(onIMEoperation()) return input.toString();\r
185 \r
186             int length = input.length();\r
187             CharBuffer buf = CharBuffer.allocate(length);\r
188 \r
189             for(int pos = 0; pos < length; pos++){\r
190                 char ch = input.charAt(pos);\r
191                 if(ch == '\u2211') ch = '\u03a3'; // Σ変換\r
192                 if(ch == '\u00ac') ch = '\uffe2'; // ¬変換\r
193 //              if(ch ==  0x005c ) ch = '\u00a5'; // バックスラッシュから円へ\r
194                 if(isValid(ch)) buf.append(ch);\r
195             }\r
196 \r
197             buf.flip();\r
198             return buf.toString();\r
199         }\r
200 \r
201         /**\r
202          * コンストラクタ。\r
203          */\r
204         public CustomFilter(){\r
205             super();\r
206             return;\r
207         }\r
208 \r
209         /**\r
210          * {@inheritDoc}\r
211          * @param fb {@inheritDoc}\r
212          * @param offset {@inheritDoc}\r
213          * @param text {@inheritDoc}\r
214          * @param attrs {@inheritDoc}\r
215          * @throws javax.swing.text.BadLocationException {@inheritDoc}\r
216          */\r
217         @Override\r
218         public void insertString(FilterBypass fb,\r
219                                  int offset,\r
220                                  String text,\r
221                                  AttributeSet attrs)\r
222                                  throws BadLocationException{\r
223             String filtered = filter(text);\r
224 \r
225             if( ! onIMEoperation() ){\r
226                 Document document = fb.getDocument();\r
227                 int docLength = document.getLength();\r
228                 int rest = MAX_DOCUMENT - docLength;\r
229                 if(rest < 0){\r
230                     return;\r
231                 }else if(rest < filtered.length()){\r
232                     filtered = filtered.substring(0, rest);\r
233                 }\r
234             }\r
235 \r
236             fb.insertString(offset, filtered, attrs);\r
237 \r
238             return;\r
239         }\r
240 \r
241         /**\r
242          *  {@inheritDoc}\r
243          * @param fb {@inheritDoc}\r
244          * @param offset {@inheritDoc}\r
245          * @param length {@inheritDoc}\r
246          * @param text {@inheritDoc}\r
247          * @param attrs {@inheritDoc}\r
248          * @throws javax.swing.text.BadLocationException {@inheritDoc}\r
249          */\r
250         @Override\r
251         public void replace(FilterBypass fb,\r
252                             int offset,\r
253                             int length,\r
254                             String text,\r
255                             AttributeSet attrs)\r
256                             throws BadLocationException{\r
257             String filtered = filter(text);\r
258 \r
259             if( ! onIMEoperation() ){\r
260                 Document document = fb.getDocument();\r
261                 int docLength = document.getLength();\r
262                 docLength -= length;\r
263                 int rest = MAX_DOCUMENT - docLength;\r
264                 if(rest < 0){\r
265                     return;\r
266                 }else if(rest < filtered.length()){\r
267                     filtered = filtered.substring(0, rest);\r
268                 }\r
269             }\r
270 \r
271             fb.replace(offset, length, filtered, attrs);\r
272 \r
273             return;\r
274         }\r
275     }\r
276 \r
277     // TODO 禁則チェック。20文字を超える長大なブレーク禁止文字列の出現の監視。\r
278     // TODO 連続したホワイトスペースに対する警告。\r
279     // TODO 先頭もしくは末尾のホワイトスペース出現に対する警告。\r
280     // TODO 改行記号の表示\r
281     // TODO 改発言記号の導入\r
282 }\r