View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common 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
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components;
21  
22  import java.awt.Color;
23  import java.awt.Graphics;
24  import java.awt.GridBagConstraints;
25  import java.awt.GridBagLayout;
26  import java.awt.Insets;
27  import java.awt.event.ActionEvent;
28  import java.awt.event.ActionListener;
29  import java.awt.event.FocusAdapter;
30  import java.awt.event.FocusEvent;
31  import java.awt.event.KeyAdapter;
32  import java.awt.event.KeyEvent;
33  import java.awt.event.WindowAdapter;
34  import java.awt.event.WindowEvent;
35  import java.beans.PropertyChangeEvent;
36  import java.beans.PropertyChangeListener;
37  import java.util.Timer;
38  import java.util.TimerTask;
39  import java.util.logging.Level;
40  import java.util.logging.Logger;
41  
42  import javax.swing.JFrame;
43  import javax.swing.JTextField;
44  import javax.swing.border.Border;
45  import javax.swing.border.LineBorder;
46  import javax.swing.event.DocumentEvent;
47  import javax.swing.event.DocumentListener;
48  
49  import com.cosylab.events.SetEvent;
50  import com.cosylab.events.SetListener;
51  import com.cosylab.gui.components.numberfield.AbstractNumberDocument;
52  import com.cosylab.gui.components.numberfield.AngleNumberDescriptor;
53  import com.cosylab.gui.components.numberfield.DefaultNumberDescriptor;
54  import com.cosylab.gui.components.numberfield.NumberDescriptor;
55  import com.cosylab.gui.components.numberfield.NumberDescriptorDocument;
56  import com.cosylab.gui.components.util.ColorHelper;
57  import com.cosylab.gui.components.util.CosyUIElements;
58  import com.cosylab.gui.components.util.PaintHelper;
59  import com.cosylab.logging.DebugLogger;
60  
61  
62  /**
63   * Descendant of <code>ResizableTextField</code> which only allows input of
64   * numbers. Double and long number formats are supported through number
65   * document classes <code>DobuleDocument</code> and <code>LongDocument</code>,
66   * both descedents of <code>AbstractNumberDocument</code>, while display
67   * format is controlled using <code>com.cosylab.util.PrintfFormat</code>
68   * string parser. The number field operates in two modes. When in focus mode,
69   * the user can type in numbers that are recognized by the set number document
70   * of the number field. When the user presses the <i>Enter</i> key or the
71   * component goes out of focus the typed value is checked for range using the
72   * set minimum and maximum allowed values. Than the new value is displayed
73   * using the <code>com.cosylab.util.PrinfFormat</code> with the set format
74   * string. Value stored by the number field can be accessed through getter and
75   * setter methods in both <code>double</code> or <code>long</code> or even
76   * generic <code>Number</code> type.
77   *
78   * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
79   * @version $id$
80   *
81   * @see com.cosylab.gui.components.ResizableTextField
82   * @see AbstractNumberDocument
83   * @see com.cosylab.util.PrintfFormat
84   */
85  public class NumberField extends ResizableTextField
86  {
87  	/**
88  	 * 
89  	 */
90  	private static final long serialVersionUID = 1L;
91  
92  	/**
93  	 * Converts format string to default safe format for appropriate number
94  	 * type.
95  	 *
96  	 * @param numberType number type for which format is created
97  	 * @param format old format to be used as reference
98  	 *
99  	 * @return safe default format
100 	 *
101 	 * @throws NullPointerException if type is null
102 	 */
103 	public static String createDefaultFormat(Class<? extends Number> numberType, String format)
104 	{
105 		if (numberType == null) {
106 			throw new NullPointerException("numberType");
107 		}
108 
109 		if (format == null) {
110 			return createDefaultFormat(numberType);
111 		}
112 
113 		String newFormat = null;
114 
115 		if (numberType == long.class || numberType == Long.class) {
116 			int percIdx = format.indexOf('%');
117 			int fltIdx = format.indexOf('f', percIdx);
118 
119 			if (fltIdx == -1) {
120 				fltIdx = format.indexOf('d', percIdx);
121 			}
122 
123 			newFormat = format.substring(0, percIdx + 1)
124 				+ (format.charAt(percIdx + 1) == '+' ? "+3d" : "3d")
125 				+ format.substring(fltIdx + 1);
126 		} else if (numberType == double.class || numberType == Double.class) {
127 			int percIdx = format.indexOf('%');
128 			int fltIdx = format.indexOf('d', percIdx);
129 
130 			if (fltIdx == -1) {
131 				fltIdx = format.indexOf('f', percIdx);
132 			}
133 
134 			newFormat = format.substring(0, percIdx + 1)
135 				+ (format.charAt(percIdx + 1) == '+' ? "+3.2f" : "3.2f")
136 				+ format.substring(fltIdx + 1);
137 		}
138 
139 		return newFormat;
140 	}
141 
142 	/**
143 	 * Returns safe default format for type.
144 	 *
145 	 * @param numberType number type
146 	 *
147 	 * @return default format
148 	 *
149 	 * @throws NullPointerException if type is null
150 	 */
151 	public static String createDefaultFormat(Class<? extends Number> numberType)
152 	{
153 		if (numberType == null) {
154 			throw new NullPointerException("numberType");
155 		}
156 
157 		String newFormat = null;
158 
159 		if ((numberType == long.class) || (numberType == Long.class)) {
160 			newFormat = "%+3d";
161 		} else if ((numberType == double.class) || (numberType == Double.class)) {
162 			newFormat = "%+3.2f";
163 		}
164 
165 		return newFormat;
166 	}
167 
168 	/**
169 	 * Listens for action events and handles the focus change.
170 	 *
171 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
172 	 * @version $id$
173 	 */
174 	protected class NumberActionAdapter implements ActionListener
175 	{
176 		/**
177 		 * Loses focus and notifies SetListeners when action is performed.
178 		 *
179 		 * @param e ActionEvent
180 		 *
181 		 * @see java.awt.event.ActionListener#actionPerformed(ActionEvent)
182 		 */
183 		public void actionPerformed(ActionEvent e)
184 		{
185 			/* If NumberField is empty, old value should be restored. */
186 			if (getText().length()!=0) updateDocumentValue();
187 			fireSetPerformed();
188 			exitOnAction = true;
189 
190 			/*Invoked to lose focus*/
191 			setEnabled(false);
192 			setEnabled(true);
193 			staticMode();
194 		}
195 	}
196 
197 	/**
198 	 * Listens for document events and handles the value property change.
199 	 *
200 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
201 	 * @version $id$
202 	 */
203 	protected class NumberDocumentAdapter implements DocumentListener
204 	{
205 		/**
206 		 * Updates the Numberfield while the text is typed.
207 		 *
208 		 * @param e DocumentEvent
209 		 *
210 		 * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
211 		 */
212 		public void changedUpdate(DocumentEvent e)
213 		{
214 			logger.finer(e.toString());
215 
216 			if (policy == SET_AS_TYPED && editing) {
217 				logger.fine("SET_AS_TYPED");
218 				updateDocumentValue();
219 				fireSetPerformed();
220 			}
221 		}
222 
223 		/**
224 		 * Updates the Numberfield while the text is typed.
225 		 *
226 		 * @param e DocumentEvent
227 		 *
228 		 * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
229 		 */
230 		public void insertUpdate(DocumentEvent e)
231 		{
232 			logger.finer(e.toString());
233 
234 			if (policy == SET_AS_TYPED && editing) {
235 				logger.fine("SET_AS_TYPED");
236 				setValue(numberDocument.getValue());
237 				fireSetPerformed();
238 			}
239 		}
240 
241 		/**
242 		 * Updates the Numberfield while the text is typed.
243 		 *
244 		 * @param e DocumentEvent
245 		 *
246 		 * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
247 		 */
248 		public void removeUpdate(DocumentEvent e)
249 		{
250 			logger.finer(e.toString());
251 
252 			if (policy == SET_AS_TYPED && editing) {
253 				logger.fine("SET_AS_TYPED");
254 				setValue(numberDocument.getValue());
255 				fireSetPerformed();
256 			}
257 		}
258 	}
259 
260 	/**
261 	 * Listens for focus events sets the appropriate display mode.
262 	 *
263 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
264 	 * @version $id$
265 	 */
266 	protected class NumberFocusAdapter extends FocusAdapter
267 	{
268 		/**
269 		 * Switches to editing mode of the Numberfield when focus is gained.
270 		 *
271 		 * @param e FocusEvent
272 		 *
273 		 * @see java.awt.event.FocusListener#focusGained(FocusEvent)
274 		 */
275 		public void focusGained(FocusEvent e)
276 		{
277 			if (isEditable()) {
278 				editMode();
279 
280 				int enterPolicy = getEnterPolicy();
281 
282 				switch (enterPolicy) {
283 				case ENTER_AT_END:
284 					setCaretPosition(getDocument().getLength());
285 
286 					break;
287 
288 				case ENTER_AT_START:
289 					setCaretPosition(0);
290 
291 					break;
292 
293 				case ENTER_SELECTED:
294 					setSelectionStart(0);
295 					setSelectionEnd(getDocument().getLength());
296 
297 					break;
298 
299 				/*case ENTER_AT_CLICKED :
300 				    int idx=0;
301 				    if (clickIdx>0) {
302 				        idx=clickIdx;
303 				        focusHandled=false;
304 				    }
305 				    setCaretPosition(idx);
306 				    break;*/
307 				default:
308 					break;
309 				}
310 			}
311 		}
312 
313 		/**
314 		 * Updates the Numberfield when focus is lost.
315 		 *
316 		 * @param e FocusEvent
317 		 *
318 		 * @see java.awt.event.FocusListener#focusLost(FocusEvent)
319 		 */
320 		public void focusLost(FocusEvent e)
321 		{
322 			logger.finer(e.toString());
323 
324 			if (exitOnAction == true) {
325 				exitOnAction = false;
326 			} else if (policy == SET_ON_EXIT) {
327 				logger.finer("new value " + numberDocument.getValue());
328 				
329 				setValue(numberDocument.getValue());
330 				fireSetPerformed();
331 			}
332 
333 			staticMode();
334 			
335 			/* If out of bounds warning is on this signals that NumberField has lost focus. */
336 			if (warnOutOfBounds) focusLost = true;
337 		}
338 	}
339 
340 	/**
341 	 * An implementation of KeyAdapter responsible for losing focus when the
342 	 * ESC key is pressed.
343 	 *
344 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
345 	 * @version $id$
346 	 */
347 	protected class NumberKeyAdapter extends KeyAdapter
348 	{
349 		/**
350 		 * Makes the Numberfield lose focus when the ESC key is pressed.
351 		 *
352 		 * @param e KeyEvent
353 		 *
354 		 * @see java.awt.event.KeyListener#keyPressed(KeyEvent)
355 		 */
356 		public void keyPressed(KeyEvent e)
357 		{
358 			if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
359 				/*Invoked to lose focus*/
360 				exitOnAction = true;
361 				setEnabled(false);
362 				setEnabled(true);
363 				staticMode();
364 			}
365 		}
366 	}
367 
368 	/**
369 	 * An extension of Timer used for periodic tilting of the Wheelswitch.
370 	 *
371 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
372 	 * @version $id$
373 	 *
374 	 */
375 	protected class TiltHandler extends Timer
376 	{
377 		private class TiltTask extends TimerTask
378 		{
379 			/**
380 			 * DOCUMENT ME!
381 			 */
382 			public void run()
383 			{
384 				logger.fine("tilting " + numberOfTilts);
385 
386 				if (numberOfTilts >= MAX_NUMBER_OF_TILTS) {
387 					cancel();
388 					tilting = false;
389 				} else {
390 					numberOfTilts++;
391 					tilting = !tilting;
392 				}
393 				repaint();
394 			}
395 		}
396 
397 		private final int MAX_NUMBER_OF_TILTS = 3;
398 		private final long TILT_RATE = 200;
399 		private int numberOfTilts = MAX_NUMBER_OF_TILTS;
400 
401 		/**
402 		 * Scedules a new tilting task if the user value equals any of the
403 		 * bounds.
404 		 */
405 		public void tilt()
406 		{
407 			if (numberOfTilts >= MAX_NUMBER_OF_TILTS) {
408 				numberOfTilts = 0;
409 				logger.info("scheduling tilting");
410 				schedule(new TiltTask(), 0, TILT_RATE);
411 			} else {
412 				numberOfTilts = 0;
413 			}
414 		}
415 	}
416 
417 	private final Logger logger = DebugLogger.getLogger("NF", Level.OFF);
418 
419 	/** Setting policy name tag. */
420 	public static final String SETTING_POLICY = "settingPolicy";
421 
422 	/** Setting policy - value set as typed. */
423 	public static final int SET_AS_TYPED = 0;
424 
425 	/** Setting policy - value set on apply. */
426 	public static final int SET_ON_APPLY = 1;
427 
428 	/** Setting policy - value set when focus is lost. */
429 	public static final int SET_ON_EXIT = 2;
430 
431 	/** Enter policy name tag. */
432 	public static final String ENTER_POLICY = "enterPolicy";
433 
434 	/** Enter policy - enter at clicked position. */
435 	public static final int ENTER_AT_CLICKED = 0;
436 
437 	/** Enter policy -  */
438 	public static final int ENTER_SELECTED = 1;
439 
440 	/** Enter policy - enter at the end of existing value. */
441 	public static final int ENTER_AT_END = 2;
442 
443 	/** Enter policy - enter at the beginning of existing value. */
444 	public static final int ENTER_AT_START = 3;
445 
446 	/** Value property name tag. */
447 	public static final String VALUE = "value";
448 
449 	/** Number type property name tag. */
450 	public static final String NUMBER_TYPE = "numberType";
451 	private NumberDescriptorDocument numberDocument;
452 	private Class<? extends Number> numberType;
453 	private Number maximum;
454 	private Number minimum;
455 	private Number value;
456 	private NumberDocumentAdapter numberDocumentHandler;
457 
458 	//private PlainDocument plainDocument;
459 	private String format = null;
460 	private TiltHandler tiltHandler;
461 	private boolean editing;
462 	private boolean exitOnAction = false;
463 	private boolean tilting;
464 	private boolean tiltingEnabled;
465 	private NumberDescriptor numberDescriptor = null;
466 	
467 	/*
468 	 * The following fields are used for signaling that the value of this 
469 	 * component is out of bounds. 
470 	 */
471 	
472 	/* Defines if NumberField should signal that the value is out of bounds. */
473 	private boolean warnOutOfBounds = false;
474 	/* Signals that the value of this NumberField is currently out of bounds. */
475 	private boolean outOfBounds = false;
476 	/* Signals that this component has lost focus. */
477 	private boolean focusLost = false;
478 	/* Signals that the value of this NumberField has changed. */
479 	private boolean valueChanged = false;
480 	/* Value displayed by this component while being edited. */
481 	private Number userInput = null;
482 	/* Background colors. */
483 	private Color normalColor = this.getBackground();
484 	private Color warningColor = new Color(255, 200, 200);
485 	private Color outOfBoundsColor = new Color(255, 100, 100);
486 
487 	private int policy = SET_ON_EXIT;
488 
489 	/**
490 	 * Constructs a new NumberField with specified value and PrintfFormat
491 	 * format string.
492 	 *
493 	 * @param newValue initial value (null not permitted)
494 	 * @param newFormat initial format (null not permitted)
495 	 *
496 	 * @throws IllegalArgumentException if value or format is null
497 	 */
498 	public NumberField(Number newValue, String newFormat)
499 	{
500 		super();
501 
502 		if (newValue == null) {
503 			throw new IllegalArgumentException("number value");
504 		}
505 
506 		if (newFormat == null) {
507 			throw new IllegalArgumentException("format");
508 		}
509 
510 		value = newValue;
511 
512 		format = newFormat;
513 
514 		//plainDocument = new PlainDocument();
515 		numberDocument = new NumberDescriptorDocument();
516 		numberDocumentHandler = new NumberDocumentAdapter();
517 		numberDocument.addDocumentListener(numberDocumentHandler);
518 
519 		setDocument(numberDocument);
520 
521 		setNumberType(newValue.getClass());
522 		setFormat(newFormat);
523 		
524 		setStaticBorder(CosyUIElements.getFlushBorder());
525 		setEditBorder(new LineBorder(ColorHelper.getFocus(), 2));
526 		setEnterPolicy(ENTER_AT_CLICKED);
527 
528 		addFocusListener(new NumberFocusAdapter());
529 		addActionListener(new NumberActionAdapter());
530 		addKeyListener(new NumberKeyAdapter());
531 		staticMode();
532 	}
533 
534 	/**
535 	 * Constructs a new NumberField with value.
536 	 *
537 	 * @param newValue initial value
538 	 */
539 	public NumberField(Number newValue)
540 	{
541 		this(newValue, createDefaultFormat(newValue.getClass()));
542 	}
543 
544 	/**
545 	 * Default constructor for NumberField creates number field with zero
546 	 * value.
547 	 */
548 	public NumberField()
549 	{
550 		this(new Double(0), createDefaultFormat(Double.class));
551 	}
552 
553 	/**
554 	 * Sets the value of type <code>doubleValue</code> to be displayed.
555 	 *
556 	 * @param newValue
557 	 */
558 	public void setDoubleValue(double newValue)
559 	{
560 		logger.info("new value " + newValue);
561 
562 		double oldValue = getDoubleValue();
563 
564 		if (oldValue == newValue) {
565 			return;
566 		}
567 
568 		internalSetValue(new Double(newValue));
569 
570 		if (!editing) {
571 			staticMode();
572 		}
573 
574 		firePropertyChange("doubleValue", oldValue, newValue);
575 		firePropertyChange(VALUE, oldValue, newValue);
576 	}
577 
578 	/**
579 	 * Returns the value of type <code>doubleValue</code>
580 	 *
581 	 * @return doubleValue
582 	 */
583 	public double getDoubleValue()
584 	{
585 		return value.doubleValue();
586 	}
587 
588 	/**
589 	 * Sets the edit border. Edit border is visible around the input field
590 	 * when editing value.
591 	 *
592 	 * @param border new edit border
593 	 */
594 	public void setEditBorder(Border border)
595 	{
596 		super.putClientProperty("editBorder", border);
597 	}
598 
599 	/**
600 	 * Returns the edit border.
601 	 *
602 	 * @return edit border
603 	 */
604 	public Border getEditBorder()
605 	{
606 		return (Border)super.getClientProperty("editBorder");
607 	}
608 
609 	/**
610 	 * Sets the enter policy. Enter policy specifies where the
611 	 * numbers will be entered once the user clicks on the displayer. Available policies:
612 	 * <ul>
613 	 * <li><code>{@link #ENTER_AT_CLICKED}</code> number will be entered at the 
614 	 * place where the user clicked</li>
615 	 * <li><code>{@link #ENTER_AT_END}</code> number will be appended to the existing
616 	 * value</li>
617 	 * <li><code>{@link #ENTER_AT_START}</code> number will be prepended to the
618 	 * existing value<li>
619 	 * <li><code>{@link #ENTER_SELECTED}</code> number will overwrite the existing
620 	 * value</li>
621 	 * </ul> 
622 	 *
623 	 * @param policy new policy
624 	 */
625 	public void setEnterPolicy(int policy)
626 	{
627 		super.putClientProperty(ENTER_POLICY, new Integer(policy));
628 	}
629 
630 	/**
631 	 * Returns the current enter policy.
632 	 *
633 	 * @return enter policy
634 	 */
635 	public int getEnterPolicy()
636 	{
637 		return ((Integer)super.getClientProperty(ENTER_POLICY)).intValue();
638 	}
639 
640 	/**
641 	 * Sets the display format. Acceptsint standard format control strings.
642 	 *
643 	 * @param newFormat java.lang.String
644 	 *
645 	 * @throws IllegalArgumentException DOCUMENT ME!
646 	 *
647 	 * @see com.cosylab.util.PrintfFormat
648 	 */
649 	public void setFormat(String newFormat)
650 	{
651 		if (newFormat == null) {
652 			throw new IllegalArgumentException("format");
653 		}
654 
655 		String oldFormat = getFormat();
656 
657 		if (oldFormat.equals(newFormat)) {
658 			return;
659 		}
660 		
661 		logger.info("New format '"+newFormat+"'");
662 
663 		format = newFormat;
664 		getNumberDescriptor().setFormat(newFormat);
665 		firePropertyChange("format", oldFormat, newFormat);
666 
667 		if (!editing) {
668 			staticMode();
669 		}
670 	}
671 
672 	/**
673 	 * Returns the diplay format
674 	 *
675 	 * @return java.lang.String
676 	 */
677 	public String getFormat()
678 	{
679 		return format;
680 	}
681 
682 	/**
683 	 * Sets the value of type <code>long</code> to be displayed.
684 	 *
685 	 * @param newValue
686 	 */
687 	public void setLongValue(long newValue)
688 	{
689 		logger.info("new value " + newValue);
690 
691 		long oldValue = getLongValue();
692 
693 		if (oldValue == newValue) {
694 			return;
695 		}
696 
697 		internalSetValue(new Long(newValue));
698 
699 		if (!editing) {
700 			staticMode();
701 		}
702 
703 		firePropertyChange("longValue", oldValue, newValue);
704 		firePropertyChange(VALUE, oldValue, newValue);
705 	}
706 
707 	/**
708 	 * Returns the value of type <code>longValue</code>
709 	 *
710 	 * @return longValue
711 	 */
712 	public long getLongValue()
713 	{
714 		return value.longValue();
715 	}
716 
717 	/**
718 	 * Sets the maximum allowed value that can be entered in this NumberField.
719 	 * Setting maximum to null means no maximum is set. This method does not
720 	 * check for validity of the limit. If maximum is set less than minimum
721 	 * then no value can be set.
722 	 *
723 	 * @param newMaximum Number
724 	 */
725 	public void setMaximum(Number newMaximum)
726 	{
727 		newMaximum = castNumber(newMaximum);
728 		
729 		Number oldMaximum = maximum;
730 
731 		if (((newMaximum == null) && (oldMaximum == null))
732 		    || ((newMaximum != null) && newMaximum.equals(oldMaximum))) {
733 			return;
734 		}
735 
736 		if ((minimum != null) && (newMaximum != null)
737 		    && (((Comparable<Number>)newMaximum).compareTo(minimum) < 0)) {
738 			newMaximum = minimum;
739 		}
740 
741 		maximum = newMaximum;
742 		internalSetValue(value);
743 
744 		if (!editing) {
745 			staticMode();
746 		}
747 
748 		firePropertyChange("maximum", oldMaximum, newMaximum);
749 	}
750 
751 	/**
752 	 * Returns the maximum value allowed by this NumberField. Can be null if no
753 	 * maximum is set.
754 	 *
755 	 * @return Number maximum allowed value.
756 	 */
757 	public Number getMaximum()
758 	{
759 		return maximum;
760 	}
761 
762 	/**
763 	 * Sets the minimum allowed value that can be entered in this NumberField.
764 	 * Setting minimum to null means no minimum is set. This method does not
765 	 * check for validity of the limit. If maximum is set less than minimum
766 	 * then no value can be set.
767 	 *
768 	 * @param newMinimum Number
769 	 */
770 	public void setMinimum(Number newMinimum)
771 	{
772 		newMinimum = castNumber(newMinimum);
773 
774 		Number oldMinimum = minimum;
775 
776 		if (((newMinimum == null) && (oldMinimum == null))
777 		    || ((newMinimum != null) && newMinimum.equals(oldMinimum))) {
778 			return;
779 		}
780 
781 		if ((maximum != null) && (newMinimum != null)
782 		    && (((Comparable<Number>)newMinimum).compareTo(maximum) > 0)) {
783 			newMinimum = maximum;
784 		}
785 
786 		minimum = newMinimum;
787 		internalSetValue(value);
788 
789 		if (!editing) {
790 			staticMode();
791 		}
792 
793 		firePropertyChange("minimum", oldMinimum, newMinimum);
794 	}
795 
796 	/**
797 	 * Returns the minimum value allowed by this NumberField. Can be null if no
798 	 * miminim is set.
799 	 *
800 	 * @return Number minimum allowed value
801 	 */
802 	public Number getMinimum()
803 	{
804 		return minimum;
805 	}
806 
807 	/**
808 	 * Sets the number format. If possible, number format is converted to one
809 	 * of two supported document types: <code>Long.class</code> of
810 	 * <code>Double.class</code>.
811 	 *
812 	 * @param newNumberFormat
813 	 *
814 	 * @throws IllegalArgumentException
815 	 */
816 	public void setNumberType(Class<? extends Number> newNumberFormat)
817 		throws IllegalArgumentException
818 	{
819 		if (newNumberFormat == numberType) {
820 			return;
821 		}
822 
823 		if ((newNumberFormat == long.class) || (newNumberFormat == Long.class)
824 		    || (newNumberFormat == int.class)
825 		    || (newNumberFormat == Integer.class)
826 		    || (newNumberFormat == short.class)
827 		    || (newNumberFormat == Short.class)) {
828 			numberType = Long.class;
829 		} else if ((newNumberFormat == double.class)
830 		    || (newNumberFormat == Double.class)
831 		    || (newNumberFormat == float.class)
832 		    || (newNumberFormat == Float.class)) {
833 			numberType = Double.class;
834 		} else {
835 			throw new IllegalArgumentException("Number type '"
836 			    + newNumberFormat + "' is not supported.");
837 		}
838 
839 		getNumberDescriptor().setNumberType(numberType);
840 		value = castNumber(value);
841 
842 		//numberDocument.setValue(value);
843 		setFormat(createDefaultFormat(numberType, format));
844 
845 		minimum = castNumber(minimum);
846 		maximum = castNumber(maximum);
847 
848 		if (!editing) {
849 			staticMode();
850 		}
851 
852 		firePropertyChange(NUMBER_TYPE, null, newNumberFormat);
853 	}
854 
855 	/**
856 	 * Returns the number format
857 	 *
858 	 * @return java.lang.Class
859 	 */
860 	public Class<? extends Number> getNumberType()
861 	{
862 		return numberType;
863 	}
864 
865 	/**
866 	 * Sets the setting policy of thi Numberdisplayer. SET_AS_TYPED updates the
867 	 * Numberfield value when typed. SET_ON_APPLY updates the Numberfield only
868 	 * on actionPerformed events (when the user presses ENTER) SET_ON_EXIT
869 	 * updates the numberfield when it loses focus
870 	 *
871 	 * @param newPolicy
872 	 *
873 	 * @throws IllegalArgumentException DOCUMENT ME!
874 	 */
875 	public void setSettingPolicy(int newPolicy)
876 	{
877 		if ((newPolicy != SET_AS_TYPED) && (newPolicy != SET_ON_APPLY)
878 		    && (newPolicy != SET_ON_EXIT)) {
879 			throw new IllegalArgumentException(SETTING_POLICY);
880 		}
881 
882 		int oldPolicy = policy;
883 		policy = newPolicy;
884 		firePropertyChange(SETTING_POLICY, oldPolicy, newPolicy);
885 	}
886 
887 	/**
888 	 * Returns the currently used seting policy by this Numberfield
889 	 *
890 	 * @return int currently used setting policy.
891 	 */
892 	public int getSettingPolicy()
893 	{
894 		return policy;
895 	}
896 
897 	/**
898 	 * Sets the static border. Static border is displayed around the input field when
899 	 * the displayer does not have focus.
900 	 *
901 	 * @param border new static border
902 	 */
903 	public void setStaticBorder(Border border)
904 	{
905 		super.putClientProperty("staticBorder", border);
906 	}
907 
908 	/**
909 	 * Returns the static border.
910 	 *
911 	 * @return the static border
912 	 */
913 	public Border getStaticBorder()
914 	{
915 		return (Border)super.getClientProperty("staticBorder");
916 	}
917 
918 	/**
919 	 * Sets the tilitng enabled property.
920 	 *
921 	 * @param b whether the component should tilt when value is out of bounds.
922 	 */
923 	public void setTiltingEnabled(boolean b)
924 	{
925 		if (tiltingEnabled == b) {
926 			return;
927 		}
928 
929 		tiltingEnabled = b;
930 		firePropertyChange("tiltingEnabled", !b, b);
931 	}
932 
933 	/**
934 	 * Returns whether the component should indicate value out of bounds
935 	 * condition by visually tilting its border.
936 	 *
937 	 * @return boolean
938 	 */
939 	public boolean isTiltingEnabled()
940 	{
941 		return tiltingEnabled;
942 	}
943 	
944 	/**
945 	 * Sets the value of warnOutOfBounds flag. If true this NumberField signals
946 	 * by change of color that displayed value is out of bounds.
947 	 * 
948 	 * @param b whether this component should change background color if value
949 	 * is out of bounds.
950 	 */
951 	public void setWarnOutOfBounds(boolean b) {
952 		warnOutOfBounds = b;
953 	}
954 	
955 	/**
956 	 * Sets the numeric value for this component. New value is subject to
957 	 * min/max test if any of them is set. If new value does not fall within
958 	 * these bounds, setValue will do nothing.
959 	 *
960 	 * @param newValue Number
961 	 */
962 	public void setValue(Number newValue)
963 	{
964 		logger.info("new value " + newValue);
965 
966 		if (newValue == null) {
967 			return;
968 		}
969 
970 		Number oldValue = value;
971 
972 		if ((oldValue != null) && oldValue.equals(newValue)) {
973 			return;
974 		}
975 
976 		internalSetValue(newValue);
977 		
978 		if (!editing) {
979 			staticMode();
980 		}
981 
982 		firePropertyChange(VALUE, oldValue, newValue);
983 
984 		if (numberType == Long.class) {
985 			firePropertyChange("longValue", oldValue, newValue);
986 		} else {
987 			firePropertyChange("doubleValue", oldValue, newValue);
988 		}
989 	}
990 
991 	/**
992 	 * Returns generic representation of the value using the
993 	 * <code>Number</code> class.
994 	 *
995 	 * @return java.lang.Number
996 	 */
997 	public Number getValue()
998 	{
999 		return value;
1000 	}
1001 
1002 	/**
1003 	 * Adds a SetListener to the list of listeners receiving events when sets
1004 	 * are performed on this Numberfield.
1005 	 *
1006 	 * @param l listener to be added.
1007 	 */
1008 	public void addSetListener(SetListener l)
1009 	{
1010 		listenerList.add(SetListener.class, l);
1011 	}
1012 
1013 	/**
1014 	 * Removes a SetListener from the list of listeners receiving events when
1015 	 * sets are performed on this Numberfield.
1016 	 *
1017 	 * @param l listener to be removed.
1018 	 */
1019 	public void removeSetListener(SetListener l)
1020 	{
1021 		listenerList.remove(SetListener.class, l);
1022 	}
1023 
1024 	/* (non-Javadoc)
1025 	 * Overriden for visualisation purposes.
1026 	 *
1027 	 * @see com.cosylab.gui.components.ResizableTextField#calculateFontSize(java.awt.Dimension, java.lang.String)
1028 	 */
1029 	/*protected int calculateFontSize(Dimension dim, String text)
1030 	{
1031 	    try {
1032 	        return super.calculateFontSize(dim,
1033 	            plainDocument.getText(0, plainDocument.getLength()));
1034 	    } catch (Exception e) {
1035 	        return super.calculateFontSize(dim, text);
1036 	    }
1037 	}*/
1038 	
1039 	protected void fireSetPerformed()
1040 	{
1041 		SetListener[] list = listenerList.getListeners(SetListener.class);
1042 		SetEvent e = new SetEvent(this, value);
1043 
1044 		for (int i = 0; i < list.length; i++) {
1045 			list[i].setPerformed(e);
1046 		}
1047 	}
1048 
1049 	/* (non-Javadoc)
1050 	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
1051 	 */
1052 	protected void paintComponent(Graphics g)
1053 	{
1054 		logger.fine("painting");
1055 		
1056 		/* If necessary synchronizes background color. */
1057 		if (warnOutOfBounds && numberDocument.isUpdated()) {
1058 			defineBackgroundColor();
1059 		}
1060 		
1061 		super.paintComponent(g);
1062 
1063 		if (tilting) {
1064 			PaintHelper.paintRectangle(g, 0, 0, getWidth() - 2,
1065 			    getHeight() - 2, ColorHelper.getEmergencyOutline(), 2);
1066 		}
1067 	}
1068 	
1069 	/**
1070 	 * Defines background color of this component.
1071 	 * 
1072 	 * Color needs to be checked and adjusted if value of this NumberField has
1073 	 * changed, if user edited its contents or this component lost focus. 
1074 	 *
1075 	 */
1076 	private void defineBackgroundColor() {
1077 		if (valueChanged || focusLost) {
1078 			if (outOfBounds) this.setBackground(outOfBoundsColor);
1079 			else this.setBackground(normalColor);
1080 			valueChanged = false;
1081 			focusLost = false;
1082 		}
1083 		else {
1084 			userInput = castNumber(getNumberDescriptor().parseNumber(this.getText()));
1085 			if (
1086 					(minimum != null && ((Comparable<Number>)minimum).compareTo(userInput) > 0) ||
1087 					(maximum != null && ((Comparable<Number>)maximum).compareTo(userInput) < 0)
1088 					) {
1089 				this.setBackground(warningColor);
1090 			}
1091 			else this.setBackground(normalColor);
1092 		}
1093 		
1094 		numberDocument.setUpdated(false);
1095 	}
1096 
1097 	private TiltHandler getTiltHandler()
1098 	{
1099 		if (tiltHandler == null) {
1100 			tiltHandler = new TiltHandler();
1101 		}
1102 
1103 		return tiltHandler;
1104 	}
1105 
1106 	/*    private int calculateDefaultColumns()
1107 	    {
1108 	        int val = 1;
1109 
1110 	        if ((format != null) && !format.equals("")) {
1111 	            // index of first digit
1112 	            int i1 = format.indexOf('%');
1113 
1114 	            if (i1 < 0) {
1115 	                return format.length();
1116 	            }
1117 
1118 	            if ((format.charAt(i1 + 1) == '+')
1119 	                || (format.charAt(i1 + 1) == '-')) {
1120 	                i1++;
1121 	            }
1122 
1123 	            if (format.charAt(i1 + 1) == '.') {
1124 	                i1++;
1125 	                val = 2;
1126 	            }
1127 
1128 	            int i2 = i1 + 1;
1129 
1130 	            for (; i2 < format.length(); i2++) {
1131 	                if (!Character.isDigit(format.charAt(i2))) {
1132 	                    break;
1133 	                }
1134 	            }
1135 
1136 	            if (((i2 - i1) == 1) || (i2 == format.length())) {
1137 	                return format.length();
1138 	            }
1139 
1140 	            try {
1141 	                val += Integer.parseInt(format.substring(i1 + 1, i2));
1142 	            } catch (NumberFormatException e) {
1143 	                Debug.out(e);
1144 	            }
1145 
1146 	            if (format.charAt(i2) == '.') {
1147 	                val++;
1148 
1149 	                for (i2++; i2 < format.length(); i2++) {
1150 	                    if (!Character.isDigit(format.charAt(i2))) {
1151 	                        i2++;
1152 
1153 	                        break;
1154 	                    }
1155 	                }
1156 	            }
1157 
1158 	            val += (format.length() - i2);
1159 	        }
1160 
1161 	        return val;
1162 	    }*/
1163 	private void editMode()
1164 	{		
1165 		int cPos = getCaretPosition();
1166 		
1167 		//setDocument(numberDocument);
1168 		Number val = trimValue(value);
1169 		//Number val = value;
1170 		
1171 		setText(getNumberDescriptor().printEditString(val));
1172 
1173 		if (!(getStaticBorder() == null && getEditBorder() == null)) {
1174 			setBorder(getEditBorder());
1175 		}
1176 
1177 		editing = true;
1178 
1179 		if (getParent() != null) {
1180 			getParent().doLayout();
1181 		}
1182 
1183 		if (cPos > getText().length()) {
1184 			cPos = getText().length();
1185 		}
1186 
1187 		setCaretPosition(cPos);
1188 	}
1189 
1190 	private void internalSetValue(Number newValue)
1191 	{
1192 		logger.info("new value " + newValue);
1193 
1194 		if (newValue != null) {
1195 			newValue = castNumber(newValue);
1196 
1197 			value = newValue;
1198 			
1199 			/*
1200 			 * If warnOutOfBounds is set, the following checks if value is out 
1201 			 * of bounds.
1202 			 */
1203 			if (warnOutOfBounds) {
1204 				valueChanged = true;
1205 				if (
1206 						(minimum != null && ((Comparable<Number>)minimum).compareTo(value) > 0) ||
1207 						(maximum != null && ((Comparable<Number>)maximum).compareTo(value) < 0)
1208 						) {
1209 					outOfBounds = true;
1210 				}
1211 				else {
1212 					outOfBounds = false;
1213 				}
1214 			}
1215 			
1216 			//numberDocument.setValue(newValue);
1217 		}
1218 	}
1219 
1220 	private void staticMode()
1221 	{
1222 		if (!isEnabled()) {
1223 			logger.finest("Not Enabled.");
1224 			return;
1225 		}
1226 
1227 		logger.fine("executing");
1228 
1229 		editing = false;
1230 
1231 		//int cPos = getCaretPosition();
1232 
1233 		/*if (getDocument() != plainDocument) {
1234 		    logger.finest("setting document");
1235 		    setDocument(plainDocument);
1236 		}*/
1237 
1238 		//		problem = false;
1239 		try {
1240 			String s = getNumberDescriptor().printString(value);
1241 
1242 			/* 
1243 			 * If statement prevents component from displaying warningColor 
1244 			 * instead of outOfBoundsColor if user has edited and reentered 
1245 			 * the same out of bounds value prior to loosing focus.
1246 			 */
1247 			if (warnOutOfBounds) setText(s);
1248 			else if (!s.equals(getText())) {
1249 				logger.finest("setting text " + s);
1250 				setText(s);
1251 			}
1252 		} catch (IllegalArgumentException iae) {
1253 			logger.warning("Exception " + iae);
1254 
1255 			//setFormat(createDefaultFormat(numberType, getFormat()));
1256 			//			problem = true;
1257 			//			setDocument(numberDocument);			
1258 			//			/			setText(toString(value));
1259 			//			System.err.println("Invalid format String: " + iae.getMessage());
1260 		}
1261 
1262 		//setDocument(numberDocument);			
1263 
1264 		/* seems llike workaround for ResizableTextField, should be handled there
1265 		if (userColumns < 1) {
1266 		    int col= calculateDefaultColumns();
1267 		    if (col!=getColumns())
1268 		    logger.finest("setting columns to "+);
1269 		    super.setColumns(calculateDefaultColumns());
1270 		}*/
1271 		if (!(getStaticBorder() == null && getEditBorder() == null)) {
1272 			if (getBorder() != getStaticBorder()) {
1273 				logger.finest("setting border");
1274 				setBorder(getStaticBorder());
1275 			}
1276 		}
1277 
1278 		/* Is this reallly necesary?
1279 		if (getParent() != null) {
1280 		    logger.finest("doLayout");
1281 		    getParent().doLayout();
1282 		}*/
1283 		/* why do we need this, if in static mode anyway caret is not visible.
1284 		 * Besides it is always 0 when this code happens.
1285 		cPos = Math.min(getDocument().getLength(), cPos);
1286 		if (cPos != getCaretPosition()) {
1287 		    logger.finest("setting carret to "+cPos+" from "+getCaretPosition());
1288 		    setCaretPosition(cPos);
1289 		}*/
1290 	}
1291 
1292 	private Number trimValue(Number newValue)
1293 	{
1294 		if (newValue == null) {
1295 			return null;
1296 		}
1297 
1298 		Number retVal = castNumber(newValue);
1299 
1300 		if ((minimum != null) && (((Comparable<Number>)retVal).compareTo(minimum) < 0)) {
1301 			retVal = minimum;
1302 		}
1303 
1304 		if ((maximum != null) && (((Comparable<Number>)retVal).compareTo(maximum) > 0)) {
1305 			retVal = maximum;
1306 		}
1307 
1308 		//retVal = newValue;
1309 
1310 		return retVal;
1311 	}
1312 
1313 	private void updateDocumentValue()
1314 	{
1315 		logger.fine("entering");
1316 
1317 		Number newValue = numberDocument.getValue();
1318 
1319 		if (newValue == null) {
1320 			return;
1321 		}
1322 
1323 		Number val = trimValue(newValue);
1324 
1325 		if (!newValue.equals(val) && tiltingEnabled) {
1326 			getTiltHandler().tilt();
1327 		}
1328 
1329 		setValue(val);
1330 	}
1331 
1332 	/**
1333 	 * Returns descriptor object, to which visualization of number as strring
1334 	 * is dlegated.
1335 	 *
1336 	 * @return number visualization
1337 	 */
1338 	public synchronized NumberDescriptor getNumberDescriptor()
1339 	{
1340 		if (numberDescriptor == null) {
1341 			setNumberDescriptor(new DefaultNumberDescriptor());
1342 		}
1343 
1344 		return numberDescriptor;
1345 	}
1346 
1347 	/**
1348 	 * When this is set with non-null value, this NumberField uses this
1349 	 * descriptor to convert Number to string and back. By this user can
1350 	 * define ovn visualization of Number.
1351 	 *
1352 	 * @param numberDescriptor new Number visualization, can be
1353 	 *        <code>null</code>
1354 	 */
1355 	public synchronized void setNumberDescriptor(
1356 	    NumberDescriptor numberDescriptor)
1357 	{
1358 		this.numberDescriptor = numberDescriptor;
1359 
1360 		if (numberDescriptor != null) {
1361 			numberDocument.setDescriptor(numberDescriptor);
1362 			numberDescriptor.setNumberType(getNumberType());
1363 			numberDescriptor.setFormat(getFormat());
1364 		}
1365 
1366 		if (!editing) {
1367 			staticMode();
1368 		}
1369 	}
1370 
1371 	/**
1372 	 * Converts provided number to <code>Number</code> instance whose class
1373 	 * matches current number type.
1374 	 *
1375 	 * @param number number to be cast to current number type
1376 	 *
1377 	 * @return correct number type
1378 	 */
1379 	public Number castNumber(Number number)
1380 	{
1381 		if (number == null) {
1382 			return null;
1383 		}
1384 
1385 		if (numberType == Double.class) {
1386 			if (number instanceof Double) {
1387 				return number;
1388 			}
1389 
1390 			return new Double(number.doubleValue());
1391 		}
1392 
1393 		if (number instanceof Long) {
1394 			return number;
1395 		}
1396 
1397 		return new Long(number.longValue());
1398 	}
1399 
1400 	/**
1401 	 * @return Returns the normalColor.
1402 	 */
1403 	public Color getNormalColor() {
1404 		return normalColor;
1405 	}
1406 
1407 	/**
1408 	 * @param normalColor The normalColor to set.
1409 	 */
1410 	public void setNormalColor(Color normalColor) {
1411 		Color oldValue = getNormalColor();
1412 		this.normalColor = normalColor;
1413 		firePropertyChange("normalColor", oldValue, normalColor);
1414 	}
1415 
1416 	/**
1417 	 * @return Returns the outOfBoundsColor.
1418 	 */
1419 	public Color getOutOfBoundsColor() {
1420 		return outOfBoundsColor;
1421 	}
1422 
1423 	/**
1424 	 * @param outOfBoundsColor The outOfBoundsColor to set.
1425 	 */
1426 	public void setOutOfBoundsColor(Color outOfBoundsColor) {
1427 		Color oldValue = getOutOfBoundsColor();
1428 		this.outOfBoundsColor = outOfBoundsColor;
1429 		firePropertyChange("outOfBoundsColor", oldValue, outOfBoundsColor);
1430 	}
1431 
1432 	/**
1433 	 * @return Returns the warningColor.
1434 	 */
1435 	public Color getWarningColor() {
1436 		return warningColor;
1437 	}
1438 
1439 	/**
1440 	 * @param warningColor The warningColor to set.
1441 	 */
1442 	public void setWarningColor(Color warningColor) {
1443 		Color oldValue = getWarningColor();
1444 		this.warningColor = warningColor;
1445 		firePropertyChange("warningColor", oldValue, warningColor);
1446 	}
1447 
1448 	
1449 	/**
1450 	 * Returns the warnOutOfBounds.
1451 	 * @return Returns the warnOutOfBounds.
1452 	 */
1453 	public boolean isWarnOutOfBounds() {
1454 		return warnOutOfBounds;
1455 	}
1456 	
1457 	/**
1458 	 * For testing and demonstration purposes.
1459 	 *
1460 	 * @param args String[]
1461 	 */
1462 	public static void main(String[] args)
1463 	{
1464 		JFrame frame = new JFrame();
1465 		frame.setSize(340, 300);
1466 
1467 		final NumberField field = new NumberField(new Long(12));
1468 
1469 		//field.setMaximum(new Integer(Integer.MAX_VALUE));
1470 		field.setMaximum(new Long(100));
1471 		field.setMinimum(new Long(0));
1472 		field.setNumberType(Long.class);
1473 		field.setValue(new Integer(20));
1474 		field.setResizable(true);
1475 		field.setTiltingEnabled(true);
1476 		field.setNumberDescriptor(new AngleNumberDescriptor());
1477 		//field.setNumberDescriptor(new DefaultNumberDescriptor());
1478 
1479 		field.addPropertyChangeListener(new PropertyChangeListener() {
1480 				public void propertyChange(PropertyChangeEvent evt)
1481 				{
1482 					//System.out.println("P> " + evt.getPropertyName() + " "
1483 					//    + evt.getNewValue());
1484 				}
1485 			});
1486 		field.addSetListener(new SetListener() {
1487 				public void setPerformed(SetEvent e)
1488 				{
1489 					System.out.println("S> " + e.getValue());
1490 				}
1491 			});
1492 		field.setSettingPolicy(SET_AS_TYPED);
1493 		frame.getContentPane().setLayout(new GridBagLayout());
1494 		frame.getContentPane().add(new JTextField(),
1495 		    new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0,
1496 		        GridBagConstraints.CENTER, GridBagConstraints.BOTH,
1497 		        new Insets(15, 15, 15, 15), 0, 0));
1498 		frame.getContentPane().add(field,
1499 		    new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0,
1500 		        GridBagConstraints.CENTER, GridBagConstraints.BOTH,
1501 		        new Insets(15, 15, 15, 15), 0, 0));
1502 		frame.addWindowListener(new WindowAdapter() {
1503 				public void windowClosing(WindowEvent e)
1504 				{
1505 					System.exit(0);
1506 				}
1507 			});
1508 		frame.setVisible(true);
1509 	}
1510 	
1511 }
1512 
1513 /* __oOo__ */