View Javadoc

1   /*
2    *  Copyright (C) 2003-2005 SINTEF
3    *  Author:  Fredrik Vraalsen (fredrik dot vraalsen at sintef dot no)
4    *  Webpage: http://coras.sourceforge.net/
5    *
6    *  This program is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public License
8    *  as published by the Free Software Foundation; either version 2.1
9    *  of the License, or (at your option) any later version.
10   *
11   *  This program is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this program; if not, write to the Free
18   *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19   *  02111-1307 USA
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  //				updatePopupMenu();
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 //		updatePopupMenu();
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; // thread safety
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 			// updateText reentry or call to setText()
164 			return;
165 		}
166 		doUpdateText = false;
167 		
168 		Collection localChoices = choices; // thread safety
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 //				if (startsWithIgnoreCase(value, text)) {
178 //					setText(userEditable ? text + value.substring(pos) : value);
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 //					setSelectionStart(pos);
186 //					setSelectionEnd(value.length());
187 					found = true;
188 				}
189 			}
190 		}
191 		doUpdateText = true;
192 //		updatePopupMenu();
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; // thread safety 
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; // thread safety
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 }