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 com.cosylab.events.SetEvent;
23  import com.cosylab.events.SetListener;
24  
25  import com.cosylab.gui.components.util.ColorHelper;
26  import com.cosylab.gui.components.util.PopupManageable;
27  import com.cosylab.gui.components.util.PopupManager;
28  import com.cosylab.gui.components.util.ScreenCapturer;
29  import com.cosylab.gui.components.wheelswitch.AbstractWheelswitchFormatter;
30  import com.cosylab.gui.components.wheelswitch.Digit;
31  import com.cosylab.gui.components.wheelswitch.StaticDigit;
32  import com.cosylab.gui.components.wheelswitch.UpDownButton;
33  import com.cosylab.gui.components.wheelswitch.ValueDigit;
34  import com.cosylab.gui.components.wheelswitch.WheelswitchFormatter;
35  import com.cosylab.gui.components.wheelswitch.WheelswitchLayout;
36  
37  import com.cosylab.util.Debug;
38  
39  import java.awt.BorderLayout;
40  import java.awt.Color;
41  import java.awt.Dimension;
42  import java.awt.event.ActionEvent;
43  import java.awt.event.ActionListener;
44  import java.awt.event.KeyAdapter;
45  import java.awt.event.KeyEvent;
46  import java.awt.event.MouseAdapter;
47  import java.awt.event.MouseEvent;
48  import java.awt.event.MouseWheelEvent;
49  import java.awt.event.MouseWheelListener;
50  
51  import java.util.ArrayList;
52  import java.util.Timer;
53  import java.util.TimerTask;
54  
55  import javax.swing.AbstractAction;
56  import javax.swing.Box;
57  import javax.swing.JButton;
58  import javax.swing.JFrame;
59  import javax.swing.JPanel;
60  import javax.swing.SwingUtilities;
61  import javax.swing.event.EventListenerList;
62  
63  
64  /**
65   * Descedant of <code>javax.swing.JPanel</code> that contains a row of digits
66   * and optionally a two way up-down buttons. It can be used for displaying and
67   * modifying a single formatted <code>double</code> value with an optional
68   * unit string (also in digits) displyed next to the value. Value manipulation
69   * and display formatting is handled by the <code>WheelswitchFormatter</code>.
70   *
71   * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
72   * @version $id$
73   *
74   * @see Digit
75   * @see WheelswitchFormatter
76   */
77  public class Wheelswitch extends JPanel implements PopupManageable
78  {
79  	private static final long serialVersionUID = 1L;
80  
81  	/**
82  	 * An implementation of KeyListener used for handling
83  	 * key commands for the wheelswitch.
84  	 *
85  	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
86  	 * @version $id$
87  	 */
88  	protected class KeyHandler extends KeyAdapter
89  	{
90  		/**
91  		 * Updates the digits in this Wheelswitch when key is pressed.
92  		 *
93  		 * @param e
94  		 */
95  		public void keyPressed(KeyEvent e)
96  		{
97  			if (editable && isEnabled()) {
98  				if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
99  //					Ike's FR by jbobnar RT#41405
100 					setSelectedDigit(-1);
101 					e.consume();
102 					return;
103 				}
104 				int i = getSelectedDigit();
105 
106 				if (i >= 0) {
107 					if ((e.getKeyChar() >= '0') && (e.getKeyChar() <= '9')) {
108 						setDigitValue(i,
109 						    Integer.parseInt(String.valueOf(e.getKeyChar())));
110 						setSelectedDigit(INCREASE_SELECTION);
111 					} else if (e.getKeyCode() == KeyEvent.VK_UP) {
112 						setDigitValue(i, ValueDigit.INCREASE_VALUE);
113 					} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
114 						setDigitValue(i, ValueDigit.DECREASE_VALUE);
115 					} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
116 						setSelectedDigit(INCREASE_SELECTION);
117 					} else if ((e.getKeyCode() == KeyEvent.VK_LEFT)
118 					    || (e.getKeyCode() == KeyEvent.VK_BACK_SPACE)) {
119 						setSelectedDigit(DECREASE_SELECTION);
120 					}
121 				} else {
122 					setSelectedDigit(0);
123 				}
124 			}
125 		}
126 	}
127 
128 	/**
129 	 * An implementation of KeyListener used for handling
130 	 * key commands for the wheelswitch.
131 	 *
132 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
133 	 * @version $id$
134 	 */
135 	protected class MouseHandler extends MouseAdapter
136 	{
137 		/**
138 		 * Updates digit selection in this Wheelswitch when left mouse button
139 		 * is pressed.
140 		 *
141 		 * @param e
142 		 */
143 		public void mousePressed(MouseEvent e)
144 		{
145 			if (e.getButton() == MouseEvent.BUTTON1) {
146 				int index = digits.indexOf(e.getSource());
147 //				Ike's FR by jbobnar RT#41405
148 				if (getSelectedDigit() == index) {
149 					setSelectedDigit(-1);
150 				} else {
151 					setSelectedDigit(index);
152 				}
153 				requestFocusInWindow();
154 			}
155 		}
156 	}
157 
158 	/**
159 	 * An implementation of the MouseWheelListener used for handling
160 	 * mouse wheel events inside the wheelswitch.
161 	 *
162 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
163 	 * @version $id$
164 	 */
165 	protected class WheelSwitchMouseHandler extends MouseAdapter implements MouseWheelListener
166 	{
167 		/**
168 		 * Updates the value of the Wheelswitch when UpDownButton is pressed.
169 		 *
170 		 * @param e DOCUMENT ME!
171 		 */
172 		public void mouseWheelMoved(MouseWheelEvent e)
173 		{
174 			Debug.out("mousewheel");
175 
176 			if (editable && isEnabled()) {
177 				int i = getSelectedDigit();
178 
179 				if (i >= 0) {
180 					if (e.getWheelRotation() > 0) {
181 						setDigitValue(i, ValueDigit.DECREASE_VALUE);
182 					} else {
183 						setDigitValue(i, ValueDigit.INCREASE_VALUE);
184 					}
185 				}
186 			}
187 		}
188 		
189 		//Ike's FR by jbobnar RT#41405
190 		@Override
191 		public void mousePressed(MouseEvent e) {
192 			requestFocus();
193 			if (getSelectedDigit() == -1) {
194 				if (SwingUtilities.isLeftMouseButton(e)) {
195 					if (selectedDigit < 0) {
196 						setSelectedDigit(0);
197 					}
198 				} else if (SwingUtilities.isRightMouseButton(e)) {
199 					if (selectedDigit < 0) {
200 						setSelectedDigit(digits.size()-1);
201 					}
202 					
203 				}
204 			}
205 		}
206 	}
207 
208 	/**
209 	 * An extension of Timer used for periodic tilting of the Wheelswitch.
210 	 *
211 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
212 	 * @version $id$
213 	 *
214 	 */
215 	protected class TiltHandler extends Timer
216 	{
217 		private class TiltTask extends TimerTask
218 		{
219 			public void run()
220 			{
221 				if (numberOfTilts >= MAX_NUMBER_OF_TILTS) {
222 					cancel();
223 
224 					for (int i = 0; i < digits.size(); i++) {
225 						(digits.get(i)).setTilting(false);
226 					}
227 				} else {
228 					numberOfTilts++;
229 
230 					for (int i = 0; i < digits.size(); i++) {
231 						(digits.get(i)).setTilting(!(digits.get(i))
232 						    .isTilting());
233 					}
234 				}
235 
236 				repaint();
237 			}
238 		}
239 
240 		//		private AudioClip clip1;
241 		//		private AudioClip clip2;
242 		private final int MAX_NUMBER_OF_TILTS = 3;
243 		private final long TILT_RATE = 200;
244 		private int numberOfTilts = MAX_NUMBER_OF_TILTS;
245 
246 		/**
247 		 * Scedules a new tilting task if the user value equals any of the
248 		 * bounds.
249 		 */
250 		public void tilt()
251 		{
252 			if ((formatter.getValue() < formatter.getMaximum())
253 			    && (formatter.getValue() > formatter.getMinimum())
254 			    || !tiltingEnabled) {
255 				//	would be a nice demonstration feature
256 				//					if (enhanced && clip1==null) {
257 				//						clip1 = 	Applet.newAudioClip(ClassLoader.getSystemResource("tick.wav"));
258 				//					}        
259 				//					if (enhanced) clip1.play();
260 				return;
261 			}
262 
263 			//			would be a nice demonstration feature
264 			//			if (enhanced && clip2==null) {
265 			//				clip2 = 	Applet.newAudioClip(ClassLoader.getSystemResource("error.wav"));
266 			//			}
267 			//			if (enhanced) clip2.play();
268 			if (numberOfTilts >= MAX_NUMBER_OF_TILTS) {
269 				numberOfTilts = 0;
270 				schedule(new TiltTask(), 0, TILT_RATE);
271 			} else {
272 				numberOfTilts = 0;
273 			}
274 		}
275 	}
276 
277 	/**
278 	 * An implementation of ActionListener used for handling
279 	 * events from the up-down button in the wheelswitch.
280 	 *
281 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
282 	 * @version $id$
283 	 */
284 	protected class UpDownActionHandler implements ActionListener
285 	{
286 		/**
287 		 * Updates the value of the Wheelswitch when UpDownButton is pressed.
288 		 *
289 		 * @param e
290 		 */
291 		public void actionPerformed(ActionEvent e)
292 		{
293 			if (editable && isEnabled()) {
294 				int i = getSelectedDigit();
295 
296 				if (i >= 0) {
297 					int value = 0;
298 
299 					if (e.getSource() == getUpDownButton().getUpButton()) {
300 						value = ValueDigit.INCREASE_VALUE;
301 					}
302 
303 					if (e.getSource() == getUpDownButton().getDownButton()) {
304 						value = ValueDigit.DECREASE_VALUE;
305 					}
306 
307 					setDigitValue(i, value);
308 				} else {
309 					// Jernej should tell if this is OK
310 					//setSelectedDigit(digits.size() - 1);
311 					// jkamenik: No, Not OK. 
312 					// This would select the exponent in exponential format causing havoc!
313 					// The setting below is consistent with key behavior.
314 					setSelectedDigit(0);
315 				}
316 
317 				//requestFocusInWindow();
318 			}
319 		}
320 	}
321 
322 	protected static int INCREASE_SELECTION = -11;
323 	protected static int DECREASE_SELECTION = -12;
324 
325 	/** Tag for value property. */
326 	public static final String VALUE = "value";
327 
328 	/** Tag for editable property */
329 	public static final String EDITABLE = "editable";
330 	protected EventListenerList listenerList = null;
331 	protected KeyHandler keyHandler;
332 	protected MouseHandler mouseHandler;
333 	protected TiltHandler tiltHandler;
334 	protected WheelSwitchMouseHandler handler;
335 	private Dimension minimumSize = null;
336 	private Dimension preferredSize = null;
337 	private java.util.List<Digit> digits;
338 	private java.util.List<Digit> unitDigits;
339 	private java.util.List<Digit> hiddenDigits;
340 	private UpDownButton upDownButton;
341 	private AbstractWheelswitchFormatter formatter;
342 	private boolean editable = true;
343 	private PopupManager popupManager;
344 	private boolean popupEnabled;
345 	private boolean enhanced = true;
346 	private boolean animated = false;
347 	private boolean tiltingEnabled;
348 	private int selectedDigit = -1;
349 	private ResizableTextLabel unitLabel;
350 	private boolean debug = false;
351 	private boolean unitSeparate = false;
352 	private boolean digitsTakeUpAllSpace = true;
353 	private int numberOfAllDigits = 10;
354 
355 	/**
356 	 * Constant string representing the property name of horizontal alignment.
357 	 */
358 	public static final String HORIZONTAL_ALIGNMENT = "horizontalAlignment";
359 
360 	/**
361 	 * Constructor for <code>Wheelswitch</code> creates a new Wheelswitch with
362 	 * the specified value, format and unit. No minimum or maximum values are
363 	 * set.
364 	 *
365 	 * @param newFormat
366 	 * @param newValue
367 	 * @param newUnit
368 	 */
369 	public Wheelswitch(String newFormat, double newValue, String newUnit)
370 	{
371 		super();
372 		listenerList = new EventListenerList();
373 		mouseHandler = new MouseHandler();
374 		keyHandler = new KeyHandler();
375 		tiltHandler = new TiltHandler();
376 		handler = new WheelSwitchMouseHandler();
377 
378 		digits = new ArrayList<Digit>();
379 		unitDigits = new ArrayList<Digit>();
380 		hiddenDigits = new ArrayList<Digit>();
381 
382 
383 		this.formatter = new WheelswitchFormatter(newFormat);
384 		this.formatter.setUnit(newUnit);
385 		this.formatter.setValue(newValue);
386 
387 		addKeyListener(keyHandler);
388 		addMouseWheelListener(handler);
389 		getUpDownButton().addMouseListener(handler);
390 		addMouseListener(handler);
391 
392 		setFocusable(true);
393 		setBackground(ColorHelper.getCosyControl());
394 		setLayout(new WheelswitchLayout());
395 
396 		setupValueDigits();
397 		setupUnitDigits();
398 		setupLayout();
399 		validate();
400 		repaint();
401 		setPopupEnabled(true);
402 	}
403 	
404 	private UpDownButton getUpDownButton() {
405 		if (upDownButton == null) {
406 			upDownButton = new UpDownButton();
407 			upDownButton.setName("upDownButton");
408 			upDownButton.getUpButton().addActionListener(new UpDownActionHandler());
409 			upDownButton.getDownButton().addActionListener(new UpDownActionHandler());
410 			upDownButton.addKeyListener(keyHandler);
411 			upDownButton.setEnabled(false);
412 		}
413 		return upDownButton;
414 	}
415 	
416 	private ResizableTextLabel getUnitLabel() {
417 		if (unitLabel == null) {
418 			unitLabel = new ResizableTextLabel();
419 			unitLabel.setResizable(true);
420 			unitLabel.setEnhanced(enhanced);
421 		}
422 
423 		return unitLabel;
424 	}
425 
426 	/*
427 	 * (non-Javadoc)
428 	 * @see com.cosylab.gui.components.util.PopupManageable#getPopupManager()
429 	 */
430 	public PopupManager getPopupManager()
431 	{
432 		if (popupManager == null) {
433 			popupManager = new PopupManager(this, false);
434 			popupManager.addAction(new AbstractAction("Capture screen...") {
435 				private static final long serialVersionUID = 1L;
436 
437 					public void actionPerformed(ActionEvent e)
438 					{
439 						ScreenCapturer sc = new ScreenCapturer(Wheelswitch.this);
440 						sc.showScreenDialog();
441 					}
442 				});
443 		}
444 
445 		return popupManager;
446 	}
447 	
448 	/**
449 	 * Return true if the popup menu is enabled or false otherwise.
450 	 * 
451 	 * @return true if popup is enabled
452 	 */
453 	public boolean isPopupEnabled() {
454 		return popupEnabled;
455 	}
456 	
457 	/**
458 	 * Enables or disables the popup menu.
459 	 * 
460 	 * @param enabled true if enable or false if disableds
461 	 */
462 	public void setPopupEnabled(boolean enabled) {
463 		if (popupEnabled == enabled) return;
464 		popupEnabled = enabled;
465 		if (enabled) {
466 			addMouseListener(getPopupManager().getMouseHook());
467 		} else {
468 			removeMouseListener(getPopupManager().getMouseHook());
469 		}
470 		firePropertyChange("popupEnabled",!popupEnabled,popupEnabled);
471 	}
472 
473 	/**
474 	 * Constructor for <code>Wheelswitch</code> setting only the value. No
475 	 * format or unit are set.
476 	 *
477 	 * @param newValue
478 	 *
479 	 * @see #Wheelswitch(String, double, String)
480 	 */
481 	public Wheelswitch(double newValue)
482 	{
483 		this(null, newValue, null);
484 	}
485 
486 	/**
487 	 * Constructor for Wheelswitch which sets no format or unit and the value
488 	 * is set to zero.
489 	 *
490 	 * @see #Wheelswitch(String, double, String)
491 	 */
492 	public Wheelswitch()
493 	{
494 		this(null, 0, null);
495 	}
496 
497 	/**
498 	 * Sets the editability of the wheelswitch. If true user can edit change the 
499 	 * values displayed by wheelswitch.
500 	 *
501 	 * @param newEditable
502 	 */
503 	public void setEditable(boolean newEditable)
504 	{
505 		if (newEditable == editable) {
506 			return;
507 		}
508 
509 		boolean oldEditable = editable;
510 		editable = newEditable;
511 		firePropertyChange(EDITABLE, oldEditable, newEditable);
512 
513 		if (!isEnabled()) {
514 			return;
515 		}
516 
517 		setupLayout();
518 		validate();
519 		repaint();
520 	}
521 
522 	/**
523 	 * Returns whether the wheelswitch can be edited by the user.
524 	 *
525 	 * @return boolean
526 	 */
527 	public boolean isEditable()
528 	{
529 		return editable;
530 	}
531 
532 	/**
533 	 * Sets the enhanced property of the wheelswitch. When enhanced the
534 	 * wheelswitch animates its digits when changing the value displayed.
535 	 *
536 	 * @see com.cosylab.gui.components.wheelswitch.Digit#setEnhanced(boolean)
537 	 */
538 	public void setEnhanced(boolean enhanced)
539 	{
540 		if (this.enhanced == enhanced) {
541 			return;
542 		}
543 
544 		firePropertyChange("enhanced", this.enhanced, enhanced);
545 		this.enhanced = enhanced;
546 
547 		for (int i = 0; i < digits.size(); i++) {
548 			(digits.get(i)).setEnhanced(enhanced);
549 		}
550 
551 		for (int i = 0; i < unitDigits.size(); i++) {
552 			(unitDigits.get(i)).setEnhanced(enhanced);
553 		}
554 
555 		getUnitLabel().setEnhanced(enhanced);
556 	}
557 
558 	/**
559 	 * Gets the enhancement mode of the <code>Wheelswitch</code>.
560 	 *
561 	 * @return true if wheelswitch is in enhacement mode
562 	 */
563 	public boolean isEnhanced()
564 	{
565 		return enhanced;
566 	}
567 
568 	/**
569 	 * Sets the format of the value display. Format style is Wheelswitch
570 	 * specific.
571 	 *
572 	 * @param newFormat new format (null not permitted)
573 	 *
574 	 * @throws NullPointerException if paraeter is null
575 	 *
576 	 * @see WheelswitchFormatter#setFormat(String)
577 	 */
578 	public void setFormat(String newFormat)
579 	{
580 		if (newFormat == null) {
581 			throw new NullPointerException("newFormat");
582 		}
583 
584 		if ((formatter.getFormat() != null)
585 		    && formatter.getFormat().equals(newFormat)) {
586 			return;
587 		}
588 
589 		String oldFormat = formatter.getFormat();
590 
591 		try {
592 			formatter.setFormat(newFormat);
593 		} catch (IllegalArgumentException e) {
594 			Debug.out(e);
595 
596 			return;
597 		}
598 
599 		firePropertyChange("format", oldFormat, newFormat);
600 
601 		if (!isEnabled()) {
602 			return;
603 		}
604 
605 		setupValueDigits();
606 		setupLayout();
607 		validate();
608 		repaint();
609 	}
610 
611 	/**
612 	 * Gets the format of the display.
613 	 *
614 	 * @return the format
615 	 *
616 	 * @see WheelswitchFormatter#getFormat()
617 	 */
618 	public String getFormat()
619 	{
620 		return formatter.getFormat();
621 	}
622 
623 	/**
624 	 * Sets the maximum allowed value.
625 	 *
626 	 * @param newValue new maximum
627 	 *
628 	 * @see WheelswitchFormatter#setMaximum(double)
629 	 */
630 	public void setGraphMax(double newValue)
631 	{
632 		double oldValue = formatter.getMaximum();
633 
634 		if (oldValue == newValue) {
635 			return;
636 		}
637 
638 		formatter.setMaximum(newValue);
639 
640 		firePropertyChange("graphMax", oldValue, newValue);
641 
642 		checkBounds();
643 	}
644 
645 	/**
646 	 * Gets the maximum alowed value.
647 	 *
648 	 * @return the maximum
649 	 *
650 	 * @see WheelswitchFormatter#getMaximum()
651 	 */
652 	public double getGraphMax()
653 	{
654 		return formatter.getMaximum();
655 	}
656 
657 	/**
658 	 * Sets the minimum allowed value.
659 	 *
660 	 * @param newValue new minimum
661 	 *
662 	 * @see WheelswitchFormatter#setMinimum(double)
663 	 */
664 	public void setGraphMin(double newValue)
665 	{
666 		double oldValue = formatter.getMinimum();
667 
668 		if (oldValue == newValue) {
669 			return;
670 		}
671 
672 		formatter.setMinimum(newValue);
673 
674 		firePropertyChange("graphMin", oldValue, newValue);
675 
676 		checkBounds();
677 	}
678 
679 	/**
680 	 * Gets the minimum alowed value.
681 	 *
682 	 * @return the minimum
683 	 *
684 	 * @see WheelswitchFormatter#getMinimum()
685 	 */
686 	public double getGraphMin()
687 	{
688 		return formatter.getMinimum();
689 	}
690 
691 	/**
692 	 * Sets the maximum and minimum allowed values. This is a convenient method
693 	 * for setting both bounds.
694 	 *
695 	 * @param max
696 	 * @param min
697 	 */
698 	public void setMaxMin(double max, double min)
699 	{
700 		setGraphMax(max);
701 		setGraphMin(min);
702 	}
703 
704 	/*
705 	 * (non-Javadoc)
706 	 * @see javax.swing.JComponent#getMinimumSize()
707 	 */
708 	public Dimension getMinimumSize()
709 	{
710 		if (minimumSize == null) {
711 			int height = 0;
712 			int width = 0;
713 
714 			for (int i = 0; i < getComponentCount(); i++) {
715 				width += getComponent(i).getMinimumSize().width;
716 				height = Math.max(height,
717 					    getComponent(i).getMinimumSize().height);
718 			}
719 
720 			minimumSize = new Dimension(width, height);
721 		}
722 
723 		return minimumSize;
724 	}
725 
726 	/*
727 	 * (non-Javadoc)
728 	 * @see javax.swing.JComponent#getPreferredSize()
729 	 */
730 	public Dimension getPreferredSize()
731 	{
732 		if (preferredSize == null) {
733 			int height = 0;
734 			int width = 0;
735 
736 			for (int i = 0; i < getComponentCount(); i++) {
737 				width += getComponent(i).getPreferredSize().width;
738 				height = Math.max(height,
739 					    getComponent(i).getPreferredSize().height);
740 			}
741 
742 			preferredSize = new Dimension(width, height);
743 		}
744 
745 		return preferredSize;
746 	}
747 
748 	/**
749 	 * Sets the tilitng enabled property. If tiliting is enabled the component
750 	 * will tile when out of bounds value is set.
751 	 *
752 	 * @param b whether the component should tilt when value is out of bounds.
753 	 */
754 	public void setTiltingEnabled(boolean b)
755 	{
756 		if (tiltingEnabled == b) {
757 			return;
758 		}
759 
760 		tiltingEnabled = b;
761 		firePropertyChange("tiltingEnabled", !b, b);
762 	}
763 
764 	/**
765 	 * Returns whether the component should indicate value out of bounds
766 	 * condition by visually tilting its border.
767 	 *
768 	 * @return boolean
769 	 */
770 	public boolean isTiltingEnabled()
771 	{
772 		return tiltingEnabled;
773 	}
774 
775 	/**
776 	 * Sets the unit to be displayed next to the value.
777 	 *
778 	 * @param newUnit new units
779 	 *
780 	 * @see WheelswitchFormatter#setUnit(String)
781 	 */
782 	public void setUnit(String newUnit)
783 	{
784 		String oldUnit = formatter.getUnit();
785 
786 		if (oldUnit == newUnit) {
787 			return;
788 		}
789 
790 		formatter.setUnit(newUnit);
791 		firePropertyChange("unit", oldUnit, newUnit);
792 
793 		if (!isEnabled()) {
794 			return;
795 		}
796 
797 		setupUnitDigits();
798 		setupLayout();
799 		doLayout();
800 		repaint();
801 	}
802 
803 	/**
804 	 * Gets the unit displayed next to the value.
805 	 *
806 	 * @return the units
807 	 *
808 	 * @see WheelswitchFormatter#getUnit()
809 	 */
810 	public String getUnit()
811 	{
812 		return formatter.getUnit();
813 	}
814 
815 	/**
816 	 * Sets the value and displays it in the wheelswitch. The method may also
817 	 * change the current digit selection if neccessary in order to point to
818 	 * the same decimal digit of the displayed value.
819 	 *
820 	 * @param newValue new value
821 	 *
822 	 * @see WheelswitchFormatter#setValue(double)
823 	 */
824 	public void setValue(double newValue)
825 	{
826 		double oldValue = formatter.getValue();
827 
828 		if (oldValue == newValue) {
829 			return;
830 		}
831 
832 		firePropertyChange(VALUE, oldValue, newValue);
833 
834 		int oldDigitSelection = getSelectedDigit();
835 		int decimalSelection = parseDecimalPosition(oldDigitSelection);
836 
837 		String oldStringValue = formatter.getString();
838 		formatter.setValue(newValue);
839 
840 		String newStringValue = formatter.getString();
841 
842 		if (!isEnabled()) {
843 			return;
844 		}
845 
846 		process(oldStringValue, newStringValue);
847 
848 		int newDigitSelection = parseDigitPosition(decimalSelection);
849 
850 		if ((newDigitSelection < digits.size()) && (newDigitSelection >= 0)) {
851 			setSelectedDigit(newDigitSelection);
852 		} else {
853 			setSelectedDigit(-1);
854 		}
855 
856 		checkBounds();
857 
858 		//tiltHandler.tilt();
859 	}
860 
861 	private void checkBounds()
862 	{
863 		if (getValue() > getGraphMax()) {
864 			getUpDownButton().setOutOfBounds(1);
865 		} else if (getValue() < getGraphMin()) {
866 			getUpDownButton().setOutOfBounds(-1);
867 		} else {
868 			getUpDownButton().setOutOfBounds(0);
869 		}
870 	}
871 
872 	/**
873 	 * Returns the value displayed by the <code>Wheelswitch</code> and stored by
874 	 * the <code>formatter</code>.
875 	 *
876 	 * @return the value
877 	 *
878 	 * @see WheelswitchFormatter#getValue()
879 	 */
880 	public double getValue()
881 	{
882 		return formatter.getValue();
883 	}
884 
885 	/**
886 	 * Adds an <code>SetListener</code> to the array of listeners currently
887 	 * registered for listening to the value sets of the
888 	 * <code>Wheelswitch</code>. These listeners are notified whenever the
889 	 * user sets a new value.
890 	 *
891 	 * @param l
892 	 *
893 	 * @see com.cosylab.events.SetListener
894 	 */
895 	public void addSetListener(SetListener l)
896 	{
897 		listenerList.add(SetListener.class, l);
898 	}
899 
900 	/*
901 	 * (non-Javadoc)
902 	 * @see javax.swing.JComponent#setEnabled(boolean)
903 	 */
904 	public void setEnabled(boolean arg0)
905 	{
906 		super.setEnabled(arg0);
907 
908 		setupValueDigits();
909 		setupUnitDigits();
910 		setupLayout();
911 		validate();
912 		repaint();
913 
914 		for (int i = 0; i < digits.size(); i++) {
915 			(digits.get(i)).setEnabled(arg0);
916 		}
917 
918 		for (int i = 0; i < unitDigits.size(); i++) {
919 			(unitDigits.get(i)).setEnabled(arg0);
920 		}
921 
922 		getUpDownButton().setEnabled(arg0);
923 	}
924 
925 	/**
926 	 * Sets the maximum number of value digits allowed to be displayed in the
927 	 * wheelswitch. The default value is 0 and is ignored.
928 	 *
929 	 * @param bound is ignored if less or equal zero.
930 	 */
931 	public void setMaximumDigits(int bound)
932 	{
933 		int oldBound = formatter.getMaximumDigits();
934 		formatter.setMaximumDigits(bound);
935 		firePropertyChange("maximumDigits", oldBound, bound);
936 	}
937 
938 	/**
939 	 * Removes an <code>SetListener</code> from the array of listeners
940 	 * currently registered for listening to the value sets of the
941 	 * <code>Wheelswitch</code>.
942 	 *
943 	 * @param l
944 	 */
945 	public void removeSetListener(SetListener l)
946 	{
947 		listenerList.remove(SetListener.class, l);
948 	}
949 
950 	/**
951 	 * Sets the value at the i-th digit
952 	 */
953 	protected void setDigitValue(int i, int newValue)
954 	{
955 		if (digits.get(i) instanceof StaticDigit) {
956 			return;
957 		}
958 
959 		String oldStringValue = formatter.getString();
960 		String newStringValue = oldStringValue;
961 
962 		//flip increase/decrease for negative values
963 		if ((newValue == ValueDigit.INCREASE_VALUE)
964 		    || (newValue == ValueDigit.DECREASE_VALUE)) {
965 			int expIndex = oldStringValue.indexOf('E');
966 
967 			if (expIndex == -1) {
968 				expIndex = oldStringValue.length();
969 			}
970 
971 			if (((oldStringValue.charAt(0) == '-') && (i < expIndex))
972 			    ^ ((i > expIndex) && (expIndex < (oldStringValue.length() - 1))
973 			    && (oldStringValue.charAt(expIndex + 1) == '-'))) {
974 				if (newValue == ValueDigit.INCREASE_VALUE) {
975 					newValue = ValueDigit.DECREASE_VALUE;
976 				} else {
977 					newValue = ValueDigit.INCREASE_VALUE;
978 				}
979 			}
980 		}
981 
982 		if ((newValue >= 0) && (newValue <= 9)) {
983 			newStringValue = oldStringValue.substring(0, i)
984 				+ String.valueOf(newValue) + oldStringValue.substring(i + 1);
985 		} else if (newValue == ValueDigit.INCREASE_VALUE) {
986 			int j;
987 
988 			for (j = i;
989 			    ((j >= 0) && (oldStringValue.charAt(j) != 'E')
990 			    && (oldStringValue.charAt(j) != '+')
991 			    && (oldStringValue.charAt(j) != '-') && (newValue != 0));
992 			    j--) {
993 				if (oldStringValue.charAt(j) == '.') {
994 					continue;
995 				} else if (oldStringValue.charAt(j) != '9') {
996 					newStringValue = newStringValue.substring(0, j)
997 						+ String.valueOf(Integer.parseInt(
998 						        newStringValue.substring(j, j + 1)) + 1)
999 						+ newStringValue.substring(j + 1);
1000 					newValue = 0;
1001 				} else {
1002 					
1003 					newStringValue = newStringValue.substring(0, j) + "0"
1004 						+ newStringValue.substring(j + 1);
1005 				}
1006 			}
1007 
1008 			if (newValue != 0) {
1009 				if (oldStringValue.charAt(0) == '.') {
1010 					newStringValue = "1" + newStringValue;
1011 				} else if ((oldStringValue.charAt(j + 1) == '.')
1012 				    && ((oldStringValue.charAt(j) == '+')
1013 				    || (oldStringValue.charAt(j) == '-')
1014 				    || (oldStringValue.charAt(j) == 'E'))) {
1015 					newStringValue = newStringValue.substring(0, 1) + "1"
1016 						+ newStringValue.substring(1);
1017 				} else {
1018 					newStringValue = newStringValue.substring(0, j + 1) + "10"
1019 						+ newStringValue.substring(j + 2);
1020 				}
1021 			}
1022 		} else if (newValue == ValueDigit.DECREASE_VALUE) {
1023 			boolean signTag = false;
1024 
1025 			for (int j = i;
1026 			    ((j >= 0) && (oldStringValue.charAt(j) != 'E')
1027 			    && (oldStringValue.charAt(j) != '+')
1028 			    && (oldStringValue.charAt(j) != '-')); j--) {
1029 				if ((oldStringValue.charAt(j) != '.')
1030 				    && (oldStringValue.charAt(j) != '0')) {
1031 					signTag = true;
1032 				}
1033 			}
1034 
1035 			if (signTag) {
1036 				for (int j = i;
1037 				    ((j >= 0) && (oldStringValue.charAt(j) != 'E')
1038 				    && (oldStringValue.charAt(j) != '+')
1039 				    && (oldStringValue.charAt(j) != '-') && (newValue != 0));
1040 				    j--) {
1041 					if (oldStringValue.charAt(j) == '.') {
1042 						
1043 					} else if (oldStringValue.charAt(j) != '0') {
1044 						newStringValue = newStringValue.substring(0, j)
1045 							+ String.valueOf(Integer.parseInt(
1046 							        newStringValue.substring(j, j + 1)) - 1)
1047 							+ newStringValue.substring(j + 1);
1048 						newValue = 0;
1049 					} else {
1050 						newStringValue = newStringValue.substring(0, j) + "9"
1051 							+ newStringValue.substring(j + 1);
1052 					}
1053 				}
1054 			} else {
1055 				int indexOfE = oldStringValue.indexOf('E');
1056 				char ones = '0';
1057 				if (getValue() == 0 && i <= indexOfE) {
1058 					ones = '1';
1059 				} else if (i > indexOfE) {
1060 					if (Double.parseDouble(oldStringValue.substring(indexOfE+1)) == 0.) {
1061 						ones = '1';
1062 					}
1063 				}
1064 				
1065 				if (i > indexOfE) {
1066 					if (oldStringValue.charAt(indexOfE + 1) == '+') {
1067 						newStringValue = newStringValue.substring(0,
1068 							    indexOfE + 1) + "-"
1069 							+ newStringValue.substring(indexOfE + 2, i) + ones
1070 							+ newStringValue.substring(i + 1);
1071 					} else if (oldStringValue.charAt(indexOfE + 1) == '-') {
1072 						newStringValue = newStringValue.substring(0,
1073 							    indexOfE + 1) + "+"
1074 							+ newStringValue.substring(indexOfE + 2, i) + ones
1075 							+ newStringValue.substring(i + 1);
1076 					} else {
1077 						newStringValue = newStringValue.substring(0,
1078 							    indexOfE + 1) + "-"
1079 							+ newStringValue.substring(indexOfE + 1, i) + ones
1080 							+ newStringValue.substring(i + 1);
1081 					}
1082 				} else {
1083 					if (oldStringValue.charAt(0) == '+') {
1084 						newStringValue = "-" + newStringValue.substring(1, i)
1085 							+ ones + newStringValue.substring(i + 1);
1086 					} else if (oldStringValue.charAt(0) == '-') {
1087 						newStringValue = "+" + newStringValue.substring(1, i)
1088 							+ ones + newStringValue.substring(i + 1);
1089 					} else {
1090 						newStringValue = "-" + newStringValue.substring(0, i)
1091 							+ ones + newStringValue.substring(i + 1);
1092 					}
1093 				}
1094 			}
1095 		}
1096 		setStringValue(newStringValue);
1097 	}
1098 
1099 	/**
1100 	 * Sets a new Digit selection.
1101 	 * @param i new digit selection.
1102 	 */
1103 	protected void setSelectedDigit(int i)
1104 	{
1105 		if (editable) {
1106 			if (i == selectedDigit) {
1107 				return;
1108 			}
1109 
1110 			if ((selectedDigit >= 0) && (selectedDigit < digits.size())) {
1111 				(digits.get(selectedDigit)).setSelected(false);
1112 			}
1113 
1114 			if ((i == INCREASE_SELECTION)
1115 			    || ((i >= 0) && (i < digits.size())
1116 			    && digits.get(i) instanceof StaticDigit)) {
1117 				if (i == INCREASE_SELECTION) {
1118 					i = selectedDigit;
1119 				}
1120 
1121 				while ((++i < digits.size())
1122 				    && digits.get(i) instanceof StaticDigit) {
1123 					
1124 				}
1125 
1126 				if (i == digits.size()) {
1127 					i = -1;
1128 
1129 					while (digits.get(++i) instanceof StaticDigit) {
1130 						
1131 					}
1132 				}
1133 			} else if (i == DECREASE_SELECTION) {
1134 				i = selectedDigit;
1135 
1136 				while ((--i >= 0) && digits.get(i) instanceof StaticDigit) {
1137 					
1138 				}
1139 
1140 				if (i < 0) {
1141 					i = digits.size();
1142 
1143 					while (digits.get(--i) instanceof StaticDigit) {
1144 						
1145 					}
1146 				}
1147 			}
1148 
1149 			if ((i >= 0) && (i < digits.size())) {
1150 				(digits.get(i)).setSelected(true);
1151 			}
1152 
1153 			selectedDigit = i;
1154 			getUpDownButton().setEnabled(selectedDigit >= 0 && selectedDigit < digits.size());
1155 		}
1156 	}
1157 
1158 	/**
1159 	 * Returns the currently selected digit.
1160 	 * @return int selection index
1161 	 */
1162 	protected int getSelectedDigit()
1163 	{
1164 		return selectedDigit;
1165 	}
1166 
1167 	/**
1168 	 * Fires a <code>SetEvent</code> to all currently
1169 	 * registered <code>SetListener</code>s of the <code>Wheelswitch</code>.
1170 	 */
1171 	protected void fireSetPerformed(double newValue)
1172 	{
1173 		Object[] listeners = listenerList.getListenerList();
1174 
1175 		for (int i = listeners.length - 2; i >= 0; i -= 2) {
1176 			if (listeners[i] == SetListener.class) {
1177 				((SetListener)listeners[i + 1]).setPerformed(new SetEvent(
1178 				        this, newValue));
1179 			}
1180 		}
1181 	}
1182 
1183 	/**
1184 	 * (Re)initializes existing value digits inside the wheelswitch.
1185 	 */
1186 	protected void initDigits()
1187 	{
1188 		String stringValue = formatter.getString();
1189 		char digitValue = 0;
1190 		boolean valueOutOfBounds = false;
1191 
1192 		if (getValue() > getGraphMax() || getValue() < getGraphMin()) {
1193 			valueOutOfBounds = true;
1194 		}
1195 
1196 		for (int i = 0; i < stringValue.length(); i++) {
1197 			digitValue = stringValue.charAt(i);
1198 			(digits.get(i)).setTilting(valueOutOfBounds);
1199 
1200 			if ((digitValue == '+') || (digitValue == '-')
1201 			    || (digitValue == 'E') || (digitValue == 'e')
1202 			    || (digitValue == '.')) {
1203 				//bugfix RT#11900 by jkamenik
1204 				(digits.get(i)).repaint();
1205 
1206 				//end bugfix
1207 			} else {
1208 				if (digits.get(i) instanceof ValueDigit) {
1209 					//bugfix RT#11900 by jkamenik
1210 					ValueDigit digit = ((ValueDigit)digits.get(i));
1211 					int newVal = Integer.parseInt(stringValue.substring(i, i
1212 						        + 1));
1213 
1214 					if (digit.getValue() != newVal) {
1215 						digit.setValue(newVal);
1216 					} else {
1217 						digit.repaint();
1218 					}
1219 
1220 					//end bugfix					    
1221 				} else {
1222 					Debug.out(
1223 					    "Wheelswitch#initDigits(): digits improperly synchronized");
1224 					setupValueDigits();
1225 					setupLayout();
1226 					validate();
1227 					repaint();
1228 
1229 					return;
1230 				}
1231 			}
1232 		}
1233 
1234 		//repaint();
1235 	}
1236 
1237 	/**
1238 	 * Repositions the components inside the wheelswitch.
1239 	 */
1240 	protected void setupLayout()
1241 	{
1242 		removeAll();
1243 
1244 		if(!digitsTakeUpAllSpace) {
1245 			for (int i = 0; i < hiddenDigits.size(); i++)
1246 				add(hiddenDigits.get(i));
1247 		}
1248 		
1249 		//  by mkadunc
1250 		//		add(Box.createHorizontalStrut(5));
1251 		for (int i = 0; i < digits.size(); i++) {
1252 			if ((digits.get(i)).getText().indexOf('E') != -1) {
1253 				add(Box.createHorizontalStrut(3));
1254 			}
1255 
1256 			add(digits.get(i));
1257 		}
1258 
1259 		if (unitDigits.size() != 0) {
1260 			add(Box.createHorizontalStrut(5));
1261 
1262 			for (int i = 0; i < unitDigits.size(); i++) {
1263 				add(unitDigits.get(i));
1264 			}
1265 		}
1266 
1267 		if (editable) {
1268 			add(Box.createHorizontalStrut(5));
1269 			add(getUpDownButton());
1270 		}
1271 
1272 		if (unitSeparate) {
1273 			add(Box.createHorizontalStrut(5));
1274 			add(getUnitLabel());
1275 		}
1276 
1277 		preferredSize = null;
1278 		minimumSize = null;
1279 
1280 		//requestFocusInWindow();
1281 	}
1282 
1283 	/**
1284 	 * Constructs unit digits from scratch.
1285 	 */
1286 	protected void setupUnitDigits()
1287 	{
1288 		unitDigits.clear();
1289 
1290 		String unit = formatter.getUnit();
1291 
1292 		if (unitSeparate) {
1293 			getUnitLabel().setText(unit);
1294 
1295 			return;
1296 		}
1297 
1298 		if (unit == null) {
1299 			return;
1300 		}
1301 
1302 		Digit digit = null;
1303 
1304 		for (int i = 0; i < unit.length(); i++) {
1305 			digit = new StaticDigit(unit.substring(i, i + 1));
1306 			digit.setEnhanced(enhanced);
1307 			digit.setEnabled(isEnabled());
1308 			digit.setAnimated(animated);
1309 			unitDigits.add(digit);
1310 		}
1311 	}
1312 
1313 	/**
1314 	 * Contructs value digits from scratch.
1315 	 */
1316 	protected void setupValueDigits()
1317 	{
1318 		digits.clear();
1319 		selectedDigit = -1;
1320 
1321 		String stringValue = formatter.getString();
1322 		char digitValue = 0;
1323 		Digit digit = null;
1324 		if (numberOfAllDigits >= stringValue.length()){
1325 		hiddenDigits.clear();
1326 			for (int i = 0; i < numberOfAllDigits - stringValue.length(); i++){
1327 				digit = new StaticDigit(" ");
1328 				digit.setVisible(false);
1329 				hiddenDigits.add(digit);
1330 			}
1331 		}
1332 		
1333 		boolean valueOutOfBounds = false;
1334 
1335 		if (getValue() > getGraphMax() || getValue() < getGraphMin()) {
1336 			valueOutOfBounds = true;
1337 		}
1338 
1339 		for (int i = 0; i < stringValue.length(); i++) {
1340 			digitValue = stringValue.charAt(i);
1341 
1342 			if ((digitValue >= '0') && (digitValue <= '9')) {
1343 				digit = new ValueDigit(Integer.parseInt(stringValue.substring(
1344 					            i, i + 1)));
1345 			} else {
1346 				digit = new StaticDigit(stringValue.substring(i, i + 1));
1347 			}
1348 
1349 			digit.addMouseListener(mouseHandler);
1350 			digit.setEnhanced(enhanced);
1351 			digit.setEnabled(isEnabled());
1352 			digit.setTilting(valueOutOfBounds);
1353 			digit.setAnimated(animated);
1354 			digits.add(digit);
1355 		}
1356 	}
1357 
1358 	/**
1359 	 * Called by setDigitValue(int,int) when the user modifies the digits.
1360 	 * Sets a new string value.
1361 	 */
1362 	private void setStringValue(String newStringValue)
1363 	{
1364 		int oldDigitSelection = getSelectedDigit();
1365 		int decimalSelection = parseDecimalPosition(oldDigitSelection);
1366 
1367 		if (debug) {
1368 			System.out.println("Wheelswitch::parseDecimalPosition = "
1369 			    + decimalSelection);
1370 		}
1371 
1372 		double oldValue = formatter.getValue();
1373 		String oldStringValue = formatter.getString();
1374 		
1375 		formatter.setString(newStringValue);
1376 
1377 		double newValue = formatter.getValue();
1378 		newStringValue = formatter.getString();
1379 
1380 		if (oldValue != newValue) {
1381 			firePropertyChange(VALUE, oldValue, newValue);
1382 			fireSetPerformed(newValue);
1383 		}
1384 
1385 		if (!isEnabled()) {
1386 			return;
1387 		}
1388 
1389 		process(oldStringValue, newStringValue);
1390 
1391 		int newDigitSelection = parseDigitPosition(decimalSelection);
1392 
1393 		if (debug) {
1394 			System.out.println("Wheelswitch::parseDigitPosition = "
1395 			    + newDigitSelection);
1396 		}
1397 
1398 		if ((newDigitSelection < digits.size()) && (newDigitSelection >= 0)) {
1399 			setSelectedDigit(newDigitSelection);
1400 		} else {
1401 			setSelectedDigit(-1);
1402 		}
1403 
1404 		checkBounds();
1405 
1406 		//tiltHandler.tilt();
1407 	}
1408 
1409 	/**
1410 	 * Called by setStringValue(String). Computes the decimal position represented
1411 	 * by the digit position
1412 	 */
1413 	private int parseDecimalPosition(int digitPosition)
1414 	{
1415 		if (digitPosition == -1) {
1416 			return Integer.MAX_VALUE;
1417 		}
1418 
1419 		String digits = formatter.getString();
1420 		int decimalPosition = 0;
1421 		int expIndex = digits.indexOf('E');
1422 		int dotIndex = digits.indexOf('.');
1423 
1424 		if ((expIndex == -1) || (digitPosition < expIndex)) {
1425 			if (dotIndex != -1) {
1426 				decimalPosition += (dotIndex - digitPosition);
1427 			} else if (expIndex != -1) {
1428 				decimalPosition += (expIndex - digitPosition);
1429 			} else {
1430 				decimalPosition += (digits.length() - digitPosition);
1431 			}
1432 
1433 			if (decimalPosition > 0) {
1434 				decimalPosition--;
1435 			}
1436 
1437 			if (expIndex != -1) {
1438 				int exponent = (int)Double.parseDouble(digits.substring(expIndex
1439 					        + 1));
1440 				decimalPosition += exponent;
1441 			}
1442 		} else {
1443 			//multiples of hundreds are returned for exponent digits
1444 			decimalPosition = 100 * (digits.length() - digitPosition);
1445 		}
1446 
1447 		return decimalPosition;
1448 	}
1449 
1450 	/**
1451 	 * Called by setStringValue(String) Computes the digit position given the decimal
1452 	 * position
1453 	 */
1454 	private int parseDigitPosition(int decimalPosition)
1455 	{
1456 		if (decimalPosition == Integer.MAX_VALUE) {
1457 			return -1;
1458 		}
1459 
1460 		String digits = formatter.getString();
1461 		int digitPosition = 0;
1462 		int expIndex = digits.indexOf('E');
1463 		int dotIndex = digits.indexOf('.');
1464 
1465 		if (((decimalPosition % 100) == 0) && (decimalPosition != 0)) {
1466 			decimalPosition /= 100;
1467 
1468 			if ((expIndex == -1)
1469 			    || ((digits.length() - expIndex) < decimalPosition)) {
1470 				return Integer.MAX_VALUE;
1471 			}
1472 
1473 			digitPosition = digits.length() - decimalPosition;
1474 
1475 			if ((digits.charAt(digitPosition) == '+')
1476 			    || (digits.charAt(digitPosition) == '-')) {
1477 				return Integer.MAX_VALUE;
1478 			}
1479 
1480 			return digitPosition;
1481 		}
1482 
1483 		if (dotIndex != -1) {
1484 			digitPosition += (dotIndex - decimalPosition);
1485 		} else if (expIndex != -1) {
1486 			digitPosition += (expIndex - decimalPosition - 1);
1487 		} else {
1488 			digitPosition += (digits.length() - decimalPosition - 1);
1489 		}
1490 
1491 		if (expIndex != -1) {
1492 			int exponent = (int)Double.parseDouble(digits.substring(expIndex
1493 				        + 1));
1494 			digitPosition += exponent;
1495 		}
1496 
1497 		if ((expIndex != -1) && (digitPosition >= expIndex)) {
1498 			return Integer.MAX_VALUE;
1499 		} else if (digitPosition >= digits.length()) {
1500 			return Integer.MAX_VALUE;
1501 		}
1502 
1503 		if (digitPosition <= dotIndex) {
1504 			digitPosition--;
1505 		}
1506 
1507 		if ((digitPosition < 0)
1508 		    || ((digitPosition == 0)
1509 		    && ((digits.charAt(0) == '+') || (digits.charAt(0) == '-')))) {
1510 			return Integer.MAX_VALUE;
1511 		}
1512 
1513 		return digitPosition;
1514 	}
1515 
1516 	private synchronized void process(String oldStringValue,
1517 	    String newStringValue)
1518 	{
1519 		if ((oldStringValue.length() == newStringValue.length())
1520 		    && (oldStringValue.indexOf(".") == newStringValue.indexOf("."))
1521 		    && (oldStringValue.indexOf("E") == newStringValue.indexOf("E"))
1522 		    && (oldStringValue.indexOf("e") == newStringValue.indexOf("e"))
1523 		    && (oldStringValue.indexOf("+") == newStringValue.indexOf("+"))
1524 		    && (oldStringValue.indexOf("-") == newStringValue.indexOf("-"))
1525 		    && (oldStringValue.indexOf("+", oldStringValue.indexOf("E")) == newStringValue
1526 		    .indexOf("+", newStringValue.indexOf("E")))
1527 		    && (oldStringValue.indexOf("+", oldStringValue.indexOf("e")) == newStringValue
1528 		    .indexOf("+", newStringValue.indexOf("e")))
1529 		    && (oldStringValue.indexOf("-", oldStringValue.indexOf("e")) == newStringValue
1530 		    .indexOf("-", newStringValue.indexOf("e")))
1531 		    && (oldStringValue.indexOf("-", oldStringValue.indexOf("E")) == newStringValue
1532 		    .indexOf("-", newStringValue.indexOf("E")))) {
1533 			initDigits();
1534 		} else {
1535 			setupValueDigits();
1536 			setupLayout();
1537 			validate();
1538 			repaint();
1539 		}
1540 	}
1541 
1542 	/**
1543 	 * Determines wether currently the wheelswitch digits are animeted
1544 	 * (scrolling) or not.
1545 	 *
1546 	 * @return animated
1547 	 */
1548 	public boolean isAnimated()
1549 	{
1550 		return animated;
1551 	}
1552 
1553 	/**
1554 	 * Sets the animation property of wheelswith digits. When enabled, the
1555 	 * digits scroll when changing. <b>Caution! This feature may eat a lot of
1556 	 * CPU.</b>
1557 	 *
1558 	 * @param b
1559 	 */
1560 	public void setAnimated(boolean b)
1561 	{
1562 		if (this.animated == b) {
1563 			return;
1564 		}
1565 
1566 		firePropertyChange("animated", this.animated, b);
1567 		this.animated = b;
1568 
1569 		for (int i = 0; i < digits.size(); i++) {
1570 			(digits.get(i)).setAnimated(b);
1571 		}
1572 
1573 		for (int i = 0; i < unitDigits.size(); i++) {
1574 			(unitDigits.get(i)).setAnimated(b);
1575 		}
1576 	}
1577 
1578 	/**
1579 	 * Returns true if units are displayed in a separate label.
1580 	 *
1581 	 * @return true if units are separated
1582 	 */
1583 	public boolean isUnitSeparate()
1584 	{
1585 		return unitSeparate;
1586 	}
1587 
1588 	/**
1589 	 * Sets weather the unit should be displayed in a separate label on the
1590 	 * right of the component.
1591 	 *
1592 	 * @param unitSeparate The unitSeparate to set.
1593 	 */
1594 	public void setUnitSeparate(boolean unitSeparate)
1595 	{
1596 		this.unitSeparate = unitSeparate;
1597 	}
1598 
1599 	/**
1600 	 * Returns the formatter employed by the wheelswitch
1601 	 *
1602 	 * @return the formatter
1603 	 */
1604 	public AbstractWheelswitchFormatter getFormatter()
1605 	{
1606 		return formatter;
1607 	}
1608 
1609 	/**
1610 	 * Sets the formatter which handles the values set to the wheelswitch.
1611 	 *
1612 	 * @param formatter new formatter
1613 	 * @see com.cosylab.gui.components.wheelswtich.AbstractWheelswitchFormatter
1614 	 */
1615 	public void setFormatter(AbstractWheelswitchFormatter formatter)
1616 	{
1617 		if (formatter==null) {
1618 			throw new NullPointerException("formatter");
1619 		}
1620 		
1621 		String unit = this.formatter.getUnit();
1622 		String format = this.formatter.getFormat();
1623 		String string = this.formatter.getString();
1624 		double value = this.formatter.getValue();
1625 		double max = this.formatter.getMaximum();
1626 		double min = this.formatter.getMinimum();
1627 		int maxDigits = this.formatter.getMaximumDigits();
1628 
1629 		this.formatter = formatter;
1630 
1631 		this.formatter.setFormat(format);
1632 		this.formatter.setUnit(unit);
1633 		this.formatter.setString(string);
1634 		this.formatter.setValue(value);
1635 		this.formatter.setMaximum(max);
1636 		this.formatter.setMinimum(min);
1637 		this.formatter.setMaximumDigits(maxDigits);
1638 
1639 		process(string, formatter.getString());
1640 	}
1641 
1642     /**
1643      * Sets the maximum font size for the units label.
1644      * 
1645      * @param max new maximum size in pixels
1646      */
1647     public void setUnitsMaximumFontSize(int max) {
1648         getUnitLabel().setMaximumFontSize(max);
1649     }
1650 
1651     /**
1652      * Sets the minimum font size for the units label.
1653      * 
1654      * @param min new minimum font size in pixels
1655      */
1656     public void setUnitsMinimumFontSize(int min) {
1657     	getUnitLabel().setMinimumFontSize(min);
1658     }
1659     
1660     /**
1661      * Returns the maximum font size of the units label.
1662      * 
1663      * @return the font size in pixels
1664      */
1665     public int getUnitsMaximumFontSize() {
1666         return getUnitLabel().getMaximumFontSize();
1667     }
1668 
1669     /**
1670      * Returns the minimum font size of the units label.
1671      * 
1672      * @return the font size in pixels
1673      */
1674     public int getUnitsMinimumFontSize() {
1675         return getUnitLabel().getMinimumFontSize();
1676     }
1677     
1678     /*
1679      * (non-Javadoc)
1680      * @see javax.swing.JComponent#setBackground(java.awt.Color)
1681      */
1682     @Override
1683     public void setBackground(Color bg) {
1684     	super.setBackground(bg);
1685     	getUpDownButton().setBackground(bg);
1686     	getUnitLabel().setBackground(bg);
1687     }
1688     
1689     /*
1690      * (non-Javadoc)
1691      * @see javax.swing.JComponent#setForeground(java.awt.Color)
1692      */
1693     @Override
1694     public void setForeground(Color fg) {
1695     	super.setForeground(fg);
1696     	getUpDownButton().setForeground(fg);
1697     	getUnitLabel().setForeground(fg);
1698     }
1699     
1700     /**
1701      * Set <code>true</code> if digits should take up all space, set 
1702      * <code>false</code> if place for not visible digits should be reserved
1703      * (there is no resizing of digits when 9 changes to 10 etc.).
1704      * </p>
1705      * Set the number of reserved place with {@link #setNumberOfAllDigits(int)}.
1706      * </p>
1707      * @param bool boolean value
1708      */
1709     public void setDigitsTakeUpAllSpace(boolean bool) {
1710     	if (digitsTakeUpAllSpace == bool)
1711     		return;
1712     	
1713     	firePropertyChange("digitsTakeUpAllSpace", digitsTakeUpAllSpace, bool);
1714     	digitsTakeUpAllSpace = bool;
1715     	
1716     	setupValueDigits();
1717 		setupUnitDigits();
1718 		setupLayout();
1719 		validate();
1720 		repaint();
1721     }
1722     
1723     /**
1724      * @return <code>true</code> if digits take up all space,
1725      *  otherwise <code>false</code>
1726      */
1727     public boolean getDigitsTakeUpAllSpace() {
1728     	return digitsTakeUpAllSpace;
1729     }
1730     
1731     /**
1732      * If {@link #setDigitsTakeUpAllSpace(boolean)} is set to <code>false</code>
1733      * the place for <code>numberOfAllDigits</code> is reserved. Not needed 
1734      * additional digits are not shown.
1735      * 
1736      * @param numberOfAllDigits number of all digits
1737      */
1738     public void setNumberOfAllDigits(int numberOfAllDigits) {
1739     	if(this.numberOfAllDigits == numberOfAllDigits)
1740     		return;
1741     	
1742     	firePropertyChange("numberOfAllDigits", this.numberOfAllDigits, numberOfAllDigits);
1743     	this.numberOfAllDigits = numberOfAllDigits;
1744     	
1745     	setupValueDigits();
1746 		setupUnitDigits();
1747 		setupLayout();
1748 		validate();
1749 		repaint();
1750     }
1751     
1752     /**
1753      * @return The number of reserved places for digits.
1754      */
1755     public int getNumberOfAllDigits() {
1756     	return(numberOfAllDigits);
1757     }
1758 
1759 	public static void main(String[] args)
1760 	{
1761 		Wheelswitch ws = new Wheelswitch(WheelswitchFormatter.transformFormat(
1762 			        "%3.2E2"), 0, "deg:min:sec");
1763 //		ws.setFormatter(new RadialWheelswitchFormatter());
1764 		ws.setAnimated(true);
1765 		ws.setDigitsTakeUpAllSpace(false);
1766 		//ws.setValue(1);
1767 		JFrame f = new JFrame();
1768 		f.getContentPane().setLayout(new BorderLayout());
1769 		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
1770 		f.getContentPane().add(ws, BorderLayout.CENTER);
1771 		f.getContentPane().add(new JButton("focus"), BorderLayout.SOUTH);
1772 		f.setSize(300, 100);
1773 		f.setVisible(true);
1774 	}
1775 
1776 }
1777 
1778 /* __oOo__ */