1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package coras.table;
22
23 import java.awt.Color;
24 import java.awt.Cursor;
25 import java.awt.Dimension;
26 import java.awt.Graphics;
27 import java.awt.Point;
28 import java.awt.Polygon;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.awt.event.MouseAdapter;
32 import java.awt.event.MouseEvent;
33 import java.awt.event.MouseMotionAdapter;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Iterator;
37
38 import javax.swing.AbstractAction;
39 import javax.swing.Action;
40 import javax.swing.JMenuItem;
41 import javax.swing.JPopupMenu;
42 import javax.swing.JSeparator;
43 import javax.swing.JTextArea;
44 import javax.swing.text.AbstractDocument;
45 import javax.swing.text.AttributeSet;
46 import javax.swing.text.BadLocationException;
47 import javax.swing.text.Caret;
48 import javax.swing.text.DocumentFilter;
49
50 import coras.table.columnchoices.jaxb.ChoiceType;
51
52 /***
53 * @author fvr
54 *
55 * TODO To change the template for this generated type comment go to
56 * Window - Preferences - Java - Code Style - Code Templates
57 */
58 public class ChoiceTextArea extends JTextArea implements ActionListener {
59
60 private static final int SIZE = 5;
61 private Collection choices = null;
62 private JPopupMenu popupMenu = new JPopupMenu();
63 private boolean userEditable;
64 private boolean textValid = true;
65 private boolean doUpdateText = false;
66
67 public ChoiceTextArea() {
68 super();
69 addMouseListener(new MouseAdapter() {
70 public void mousePressed(MouseEvent e) {
71 maybePopup(e);
72 }
73 public void mouseReleased(MouseEvent e) {
74 maybePopup(e);
75 }
76 });
77 addMouseMotionListener(new MouseMotionAdapter() {
78 boolean lastInside = false;
79 public void mouseMoved(MouseEvent e) {
80 boolean inside = inside(e.getPoint());
81 if (inside != lastInside) {
82 lastInside = inside;
83 if (inside(e.getPoint())) {
84 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
85 } else {
86 setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
87 }
88 }
89 }
90 });
91 final AbstractDocument document = (AbstractDocument) getDocument();
92 document.setDocumentFilter(new DocumentFilter() {
93 public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
94 super.insertString(fb, offset, string, attr);
95 updateText();
96 }
97 public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
98 super.remove(fb, offset, length);
99
100 stateChanged();
101 }
102 public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
103 super.replace(fb, offset, length, text, attrs);
104 updateText();
105 }
106 });
107 }
108
109 public void actionPerformed(ActionEvent e) {
110 setText(e.getActionCommand());
111 }
112
113 public void setUserEditable(boolean editable) {
114 userEditable = editable;
115 stateChanged();
116 }
117
118 public synchronized void setText(String t) {
119 doUpdateText = false;
120 super.setText(t);
121 doUpdateText = true;
122 stateChanged();
123 }
124
125 public synchronized void setChoices(Collection newChoices) {
126 if (choices == newChoices || (choices != null && choices.equals(newChoices))) {
127 return;
128 }
129 choices = newChoices;
130
131 stateChanged();
132 }
133
134 public int getRowHeight() {
135 return super.getRowHeight();
136 }
137
138 public void paint(Graphics g) {
139 super.paint(g);
140 Collection localChoices = choices;
141 if (localChoices != null && localChoices.size() > 0) {
142 Polygon p = getPolygon();
143 g.setColor(Color.black);
144 g.fillPolygon(p);
145 }
146 if (!textValid) {
147 Polygon p = getPolygon();
148 g.setColor(Color.red);
149 g.fillPolygon(p);
150 }
151 }
152
153 private synchronized void maybePopup(MouseEvent e) {
154 Point p = e.getPoint();
155 if (inside(p) || e.isPopupTrigger()) {
156 updatePopupMenu();
157 popupMenu.show(this, p.x, p.y);
158 }
159 }
160
161 private synchronized void updateText() {
162 if (!doUpdateText) {
163
164 return;
165 }
166 doUpdateText = false;
167
168 Collection localChoices = choices;
169 String text = getText();
170 int length = text.length();
171 int pos = getCaretPosition();
172 if (length > 0 && pos == length && localChoices != null && localChoices.size() > 0) {
173 boolean found = false;
174 for (Iterator i = localChoices.iterator(); !found && i.hasNext();) {
175 ChoiceType choice = (ChoiceType) i.next();
176 String value = choice.getValue();
177
178
179 if ((userEditable && value.startsWith(text))
180 || (!userEditable && startsWithIgnoreCase(value, text))) {
181 setText(value);
182 Caret caret = getCaret();
183 caret.setDot(value.length());
184 caret.moveDot(pos);
185
186
187 found = true;
188 }
189 }
190 }
191 doUpdateText = true;
192
193 stateChanged();
194 }
195
196 private void stateChanged() {
197 textValid = isTextValid();
198 if (textValid) {
199 setForeground(Color.black);
200 setToolTipText(null);
201 } else {
202 setForeground(Color.red);
203 String text = "Allowed choices: ";
204 Collection localChoices = choices;
205 for (Iterator i = localChoices.iterator(); i.hasNext();) {
206 ChoiceType choice = (ChoiceType) i.next();
207 text += choice.getValue();
208 if (i.hasNext()) {
209 text += ", ";
210 }
211 }
212 setToolTipText(text);
213 }
214 setSize(getPreferredSize());
215 repaint();
216 }
217
218 /***
219 * @return
220 */
221 private boolean isTextValid() {
222 if (userEditable) {
223 return true;
224 }
225 String text = getText();
226 Collection localChoices = choices;
227 if (localChoices == null || localChoices.size() == 0) {
228 return true;
229 }
230 for (Iterator i = localChoices.iterator(); i.hasNext();) {
231 ChoiceType choice = (ChoiceType) i.next();
232 if (text.equals(choice.getValue())) {
233 return true;
234 }
235 }
236 return false;
237 }
238
239 /***
240 *
241 */
242 private void updatePopupMenu() {
243 popupMenu.removeAll();
244 Collection localChoices = choices != null ? new ArrayList(choices) : new ArrayList();
245 String prefix = getPrefix();
246 int count = 0;
247 for (Iterator i = localChoices.iterator(); i.hasNext();) {
248 ChoiceType choice = (ChoiceType) i.next();
249 if (startsWithIgnoreCase(choice.getValue(), prefix)) {
250 popupMenu.add(createPopupMenuItem(choice));
251 count++;
252 }
253 }
254 if (count < localChoices.size()) {
255 popupMenu.add(new JSeparator());
256 for (Iterator i = localChoices.iterator(); i.hasNext();) {
257 ChoiceType choice = (ChoiceType) i.next();
258 if (!startsWithIgnoreCase(choice.getValue(), prefix)) {
259 popupMenu.add(createPopupMenuItem(choice));
260 }
261 }
262 }
263 }
264
265 /***
266 * @return
267 */
268 private String getPrefix() {
269 String text = getText();
270 Caret caret = getCaret();
271 int dot = caret.getDot();
272 int mark = caret.getMark();
273 if (dot != mark) {
274 int endIndex = text.length();
275 if (dot < mark && mark == text.length()) {
276 endIndex = dot;
277 } else if (mark < dot && dot == text.length()) {
278 endIndex = mark;
279 }
280 text = text.substring(0, endIndex);
281 }
282 return text;
283 }
284
285 /***
286 * @param choice
287 * @return
288 */
289 private JMenuItem createPopupMenuItem(ChoiceType choice) {
290 final String val = choice.getValue();
291 String description = choice.getDescription();
292 if (description != null && description.trim().length() == 0) {
293 description = null;
294 }
295 Action a = new AbstractAction(val) {
296 public void actionPerformed(ActionEvent e) {
297 setText(val);
298 }
299 };
300 JMenuItem result = new JMenuItem(a);
301 result.setToolTipText(description);
302 return result;
303 }
304
305 /***
306 * @param value
307 * @param text
308 * @return
309 */
310 private boolean startsWithIgnoreCase(String text, String prefix) {
311 if (text == null || prefix == null) {
312 return false;
313 }
314 int prefixLength = prefix.length();
315 if (text.length() < prefixLength) {
316 return false;
317 }
318 return prefix.equalsIgnoreCase(text.substring(0, prefixLength));
319 }
320
321 private boolean inside(Point point) {
322 Polygon poly = getPolygon();
323 return poly.contains(point);
324 }
325
326 /***
327 * @return
328 */
329 private Polygon getPolygon() {
330 Dimension d = getSize();
331 Polygon p = new Polygon();
332 int x = ((int)d.getWidth());
333 p.addPoint(x - SIZE, 0);
334 p.addPoint(x, 0);
335 p.addPoint(x, SIZE);
336 return p;
337 }
338
339 }