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.AlphaComposite;
23  import java.awt.BasicStroke;
24  import java.awt.Color;
25  import java.awt.Component;
26  import java.awt.Container;
27  import java.awt.Dimension;
28  import java.awt.Font;
29  import java.awt.FontMetrics;
30  import java.awt.GradientPaint;
31  import java.awt.Graphics;
32  import java.awt.Graphics2D;
33  import java.awt.Insets;
34  import java.awt.LayoutManager;
35  import java.awt.event.ActionEvent;
36  import java.awt.event.ActionListener;
37  import java.awt.event.ComponentAdapter;
38  import java.awt.event.ComponentEvent;
39  import java.awt.event.MouseAdapter;
40  import java.awt.event.MouseEvent;
41  import java.awt.event.MouseListener;
42  import java.awt.event.MouseMotionListener;
43  import java.awt.image.BufferedImage;
44  import java.util.Timer;
45  import java.util.TimerTask;
46  
47  import javax.swing.AbstractAction;
48  import javax.swing.JButton;
49  import javax.swing.JComponent;
50  import javax.swing.JFrame;
51  import javax.swing.JPanel;
52  import javax.swing.TransferHandler;
53  
54  import com.cosylab.application.state.State;
55  import com.cosylab.application.state.StateFactory;
56  import com.cosylab.application.state.StateOriginator;
57  import com.cosylab.events.SetAdapter;
58  import com.cosylab.events.SetEvent;
59  import com.cosylab.events.SetListener;
60  import com.cosylab.gui.components.customizer.AbstractCustomizerPanel;
61  import com.cosylab.gui.components.ledder.LedPaint;
62  import com.cosylab.gui.components.ledder.LedPaintParameters;
63  import com.cosylab.gui.components.range2.LinearRange;
64  import com.cosylab.gui.components.range2.RangedValueController;
65  import com.cosylab.gui.components.range2.RangedValueEvent;
66  import com.cosylab.gui.components.range2.RangedValueListener;
67  import com.cosylab.gui.components.range2.RangedValuePolicy;
68  import com.cosylab.gui.components.range2.RescalingValuePolicy;
69  import com.cosylab.gui.components.range2.Tick;
70  import com.cosylab.gui.components.range2.TickParameters;
71  import com.cosylab.gui.components.range2.TrimValuePolicy;
72  import com.cosylab.gui.components.util.ColorHelper;
73  import com.cosylab.gui.components.util.CosyTransferHandler;
74  import com.cosylab.gui.components.util.FontHelper;
75  import com.cosylab.gui.components.util.PaintHelper;
76  import com.cosylab.gui.components.util.PopupManageable;
77  import com.cosylab.gui.components.util.PopupManager;
78  import com.cosylab.gui.components.util.ScreenCapturer;
79  import com.cosylab.util.PrintfFormat;
80  
81  /**
82   * A visual component for displaying and manipulating a double ranged value.
83   * The value is set and displayed by a round dial knob GUI which can be
84   * rotated by the user via mouse drag.
85   *
86   * @author Nejc Kosnik
87   * @author Jernej Kamenik (<a
88   *         href="mailto:jernej.kamenik@cosylab.com">jernej.kamenik&x40;cosylab.com</a>)
89   * @version $id$
90   */
91  public class DialKnob extends JComponent implements PopupManageable,
92  	StateOriginator, CosyTransferHandler.MouseFilter
93  {
94  	private static final long serialVersionUID = 1L;
95  
96  	/**
97  	 * <code>ValueListener</code> handles events that occured due to externally 
98  	 * made changes to the RangedValue. 
99  	 *
100 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
101 	 * @version $Id: DialKnob.java,v 1.38 2008-05-13 12:37:40 jbobnar Exp $
102 	 * 
103 	 * @since Feb 12, 2004.
104 	 */
105 	protected class ValueListener implements RangedValueListener {
106 
107 		/* (non-Javadoc)
108 		 * @see com.cosylab.gui.components.range.RangedValueListener#valueChanged(com.cosylab.gui.components.range.RangedValueEvent)
109 		 */
110 		public void valueChanged(RangedValueEvent event) {
111 			if (!isEnabled()) {
112 				return;
113 			}			
114 			if (event.isMinOrMaxChanged()) {
115 				renderer.clearScale();			
116 			}
117 
118 			if (autoSynchronize) {
119 				getAutoSyncTimer().restart();
120 				lastValueTimestamp = System.currentTimeMillis();
121 			}
122 			
123 			renderer.checkValue();
124 			repaint();
125 		}
126 
127 	}
128 	/**
129 	 * Utiliy class that handles user mouse actions on the Dial knob.
130 	 *
131 	 * @author Nejc Kosnik
132 	 */
133 	protected class MouseHandler extends MouseAdapter implements MouseListener, MouseMotionListener
134 	{
135 		private boolean inhibit;
136 		private boolean isDragged = false;
137 		private double angleBackground;
138 		private double currentMouseAngle;
139 		private double initMouseAngle;
140 		private double initUserRelativeValue;
141 
142 		/**
143 		 * Method which enables/disables setting of value via mouse drag.
144 		 * Usually called when knob is to small to be painted and setting
145 		 * value must also be disabled.
146 		 *
147 		 * @param newInhibit true to enable and false to disable user setting
148 		 */
149 		public void inhibit(boolean newInhibit)
150 		{
151 			inhibit = newInhibit;
152 		}
153 
154 		/**
155 		 * Does the value setting.
156 		 *
157 		 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
158 		 */
159 		public void mouseDragged(MouseEvent arg0)
160 		{
161 			if (inhibit || !isDragged) {
162 				return;
163 			}
164 
165 			//this prevents dragging outside of knob
166 
167 			/*
168 			if (isMouseOutside(arg0)) {
169 			    initMouseAngle = getMouseAngle(arg0);
170 			    angleBackground = 0.0;
171 			    return ;
172 			}
173 			*/
174 			double mouseAngle = getMouseAngle(arg0);
175 			double ratio = (rangedValue.getMaximum()-rangedValue.getMinimum())/(userRangedValue.getMaximum()-userRangedValue.getMinimum());
176 			double valueToSet = initUserRelativeValue
177 				+ (angleBackground + mouseAngle - initMouseAngle) / 1.5 / Math.PI * ratio;
178 			valueToSet = (valueToSet > 1.0 ? 1.0
179 				: (valueToSet < 0.0 ? 0.0 : valueToSet));
180 
181 			if (valueToSet == 0.0 || valueToSet == 1.0) {
182 				mousePressed(arg0);
183 			}
184 			userRangedValue.setRelativeValue(valueToSet); /* edit apikus */
185 		}
186 
187 		/**
188 		 * Starts the value setting.
189 		 *
190 		 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
191 		 */
192 		public void mousePressed(MouseEvent arg0)
193 		{
194 			if (isMouseOutside(arg0) || inhibit) {
195 				return;
196 			}
197 
198 			isDragged = true;
199 			allowAutoSync = false;
200 			
201 			int relativeX = arg0.getX() - renderer.knobCenterX;
202 			int relativeY = arg0.getY() - renderer.knobCenterY;
203 			double tangens = (double)relativeY / relativeX;
204 			currentMouseAngle = (relativeX >= 0.0
205 				? Math.atan(tangens) + Math.PI / 2
206 				: Math.atan(tangens) + 1.5 * Math.PI);
207 			initMouseAngle = currentMouseAngle;
208 			angleBackground = 0.0;
209 			initUserRelativeValue = userRangedValue.getRelativeValue(); /* edit apikus */
210 		}
211 
212 		/**
213 		 * Finishes the value setting.
214 		 *
215 		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
216 		 */
217 		public void mouseReleased(MouseEvent arg0)
218 		{
219 			isDragged = false;
220 			if (arg0.getButton() == MouseEvent.BUTTON1 && autoSynchronize){
221 				allowAutoSync = true;
222 				lastValueTimestamp = System.currentTimeMillis();
223 			}
224 		}
225 
226 		//angle according to the center of the knob
227 		private double getMouseAngle(MouseEvent arg0)
228 		{
229 			int relativeX = arg0.getX() - renderer.knobCenterX;
230 			int relativeY = arg0.getY() - renderer.knobCenterY;
231 			double tangens = (double)relativeY / relativeX;
232 			double newAngle = (relativeX >= 0.0
233 				? Math.atan(tangens) + Math.PI / 2
234 				: Math.atan(tangens) + 1.5 * Math.PI);
235 
236 			if (Math.abs(newAngle - currentMouseAngle) > Math.PI) {
237 				if (newAngle > currentMouseAngle) {
238 					angleBackground -= 2 * Math.PI;
239 				} else {
240 					angleBackground += 2 * Math.PI;
241 				}
242 			}
243 
244 			currentMouseAngle = newAngle;
245 
246 			return currentMouseAngle;
247 		}
248 
249 		//checks if mouse is out of allowable drag area
250 		private boolean isMouseOutside(MouseEvent arg0)
251 		{
252 			int x = arg0.getX();
253 			int y = arg0.getY();
254 			int relativeX = x - renderer.knobCenterX;
255 			int relativeY = y - renderer.knobCenterY;
256 
257 			if (relativeX * relativeX + relativeY * relativeY > renderer.knobDiameter * renderer.knobDiameter / 4.0) {
258 				allowAutoSync = true;
259 				lastValueTimestamp = System.currentTimeMillis();
260 				return true;
261 			}
262 
263 			return false;
264 		}
265 
266 		public void mouseMoved(MouseEvent e) {
267 			// TODO Auto-generated method stub
268 			
269 		}
270 	}
271 
272 	/**
273 	 * Utility class that rendering of the component
274 	 *
275 	 * @author Nejc Kosnik
276 	 */
277 	protected class Renderer implements TickParameters
278 	{
279 		//private TickParameters measurer;
280 		private int knobCenterX;
281 		private int knobCenterY;
282 		private int knobDiameter;
283 		private BufferedImage knobImage;
284 		private BufferedImage markerImage;
285 		private BufferedImage scaleImage;
286 		private FontMetrics fontMetrics;
287 		private Tick[] ticks;
288 		private boolean checkedValue;
289 		private boolean prohibitDrawing;
290 		private int titleHeight;
291 		private int fontHeight;
292 		private int height;
293 		private int markerImageSize;
294 		private int scaleSize;
295 		private int size;
296 		private int width;
297 
298 		public void checkValue()
299 		{
300 			if (checkedValue != (userRangedValue.getValue() == labelValue)) {
301 				checkedValue = (userRangedValue.getValue() == labelValue);
302 				markerImage = null;
303 			}
304 		}
305 
306 		/**
307 		 * Clears all buffered image resources, so they all have to be created
308 		 * during the next repaint.
309 		 */
310 		public void clearBuffer()
311 		{
312 			knobImage = null;
313 			scaleImage = null;
314 			markerImage = null;
315 		}
316 
317 		/**
318 		 * Clears scale image, so it has to be reconstructed on the next
319 		 * repaint. Typically called when scale range changes or when
320 		 * component is resized.
321 		 */
322 		public void clearScale()
323 		{
324 			scaleImage = null;
325 		}
326 
327 		/**
328 		 * Gathers vital information about this component such as width,
329 		 * height, font metrics and where to place the knob. Has to be called
330 		 * before any painting methods to ensure proper rendering.
331 		 *
332 		 * @param g2d graphics representing this component
333 		 */
334 		public void initialize(Graphics2D g2d)
335 		{
336 			width = DialKnob.this.getWidth();
337 			height = DialKnob.this.getHeight();
338 			fontHeight = (resizable ? 5 + Math.min(width, height) / 25
339 				: FontHelper.getDefaultFontSize());
340 			g2d.setFont(FontHelper.getFontWithSize(fontHeight, g2d.getFont()));
341 			fontMetrics = g2d.getFontMetrics();
342 			titleHeight = (titleVisible && title!=null && title.length()>0 ? fontHeight * 12 / 10 : 0);
343 			size = Math.min(width, height - titleHeight);
344 
345 			if (size <= 0) {
346 				titleHeight = 0;
347 				size = Math.min(width, height);
348 			}
349 
350 			scaleSize = (size - 2 * fontHeight * 10 / 8);
351 			knobDiameter = scaleSize * 8 / 10;
352 
353 			if (knobDiameter <= 0) {
354 				prohibitDrawing = true;
355 				if (isEditable())mouseHandler.inhibit(true);
356 
357 				return;
358 			}
359 
360 			if (enhanced) {
361 				g2d.addRenderingHints(PaintHelper.getAntialiasingHints());
362 			}
363 
364 			knobCenterX = width / 2;
365 			knobCenterY = height / 2 + titleHeight;
366 			markerImageSize = Math.max(knobDiameter / 8, 10);
367 			prohibitDrawing = false;
368 			if (isEditable())getMouseHandler().inhibit(false);
369 		}
370 
371 		/*
372 		 * (non-Javadoc)
373 		 * @see com.cosylab.gui.components.range2.TickParameters#measureTick(double, java.lang.String)
374 		 */
375 		public int measureTick(double position, String text)
376 		{
377 			return fontMetrics.stringWidth(text);
378 		}
379 
380 		/**
381 		 * Paints the Round shape representing the dial knob.
382 		 *
383 		 * @param g2d graphics representing this component
384 		 */
385 		public void paintKnob(Graphics2D g2d)
386 		{
387 			if (prohibitDrawing) {
388 				return;
389 			}
390 
391 			if (knobImage == null) {
392 				knobImage = new BufferedImage(knobDiameter, knobDiameter,
393 					    BufferedImage.TYPE_4BYTE_ABGR);
394 
395 				Graphics2D tmpG2d = knobImage.createGraphics();
396 
397 				if (enhanced) {
398 					tmpG2d.addRenderingHints(PaintHelper.getAntialiasingHints());
399 					tmpG2d.setPaint(new GradientPaint(0, 0, Color.WHITE,
400 					        knobDiameter, knobDiameter,
401 					        new Color(0.01f, 0, 0.3f)));
402 					tmpG2d.fillOval(0, 0, knobDiameter, knobDiameter);
403 				}
404 
405 				int edge = knobDiameter / 25;
406 				tmpG2d.setColor(new Color(107, 109, 169));
407 				tmpG2d.fillOval(edge, edge, knobDiameter - 2 * edge,
408 				    knobDiameter - 2 * edge);
409 			}
410 
411 			g2d.drawImage(knobImage, null, (width - knobDiameter) / 2,
412 			    knobCenterY - knobDiameter / 2);
413 		}
414 
415 		/**
416 		 * Paints the marker of the DialKnob denoting the value set by the
417 		 * user.
418 		 *
419 		 * @param g2d graphics representing this component
420 		 */
421 		public void paintMarker(Graphics2D g2d)
422 		{
423 			if (prohibitDrawing) {
424 				return;
425 			}
426 
427 			if (markerImageSize == 0) {
428 				return;
429 			}
430 
431 			int x = knobCenterX;
432 			int y = knobCenterY;
433 			int r = knobDiameter / 2 - markerImageSize;
434 			float strokeWidth = Math.max(knobDiameter / 60.f, 3.f);
435 			int r1 = knobDiameter / 2 + (int)strokeWidth / 2;
436 			int r2 = knobDiameter / 2 * 17 / 15 - (int)strokeWidth / 2;
437 
438 			//double tempVal = (value>userRangedValue.getMaximum() ? userRangedValue.getMaximum() : value);
439 			//tempVal = (tempVal<userRangedValue.getMinimum() ? userRangedValue.getMinimum() : tempVal);
440 			double val = (rangedValue.getValue() - rangedValue.getMinimum()) / (rangedValue
441 				.getMaximum() - rangedValue.getMinimum()); 
442 			double angle = (val - 0.5) * Math.PI * 1.5;
443 			boolean valueCheck = true;
444 			if (labelValue>getGraphMax() || labelValue<getGraphMin()) valueCheck = false;
445 			/*
446 			 * The following selects the color of the marker needle.
447 			 */
448 			if(!rangeColors) {
449 				markerColor = (valueCheck ? Color.GREEN : Color.YELLOW);
450 			} else if(!valueCheck) {
451 				if (labelValue < getGraphMin()) angle = angle-overLimitAngle;
452 				else angle = angle+overLimitAngle;
453 				markerColor = outOfBoundsColor;
454 			} else if( (val <= lowAlarmLimit) || (val >= 1.0-highAlarmLimit) ){
455 				markerColor = alarmColor;
456 			} else if( (val <= lowWarningLimit) || (val >= 1.0-highWarningLimit) ){ 
457 				markerColor = warningColor;
458 			} else{
459 				markerColor = normalColor;
460 			}
461 			g2d.setColor(markerColor);
462 			
463 			g2d.setStroke(new BasicStroke(strokeWidth));
464 			g2d.drawLine((int)(x + r1 * Math.sin(angle)),
465 			    (int)(y - r1 * Math.cos(angle)),
466 			    (int)(x + r2 * Math.sin(angle)), (int)(y - r2 * Math.cos(angle)));
467 
468 			if (markerImage == null) {
469 				markerImage = getMarkerImage();
470 			}
471 
472 			//if (editable) {
473 				val = userRangedValue.getValue();
474 				if (val>rangedValue.getMaximum()) val = rangedValue.getMaximum();
475 				if (val<rangedValue.getMinimum()) val = rangedValue.getMinimum();
476 				val = (val - rangedValue.getMinimum()) / (rangedValue
477 					.getMaximum() - rangedValue.getMinimum());  
478 				angle = (val - 0.5) * Math.PI * 1.5;
479 			//}
480 
481 			
482 
483 			g2d.drawImage(markerImage, null,
484 			    (int)(x + r * Math.sin(angle) - markerImageSize / 2),
485 			    (int)(y - r * Math.cos(angle) - markerImageSize / 2));
486 
487 			if (enhanced) {
488 				g2d.setComposite(AlphaComposite.getInstance(
489 				        AlphaComposite.SRC_ATOP, 0.5f));
490 			}
491 
492 			if (editable) {
493 				g2d.setColor(userRangedValue.getValue() == labelValue ? markerColor
494 						: Color.RED);
495 			} else {
496 				g2d.setColor(Color.GRAY);
497 			}
498 
499 			g2d.drawLine((int)(x + r1 * Math.sin(angle)),
500 			    (int)(y - r1 * Math.cos(angle)),
501 			    (int)(x + r2 * Math.sin(angle)), (int)(y - r2 * Math.cos(angle)));
502 
503 			//Restore original state of Graphics
504 			if (enhanced) {
505 				g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
506 			}
507 
508 			g2d.setStroke(new BasicStroke());
509 		}
510 
511 		/**
512 		 * Paints the ticks of the DialKnob.
513 		 *
514 		 * @param g2d graphics representing this component
515 		 */
516 		public void paintTicks(Graphics2D g2d)
517 		{
518 			if (prohibitDrawing) {
519 				return;
520 			}
521 
522 			if (scaleImage == null) {
523 				scaleImage = getScaleImage();
524 			}
525 
526 			g2d.drawImage(scaleImage, null, knobCenterX - size / 2,
527 			    knobCenterY - size / 2);
528 		}
529 
530 		/**
531 		 * Paints the title title on the top of the dial knob.
532 		 *
533 		 * @param g2d graphics representing this component
534 		 */
535 		public void paintTitleLabel(Graphics2D g2d)
536 		{
537 			if (titleHeight <= 0 || title == null || title.length()==0) {
538 				return;
539 			}
540 
541 //			g2d.setColor((isEnabled() ? Color.BLACK : Color.GRAY));
542 			g2d.setColor((isEnabled() ? getForeground() : Color.GRAY));
543 			g2d.drawString(title, (width - fontMetrics.stringWidth(title)) / 2,
544 			    titleHeight);
545 		}
546 
547 		/**
548 		 * Paints the value title in the middle of the dial knob.
549 		 *
550 		 * @param g2d graphics representing this component
551 		 */
552 		public void paintValueLabel(Graphics2D g2d)
553 		{
554 			String text;
555 			
556 			try {
557 				//text = formatter.sprintf(labelValue);
558 				text = this.formatNumber(labelValue);
559 			} catch (Exception e) {
560 				text = Double.toString(labelValue);
561 			}
562 			
563 			text += ((units == null || !unitsVisible) ? "" : " " + units);
564 
565 			if (fontMetrics.stringWidth(text) >= knobDiameter) {
566 				return;
567 			}
568 
569 //			g2d.setColor((isEnabled() ? Color.BLACK : Color.LIGHT_GRAY));
570 			g2d.setColor((isEnabled() ? getForeground() : Color.LIGHT_GRAY));
571 			g2d.drawString(text, (width - fontMetrics.stringWidth(text)) / 2,
572 			    knobCenterY);
573 		}
574 
575 		private BufferedImage getMarkerImage()
576 		{
577 			BufferedImage markerImage = new BufferedImage(markerImageSize,
578 				    markerImageSize, BufferedImage.TYPE_4BYTE_ABGR);
579 			Graphics2D tmpG2d = markerImage.createGraphics();
580 
581 			if (enhanced) {
582 				tmpG2d.addRenderingHints(PaintHelper.getAntialiasingHints());
583 			}
584 
585 			Color c = null;
586 			if (editable) {
587 				c = (userRangedValue.getValue() == labelValue? Color.GREEN : Color.RED);
588 			} else {
589 				c = Color.GRAY;
590 			}
591 			
592 
593 			if (enhanced) {
594 				LedPaintParameters param = new LedPaintParameters();
595 				param.lightX = -0.2;
596 				param.lightY = -0.2;
597 				param.shadowIntensity = 4;
598 				param.radius = markerImageSize * 10 / 8;
599 				param.borderSize = 0;
600 				param.color = c;
601 				tmpG2d.setPaint(new LedPaint(param,markerImageSize / 2,markerImageSize / 2));
602 			} else {
603 				tmpG2d.setColor(c);
604 			}
605 
606 			tmpG2d.fillOval(0, 0, markerImageSize, markerImageSize);
607 
608 			return markerImage;
609 		}
610 
611 		private BufferedImage getScaleImage()
612 		{
613 			BufferedImage image = new BufferedImage(size, size,
614 				    BufferedImage.TYPE_4BYTE_ABGR);
615 			Graphics2D g2d = image.createGraphics();
616 
617 			if (enhanced) {
618 				g2d.addRenderingHints(PaintHelper.getAntialiasingHints());
619 			}
620 
621 			int width = (scaleSize - knobDiameter) / 4;
622 			int size0 = scaleSize - 4 * width;
623 			g2d.setColor((tilting ? ColorHelper.getAlarm()
624 			    : getBackground())); //ColorHelper.getCosyControl()));
625 			g2d.setStroke(new BasicStroke(width));
626 
627 			int size1 = scaleSize - 3 * width;
628 			g2d.drawOval((size - size1) / 2, (size - size1) / 2, size1, size1);
629 
630 			g2d.setStroke(new BasicStroke());
631 			g2d.setColor(ColorHelper.getCosyControlShadow());
632 
633 			int size2 = scaleSize - 2 * width;
634 			g2d.drawOval((size - size2) / 2, (size - size2) / 2, size2, size2);
635 
636 //			g2d.setColor((isEnabled() ? Color.BLACK : Color.GRAY));
637 			g2d.setColor((isEnabled() ? getForeground() : Color.GRAY));
638 			g2d.setFont(FontHelper.getFontWithSize(fontHeight, g2d.getFont()));
639 			
640 			//ticks = rangedValue.getRange().getTicks((int)(0.75 * Math.PI * (scaleSize - fontHeight))).toTicks(this);
641 			ticks = rangedValue.calculateTicks((int)(0.75 * Math.PI * (scaleSize - fontHeight)),this);
642 									
643 			if (ticks == null) {
644 				return image;
645 			}
646 
647 			g2d.rotate(-Math.PI * 3 / 4, size / 2, size / 2);
648 
649 			double prop = 0.0;
650 			if (getMinimum() > getMaximum()) return image;
651 			for (int i = 0; i < ticks.length; i++) {
652 				g2d.rotate((ticks[i].proportional - prop) * Math.PI * 3 / 2,
653 				    size / 2, size / 2);
654 				prop = ticks[i].proportional;
655 
656 				if (ticks[i].major) { //major tick
657 					g2d.drawLine(size / 2, (size - size0) / 2, size / 2,
658 					    (size - size2) / 2 + 1);
659 
660 					String text = ticks[i].text;
661 
662 					if (text != null) {
663 						g2d.drawString(text,
664 						    (size - fontMetrics.stringWidth(text)) / 2,
665 						    (size - size2) / 2 - 5);
666 					}
667 				} else { //minor tick
668 					g2d.drawLine(size / 2, (size - size0) / 2, size / 2,
669 					    (size - size2) / 4 + (size - size0) / 4 + 1);
670 				}
671 			}
672 
673 			return image;
674 		}
675 		
676 		/*
677 		 * (non-Javadoc)
678 		 * @see com.cosylab.gui.components.range2.TickParameters#formatNumber(double)
679 		 */
680 		public String formatNumber(double x) { 
681 			return formatter.sprintf(x);
682 		}
683 	}
684 
685 	private class ResizeListener extends ComponentAdapter
686 	{
687 		/*
688 		 * (non-Javadoc)
689 		 * @see java.awt.event.ComponentAdapter#componentResized(java.awt.event.ComponentEvent)
690 		 */
691 		public void componentResized(ComponentEvent cEvent)
692 		{
693 			renderer.clearBuffer();
694 			repaint();
695 		}
696 	}
697 
698 	/**
699 	 * An extension of Timer used for periodic tilting of the Dial Knob.
700 	 *
701 	 * @author <a href="mailto:jernej.kamenik@cosylab.com">Jernej Kamenik</a>
702 	 * @version $id$
703 	 *
704 	 */
705 	protected class TiltHandler extends Timer
706 	{
707 		private class TiltTask extends TimerTask
708 		{
709 			/**
710 			 * DOCUMENT ME!
711 			 */
712 			public void run()
713 			{
714 				if (numberOfTilts >= MAX_NUMBER_OF_TILTS) {
715 					cancel();
716 					tilting = false;
717 				} else {
718 					numberOfTilts++;
719 					tilting = !tilting;
720 				}
721 
722 				renderer.clearScale();
723 				repaint();
724 			}
725 		}
726 
727 		private final int MAX_NUMBER_OF_TILTS = 3;
728 		private final long TILT_RATE = 200;
729 		private int numberOfTilts = MAX_NUMBER_OF_TILTS;
730 
731 		/**
732 		 * Scedules a new tilting task if the user value equals any of the
733 		 * bounds.
734 		 */
735 		public void tilt()
736 		{
737 			if (numberOfTilts >= MAX_NUMBER_OF_TILTS) {
738 				numberOfTilts = 0;
739 				schedule(new TiltTask(), 0, TILT_RATE);
740 			} else {
741 				numberOfTilts = 0;
742 			}
743 		}
744 	}
745 
746 	/**
747 	 * Listens for changes in uservalue.
748 	 *
749 	 * @author Nejc Kosnik
750 	 */
751 	protected class UserValueListener implements RangedValueListener
752 	{
753 		/*
754 		 * (non-Javadoc)
755 		 * @see com.cosylab.gui.components.range2.RangedValueListener#valueChanged(com.cosylab.gui.components.range2.RangedValueEvent)
756 		 */
757 		public void valueChanged(RangedValueEvent rvEvent)
758 		{
759 			if (rvEvent.isValueChanged()) {
760 				/*
761 				 * Fix for out of bounds behaviour of this component (RT #26250).
762 				 * Replaced:
763 				 * notifyListeners(userRangedValue.getValue());
764 				 */
765 				notifyListeners(userRangedValue.getRawValue());
766 			}
767 			if (autoSynchronize) {
768 				getAutoSyncTimer().stop();
769 				lastValueTimestamp = System.currentTimeMillis();
770 			}
771 
772 			//			if (rvEvent.minMaxChanged())
773 			//				renderer.clearScale();
774 			renderer.checkValue();
775 			
776 			if (!isEnabled()) {
777 				return;
778 			}
779 			
780 			DialKnob.this.repaint();
781 		}
782 	}
783 
784 	private JButton syncButton;
785 	private RangedValuePolicy policy;
786 	protected MouseHandler mouseHandler;
787 	protected PrintfFormat formatter;
788 	protected RangedValueController userRangedValue;
789 	protected Renderer renderer;
790 	protected String format;
791 	protected String title;
792 	protected String units;
793 	protected boolean titleVisible;
794 	protected RangedValueController rangedValue;
795 	private AbstractCustomizerPanel customizer;
796 	private PopupManager popupManager;
797 	private TiltHandler tiltHandler;
798 	private boolean editable;
799 	private boolean enhanced = true;
800 	private boolean painting;
801 	private boolean popupEnabled;
802 	private boolean resizable = true;
803 	private boolean tilting;
804 	private boolean tiltingEnabled;
805 	private double labelValue;
806 	private boolean unitsVisible = true;
807 	
808 	/* 
809 	 * Selects if marker needle color should change with respect to its 
810 	 * position on the scale. 
811 	 */
812 	private boolean rangeColors = true;
813 	
814 	/* Range boundaries. */
815 	private double lowAlarmLimit = 0.05;
816 	private double lowWarningLimit= 0.2;
817 	private double highWarningLimit= 0.2;
818 	private double highAlarmLimit= 0.05;
819 	
820 	/* Colors. */
821 	private Color normalColor = new Color(0, 192, 0);
822 	private Color alarmColor = new Color(239,0,0);
823 	private Color warningColor = new Color(0xEEEC300);
824 	private Color outOfBoundsColor = new Color(239,0,0);
825 	/* 
826 	 * Angle that is added to maximum deflection of the marker when value 
827 	 * is out ob bounds.
828 	 */
829 	private double overLimitAngle = 5.0 / 180 * Math.PI;
830 	/* Current color of the marker. */
831 	private Color markerColor = null;
832 	
833 	/* Flag indicating whether the slider is automatically synchronized each time the
834 	 *  readback changes. Synchronization occurs after the autoSynchronizeDelay
835 	 *  miliseconds pass since the last user intervention. */
836 	private boolean autoSynchronize = false;
837 	
838 	/* The time in milliseconds that have to pass since last user intervention for
839 	 *  the automatical synchronization to occur for the first time. */
840 	private long autoSynchronizeDelay = 2000;
841 	
842 	private boolean allowAutoSync = true;
843 	long lastValueTimestamp = System.currentTimeMillis();
844 	
845 	/**
846 	 * Creates a new DialKnob object with null title and 0 values.
847 	 */
848 	public DialKnob()
849 	{
850 		this(null, 0, 0);
851 	}
852 
853 	/**
854 	 * Constructs a new DialKnob object with the given title of the widget.
855 	 *
856 	 * @param newLabel title of the widget
857 	 */
858 	public DialKnob(String newLabel)
859 	{
860 		this(newLabel, -20, -20);
861 	}
862 
863 	/**
864 	 * Constructs DialKnob with the given title and given user and readback values.
865 	 *
866 	 * @param newLabel title of the widget
867 	 * @param newUserValue user value
868 	 * @param newValue readback value (shadow)
869 	 */
870 	public DialKnob(String newLabel, double newUserValue, double newValue)
871 	{
872 		super();
873 
874 		title = newLabel;
875 
876 		format = "%3.2f";
877 		formatter = new PrintfFormat(format);
878 
879 		renderer = new Renderer();
880 
881 		userRangedValue = new RangedValueController(); /* edit apikus */
882 		userRangedValue.setRange(new LinearRange()); /* edit apikus */
883 		
884 		labelValue = newValue;
885 
886 		if (newUserValue == 0) {
887 			userRangedValue.setValue(0, 1, 0);
888 		} else {
889 			userRangedValue.setValue(newUserValue - Math.abs(newUserValue),
890 			    newUserValue + 3 * Math.abs(newUserValue), newUserValue);
891 		}
892 
893 		userRangedValue.addPolicy(new TrimValuePolicy());
894 		userRangedValue.addRangedValueListener(new UserValueListener());
895 				
896 		addComponentListener(new ResizeListener());
897 		
898 		rangedValue = new RangedValueController();
899 		rangedValue.setRange(new LinearRange());
900 
901 		if (newValue == 0) {
902 			rangedValue.setValue(0, 1, 0);
903 		} else {
904 			rangedValue.setValue(newValue - Math.abs(newValue),
905 				newValue + 3 * Math.abs(newValue), newValue);
906 		}
907 
908 		policy = new RescalingValuePolicy();
909 		rangedValue.addPolicy(policy);
910 		rangedValue.addRangedValueListener(new ValueListener());
911 
912 		
913 		setLayout(new LayoutManager() {
914 			public void removeLayoutComponent(Component comp) {
915 			}
916 
917 			public void layoutContainer(Container parent) {
918 				Dimension size = getSyncButton().getPreferredSize();
919 				int x = getWidth()-size.width-getInsets().right-2;
920 				int y = getInsets().top+2;
921 				getSyncButton().setBounds(x,y,size.width,size.height);
922 			}
923 
924 			public void addLayoutComponent(String name, Component comp) {
925 			}
926 
927 			public Dimension minimumLayoutSize(Container parent) {
928 				return null;
929 			}
930 
931 			public Dimension preferredLayoutSize(Container parent) {
932 				return null;
933 			}
934 		});
935 		//setLayout(new GridBagLayout());
936 		//GridBagConstraints c = new GridBagConstraints(0,0,1,1,0,0,GridBagConstraints.NORTHEAST,GridBagConstraints.NONE,new Insets(2,2,2,2),0,0);
937 		add(getSyncButton());
938 
939 		setBackground(ColorHelper.getCosyControl());
940 		setPreferredSize(new Dimension(200, 200));
941 		setMinimumSize(new Dimension(85, 85));
942 		setOpaque(true);
943 		setVisible(true);
944 		setPopupEnabled(true);
945 	}
946 	
947 	private JButton getSyncButton() {
948 		if (syncButton == null) {
949 			syncButton = new JButton("Sync");
950 			syncButton.setOpaque(false);
951 			syncButton.setMargin(new Insets(0, 1, 0, 1));
952 			syncButton.setFont(new Font(syncButton.getFont().getName(), Font.PLAIN, 9));
953 			syncButton.addActionListener(new ActionListener() {
954 				public void actionPerformed(ActionEvent e) {
955 					synchronize();
956 				}
957 			});
958 			
959 		}
960 
961 		return syncButton;
962 	}
963 
964 	/**
965 	 * Loads and returns the default <code>Customizer</code> for this displayer.
966 	 *
967 	 * @return the Customizer intance for this displayer
968 	 * @see com.cosylab.gui.components.customizer.Customizer
969 	 */
970 	public AbstractCustomizerPanel getCustomizer()
971 	{
972 		if (customizer == null) {
973 			customizer = AbstractCustomizerPanel.findCustomizer(this);
974 		}
975 
976 		return customizer;
977 	}
978 
979 	/**
980 	 * Sets the enhanced rendering of the widget. If property is true
981 	 * there will be much more attention payed to the visual details when 
982 	 * rendering of the displayer occurs. Such rendering might cost
983 	 * more CPU time. By default enhanced rendering is on. 
984 	 *
985 	 * @param b
986 	 */
987 	public void setEnhanced(boolean b)
988 	{
989 		if (enhanced == b) {
990 			return;
991 		}
992 
993 		enhanced = b;
994 		renderer.clearBuffer();
995 		repaint();
996 		firePropertyChange("enhanced", !b, b);
997 	}
998 
999 	/**
1000 	 * Returns whether the component should be rendered enhanced.
1001 	 *
1002 	 * @return enhanced
1003 	 */
1004 	public boolean isEnhanced()
1005 	{
1006 		return enhanced;
1007 	}
1008 
1009 	/**
1010 	 * Sets the C-style number display format string.
1011 	 *
1012 	 * @param newFormat C-style format string.
1013 	 */
1014 	public void setFormat(String newFormat)
1015 	{
1016 		String oldFormat = format;
1017 		format = newFormat;
1018 		formatter = new PrintfFormat(format);
1019 		firePropertyChange("format", oldFormat, newFormat);
1020 		renderer.clearBuffer();
1021 		if (!isEnabled()) {
1022 			return;
1023 		}
1024 		
1025 		repaint();
1026 	}
1027 
1028 	/**
1029 	 * Gets the C-style number display format string.
1030 	 *
1031 	 * @return String
1032 	 */
1033 	public String getFormat()
1034 	{
1035 		return format;
1036 	}
1037 
1038 	/**
1039 	 * Sets the maximum value the user can set.
1040 	 *
1041 	 * @param newMax maximum value the user can set.
1042 	 */
1043 	public void setUserMax(double newMax)
1044 	{
1045 
1046 		double oldMax = userRangedValue.getMaximum(); /* edit apikus */
1047 		userRangedValue.setMaximum(newMax); /* edit apikus */
1048 						
1049 		firePropertyChange("userMax", oldMax, newMax);
1050 		if (newMax<rangedValue.getMaximum()) setGraphMax(newMax);
1051 	}
1052 
1053 	/**
1054 	 * Sets the maximum value displayed.
1055 	 *
1056 	 * @param newMax maximum for the knob scale
1057 	 */
1058 	public void setGraphMax(double newMax)
1059 	{
1060 		double oldMax = rangedValue.getMaximum();
1061 		rangedValue.setValue(rangedValue.getMinimum(), newMax,rangedValue.getValue());
1062 		firePropertyChange("graphMax", oldMax, newMax);
1063 	}
1064 
1065 	/**
1066 	 * Sets all max values for this widget. This method will set the max
1067 	 * value a user can set as well as the max displayed value.
1068 	 * 
1069 	 * @param value new maximum value
1070 	 * @see #setUserMax(double)
1071 	 * @see #setGraphMax(double)
1072 	 */
1073 	public void setMaximum(double value) {
1074 		double old = getMaximum();
1075 		setUserMax(value);
1076 		setGraphMax(value);
1077 		firePropertyChange("maximum",old,value);
1078 	}
1079 
1080 	/**
1081 	 * Returns the maximum value the user can set.
1082 	 *
1083 	 * @return double upper bound value the user can set.
1084 	 */
1085 	public double getUserMax()
1086 	{
1087 		return userRangedValue.getMaximum(); /* edit apikus */
1088 	}
1089 
1090 	/**
1091 	 * A convenience method returns the user maximum value
1092 	 *
1093 	 * @return double upper bound value the user can set.
1094 	 */
1095 	public double getMaximum()
1096 	{
1097 		return getUserMax();
1098 	}
1099 	/**
1100 	 * Gets the maximum value displayed.
1101 	 *
1102 	 * @return double upper bound value for knob scale
1103 	 */
1104 	public double getGraphMax()
1105 	{
1106 		return rangedValue.getMaximum(); /* edit apikus */
1107 	}
1108 
1109 
1110 	/**
1111 	 * Sets the minimum value the user can set.
1112 	 *
1113 	 * @param newMin minimum value the user can set.
1114 	 */
1115 	public void setUserMin(double newMin)
1116 	{
1117 		double oldMin = userRangedValue.getMinimum(); /* edit apikus */
1118 		userRangedValue.setMinimum(newMin);
1119 		
1120 		firePropertyChange("userMin", oldMin, newMin);
1121 		if (newMin<rangedValue.getMinimum()) setGraphMin(newMin);
1122 	}
1123 
1124 	/**
1125 	 * Sets the minimum value displayed.
1126 	 *
1127 	 * @param newMin minimum for the knob scale
1128 	 */
1129 	public void setGraphMin(double newMin)
1130 	{
1131 		double oldMin = rangedValue.getMinimum();
1132 		rangedValue.setValue(newMin, rangedValue.getMaximum(),rangedValue.getValue());
1133 		firePropertyChange("graphMin", oldMin, newMin);
1134 	}
1135 
1136 	/**
1137 	 * Sets all min values for this widget. This method will set the min
1138 	 * value a user can set as well as the min displayed value.
1139 	 * 
1140 	 * @param value new minimum value
1141 	 * @see #setUserMin(double)
1142 	 * @see #setGraphMin(double)
1143 	 */
1144 	public void setMinimum(double value) {
1145 		double oldValue = getMinimum();
1146 		setGraphMin(value);
1147 		setUserMin(value);
1148 		firePropertyChange("minimum", oldValue, value);
1149 	}
1150 	
1151 	/**
1152 	 * Convenience method returns the minimum user value.
1153 	 *
1154 	 * @return double lower bound value the user can set
1155 	 */
1156 	public double getMinimum()
1157 	{
1158 		return getUserMin();
1159 	}
1160 
1161 	/**
1162 	 * Returns the minimum value the user can set.
1163 	 * @return user minimum
1164 	 */
1165 	public double getUserMin() {
1166 		return userRangedValue.getMinimum(); /* edit apikus */
1167 	}
1168 
1169 	/**
1170 	 * Returns the minimum value displayed.
1171 	 *
1172 	 * @return double lower bound value for knob scale
1173 	 */
1174 	public double getGraphMin() {
1175 		return rangedValue.getMinimum(); /* edit apikus */
1176 	}
1177 	
1178 	/**
1179 	 * Returns whether the user can trigger the component's popup menu through a
1180 	 * mouse click.
1181 	 *
1182 	 * @return true if popup is enabled
1183 	 */
1184 	public boolean isPopupEnabled()
1185 	{
1186 		return popupEnabled;
1187 	}
1188 
1189 	/**
1190 	 * Returns the popup manager employed by this widget. Default values in the
1191 	 * popup menu are <code>Synchronize</code>, <code>Preferences...</code>, and
1192 	 * <code>Capture Screen...</code>.
1193 	 * @see com.cosylab.gui.components.util.PopupManageable#getPopupManager()
1194 	 */
1195 	public PopupManager getPopupManager()
1196 	{
1197 		if (popupManager == null) {
1198 			popupManager = new PopupManager(this, false);
1199 			popupManager.addAction(new AbstractAction("Synchronize") {
1200 				private static final long serialVersionUID = 1L;
1201 
1202 					public void actionPerformed(ActionEvent e)
1203 					{
1204 						synchronize();
1205 					}
1206 				});
1207 			popupManager.addAction(new AbstractAction("Preferences...") {
1208 				private static final long serialVersionUID = 1L;
1209 
1210 				public void actionPerformed(ActionEvent e)
1211 				{
1212 					getCustomizer().showDialog();
1213 				}
1214 			});
1215 			popupManager.addAction(new AbstractAction("Capture Screen...") {
1216 				private static final long serialVersionUID = 1L;
1217 
1218 					public void actionPerformed(ActionEvent e)
1219 					{	ScreenCapturer sc = new ScreenCapturer(DialKnob.this);
1220 						sc.showScreenDialog();
1221 					}
1222 					
1223 				});
1224 		}
1225 
1226 		return popupManager;
1227 	}
1228 
1229 	/**
1230 	 * Enables/disables text adjusting.
1231 	 *
1232 	 * @param b whether the text should adjust its side to the size of the
1233 	 *        component.
1234 	 */
1235 	public void setResizable(boolean b)
1236 	{
1237 		if (b == resizable) {
1238 			return;
1239 		}
1240 
1241 		resizable = b;
1242 
1243 		renderer.clearBuffer();
1244 		repaint();
1245 		firePropertyChange("resizable", !b, b);
1246 	}
1247 
1248 	/**
1249 	 * Returns true if the component's text adjusts to the size of the component.
1250 	 *
1251 	 * @return resizable property.
1252 	 */
1253 	public boolean isResizable()
1254 	{
1255 		return resizable;
1256 	}
1257 
1258 	/**
1259 	 * Sets the state to the component.
1260 	 *
1261 	 * @param state to set.
1262 	 *
1263 	 * @see com.cosylab.application.state.StateOriginator#setState(com.cosylab.application.state.State)
1264 	 */
1265 	public void setState(State state)
1266 	{
1267 		setFormat(state.getString("format", getFormat()));
1268 		setMaximum(state.getDouble("maximum", getMaximum()));
1269 		setMinimum(state.getDouble("minimum", getMinimum()));
1270 		setTitle(state.getString("title", getTitle()));
1271 		setUnits(state.getString("units", getUnits()));
1272 		setTitleVisible(state.getBoolean("titleVisible", isTitleVisible()));
1273 		setEnhanced(state.getBoolean("enhanced", isEnhanced()));
1274 		setResizable(state.getBoolean("resizable", isResizable()));
1275 		setTiltingEnabled(state.getBoolean("tiltingEnabled", isTiltingEnabled()));
1276 	}
1277 
1278 	/**
1279 	 * Returns the current state of the component.
1280 	 *
1281 	 * @return current state.
1282 	 *
1283 	 * @see com.cosylab.application.state.StateOriginator#getState()
1284 	 */
1285 	public State getState()
1286 	{
1287 		State state = StateFactory.createState();
1288 
1289 		state.putString("format", getFormat());
1290 		state.putDouble("maximum", getMaximum());
1291 		state.putDouble("minimum", getMinimum());
1292 		state.putString("title", getTitle());
1293 		state.putString("units", getUnits());
1294 		state.putBoolean("titleVisible", isTitleVisible());
1295 		state.putBoolean("enhanced", isEnhanced());
1296 		state.putBoolean("resizable", isResizable());
1297 		state.putBoolean("tiltingEnabled", isTiltingEnabled());
1298 
1299 		return state;
1300 	}
1301 
1302 	/**
1303 	 * Sets the title above the knob.
1304 	 *
1305 	 * @param string new title
1306 	 */
1307 	public void setTitle(String string)
1308 	{
1309 		String oldTitle = title;
1310 		title = string;
1311 		firePropertyChange("title", oldTitle, string);
1312 
1313 		if (!isEnabled()) {
1314 			return;
1315 		}
1316 
1317 		repaint();
1318 	}
1319 
1320 	/**
1321 	 * Returns the title of the widget.
1322 	 *
1323 	 * @return Title
1324 	 */
1325 	public String getTitle()
1326 	{
1327 		return title;
1328 	}
1329 
1330 	/**
1331 	 * Toggles title's visibility.
1332 	 *
1333 	 * @param b visibility of the title
1334 	 */
1335 	public void setTitleVisible(boolean b)
1336 	{
1337 		if (titleVisible == b) return;
1338 		boolean oldB = titleVisible;
1339 		titleVisible = b;
1340 		firePropertyChange("titleVisible", oldB, b);
1341 
1342 		if (!isEnabled()) {
1343 			return;
1344 		}
1345 
1346 		repaint();
1347 	}
1348 
1349 	/**
1350 	 * Returns true if title is visible.
1351 	 *
1352 	 * @return titleVisible visibility of the title
1353 	 */
1354 	public boolean isTitleVisible()
1355 	{
1356 		return titleVisible;
1357 	}
1358 
1359 	/**
1360 	 * Sets the displayed units.
1361 	 *
1362 	 * @param newUnit units to be displayed on the value title
1363 	 */
1364 	public void setUnits(String newUnit)
1365 	{
1366 		String oldUnits = units;
1367 		units = newUnit;
1368 		firePropertyChange("units", oldUnits, newUnit);
1369 
1370 		if (!isEnabled()) {
1371 			return;
1372 		}
1373 
1374 		repaint();
1375 	}
1376 
1377 	/**
1378 	 * Returns the displayed units.
1379 	 *
1380 	 * @return String current units shown on the value title
1381 	 */
1382 	public String getUnits()
1383 	{
1384 		return units;
1385 	}
1386 
1387 	/**
1388 	 * Sets the user value.
1389 	 *
1390 	 * @param newUserValue new value.
1391 	 */
1392 	public void setUserValue(double newUserValue)
1393 	{
1394 		double oldUserValue = userRangedValue.getValue();
1395 
1396 		userRangedValue.setValue(newUserValue);
1397 		renderer.checkValue();
1398 		firePropertyChange("userValue", oldUserValue, getUserValue());
1399 
1400 		if (autoSynchronize) {
1401 			getAutoSyncTimer().stop();
1402 			lastValueTimestamp = System.currentTimeMillis();
1403 		}
1404 		
1405 		if (!isEnabled()) {
1406 			return;
1407 		}
1408 
1409 		repaint();
1410 	}
1411 
1412 	/**
1413 	 * Return the value as set by the user.
1414 	 *
1415 	 * @return double user value setting
1416 	 */
1417 	public double getUserValue()
1418 	{
1419 		return userRangedValue.getValue();
1420 	}
1421 
1422 	/**
1423 	 * Sets the value displayed
1424 	 *
1425 	 * @param newValue value to be displayed on the title marker
1426 	 */
1427 	public void setValue(double newValue)
1428 	{
1429 //		double oldValue = rangedValue.getValue();
1430 		double oldValue = getValue();
1431 
1432 		if (oldValue == newValue) {
1433 			return;
1434 		}
1435 		
1436 		labelValue = newValue;
1437 		renderer.checkValue();
1438 
1439 		rangedValue.setValue(newValue);
1440 		
1441 
1442 		if (autoSynchronize) {
1443 			if (oldValue != newValue) {
1444 				getAutoSyncTimer().restart();
1445 			} else {
1446 				if (!getAutoSyncTimer().isRunning()) {
1447 					getAutoSyncTimer().start();
1448 				}
1449 			}
1450 		
1451 		} 
1452 		
1453 		firePropertyChange("value", oldValue, getValue());
1454 
1455 		if ((newValue > userRangedValue.getMaximum()
1456 		    || newValue < userRangedValue.getMinimum()) && tiltingEnabled) {
1457 			getTiltHandler().tilt();
1458 		}
1459 
1460 	}
1461 
1462 	/**
1463 	 * Returns the displayed value.
1464 	 *
1465 	 * @return double value displayed on the title marker
1466 	 */
1467 	public double getValue()
1468 	{
1469 		return labelValue;
1470 	}
1471 
1472 	/**
1473 	 * Adds a listener which receive event notifications when the user 
1474 	 * sets a new value.
1475 	 *
1476 	 * @param listener listener to be added
1477 	 */
1478 	public void addSetListener(SetListener listener)
1479 	{
1480 		listenerList.add(SetListener.class, listener);
1481 	}
1482 
1483 	/**
1484 	 * Sets the editable property. If editable, the user can change the user
1485 	 * value by mouse dragg.
1486 	 *
1487 	 * @param b editable
1488 	 */
1489 	public void setEditable(boolean b)
1490 	{
1491 		if (editable == b) {
1492 			return;
1493 		}
1494 
1495 		editable = b;
1496 
1497 		if (b) {
1498 			addMouseListener(getMouseHandler());
1499 			addMouseMotionListener(getMouseHandler());
1500 		} else {
1501 			removeMouseListener(getMouseHandler());
1502 			removeMouseMotionListener(getMouseHandler());
1503 		}
1504 
1505 		firePropertyChange("editable", !b, b);
1506 
1507 		if (!isEnabled()) {
1508 			return;
1509 		}
1510 
1511 		renderer.markerImage=null;
1512 		repaint();
1513 	}
1514 
1515 	/**
1516 	 * Returns whether the value can be edited by user.
1517 	 *
1518 	 * @return editable
1519 	 */
1520 	public boolean isEditable()
1521 	{
1522 		return editable;
1523 	}
1524 
1525 	/**
1526 	 * Overriden to implement visual notification of enabled state.
1527 	 *
1528 	 * @see java.awt.Component#setEnabled(boolean)
1529 	 */
1530 	public void setEnabled(boolean enabled)
1531 	{
1532 		super.setEnabled(enabled);
1533 
1534 		renderer.clearBuffer();
1535 		repaint();
1536 	}
1537 
1538 	/**
1539 	 * Enables or disables the popup capabilities of the component.
1540 	 *
1541 	 * @param b the component should bring up popup dialog on mouse click.
1542 	 */
1543 	public void setPopupEnabled(boolean b)
1544 	{
1545 		if (popupEnabled != b) {
1546 			if (b) {
1547 				addMouseListener(getPopupManager().getMouseHook());
1548 			} else {
1549 				removeMouseListener(getPopupManager().getMouseHook());
1550 			}
1551 		}
1552 
1553 		popupEnabled = b;
1554 		firePropertyChange("popupEnabled", !b, b);
1555 	}
1556 
1557 	/**
1558 	 * Enables/disables the tilting. Component tilts when value is out of
1559 	 * bounds.
1560 	 *
1561 	 * @param b whether the component should tilt when value is out of bounds.
1562 	 */
1563 	public void setTiltingEnabled(boolean b)
1564 	{
1565 		if (tiltingEnabled == b) {
1566 			return;
1567 		}
1568 
1569 		tiltingEnabled = b;
1570 		firePropertyChange("tiltingEnabled", !b, b);
1571 	}
1572 
1573 	/**
1574 	 * Returns whether the component should indicate value out of bounds
1575 	 * condition by visually tilting its border.
1576 	 *
1577 	 * @return boolean
1578 	 */
1579 	public boolean isTiltingEnabled()
1580 	{
1581 		return tiltingEnabled;
1582 	}
1583 
1584 	/**
1585 	 * Removes a listener from the list of listeners receiving event
1586 	 * notifications when the user sets a new value.
1587 	 *
1588 	 * @param listener listener to remove
1589 	 */
1590 	public void removeSetListener(SetListener listener)
1591 	{
1592 		listenerList.remove(SetListener.class, listener);
1593 	}
1594 
1595 	/*
1596 	 * (non-Javadoc)
1597 	 * @see java.awt.Component#repaint()
1598 	 */
1599 	public void repaint()
1600 	{
1601 		if (painting) {
1602 			return;
1603 		}
1604 
1605 		super.repaint();
1606 	}
1607 
1608 	/**
1609 	 * Synchronizes the user value with the value;
1610 	 */
1611 	public void synchronize()
1612 	{
1613 		setUserValue(getValue());
1614 	}
1615 
1616 	/**
1617 	 * Notifies all the listeners attached to user value.
1618 	 *
1619 	 * @param newValue new user value
1620 	 */
1621 	protected void notifyListeners(double newValue)
1622 	{
1623 		SetEvent e = new SetEvent(this, newValue);
1624 		SetListener[] list = listenerList.getListeners(SetListener.class);
1625 
1626 		for (int i = 0; i < list.length; i++) {
1627 			list[i].setPerformed(e);
1628 		}
1629 	}
1630 
1631 	/**
1632 	 * Paints the Dial Knob component by utilizing the Renderer. The GUI
1633 	 * comprises of a round shape with a value title in the middle, a title on
1634 	 * the top and value tick marks around approximately upper two thirds of
1635 	 * the circle.
1636 	 *
1637 	 * @param arg0 Graphics of the DialKnob component.
1638 	 *
1639 	 * @see com.cosylab.gui.components.range.Tick
1640 	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
1641 	 */
1642 	protected void paintComponent(Graphics arg0)
1643 	{
1644 		if (painting) {
1645 			return;
1646 		}
1647 
1648 		painting = true;
1649 
1650 		arg0.setColor(getBackground());
1651 		arg0.fillRect(0, 0, getWidth(), getHeight());
1652 
1653 		Graphics2D g2d = (Graphics2D)arg0;
1654 		renderer.initialize(g2d);
1655 		renderer.paintTicks(g2d);
1656 		renderer.paintKnob(g2d);
1657 		renderer.paintMarker(g2d);
1658 		renderer.paintTitleLabel(g2d);
1659 		renderer.paintValueLabel(g2d);
1660 
1661 		painting = false;
1662 	
1663 	}
1664 
1665 	private MouseHandler getMouseHandler()
1666 	{
1667 		if (mouseHandler == null) {
1668 			mouseHandler = new MouseHandler();
1669 		}
1670 
1671 		return mouseHandler;
1672 	}
1673 
1674 	private TiltHandler getTiltHandler()
1675 	{
1676 		if (tiltHandler == null) {
1677 			tiltHandler = new TiltHandler();
1678 		}
1679 
1680 		return tiltHandler;
1681 	}
1682 	
1683 	/**
1684 	 * Sets the ValuePolicy.
1685 	 * 
1686 	 * @param p new value policy
1687 	 */
1688 	public void setValuePolicy(RangedValuePolicy p) {
1689 		RangedValuePolicy oldValue = getValuePolicy();
1690 		rangedValue.removePolicy(policy);
1691 		this.policy = p;
1692 		rangedValue.addPolicy(p);
1693 		firePropertyChange("valuePolicy", oldValue, p);
1694 	}
1695 
1696 	/**
1697 	 * Returns the value policy.
1698 	 * 
1699 	 * @return
1700 	 */
1701 	public RangedValuePolicy getValuePolicy() {
1702 		return policy;
1703 	}
1704 	
1705 	/**
1706 	 * Returns the main ranged value.
1707 	 * 
1708 	 * @return
1709 	 */
1710 	protected RangedValueController getRangedValue() {
1711 		return rangedValue;
1712 	}
1713 
1714 	/**
1715 	 * Returns the user ranged value.
1716 	 * 
1717 	 * @return
1718 	 */
1719 	protected RangedValueController getUserRangedValue() {
1720 		return userRangedValue;
1721 	}
1722 
1723 	/**
1724 	 * Returns true if units are visible.
1725 	 * 
1726 	 * @return units visibile
1727 	 */
1728 	public boolean isUnitsVisible() {
1729 		return unitsVisible;
1730 	}
1731 
1732 	/**
1733 	 * Sets the units visible/invisible.
1734 	 * 
1735 	 * @param b units visible
1736 	 */
1737 	public void setUnitsVisible(boolean b) {
1738 		if (unitsVisible==b) return;
1739 		boolean oldB = unitsVisible;
1740 		unitsVisible = b;
1741 		firePropertyChange("unitsVisible",oldB,b);
1742 		repaint();
1743 	}
1744 
1745 	/**
1746 	 * Returns the color which indicates the alarm state.
1747 	 * 
1748 	 * @return Returns the alarmColor.
1749 	 */
1750 	public Color getAlarmColor() {
1751 		return alarmColor;
1752 	}
1753 
1754 	/**
1755 	 * Sets the color which indicates the alarm state.
1756 	 * 
1757 	 * @param alarmColor The alarmColor to set.
1758 	 */
1759 	public void setAlarmColor(Color alarmColor) {
1760 		Color oldValue = getAlarmColor();
1761 		this.alarmColor = alarmColor;
1762 		firePropertyChange("alarmColor", oldValue, alarmColor);
1763 	}
1764 
1765 	/**
1766 	 * Returns the high alarm limit.
1767 	 * 
1768 	 * @return Returns the highAlarmLimit.
1769 	 */
1770 	public double getHighAlarmLimit() {
1771 		return highAlarmLimit;
1772 	}
1773 
1774 	/**
1775 	 * Sets the high alarm limit.
1776 	 * 
1777 	 * @param highAlarmLimit new high alarm limit
1778 	 */
1779 	public void setHighAlarmLimit(double highAlarmLimit) {
1780 		double oldValue = getHighAlarmLimit();
1781 		this.highAlarmLimit = highAlarmLimit;
1782 		firePropertyChange("highAlarmLimir", oldValue, this.highAlarmLimit);
1783 	}
1784 
1785 	/**
1786 	 * Returns the high warning limit.
1787 	 * 
1788 	 * @return Returns the highWarningLimit.
1789 	 */
1790 	public double getHighWarningLimit() {
1791 		return highWarningLimit;
1792 	}
1793 
1794 	/**
1795 	 * Sets the high warning limit.
1796 	 * 
1797 	 * @param highWarningLimit new high warning limit
1798 	 */
1799 	public void setHighWarningLimit(double highWarningLimit) {
1800 		double oldValue = getHighWarningLimit();
1801 		this.highWarningLimit = highWarningLimit;
1802 		firePropertyChange("highWarningLimit", oldValue, this.highWarningLimit);
1803 	}
1804 
1805 	/**
1806 	 * Returns the low alarm limit.
1807 	 * 
1808 	 * @return Returns the lowAlarmLimit.
1809 	 */
1810 	public double getLowAlarmLimit() {
1811 		return lowAlarmLimit;
1812 	}
1813 
1814 	/**
1815 	 * Sets the low alarm limit.
1816 	 * 
1817 	 * @param lowAlarmLimit new low alarm limit
1818 	 */
1819 	public void setLowAlarmLimit(double lowAlarmLimit) {
1820 		double oldValue = getLowAlarmLimit();
1821 		this.lowAlarmLimit = lowAlarmLimit;
1822 		firePropertyChange("lowAlarmLimit", oldValue, this.lowAlarmLimit);
1823 	}
1824 
1825 	/**
1826 	 * Returns the low warning limit.
1827 	 * 
1828 	 * @return Returns the lowWarningLimit.
1829 	 */
1830 	public double getLowWarningLimit() {
1831 		return lowWarningLimit;
1832 	}
1833 
1834 	/**
1835 	 * Sets the low warning limit.
1836 	 * 
1837 	 * @param lowWarningLimit new low warning limit
1838 	 */
1839 	public void setLowWarningLimit(double lowWarningLimit) {
1840 		double oldValue = getLowWarningLimit();
1841 		this.lowWarningLimit = lowWarningLimit;
1842 		firePropertyChange("lowWarningLimit", oldValue, this.lowWarningLimit);
1843 	}
1844 
1845 	/**
1846 	 * Returns the color which indicates the normal state.
1847 	 * 
1848 	 * @return Returns the normalColor.
1849 	 */
1850 	public Color getNormalColor() {
1851 		return normalColor;
1852 	}
1853 
1854 	/**
1855 	 * Sets the color which indicates the normal state.
1856 	 * 
1857 	 * @param normalColor new normal color
1858 	 */
1859 	public void setNormalColor(Color normalColor) {
1860 		Color oldValue = getNormalColor();
1861 		this.normalColor = normalColor;
1862 		firePropertyChange("normalColor", oldValue, this.normalColor);
1863 	}
1864 
1865 	/**
1866 	 * Returns the color that indicates out of bound state.
1867 	 * 
1868 	 * @return Returns the outOfBoundsColor.
1869 	 */
1870 	public Color getOutOfBoundsColor() {
1871 		return outOfBoundsColor;
1872 	}
1873 
1874 	/**
1875 	 * Sets the color that indicates out of bound state.
1876 	 * 
1877 	 * @param outOfBoundsColor new out of bounds color
1878 	 */
1879 	public void setOutOfBoundsColor(Color outOfBoundsColor) {
1880 		Color oldValue = getOutOfBoundsColor();
1881 		this.outOfBoundsColor = outOfBoundsColor;
1882 		firePropertyChange("outOfBoundsColor", oldValue, this.outOfBoundsColor);
1883 	}
1884 
1885 	/**
1886 	 * Returns true if marker is colored when out of range.
1887 	 * 
1888 	 * @return Returns the rangeColors.
1889 	 */
1890 	public boolean isRangeColors() {
1891 		return rangeColors;
1892 	}
1893 
1894 	/**
1895 	 * Set whether the value marker is colored when out of range.
1896 	 * 
1897 	 * @param rangeColors color value marker
1898 	 */
1899 	public void setRangeColors(boolean rangeColors) {
1900 		this.rangeColors = rangeColors;
1901 	}
1902 
1903 	/**
1904 	 * Returns the warning color.
1905 	 * 
1906 	 * @return Returns the warningColor.
1907 	 */
1908 	public Color getWarningColor() {
1909 		return warningColor;
1910 	}
1911 
1912 	/**
1913 	 * Sets the color which idicates warning state.
1914 	 * 
1915 	 * @param warningColor The warningColor to set.
1916 	 */
1917 	public void setWarningColor(Color warningColor) {
1918 		Color oldValue = getWarningColor();
1919 		this.warningColor = warningColor;
1920 		firePropertyChange("warningColor", oldValue, warningColor);
1921 	}
1922 	
1923 	/* (non-Javadoc)
1924 	 * @see com.cosylab.gui.components.util.CosyTransferHandler.MouseFilter#isDragArea(java.awt.event.MouseEvent)
1925 	 */
1926 	public boolean isDragArea(MouseEvent e) {
1927 		return getMouseHandler().isMouseOutside(e);
1928 	}
1929 	
1930 	/* (non-Javadoc)
1931 	 * @see javax.swing.JComponent#setTransferHandler(javax.swing.TransferHandler)
1932 	 */
1933 	@Override
1934 	public void setTransferHandler(TransferHandler newHandler) {
1935 		super.setTransferHandler(newHandler);
1936 		if (newHandler instanceof CosyTransferHandler) {
1937 			CosyTransferHandler t = (CosyTransferHandler) newHandler;
1938 			t.addMouseFilter(this);
1939 			
1940 		}
1941 	}
1942 	
1943 	/**
1944 	 * Enables/disables mouse dragging. Dragging can only be enabled if
1945 	 * this component uses CosyTransferHandler.
1946 	 * 
1947 	 * @param enabled
1948 	 */
1949 	public void setDragEnabled(boolean enabled) {
1950 		if (isDragEnabled() == enabled) return;
1951 		if (getTransferHandler() instanceof CosyTransferHandler) {
1952 			if (((CosyTransferHandler)getTransferHandler()).isExportEnabled() == enabled) return;
1953 			
1954 			((CosyTransferHandler)getTransferHandler()).setExportEnabled(enabled, this);
1955 		}
1956 		firePropertyChange("dragEnabled", !enabled, enabled);
1957 	}
1958 	
1959 	/**
1960 	 * Returns true if drag is enabled.
1961 	 * 
1962 	 * @return
1963 	 */
1964 	public boolean isDragEnabled() {
1965 		if (getTransferHandler() instanceof CosyTransferHandler) {
1966 			return ((CosyTransferHandler)getTransferHandler()).isExportEnabled();
1967 		} else {
1968 			return getTransferHandler() != null;
1969 		}
1970 	}
1971 	
1972 	/**
1973 	 * Enable/disable the mouse drop. Drop can only be enabled if this component uses
1974 	 * CosyTransferHandler.
1975 	 * 
1976 	 * @param enabled
1977 	 */
1978 	public void setDropEnabled(boolean enabled) {
1979 		if (isDropEnabled() == enabled) return;
1980 		if (getTransferHandler() instanceof CosyTransferHandler) {
1981 			if (((CosyTransferHandler)getTransferHandler()).isReceiveEnabled() == enabled) return;
1982 			
1983 			((CosyTransferHandler)getTransferHandler()).setReceiveEnabled(enabled, this);
1984 		}
1985 		firePropertyChange("dropEnabled", !enabled, enabled);
1986 	}
1987 	
1988 	/**
1989 	 * Returns true if drop is enabled.
1990 	 * 
1991 	 * @return
1992 	 */
1993 	public boolean isDropEnabled() {
1994 		if (getTransferHandler() instanceof CosyTransferHandler) {
1995 			return ((CosyTransferHandler)getTransferHandler()).isReceiveEnabled();
1996 		} else {
1997 			return getTransferHandler() != null;
1998 		}
1999 	}
2000 	
2001 	/*
2002 	 * (non-Javadoc)
2003 	 * @see javax.swing.JComponent#setBackground(java.awt.Color)
2004 	 */
2005 	@Override
2006 	public void setBackground(Color bg) {
2007 		super.setBackground(bg);
2008 		getSyncButton().setBackground(bg);
2009 	}
2010 	
2011 	/*
2012 	 * (non-Javadoc)
2013 	 * @see javax.swing.JComponent#setForeground(java.awt.Color)
2014 	 */
2015 	@Override
2016 	public void setForeground(Color fg) {
2017 		super.setForeground(fg);
2018 		getSyncButton().setForeground(fg);
2019 	}
2020 		
2021 	/**
2022 	 * Returns true if auto synchronization is turn on.
2023 	 * 
2024 	 * @return
2025 	 */
2026 	public boolean isAutoSynchronize() {
2027 		return autoSynchronize;
2028 	}
2029 
2030 	/**
2031 	 * Turns the autoSynchronization on/off. When auto synchronization is
2032 	 * on, the knob will automatically synchronize with the readback value if
2033 	 * inactive for more than specified by auto sycnhronbize delay.
2034 	 * 
2035 	 * 
2036 	 * @param autoSynchronize
2037 	 * @see #setAutoSynchronizeDelay(long)
2038 	 */
2039 	public void setAutoSynchronize(boolean autoSynchronize) {
2040 		if(isAutoSynchronize() == autoSynchronize) return;
2041 		this.autoSynchronize = autoSynchronize;
2042 		allowAutoSync = true;
2043 		if (autoSynchronize) {
2044 			getAutoSyncTimer().restart();
2045 		} else {
2046 			getAutoSyncTimer().stop();
2047 		}
2048 		firePropertyChange("autoSynchronize", !autoSynchronize, autoSynchronize);
2049 	}
2050 	
2051 	private javax.swing.Timer autoSyncTimer;
2052 	private javax.swing.Timer getAutoSyncTimer() {
2053 		if (autoSyncTimer == null) {
2054 			autoSyncTimer = new javax.swing.Timer((int)autoSynchronizeDelay, new ActionListener(){
2055 				public void actionPerformed(ActionEvent e) {
2056 					if (allowAutoSync)
2057 						synchronize();
2058 					autoSyncTimer.stop();
2059 				}
2060 			});
2061 
2062 			
2063 		}
2064 
2065 		return autoSyncTimer;
2066 	}
2067 
2068 	/**
2069 	 * Returns the time in milliseconds that specify the delay of 
2070 	 * the auto synchronization.
2071 	 * 
2072 	 * @return
2073 	 */
2074 	public long getAutoSynchronizeDelay() {
2075 		return autoSynchronizeDelay;
2076 	}
2077 
2078 	/**
2079 	 * Sets the autoSynchronization in miliseconds.
2080 	 * 
2081 	 * @param autoSynchronizeDelay
2082 	 * @see #setAutoSynchronize(boolean)
2083 	 */
2084 	public void setAutoSynchronizeDelay(long autoSynchronizeDelay) {
2085 		long oldValue = getAutoSynchronizeDelay();
2086 		this.autoSynchronizeDelay = autoSynchronizeDelay;
2087 		
2088 		getAutoSyncTimer().setDelay((int) autoSynchronizeDelay);
2089 		getAutoSyncTimer().setInitialDelay((int) autoSynchronizeDelay);
2090 		allowAutoSync=true;
2091 		if (autoSynchronize) {
2092 			getAutoSyncTimer().restart();
2093 		} else {
2094 			getAutoSyncTimer().stop();
2095 		}
2096 		firePropertyChange("autoSynchronizeDelay", oldValue, this.autoSynchronizeDelay);
2097 		
2098 	}
2099 	
2100 	/**
2101 	 * Demonstration.
2102 	 *
2103 	 * @param args Command line arguments.
2104 	 */
2105 	public static void main(String[] args)
2106 	{
2107 		final DialKnob knob = new DialKnob("Voltage");
2108 		//simulates asymptotical approaching to user value
2109 		Thread t = new Thread() {
2110 				private double value;
2111 				private double initValue;
2112 				private long timeLastChanged = System.currentTimeMillis();
2113 
2114 				public void run()
2115 				{
2116 					JFrame frame = new javax.swing.JFrame("DialKnob");
2117 					frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
2118 
2119 					JPanel panel = new JPanel();
2120 					frame.setContentPane(panel);
2121 					frame.setSize(300, 300);
2122 					knob.setTitleVisible(true);
2123 					knob.setFormat("%d");
2124 					knob.setUnits("V");
2125 					knob.setPopupEnabled(true);
2126 					knob.setEditable(true);
2127 					knob.addSetListener(new SetAdapter() {
2128 							public void setPerformed(SetEvent se)
2129 							{
2130 								timeLastChanged = System.currentTimeMillis();
2131 								initValue = value;
2132 								
2133 //								System.out.println(knob.getValue() + " " + knob.getGraphMin() + " " + knob.getGraphMax()+ " " + knob.getUserValue() + " " + knob.getUserMin() + " " + knob.getUserMax());
2134 							}
2135 						});
2136 					panel.setLayout(new java.awt.CardLayout());
2137 					panel.add(knob, "DialKnob");
2138 					frame.setVisible(true);
2139 
2140 					initValue = value = knob.getUserValue();
2141 
2142 					double relaxTime = 50;
2143 					
2144 					
2145 					while (true) {
2146 						value = knob.getUserValue()
2147 							+ (initValue - knob.getUserValue()) * Math.exp(-(System
2148 							    .currentTimeMillis() - timeLastChanged) / relaxTime);
2149 						knob.setValue(value);
2150 						System.out.println(value);
2151 						try {
2152 							sleep(10);
2153 						} catch (Exception e) {
2154 						}
2155 					}
2156 				}
2157 			};
2158 				
2159 		t.run();
2160 		 
2161 	}
2162 }
2163 
2164 /* __oOo__ */