View Javadoc

1   /*
2    * @(#) DefaultEditor.java Apr 11, 2005
3    * 
4    * Copyright (c) 2003-2005 Delft University of Technology, Jaffalaan 5, 2628 BX
5    * Delft, the Netherlands. All rights reserved.
6    * 
7    * See for project information <a href="http://www.simulation.tudelft.nl/">
8    * www.simulation.tudelft.nl </a>.
9    * 
10   * The source code and binary code of this software are proprietary information
11   * of Delft University of Technology.
12   */
13  package org.gscg.common.gui.components.interactivespinner;
14  
15  import java.awt.Component;
16  import java.awt.Container;
17  import java.awt.Dimension;
18  import java.awt.Insets;
19  import java.awt.LayoutManager;
20  import java.awt.event.ActionEvent;
21  import java.beans.PropertyChangeEvent;
22  import java.beans.PropertyChangeListener;
23  
24  import javax.swing.Action;
25  import javax.swing.ActionMap;
26  import javax.swing.JPanel;
27  import javax.swing.event.ChangeEvent;
28  import javax.swing.event.ChangeListener;
29  
30  
31  /***
32   * <p>
33   * (c) copyright 2005 <a href="http://www.simulation.tudelft.nl">Delft
34   * University of Technology </a>, the Netherlands. <br>
35   * See for project information <a
36   * href="http://www.simulation.tudelft.nl">www.simulation.tudelft.nl </a> <br>
37   * 
38   * Copyright (c) 2003-2005 Delft University of Technology, Jaffalaan 5, 2628 BX
39   * Delft, the Netherlands. All rights reserved.
40   * 
41   * See for project information <a href="http://www.simulation.tudelft.nl/">
42   * www.simulation.tudelft.nl </a>.
43   * 
44   * The source code and binary code of this software are proprietary information
45   * of Delft University of Technology.
46   * 
47   * @author <a
48   *         href="http://www.tbm.tudelft.nl/webstaf/stijnh/index.htm">Stijn-Pieter
49   *         van Houten </a>
50   * @version $Revision: 1.1 $ $Date: 2005/06/16 12:33:56 $
51   * @since 1.1.3
52   */
53  /***
54   * A simple base class for more specialized editors that displays a read-only
55   * view of the model's current value with a
56   * <code>JFormattedTextField<code>.  Subclasses
57   * can configure the <code>JFormattedTextField<code> to create
58   * an editor that's appropriate for the type of model they
59   * support and they may want to override
60   * the <code>stateChanged</code> and <code>propertyChanged</code>
61   * methods, which keep the model and the text field in sync.
62   * <p>
63   * This class defines a <code>dismiss</code> method that removes the
64   * editors <code>ChangeListener</code> from the <code>JSpinner</code>
65   * that it's part of.   The <code>setEditor</code> method knows about
66   * <code>DefaultEditor.dismiss</code>, so if the developer
67   * replaces an editor that's derived from <code>JSpinner.DefaultEditor</code>
68   * its <code>ChangeListener</code> connection back to the 
69   * <code>JSpinner</code> will be removed.  However after that,
70   * it's up to the developer to manage their editor listeners.
71   * Similarly, if a subclass overrides <code>createEditor</code>,
72   * it's up to the subclasser to deal with their editor
73   * subsequently being replaced (with <code>setEditor</code>).
74   * We expect that in most cases, and in editor installed
75   * with <code>setEditor</code> or created by a <code>createEditor</code>
76   * override, will not be replaced anyway.
77   * <p>
78   * This class is the <code>LayoutManager<code> for it's single
79   * <code>JFormattedTextField</code> child.   By default the
80   * child is just centered with the parents insets.
81   */
82  public class DefaultEditor extends JPanel implements ChangeListener,
83  		PropertyChangeListener, LayoutManager
84  {
85  	/*** the default disable action for the ftf */
86  	private static final Action DISABLED_ACTION = new DisabledAction();
87  
88  	/***
89  	 * Constructs an editor component for the specified <code>JSpinner</code>.
90  	 * This <code>DefaultEditor</code> is it's own layout manager and it is
91  	 * added to the spinner's <code>ChangeListener</code> list. The
92  	 * constructor creates a single <code>JFormattedTextField<code> child,
93  	 * initializes it's value to be the spinner model's current value
94  	 * and adds it to <code>this</code> <code>DefaultEditor</code>.  
95  	 * 
96  	 * @param spinner the spinner whose model <code>this</code> editor will monitor
97  	 * @see #getTextField
98  	 */
99  	public DefaultEditor(final JInteractiveSpinner spinner)
100 	{
101 		super(null);
102 
103 		JFormattedTextField ftf = new JFormattedTextField();
104 		ftf.setValue(spinner.getValue());
105 		ftf.addPropertyChangeListener(this);
106 
107 		add(ftf);
108 
109 		setLayout(this);
110 		spinner.addChangeListener(this);
111 
112 		// We want the spinner's increment/decrement actions to be
113 		// active vs those of the JFormattedTextField. As such we
114 		// put disabled actions in the JFormattedTextField's actionmap.
115 		// A binding to a disabled action is treated as a nonexistant
116 		// binding.
117 		ActionMap ftfMap = ftf.getActionMap();
118 
119 		if (ftfMap != null)
120 		{
121 			ftfMap.put("increment", DISABLED_ACTION);
122 			ftfMap.put("decrement", DISABLED_ACTION);
123 		}
124 	}
125 
126 	/***
127 	 * Disconnect <code>this</code> editor from the specified
128 	 * <code>JSpinner</code>. By default, this method removes itself from the
129 	 * spinners <code>ChangeListener</code> list.
130 	 * 
131 	 * @param spinner the <code>JSpinner</code> to disconnect this editor
132 	 *        from; the same spinner as was passed to the constructor.
133 	 */
134 	public void dismiss(final JInteractiveSpinner spinner)
135 	{
136 		spinner.removeChangeListener(this);
137 	}
138 
139 
140 	/***
141 	 * Returns the <code>JSpinner</code> ancestor of this editor or null.
142 	 * Typically the editor's parent is a <code>JSpinner</code> however
143 	 * subclasses of <codeJSpinner</code> may override the the <code>
144 	 * createEditor</code> method and insert one or more containers between the
145 	 * <code>JSpinner</code> and it's editor.
146 	 * 
147 	 * @return <code>JSpinner</code> ancestor
148 	 */
149 	public JInteractiveSpinner getSpinner()
150 	{
151 		for (Component c = this; c != null; c = c.getParent())
152 		{
153 			if (c instanceof JInteractiveSpinner)
154 			{
155 				return (JInteractiveSpinner) c;
156 			}
157 		}
158 		return null;
159 	}
160 
161 
162 	/***
163 	 * Returns the <code>JFormattedTextField</code> child of this editor. By
164 	 * default the text field is the first and only child of editor.
165 	 * 
166 	 * @return the <code>JFormattedTextField</code> that gives the user access
167 	 *         to the <code>SpinnerDateModel's</code> value.
168 	 * @see #getSpinner
169 	 */
170 	public JFormattedTextField getTextField()
171 	{
172 		return (JFormattedTextField) getComponent(0);
173 	}
174 
175 
176 	/***
177 	 * This method is called when the spinner's model's state changes. It sets
178 	 * the <code>value</code> of the text field to the current value of the
179 	 * spinners model.
180 	 * 
181 	 * @param e not used
182 	 * @see #getTextField
183 	 */
184 	public void stateChanged(final ChangeEvent e)
185 	{
186 		JInteractiveSpinner spinner = (JInteractiveSpinner) (e.getSource());
187 		getTextField().setValue(spinner.getValue());
188 	}
189 
190 
191 	/***
192 	 * Called by the <code>JFormattedTextField</code> 
193 	 * <code>PropertyChangeListener</code>.
194 	 * When the <code>"value"</code> property changes, which implies that the
195 	 * user has typed a new number, we set the value of the spinners model.
196 	 * <p>
197 	 * This class ignores <code>PropertyChangeEvents</code> whose source is
198 	 * not the <code>JFormattedTextField</code>, so subclasses may safely
199 	 * make <code>this</code> <code>DefaultEditor</code> a
200 	 * <code>PropertyChangeListener</code> on other objects.
201 	 * 
202 	 * @param e the <code>PropertyChangeEvent</code> whose source is the
203 	 *        <code>JFormattedTextField</code> created by this class.
204 	 * @see #getTextField
205 	 */
206 	public void propertyChange(final PropertyChangeEvent e)
207 	{
208 		JInteractiveSpinner spinner = getSpinner();
209 		if (spinner == null)
210 		{
211 			// Indicates we aren't installed anywhere.
212 			return;
213 		}
214 
215 		Object source = e.getSource();
216 		String name = e.getPropertyName();
217 		if ((source instanceof JFormattedTextField) && "value".equals(name))
218 		{
219 			spinner.setValue(getTextField().getValue());
220 		}
221 	}
222 
223 
224 	/***
225 	 * This <code>LayoutManager</code> method does nothing. We're only
226 	 * managing a single child and there's no support for layout constraints.
227 	 * 
228 	 * @param name ignored
229 	 * @param child ignored
230 	 */
231 	public void addLayoutComponent(final String name, final Component child)
232 	{
233 		// nothing to do
234 	}
235 
236 
237 	/***
238 	 * This <code>LayoutManager</code> method does nothing. There isn't any
239 	 * per-child state.
240 	 * 
241 	 * @param child ignored
242 	 */
243 	public void removeLayoutComponent(final Component child)
244 	{
245 		// nothing to do
246 	}
247 
248 
249 	/***
250 	 * Returns the size of the parents insets.
251 	 * 
252 	 * @param parent the parent
253 	 * @return the size as a Dimension
254 	 */
255 	private Dimension insetSize(final Container parent)
256 	{
257 		Insets insets = parent.getInsets();
258 		int w = insets.left + insets.right;
259 		int h = insets.top + insets.bottom;
260 		return new Dimension(w, h);
261 	}
262 
263 
264 	/***
265 	 * Returns the preferred size of first (and only) child plus the size of the
266 	 * parents insets.
267 	 * 
268 	 * @param parent the Container that's managing the layout
269 	 * @return the preferred dimensions to lay out the subcomponents of the
270 	 *         specified container.
271 	 */
272 	public Dimension preferredLayoutSize(final Container parent)
273 	{
274 		Dimension preferredSize = insetSize(parent);
275 		if (parent.getComponentCount() > 0)
276 		{
277 			Dimension childSize = getComponent(0).getPreferredSize();
278 			preferredSize.width += childSize.width;
279 			preferredSize.height += childSize.height;
280 		}
281 		return preferredSize;
282 	}
283 
284 
285 	/***
286 	 * Returns the minimum size of first (and only) child plus the size of the
287 	 * parents insets.
288 	 * 
289 	 * @param parent the Container that's managing the layout
290 	 * @return the minimum dimensions needed to lay out the subcomponents of the
291 	 *         specified container.
292 	 */
293 	public Dimension minimumLayoutSize(final Container parent)
294 	{
295 		Dimension minimumSize = insetSize(parent);
296 		if (parent.getComponentCount() > 0)
297 		{
298 			Dimension childSize = getComponent(0).getMinimumSize();
299 			minimumSize.width += childSize.width;
300 			minimumSize.height += childSize.height;
301 		}
302 		return minimumSize;
303 	}
304 
305 
306 	/***
307 	 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
308 	 */
309 	public void layoutContainer(final Container parent)
310 	{
311 		if (parent.getComponentCount() > 0)
312 		{
313 			Insets insets = parent.getInsets();
314 			int w = parent.getWidth() - (insets.left + insets.right);
315 			int h = parent.getHeight() - (insets.top + insets.bottom);
316 			getComponent(0).setBounds(insets.left, insets.top, w, h);
317 		}
318 	}
319 
320 	/***
321 	 * An Action implementation that is always disabled.
322 	 */
323 	private static class DisabledAction implements Action
324 	{
325 		/***
326 		 * @see javax.swing.Action#getValue(java.lang.String)
327 		 */
328 		public Object getValue(final String key)
329 		{
330 			return null;
331 		}
332 
333 		/***
334 		 * @see javax.swing.Action#putValue(java.lang.String, java.lang.Object)
335 		 */
336 		public void putValue(final String key, final Object value)
337 		{
338 			// nothing to do
339 		}
340 
341 		/***
342 		 * @see javax.swing.Action#setEnabled(boolean)
343 		 */
344 		public void setEnabled(final boolean b)
345 		{
346 			// nothing to do
347 		}
348 
349 		/***
350 		 * @see javax.swing.Action#isEnabled()
351 		 */
352 		public boolean isEnabled()
353 		{
354 			return false;
355 		}
356 
357 		/***
358 		 * @see javax.swing.Action#addPropertyChangeListener(java.beans.PropertyChangeListener)
359 		 */
360 		public void addPropertyChangeListener(final PropertyChangeListener l)
361 		{
362 			// nothing to do
363 		}
364 
365 		/***
366 		 * @see javax.swing.Action#removePropertyChangeListener(java.beans.PropertyChangeListener)
367 		 */
368 		public void removePropertyChangeListener(final PropertyChangeListener l)
369 		{
370 			// nothing to do
371 		}
372 
373 		/***
374 		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
375 		 */
376 		public void actionPerformed(final ActionEvent ae)
377 		{
378 			// nothing to do
379 		}
380 	}
381 }